Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Generate end to end tests for retries #1117

Open
wants to merge 60 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e368301
Getting grpc set up for tests
danieljbruce May 18, 2022
eb94439
test for error sent through gax
danieljbruce May 18, 2022
95a281c
Trying other things
danieljbruce May 19, 2022
cdf8149
Group everything into describe blocks.
danieljbruce May 19, 2022
091b08c
Add Google header
danieljbruce May 19, 2022
7d5fa9c
Added more tests
danieljbruce May 20, 2022
1698902
Create mock service files
danieljbruce May 20, 2022
47eb8d8
refactor a check
danieljbruce May 20, 2022
b9d82f8
mock server tests
danieljbruce May 24, 2022
3256cdc
undo tests
danieljbruce May 24, 2022
b25068a
Pass metadata through
danieljbruce May 25, 2022
4bdd4bb
Merge branch 'main' of https://github.com/googleapis/nodejs-bigtable …
danieljbruce May 26, 2022
6ef1bd4
build fix
danieljbruce May 26, 2022
8ae117a
work in progress
danieljbruce May 27, 2022
e8cd776
Add service error check and change test
danieljbruce May 27, 2022
148bd42
Remove TODO and add done hook.
danieljbruce May 31, 2022
d3d9fb6
Logs for debugging
danieljbruce Jun 13, 2022
7153399
use the tcp-port-used library instead
danieljbruce Jun 13, 2022
8db378b
Merge branch 'main' of https://github.com/googleapis/nodejs-bigtable …
danieljbruce Jun 17, 2022
3247820
use await
danieljbruce Jun 17, 2022
1bb0c10
Merge branch 'main' into errors-from-gax-layer
danieljbruce Jun 20, 2022
22314fe
Merge branch 'main' into errors-from-gax-layer
danieljbruce Jun 21, 2022
a9d94b6
Merge branch 'errors-from-gax-layer' of https://github.com/danieljbru…
danieljbruce Jun 24, 2022
d52f504
Set of tests for retriable errors
danieljbruce Jun 27, 2022
e23127c
Add modularity
danieljbruce Jun 27, 2022
05f9910
small refactor step
danieljbruce Jun 27, 2022
dcf21eb
A step toward the right abstraction
danieljbruce Jun 27, 2022
0150022
send error handler abstraction
danieljbruce Jun 27, 2022
93bfea3
pass endpoint into constructor
danieljbruce Jun 27, 2022
7a4dbca
expand class hierarchy
danieljbruce Jun 28, 2022
7ee6ab8
Abstract snapshot output
danieljbruce Jun 28, 2022
df82957
remove code parameter
danieljbruce Jun 28, 2022
05a2996
service tester done
danieljbruce Jun 28, 2022
516b5c1
Add licenses
danieljbruce Jun 28, 2022
54f67cd
new snapshot mechanism
danieljbruce Jun 29, 2022
32b3da5
new snapshots
danieljbruce Jun 29, 2022
1230ee7
Change input/output snapshots
danieljbruce Jun 29, 2022
85f592b
restructure snapshot to logical input / output
danieljbruce Jun 29, 2022
46cb7f2
Small refactors
danieljbruce Jun 29, 2022
1e33d95
In a state with passing tests
danieljbruce Jun 29, 2022
ec39fd7
Merge branch 'main' into refactor-create-read-stream
danieljbruce Jun 29, 2022
1db4368
Merge branch 'main' into refactor-create-read-stream
danieljbruce Jul 4, 2022
d379b45
Adding event driven functionality to serve handler
danieljbruce Jul 5, 2022
629c3d5
Attempt to avoid race condition
danieljbruce Jul 6, 2022
a45675e
Merge branch 'main' of https://github.com/googleapis/nodejs-bigtable …
danieljbruce Jul 6, 2022
0611455
Merge branch 'refactor-create-read-stream' of https://github.com/dani…
danieljbruce Jul 6, 2022
0e61aab
make a small call handler function
danieljbruce Jul 6, 2022
0f01291
this works
danieljbruce Jul 7, 2022
054bdf8
Working snapshot with time delay.
danieljbruce Jul 7, 2022
f97d4db
new snapshot
danieljbruce Jul 7, 2022
f6240ff
remove unused imports
danieljbruce Jul 7, 2022
f1ae2bf
Change snapshot structure
danieljbruce Jul 7, 2022
0c445c4
Remove unused code
danieljbruce Jul 7, 2022
35bc274
Added a couple more tests
danieljbruce Jul 7, 2022
0ddc3d7
Added another test for retries
danieljbruce Jul 7, 2022
5dca0f7
Four more tests
danieljbruce Jul 7, 2022
61df10f
generated another snapshot
danieljbruce Jul 8, 2022
f28813d
Comment out a test
danieljbruce Jul 8, 2022
5714dd6
Move the mock server file
danieljbruce Jul 8, 2022
776e692
Move test utilities
danieljbruce Jul 8, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
598 changes: 598 additions & 0 deletions __snapshots__/read-rows.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@google-cloud/promisify": "^3.0.0",
"arrify": "^2.0.0",
"concat-stream": "^2.0.0",
"deep-equal": "^2.0.5",
"dot-prop": "^6.0.0",
"escape-string-regexp": "^4.0.0",
"extend": "^3.0.2",
Expand Down Expand Up @@ -89,6 +90,7 @@
"sinon": "^14.0.0",
"tcp-port-used": "^1.0.2",
"snap-shot-it": "^7.9.1",
"tcp-port-used": "^1.0.2",
"ts-loader": "^9.0.0",
"typescript": "^4.6.4",
"uuid": "^8.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright 2022 Google LLC
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have caught this in the last review, since these servers (I believe) will only be used in testing, I'd be tempted to have them in test/util, rather than src/util. I think of src as being components of the library itself, vs., components used during testing.

