Skip to content

Commit a5bf2da

Browse files
retryable writes done
1 parent 68059f9 commit a5bf2da

File tree

1 file changed

+16
-199
lines changed

1 file changed

+16
-199
lines changed
Lines changed: 16 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -1,207 +1,24 @@
1-
import { expect } from 'chai';
21
import * as path from 'path';
32

4-
import type { Collection, Db, MongoClient } from '../../mongodb';
53
import { loadSpecTests } from '../../spec';
6-
import { legacyRunOnToRunOnRequirement } from '../../tools/spec-runner';
74
import { runUnifiedSuite } from '../../tools/unified-spec-runner/runner';
8-
import { isAnyRequirementSatisfied } from '../../tools/unified-spec-runner/unified-utils';
95

10-
interface RetryableWriteTestContext {
11-
client?: MongoClient;
12-
db?: Db;
13-
collection?: Collection;
14-
failPointName?: any;
15-
}
16-
17-
describe('Legacy Retryable Writes Specs', function () {
18-
let ctx: RetryableWriteTestContext = {};
19-
20-
const retryableWrites = loadSpecTests('retryable-writes', 'legacy');
21-
22-
for (const suite of retryableWrites) {
23-
describe(suite.name, function () {
24-
beforeEach(async function () {
25-
let utilClient: MongoClient;
26-
if (this.configuration.isLoadBalanced) {
27-
// The util client can always point at the single mongos LB frontend.
28-
utilClient = this.configuration.newClient(this.configuration.singleMongosLoadBalancerUri);
29-
} else {
30-
utilClient = this.configuration.newClient();
31-
}
32-
33-
await utilClient.connect();
34-
35-
const allRequirements = suite.runOn.map(legacyRunOnToRunOnRequirement);
36-
37-
const someRequirementMet =
38-
!allRequirements.length ||
39-
(await isAnyRequirementSatisfied(this.currentTest.ctx, allRequirements, utilClient));
40-
41-
await utilClient.close();
42-
43-
if (!someRequirementMet) this.skip();
44-
});
45-
46-
beforeEach(async function () {
47-
// Step 1: Test Setup. Includes a lot of boilerplate stuff
48-
// like creating a client, dropping and refilling data collections,
49-
// and enabling failpoints
50-
const { spec } = this.currentTest;
51-
await executeScenarioSetup(suite, spec, this.configuration, ctx);
52-
});
53-
54-
afterEach(async function () {
55-
// Step 3: Test Teardown. Turn off failpoints, and close client
56-
if (!ctx.db || !ctx.client) {
57-
return;
58-
}
59-
60-
if (ctx.failPointName) {
61-
await turnOffFailPoint(ctx.client, ctx.failPointName);
62-
}
63-
await ctx.client.close();
64-
ctx = {}; // reset context
65-
});
66-
for (const spec of suite.tests) {
67-
// Step 2: Run the test
68-
const mochaTest = it(spec.description, async () => await executeScenarioTest(spec, ctx));
69-
70-
// A pattern we don't need to repeat for unified tests
71-
// In order to give the beforeEach hook access to the
72-
// spec test so it can be responsible for skipping it
73-
// and executeScenarioSetup
74-
mochaTest.spec = spec;
75-
}
76-
});
77-
}
78-
});
79-
80-
async function executeScenarioSetup(scenario, test, config, ctx) {
81-
const url = config.url();
82-
const options = {
83-
...test.clientOptions,
84-
heartbeatFrequencyMS: 100,
85-
monitorCommands: true,
86-
minPoolSize: 10
87-
};
88-
89-
ctx.failPointName = test.failPoint && test.failPoint.configureFailPoint;
90-
91-
const client = config.newClient(url, options);
92-
await client.connect();
93-
94-
ctx.client = client;
95-
ctx.db = client.db(config.db);
96-
ctx.collection = ctx.db.collection(`retryable_writes_test_${config.name}_${test.operation.name}`);
97-
98-
try {
99-
await ctx.collection.drop();
100-
} catch (error) {
101-
if (!error.message.match(/ns not found/)) {
102-
throw error;
103-
}
104-
}
105-
106-
if (Array.isArray(scenario.data) && scenario.data.length) {
107-
await ctx.collection.insertMany(scenario.data);
108-
}
109-
110-
if (test.failPoint) {
111-
await ctx.client.db('admin').command(test.failPoint);
112-
}
113-
}
114-
115-
async function executeScenarioTest(test, ctx: RetryableWriteTestContext) {
116-
const args = generateArguments(test);
117-
118-
// In case the spec files or our API changes
119-
expect(ctx.collection).to.have.property(test.operation.name).that.is.a('function');
120-
121-
// TODO(NODE-4033): Collect command started events and assert txn number existence or omission
122-
// have to add logic to handle bulkWrites which emit multiple command started events
123-
const resultOrError = await ctx.collection[test.operation.name](...args).catch(error => error);
124-
125-
const outcome = test.outcome && test.outcome.result;
126-
const errorLabelsContain = outcome && outcome.errorLabelsContain;
127-
const errorLabelsOmit = outcome && outcome.errorLabelsOmit;
128-
const hasResult = outcome && !errorLabelsContain && !errorLabelsOmit;
129-
if (test.outcome.error) {
130-
const thrownError = resultOrError;
131-
expect(thrownError, `${test.operation.name} was supposed to fail but did not!`).to.exist;
132-
expect(thrownError).to.have.property('message');
133-
134-
if (hasResult) {
135-
expect(thrownError.result).to.matchMongoSpec(test.outcome.result);
136-
}
137-
138-
if (errorLabelsContain) {
139-
expect(thrownError.errorLabels).to.include.members(errorLabelsContain);
140-
}
141-
142-
if (errorLabelsOmit) {
143-
for (const label of errorLabelsOmit) {
144-
expect(thrownError.errorLabels).to.not.contain(label);
145-
}
146-
}
147-
} else if (test.outcome.result) {
148-
expect(resultOrError, resultOrError.stack).to.not.be.instanceOf(Error);
149-
const result = resultOrError;
150-
const expected = test.outcome.result;
151-
152-
const actual = result.value ?? result;
153-
// Some of our result classes contain the optional 'acknowledged' property which is
154-
// not part of the test expectations.
155-
for (const property in expected) {
156-
expect(actual).to.have.deep.property(property, expected[property]);
157-
}
158-
}
159-
160-
if (test.outcome.collection) {
161-
const collectionResults = await ctx.collection.find({}).toArray();
162-
expect(collectionResults).to.deep.equal(test.outcome.collection.data);
163-
}
164-
}
165-
166-
// Helper Functions
167-
168-
/** Transforms the arguments from a test into actual arguments for our function calls */
169-
function generateArguments(test) {
170-
const args = [];
171-
172-
if (test.operation.arguments) {
173-
const options: Record<string, any> = {};
174-
for (const arg of Object.keys(test.operation.arguments)) {
175-
if (arg === 'requests') {
176-
/** Transforms a request arg into a bulk write operation */
177-
args.push(
178-
test.operation.arguments[arg].map(({ name, arguments: args }) => ({ [name]: args }))
179-
);
180-
} else if (arg === 'upsert') {
181-
options.upsert = test.operation.arguments[arg];
182-
} else if (arg === 'returnDocument') {
183-
options.returnDocument = test.operation.arguments[arg].toLowerCase();
184-
} else {
185-
args.push(test.operation.arguments[arg]);
186-
}
187-
}
188-
189-
if (Object.keys(options).length > 0) {
190-
args.push(options);
191-
}
192-
}
193-
194-
return args;
195-
}
196-
197-
/** Runs a command that turns off a fail point */
198-
async function turnOffFailPoint(client, name) {
199-
return await client.db('admin').command({
200-
configureFailPoint: name,
201-
mode: 'off'
202-
});
203-
}
6+
const clientBulkWriteTests = [
7+
'client bulkWrite with one network error succeeds after retry',
8+
'client bulkWrite with two network errors fails after retry',
9+
'client bulkWrite with no multi: true operations succeeds after retryable top-level error',
10+
'client bulkWrite with multi: true operations fails after retryable top-level error',
11+
'client bulkWrite with no multi: true operations succeeds after retryable writeConcernError',
12+
'client bulkWrite with multi: true operations fails after retryable writeConcernError',
13+
'client bulkWrite with retryWrites: false does not retry',
14+
'client.clientBulkWrite succeeds after retryable handshake network error',
15+
'client.clientBulkWrite succeeds after retryable handshake server error (ShutdownInProgress)'
16+
];
20417

20518
describe('Retryable Writes (unified)', function () {
206-
runUnifiedSuite(loadSpecTests(path.join('retryable-writes', 'unified')));
19+
runUnifiedSuite(loadSpecTests(path.join('retryable-writes', 'unified')), ({ description }) => {
20+
return clientBulkWriteTests.includes(description)
21+
? `TODO(NODE-xxxx): implement client-level bulk write.`
22+
: false;
23+
});
20724
});

0 commit comments

Comments
 (0)