Skip to content

Commit b3c3721

Browse files
authored
test(NODE-3188): refactor transaction spec tests: (#2831)
1 parent 536e5ff commit b3c3721

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+547
-36
lines changed

src/cmap/connection.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,18 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
368368
clusterTime = session.clusterTime;
369369
}
370370

371+
// We need to unpin any read or write commands that happen outside of a pinned
372+
// transaction, so we check if we have a pinned transaction that is no longer
373+
// active, and unpin for all except start or commit.
374+
if (
375+
!session.transaction.isActive &&
376+
session.transaction.isPinned &&
377+
!finalCmd.startTransaction &&
378+
!finalCmd.commitTransaction
379+
) {
380+
session.transaction.unpinServer();
381+
}
382+
371383
const err = applySession(session, finalCmd, options as CommandOptions);
372384
if (err) {
373385
return callback(err);

src/cursor/abstract_cursor.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,15 @@ export abstract class AbstractCursor<
189189
return this[kOptions].readConcern;
190190
}
191191

192+
/** @internal */
192193
get session(): ClientSession | undefined {
193194
return this[kSession];
194195
}
195196

197+
set session(clientSession: ClientSession | undefined) {
198+
this[kSession] = clientSession;
199+
}
200+
196201
/** @internal */
197202
get cursorOptions(): InternalAbstractCursorOptions {
198203
return this[kOptions];

src/sessions.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -549,30 +549,30 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
549549
}
550550