//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

import {SameCallHandler} from './same-call-handler';
import {MockService} from '../../../mock-service';
import {Mutation} from '../../../../../mutation';

function rowResponse(rowKey: {}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there going to be a different "implementation" file for different test cases, e.g, ensuring that streaming errors are gracefully recovered from?

If so, I would be tempted to name the implementation file after the specific behaviour it's a test for. Then you'll have a bunch of implementation files for a bunch of different regression tests.

return {
rowKey: Mutation.convertToBytes(rowKey),
familyName: {value: 'family'},
qualifier: {value: Mutation.convertToBytes('qualifier')},
valueSize: 0,
timestampMicros: 0,
labels: [],
commitRow: true,
value: Mutation.convertToBytes('value'),
};
}

export interface ReadRowsResponse {
row_keys?: string[];
last_row_key?: string;
end_with_error?: number;
}

export class ReadRowsHandler extends SameCallHandler {
responses: ReadRowsResponse[];
request: any = null;
callCount = 0;
message: any;

// TODO: service and endpoint should be bundled into one object.
constructor(
service: MockService,
endpoint: string,
responses: ReadRowsResponse[],
message?: any
) {
super(service, endpoint);
this.responses = responses;
this.message = Object.assign({}, message);
}

// TODO: Create interface for this.
callHandler(call: any) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would b good to use a better type for call, you could potentially just create your own interface:

interface {
  emit: () => {}
}

But I wouldn't be surprised if there's a type available in the grpc library.

const lastResponse = this.responses[this.callCount - 1];
if (lastResponse && lastResponse.row_keys) {
const grpcResponse = {
chunks: lastResponse.row_keys.map(rowResponse),
lastScannedRowKey: Mutation.convertToBytes(lastResponse.last_row_key),
};
call.write(grpcResponse);
}
// Set a timer and send an error if we are confident that all data has been sent back to the user
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
const endRequest = (lastResponse: any) => {
const errorCode = lastResponse.end_with_error;
if (errorCode) {
call.emit('error', {
code: errorCode,
details: 'Details for a particular type of error',
});
} else {
call.end();
}
};
const checkCollected = () => {
// Send the error if all data was collected
const lastIndex = self.data.length - 1;
const lastResponse = self.responses[self.callCount - 1];
if (lastResponse) {
if (lastResponse.row_keys) {
if (self.data[lastIndex].length === lastResponse.row_keys.length) {
endRequest(lastResponse);
} else {
startTimer();
}
} else {
endRequest(lastResponse);
}
} else {
throw Error('Ran out of requests to send');
}
};
const startTimer = () => {
setTimeout(checkCollected, 2500);
};
startTimer();
}

