Skip to content

Commit 763f487

Browse files
committed
use mock performance.now to simulate timeout reliably
1 parent 9483038 commit 763f487

1 file changed

Lines changed: 28 additions & 22 deletions

File tree

test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -100,22 +100,28 @@ describe('Retry Timeout is Enforced', function () {
100100
// Drivers should test that withTransaction enforces a non-configurable timeout before retrying
101101
// both commits and entire transactions.
102102
//
103-
// Note: We use CSOT's timeoutMS to enforce a short timeout instead of blocking for the full
104-
// 120-second retry timeout, as recommended by the spec: "This might be done by internally
105-
// modifying the timeout value used by withTransaction with some private API or using a mock timer."
103+
// We stub performance.now() to simulate elapsed time exceeding the 120-second retry limit,
104+
// as recommended by the spec: "This might be done by internally modifying the timeout value
105+
// used by withTransaction with some private API or using a mock timer."
106106
//
107107
// The error SHOULD be propagated as a timeout error if the language allows to expose the
108108
// underlying error as a cause of a timeout error.
109109

110110
let client: MongoClient;
111111
let collection: Collection;
112+
let timeOffset: number;
112113

113114
beforeEach(async function () {
114-
client = this.configuration.newClient({ timeoutMS: 500 });
115+
client = this.configuration.newClient();
115116
collection = client.db('foo').collection('bar');
117+
118+
timeOffset = 0;
119+
const originalNow = performance.now.bind(performance);
120+
sinon.stub(performance, 'now').callsFake(() => originalNow() + timeOffset);
116121
});
117122

118123
afterEach(async function () {
124+
sinon.restore();
119125
await clearFailPoint(this.configuration);
120126
await client?.close();
121127
});
@@ -131,23 +137,23 @@ describe('Retry Timeout is Enforced', function () {
131137
}
132138
},
133139
async function () {
134-
// 1. Configure a failpoint that always fails insert with TransientTransactionError.
140+
// 1. Configure a failpoint that fails insert with TransientTransactionError.
135141
await configureFailPoint(this.configuration, {
136142
configureFailPoint: 'failCommand',
137-
mode: 'alwaysOn',
143+
mode: { times: 1 },
138144
data: {
139145
failCommands: ['insert'],
140146
errorCode: 24,
141147
errorLabels: ['TransientTransactionError']
142148
}
143149
});
144150

145-
// 2. Run withTransaction with a callback that performs an insert.
146-
// The insert will always fail with TransientTransactionError, triggering retries
147-
// until the timeout (timeoutMS: 100) is exceeded at the backoff check.
151+
// 2. Run withTransaction. The callback advances the clock past the 120-second retry
152+
// limit before the insert fails, so the timeout is detected immediately.
148153
const { result } = await measureDuration(() => {
149154
return client.withSession(async s => {
150155
await s.withTransaction(async session => {
156+
timeOffset = 120_000;
151157
await collection.insertOne({}, { session });
152158
});
153159
});
@@ -171,31 +177,31 @@ describe('Retry Timeout is Enforced', function () {
171177
}
172178
},
173179
async function () {
174-
// 1. Configure a failpoint that always fails commitTransaction with
175-
// UnknownTransactionCommitResult.
180+
// 1. Configure a failpoint that fails commitTransaction with UnknownTransactionCommitResult.
176181
await configureFailPoint(this.configuration, {
177182
configureFailPoint: 'failCommand',
178-
mode: 'alwaysOn',
183+
mode: { times: 1 },
179184
data: {
180185
failCommands: ['commitTransaction'],
181186
errorCode: 64,
182187
errorLabels: ['UnknownTransactionCommitResult']
183188
}
184189
});
185190

186-
// 2. Run withTransaction with a callback that performs an insert (succeeds).
187-
// The commit will always fail with UnknownTransactionCommitResult, triggering commit
188-
// retries until the timeout (timeoutMS: 100) is exceeded.
191+
// 2. Run withTransaction. The callback advances the clock past the 120-second retry
192+
// limit. The insert succeeds, but the commit fails and the timeout is detected.
189193
const { result } = await measureDuration(() => {
190194
return client.withSession(async s => {
191195
await s.withTransaction(async session => {
196+
timeOffset = 120_000;
192197
await collection.insertOne({}, { session });
193198
});
194199
});
195200
});
196201

197-
// 3. Assert that the error is a timeout error.
202+
// 3. Assert that the error is a timeout error wrapping the commit error.
198203
expect(result).to.be.instanceOf(MongoOperationTimeoutError);
204+
expect((result as MongoOperationTimeoutError).cause).to.be.an('error');
199205
}
200206
);
201207

@@ -212,24 +218,24 @@ describe('Retry Timeout is Enforced', function () {
212218
}
213219
},
214220
async function () {
215-
// 1. Configure a failpoint that always fails commitTransaction with
216-
// TransientTransactionError (errorCode 251 = NoSuchTransaction).
221+
// 1. Configure a failpoint that fails commitTransaction with TransientTransactionError
222+
// (errorCode 251 = NoSuchTransaction).
217223
await configureFailPoint(this.configuration, {
218224
configureFailPoint: 'failCommand',
219-
mode: 'alwaysOn',
225+
mode: { times: 1 },
220226
data: {
221227
failCommands: ['commitTransaction'],
222228
errorCode: 251,
223229
errorLabels: ['TransientTransactionError']
224230
}
225231
});
226232

227-
// 2. Run withTransaction with a callback that performs an insert (succeeds).
228-
// The commit will always fail with TransientTransactionError, triggering full
229-
// transaction retries until the timeout (timeoutMS: 100) is exceeded.
233+
// 2. Run withTransaction. The callback advances the clock past the 120-second retry
234+
// limit. The insert succeeds, but the commit fails and the timeout is detected.
230235
const { result } = await measureDuration(() => {
231236
return client.withSession(async s => {
232237
await s.withTransaction(async session => {
238+
timeOffset = 120_000;
233239
await collection.insertOne({}, { session });
234240
});
235241
});

0 commit comments

Comments
 (0)