551551
function commandHandler(e?: MongoError, r?: Document) {
552-
if (commandName === 'commitTransaction') {
553-
session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
552+
if (commandName !== 'commitTransaction') {
553+
session.transaction.transition(TxnState.TRANSACTION_ABORTED);
554+
// The spec indicates that we should ignore all errors on `abortTransaction`
555+
return callback();
556+
}
554557

558+
session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
559+
if (e) {
555560
if (
556-
e &&
557-
(e instanceof MongoNetworkError ||
558-
e instanceof MongoWriteConcernError ||
559-
isRetryableError(e) ||
560-
isMaxTimeMSExpiredError(e))
561+
e instanceof MongoNetworkError ||
562+
e instanceof MongoWriteConcernError ||
563+
isRetryableError(e) ||
564+
isMaxTimeMSExpiredError(e)
561565
) {
562566
if (isUnknownTransactionCommitResult(e)) {
563567
e.addErrorLabel('UnknownTransactionCommitResult');
564568

565569
// per txns spec, must unpin session in this case
566570
session.transaction.unpinServer();
567571
}
572+
} else if (e.hasErrorLabel('TransientTransactionError')) {
573+
session.transaction.unpinServer();
568574
}
569-
} else {
570-
session.transaction.transition(TxnState.TRANSACTION_ABORTED);
571-
572-
// The spec indicates that we should ignore all errors on `abortTransaction`
573-
return callback();
574575
}
575-
576576
callback(e, r);
577577
}
578578

src/transactions.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,11 @@ export class Transaction {
145145
const nextStates = stateMachine[this.state];
146146
if (nextStates && nextStates.includes(nextState)) {
147147
this.state = nextState;
148-
if (this.state === TxnState.NO_TRANSACTION || this.state === TxnState.STARTING_TRANSACTION) {
148+
if (
149+
this.state === TxnState.NO_TRANSACTION ||
150+
this.state === TxnState.STARTING_TRANSACTION ||
151+
this.state === TxnState.TRANSACTION_ABORTED
152+
) {
149153
this.unpinServer();
150154
}
151155
return;

test/functional/transactions.test.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
'use strict';
22

3+
const path = require('path');
34
const { expect } = require('chai');
45
const { Topology } = require('../../src/sdam/topology');
56
const { ClientSession } = require('../../src/sessions');
67
const { TestRunnerContext, generateTopologyTests } = require('./spec-runner');
78
const { loadSpecTests } = require('../spec');
9+
const { runUnifiedTest } = require('./unified-spec-runner/runner');
810
const { MongoNetworkError } = require('../../src/error');
911

1012
function ignoreNsNotFoundForListIndexes(err) {
@@ -79,14 +81,30 @@ class TransactionsRunnerContext extends TestRunnerContext {
7981
}
8082
}
8183

84+
describe('Transactions Spec Unified Tests', function () {
85+
for (const transactionTest of loadSpecTests(path.join('transactions', 'unified'))) {
86+
expect(transactionTest).to.exist;
87+
context(String(transactionTest.description), function () {
88+
for (const test of transactionTest.tests) {
89+
it(String(test.description), {
90+
metadata: { sessions: { skipLeakTests: true } },
91+
test: async function () {
92+
await runUnifiedTest(this, transactionTest, test);
93+
}
94+
});
95+
}
96+
});
97+
}
98+
});
99+
82100
describe('Transactions', function () {
83101
const testContext = new TransactionsRunnerContext();
84102

85103
[
86-
{ name: 'spec tests', specPath: 'transactions' },
104+
{ name: 'spec tests', specPath: path.join('transactions', 'legacy') },
87105
{
88106
name: 'withTransaction spec tests',
89-
specPath: 'transactions/convenient-api'
107+
specPath: path.join('transactions', 'convenient-api')
90108
}
91109
].forEach(suiteSpec => {
92110
describe(suiteSpec.name, function () {

test/functional/unified-spec-runner/entities.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,6 @@ export class EntitiesMap<E = Entity> extends Map<string, E> {
272272
options.defaultTransactionOptions.maxCommitTimeMS = defaultOptions.maxCommitTimeMS;
273273
}
274274
}
275-
276275
const session = client.startSession(options);
277276
map.set(entity.session.id, session);
278277
} else if ('bucket' in entity) {

test/functional/unified-spec-runner/operations.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable @typescript-eslint/no-unused-vars */
22
import { expect } from 'chai';
3-
import { Collection, Db, GridFSFile, MongoClient, ObjectId } from '../../../src';
3+
import { Collection, Db, GridFSFile, MongoClient, ObjectId, AbstractCursor } from '../../../src';
44
import { ReadConcern } from '../../../src/read_concern';
55
import { ReadPreference } from '../../../src/read_preference';
66
import { WriteConcern } from '../../../src/write_concern';
@@ -11,6 +11,7 @@ import { expectErrorCheck, resultCheck } from './match';
1111
import type { OperationDescription } from './schema';
1212
import { CommandStartedEvent } from '../../../src/cmap/command_monitoring_events';
1313
import { translateOptions } from './unified-utils';
14+
import { getSymbolFrom } from '../../tools/utils';
1415

1516
interface OperationFunctionParams {
1617
client: MongoClient;
@@ -21,6 +22,18 @@ interface OperationFunctionParams {
2122
type RunOperationFn = (p: OperationFunctionParams) => Promise<Document | boolean | number | void>;
2223
export const operations = new Map<string, RunOperationFn>();
2324

25+
function executeWithPotentialSession(
26+
entities: EntitiesMap,
27+
operation: OperationDescription,
28+
cursor: AbstractCursor
29+
) {
30+
const session = entities.getEntity('session', operation.arguments.session, false);
31+
if (session) {
32+
cursor.session = session;
33+
}
34+
return cursor.toArray();
35+
}
36+
2437
operations.set('abortTransaction', async ({ entities, operation }) => {
2538
const session = entities.getEntity('session', operation.object);
2639
return session.abortTransaction();
@@ -31,19 +44,18 @@ operations.set('aggregate', async ({ entities, operation }) => {
3144
if (!(dbOrCollection instanceof Db || dbOrCollection instanceof Collection)) {
3245
throw new Error(`Operation object '${operation.object}' must be a db or collection`);
3346
}
34-
return dbOrCollection
35-
.aggregate(operation.arguments.pipeline, {
36-
allowDiskUse: operation.arguments.allowDiskUse,
37-
batchSize: operation.arguments.batchSize,
38-
bypassDocumentValidation: operation.arguments.bypassDocumentValidation,
39-
maxTimeMS: operation.arguments.maxTimeMS,
40-
maxAwaitTimeMS: operation.arguments.maxAwaitTimeMS,
41-
collation: operation.arguments.collation,
42-
hint: operation.arguments.hint,
43-
let: operation.arguments.let,
44-
out: operation.arguments.out
45-
})
46-
.toArray();
47+
const cursor = dbOrCollection.aggregate(operation.arguments.pipeline, {
48+
allowDiskUse: operation.arguments.allowDiskUse,
49+
batchSize: operation.arguments.batchSize,
50+
bypassDocumentValidation: operation.arguments.bypassDocumentValidation,
51+
maxTimeMS: operation.arguments.maxTimeMS,
52+
maxAwaitTimeMS: operation.arguments.maxAwaitTimeMS,
53+
collation: operation.arguments.collation,
54+
hint: operation.arguments.hint,
55+
let: operation.arguments.let,
56+
out: operation.arguments.out
57+
});
58+
return executeWithPotentialSession(entities, operation, cursor);
4759
});
4860

4961
operations.set('assertCollectionExists', async ({ operation, client }) => {
@@ -232,7 +244,8 @@ operations.set('endSession', async ({ entities, operation }) => {
232244
operations.set('find', async ({ entities, operation }) => {
233245
const collection = entities.getEntity('collection', operation.object);
234246
const { filter, sort, batchSize, limit, let: vars } = operation.arguments;
235-
return collection.find(filter, { sort, batchSize, limit, let: vars }).toArray();
247+
const cursor = collection.find(filter, { sort, batchSize, limit, let: vars });
248+
return executeWithPotentialSession(entities, operation, cursor);
236249
});
237250

238251
operations.set('findOneAndReplace', async ({ entities, operation }) => {

test/spec/transactions/README.rst

Lines changed: 8 additions & 4 deletions

0 commit comments

Comments
 (0)