addData(data: string) {
// Add data collected from the stream
const lastIndex = this.data.length - 1;
this.data[lastIndex].push(data);
}

snapshot(results: any): any {
return {
input: Object.assign(
{responses: this.responses},
this.message ? {message: this.message} : null
),
output: {
results,
requestData: this.requests(),
},
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

import {MockService} from '../../../mock-service';
import {ServiceHandler} from '../service-handler';

const equal = require('deep-equal');

export abstract class SameCallHandler extends ServiceHandler {
service: MockService;
request: any = null;
requestList: any[] = [];
requestOrder: number[] = [];
callCount = 0;
endpoint: string;
data: string[][] = [];

protected constructor(service: MockService, endpoint: string) {
super();
this.endpoint = endpoint;
this.service = service;
}

setupService(): void {
const handleRpcCall = (call: any) => {
// TODO: Make an abstraction of this
const callRequest = call.request;
const requestIndex = this.requestList.findIndex(request => {
return equal(request, callRequest);
});
if (requestIndex === -1) {
this.requestList.push(callRequest);
this.requestOrder.push(this.requestList.length - 1);
} else {
this.requestOrder.push(requestIndex);
}
this.request = callRequest;
this.callCount++;
this.data.push([]);
this.callHandler(call);
};
this.service.setService({
// Abstraction: Always emit error
[this.endpoint]: handleRpcCall,
});
}

getData() {
return this.data;
}

requests() {
return {
requests: Object.assign({}, this.requestList),
requestOrder: this.requestOrder,
callCount: this.callCount,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

import {grpc} from 'google-gax';
import {SameCallHandler} from './same-call-handler';
import {MockService} from '../../../mock-service';
import {Row} from '../../../../../row';

export class SendErrorHandler extends SameCallHandler {
code: grpc.status;
request: any = null;
callCount = 0;
message: any;

// TODO: service and endpoint should be bundled into one object.
constructor(
service: MockService,
endpoint: string,
code: grpc.status,
message?: any
) {
super(service, endpoint);
this.code = code;
this.message = Object.assign({}, message);
}

callHandler(call: any) {
call.emit('error', {
code: this.code,
details: 'Details for a particular type of error',
});
}

addData(data: string) {
const lastIndex = this.data.length - 1;
this.data[lastIndex].push(data);
}

snapshot(results: any): any {
return {
input: Object.assign(
{code: this.code},
this.message ? {message: this.message} : null
),
output: {
results,
requestData: this.requests(),
},
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

export abstract class ServiceHandler {
/*
callHandler accepts a grpc call and provides behaviour for that grpc call
which may involve sending errors or data back to the client for
example.
*/
abstract callHandler(call: any): void;

/*
snapshotOutput is used to provide a custom json object that represents the
results of the test that was run with this service handler.
*/
abstract snapshot(results: any): any;

/*
setupService is called to setup the service we use for collecting data about
a running test.
*/
abstract setupService(): void;

/*
addData is called to add data which will be reported in the snapshot later on.
*/
abstract addData(data: string): void;

/*
getData is called to get all data which was collected from requests.
*/
abstract getData(): string[][];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

import {GetRowsOptions, Table} from '../../../../../table';
import internal = require('stream');
import {StreamFetcher} from '../stream-fetcher';

export class ReadRowsFetcher extends StreamFetcher {
table: Table;
opts: GetRowsOptions;

constructor(table: Table, opts?: GetRowsOptions) {
super();
this.opts = opts ?? {};
this.table = table;
}

fetchStream(): internal.PassThrough {
return this.table.createReadStream(this.opts);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

import internal = require('stream');

export abstract class StreamFetcher {
abstract fetchStream(): internal.PassThrough;
}