Skip to content

Commit fa4352d

Browse files
authored
test: new test runner changes from lb support (#2901)
* test: new test runner changes from lb support * test: refactoring unified runner * test: bring back unified event length check * test: run txn tests in manual lb tests * test: skip failing snapshot test and refactor matchers * test: remove lb manual test * test: skip other failed tests
1 parent eadeb01 commit fa4352d

File tree

9 files changed

+344
-63
lines changed

9 files changed

+344
-63
lines changed

test/functional/sessions.test.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,10 @@ describe('Sessions - functional', function () {
217217
'Server returns an error on listCollections with snapshot',
218218
'Server returns an error on listDatabases with snapshot',
219219
'Server returns an error on listIndexes with snapshot',
220-
'Server returns an error on runCommand with snapshot'
220+
'Server returns an error on runCommand with snapshot',
221+
'Server returns an error on findOneAndUpdate with snapshot',
222+
'Server returns an error on deleteOne with snapshot',
223+
'Server returns an error on updateOne with snapshot'
221224
]
222225
};
223226
const testsToSkip = skipTestMap[sessionTests.description] || [];

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

+120-24
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,20 @@ import { WriteConcern } from '../../../src/write_concern';
1111
import { ReadPreference } from '../../../src/read_preference';
1212
import { ClientSession } from '../../../src/sessions';
1313
import { ChangeStream } from '../../../src/change_stream';
14+
import { FindCursor } from '../../../src/cursor/find_cursor';
1415
import type { ClientEntity, EntityDescription } from './schema';
16+
import type {
17+
ConnectionPoolCreatedEvent,
18+
ConnectionPoolClosedEvent,
19+
ConnectionCreatedEvent,
20+
ConnectionReadyEvent,
21+
ConnectionClosedEvent,
22+
ConnectionCheckOutStartedEvent,
23+
ConnectionCheckOutFailedEvent,
24+
ConnectionCheckedOutEvent,
25+
ConnectionCheckedInEvent,
26+
ConnectionPoolClearedEvent
27+
} from '../../../src/cmap/connection_pool_events';
1528
import type {
1629
CommandFailedEvent,
1730
CommandStartedEvent,
@@ -26,6 +39,17 @@ interface UnifiedChangeStream extends ChangeStream {
2639
}
2740

2841
export type CommandEvent = CommandStartedEvent | CommandSucceededEvent | CommandFailedEvent;
42+
export type CmapEvent =
43+
| ConnectionPoolCreatedEvent
44+
| ConnectionPoolClosedEvent
45+
| ConnectionCreatedEvent
46+
| ConnectionReadyEvent
47+
| ConnectionClosedEvent
48+
| ConnectionCheckOutStartedEvent
49+
| ConnectionCheckOutFailedEvent
50+
| ConnectionCheckedOutEvent
51+
| ConnectionCheckedInEvent
52+
| ConnectionPoolClearedEvent;
2953

3054
function serverApiConfig() {
3155
if (process.env.MONGODB_API_VERSION) {
@@ -38,52 +62,105 @@ function getClient(address) {
3862
return new MongoClient(`mongodb://${address}`, serverApi ? { serverApi } : {});
3963
}
4064

65+
type PushFunction = (e: CommandEvent | CmapEvent) => void;
66+
4167
export class UnifiedMongoClient extends MongoClient {
42-
events: CommandEvent[];
68+
commandEvents: CommandEvent[];
69+
cmapEvents: CmapEvent[];
4370
failPoints: Document[];
4471
ignoredEvents: string[];
45-
observedEvents: ('commandStarted' | 'commandSucceeded' | 'commandFailed')[];
72+
observedCommandEvents: ('commandStarted' | 'commandSucceeded' | 'commandFailed')[];
73+
observedCmapEvents: (
74+
| 'connectionPoolCreated'
75+
| 'connectionPoolClosed'
76+
| 'connectionPoolCleared'
77+
| 'connectionCreated'
78+
| 'connectionReady'
79+
| 'connectionClosed'
80+
| 'connectionCheckOutStarted'
81+
| 'connectionCheckOutFailed'
82+
| 'connectionCheckedOut'
83+
| 'connectionCheckedIn'
84+
)[];
4685

47-
static EVENT_NAME_LOOKUP = {
86+
static COMMAND_EVENT_NAME_LOOKUP = {
4887
commandStartedEvent: 'commandStarted',
4988
commandSucceededEvent: 'commandSucceeded',
5089
commandFailedEvent: 'commandFailed'
5190
} as const;
5291

92+
static CMAP_EVENT_NAME_LOOKUP = {
93+
poolCreatedEvent: 'connectionPoolCreated',
94+
poolClosedEvent: 'connectionPoolClosed',
95+
poolClearedEvent: 'connectionPoolCleared',
96+
connectionCreatedEvent: 'connectionCreated',
97+
connectionReadyEvent: 'connectionReady',
98+
connectionClosedEvent: 'connectionClosed',
99+
connectionCheckOutStartedEvent: 'connectionCheckOutStarted',
100+
connectionCheckOutFailedEvent: 'connectionCheckOutFailed',
101+
connectionCheckedOutEvent: 'connectionCheckedOut',
102+
connectionCheckedInEvent: 'connectionCheckedIn'
103+
} as const;
104+
53105
constructor(url: string, description: ClientEntity) {
54106
super(url, {
55107
monitorCommands: true,
56108
...description.uriOptions,
57109
serverApi: description.serverApi ? description.serverApi : serverApiConfig()
58110
});
59-
this.events = [];
111+
this.commandEvents = [];
112+
this.cmapEvents = [];
60113
this.failPoints = [];
61114
this.ignoredEvents = [
62115
...(description.ignoreCommandMonitoringEvents ?? []),
63116
'configureFailPoint'
64117
];
65-
// apm
66-
this.observedEvents = (description.observeEvents ?? []).map(
67-
e => UnifiedMongoClient.EVENT_NAME_LOOKUP[e]
68-
);
69-
for (const eventName of this.observedEvents) {
70-
this.on(eventName, this.pushEvent);
118+
this.observedCommandEvents = (description.observeEvents ?? [])
119+
.map(e => UnifiedMongoClient.COMMAND_EVENT_NAME_LOOKUP[e])
120+
.filter(e => !!e);
121+
this.observedCmapEvents = (description.observeEvents ?? [])
122+
.map(e => UnifiedMongoClient.CMAP_EVENT_NAME_LOOKUP[e])
123+
.filter(e => !!e);
124+
for (const eventName of this.observedCommandEvents) {
125+
this.on(eventName, this.pushCommandEvent);
126+
}
127+
for (const eventName of this.observedCmapEvents) {
128+
this.on(eventName, this.pushCmapEvent);
71129
}
72130
}
73131

74-
// NOTE: pushEvent must be an arrow function
75-
pushEvent: (e: CommandEvent) => void = e => {
76-
if (!this.ignoredEvents.includes(e.commandName)) {
77-
this.events.push(e);
132+
isIgnored(e: CommandEvent | CmapEvent): boolean {
133+
return this.ignoredEvents.includes(e.commandName);
134+
}
135+
136+
// NOTE: pushCommandEvent must be an arrow function
137+
pushCommandEvent: (e: CommandEvent) => void = e => {
138+
if (!this.isIgnored(e)) {
139+
this.commandEvents.push(e);
78140
}
79141
};
80142

81-
/** Disables command monitoring for the client and returns a list of the captured events. */
82-
stopCapturingEvents(): CommandEvent[] {
83-
for (const eventName of this.observedEvents) {
84-
this.off(eventName, this.pushEvent);
143+
// NOTE: pushCmapEvent must be an arrow function
144+
pushCmapEvent: (e: CmapEvent) => void = e => {
145+
this.cmapEvents.push(e);
146+
};
147+
148+
stopCapturingEvents(pushFn: PushFunction): void {
149+
const observedEvents = this.observedCommandEvents.concat(this.observedCmapEvents);
150+
for (const eventName of observedEvents) {
151+
this.off(eventName, pushFn);
85152
}
86-
return this.events;
153+
}
154+
155+
/** Disables command monitoring for the client and returns a list of the captured events. */
156+
stopCapturingCommandEvents(): CommandEvent[] {
157+
this.stopCapturingEvents(this.pushCommandEvent);
158+
return this.commandEvents;
159+
}
160+
161+
stopCapturingCmapEvents(): CmapEvent[] {
162+
this.stopCapturingEvents(this.pushCmapEvent);
163+
return this.cmapEvents;
87164
}
88165
}
89166

@@ -137,6 +214,7 @@ export type Entity =
137214
| Db
138215
| Collection
139216
| ClientSession
217+
| FindCursor
140218
| UnifiedChangeStream
141219
| GridFSBucket
142220
| Document; // Results from operations
@@ -147,16 +225,25 @@ export type EntityCtor =
147225
| typeof Collection
148226
| typeof ClientSession
149227
| typeof ChangeStream
228+
| typeof FindCursor
150229
| typeof GridFSBucket;
151230

152-
export type EntityTypeId = 'client' | 'db' | 'collection' | 'session' | 'bucket' | 'stream';
231+
export type EntityTypeId =
232+
| 'client'
233+
| 'db'
234+
| 'collection'
235+
| 'session'
236+
| 'bucket'
237+
| 'cursor'
238+
| 'stream';
153239

154240
const ENTITY_CTORS = new Map<EntityTypeId, EntityCtor>();
155241
ENTITY_CTORS.set('client', UnifiedMongoClient);
156242
ENTITY_CTORS.set('db', Db);
157243
ENTITY_CTORS.set('collection', Collection);
158244
ENTITY_CTORS.set('session', ClientSession);
159245
ENTITY_CTORS.set('bucket', GridFSBucket);
246+
ENTITY_CTORS.set('cursor', FindCursor);
160247
ENTITY_CTORS.set('stream', ChangeStream);
161248

162249
export class EntitiesMap<E = Entity> extends Map<string, E> {
@@ -172,6 +259,7 @@ export class EntitiesMap<E = Entity> extends Map<string, E> {
172259
mapOf(type: 'collection'): EntitiesMap<Collection>;
173260
mapOf(type: 'session'): EntitiesMap<ClientSession>;
174261
mapOf(type: 'bucket'): EntitiesMap<GridFSBucket>;
262+
mapOf(type: 'cursor'): EntitiesMap<FindCursor>;
175263
mapOf(type: 'stream'): EntitiesMap<UnifiedChangeStream>;
176264
mapOf(type: EntityTypeId): EntitiesMap<Entity> {
177265
const ctor = ENTITY_CTORS.get(type);
@@ -186,6 +274,7 @@ export class EntitiesMap<E = Entity> extends Map<string, E> {
186274
getEntity(type: 'collection', key: string, assertExists?: boolean): Collection;
187275
getEntity(type: 'session', key: string, assertExists?: boolean): ClientSession;
188276
getEntity(type: 'bucket', key: string, assertExists?: boolean): GridFSBucket;
277+
getEntity(type: 'cursor', key: string, assertExists?: boolean): FindCursor;
189278
getEntity(type: 'stream', key: string, assertExists?: boolean): UnifiedChangeStream;
190279
getEntity(type: EntityTypeId, key: string, assertExists = true): Entity {
191280
const entity = this.get(key);
@@ -205,11 +294,17 @@ export class EntitiesMap<E = Entity> extends Map<string, E> {
205294

206295
async cleanup(): Promise<void> {
207296
await this.failPoints.disableFailPoints();
208-
for (const [, client] of this.mapOf('client')) {
209-
await client.close();
297+
for (const [, cursor] of this.mapOf('cursor')) {
298+
await cursor.close();
299+
}
300+
for (const [, stream] of this.mapOf('stream')) {
301+
await stream.close();
210302
}
211303
for (const [, session] of this.mapOf('session')) {
212-
await session.endSession();
304+
await session.endSession({ force: true });
305+
}
306+
for (const [, client] of this.mapOf('client')) {
307+
await client.close();
213308
}
214309
this.clear();
215310
}
@@ -222,7 +317,8 @@ export class EntitiesMap<E = Entity> extends Map<string, E> {
222317
for (const entity of entities ?? []) {
223318
if ('client' in entity) {
224319
const useMultipleMongoses =
225-
config.topologyType === 'Sharded' && entity.client.useMultipleMongoses;
320+
(config.topologyType === 'LoadBalanced' || config.topologyType === 'Sharded') &&
321+
entity.client.useMultipleMongoses;
226322
const uri = config.url({ useMultipleMongoses });
227323
const client = new UnifiedMongoClient(uri, entity.client);
228324
await client.connect();

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

+65-14
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
import { expect } from 'chai';
2+
import { inspect } from 'util';
23
import { Binary, Document, Long, ObjectId, MongoError } from '../../../src';
34
import {
45
CommandFailedEvent,
56
CommandStartedEvent,
67
CommandSucceededEvent
78
} from '../../../src/cmap/command_monitoring_events';
8-
import { CommandEvent, EntitiesMap } from './entities';
9-
import { ExpectedError, ExpectedEvent } from './schema';
9+
import {
10+
ConnectionPoolCreatedEvent,
11+
ConnectionPoolClosedEvent,
12+
ConnectionCreatedEvent,
13+
ConnectionReadyEvent,
14+
ConnectionClosedEvent,
15+
ConnectionCheckOutStartedEvent,
16+
ConnectionCheckOutFailedEvent,
17+
ConnectionCheckedOutEvent,
18+
ConnectionCheckedInEvent,
19+
ConnectionPoolClearedEvent
20+
} from '../../../src/cmap/connection_pool_events';
21+
import { CommandEvent, CmapEvent, EntitiesMap } from './entities';
22+
import { ExpectedCmapEvent, ExpectedCommandEvent, ExpectedError } from './schema';
1023

1124
export interface ExistsOperator {
1225
$$exists: boolean;
@@ -235,32 +248,70 @@ export function specialCheck(
235248
}
236249
}
237250

251+
// CMAP events where the payload does not matter.
252+
const EMPTY_CMAP_EVENTS = {
253+
poolCreatedEvent: ConnectionPoolCreatedEvent,
254+
poolClosedEvent: ConnectionPoolClosedEvent,
255+
connectionCreatedEvent: ConnectionCreatedEvent,
256+
connectionReadyEvent: ConnectionReadyEvent,
257+
connectionCheckOutStartedEvent: ConnectionCheckOutStartedEvent,
258+
connectionCheckOutFailedEvent: ConnectionCheckOutFailedEvent,
259+
connectionCheckedOutEvent: ConnectionCheckedOutEvent,
260+
connectionCheckedInEvent: ConnectionCheckedInEvent
261+
};
262+
263+
function validEmptyCmapEvent(
264+
expected: ExpectedCommandEvent | ExpectedCmapEvent,
265+
actual: CommandEvent | CmapEvent
266+
) {
267+
return Object.values(EMPTY_CMAP_EVENTS).some(value => {
268+
return actual instanceof value;
269+
});
270+
}
271+
238272
export function matchesEvents(
239-
expected: ExpectedEvent[],
240-
actual: CommandEvent[],
273+
expected: (ExpectedCommandEvent & ExpectedCmapEvent)[],
274+
actual: (CommandEvent & CmapEvent)[],
241275
entities: EntitiesMap
242276
): void {
243-
// TODO: NodeJS Driver has extra events
244-
// expect(actual).to.have.lengthOf(expected.length);
277+
if (actual.length !== expected.length) {
278+
const actualNames = actual.map(a => a.constructor.name);
279+
const expectedNames = expected.map(e => Object.keys(e)[0]);
280+
expect.fail(
281+
`Expected event count mismatch, expected ${inspect(expectedNames)} but got ${inspect(
282+
actualNames
283+
)}`
284+
);
285+
}
245286

246287
for (const [index, actualEvent] of actual.entries()) {
247288
const expectedEvent = expected[index];
248289

249-
if (expectedEvent.commandStartedEvent && actualEvent instanceof CommandStartedEvent) {
290+
if (expectedEvent.commandStartedEvent) {
291+
expect(actualEvent).to.be.instanceOf(CommandStartedEvent);
250292
resultCheck(actualEvent, expectedEvent.commandStartedEvent, entities, [
251293
`events[${index}].commandStartedEvent`
252294
]);
253-
} else if (
254-
expectedEvent.commandSucceededEvent &&
255-
actualEvent instanceof CommandSucceededEvent
256-
) {
295+
} else if (expectedEvent.commandSucceededEvent) {
296+
expect(actualEvent).to.be.instanceOf(CommandSucceededEvent);
257297
resultCheck(actualEvent, expectedEvent.commandSucceededEvent, entities, [
258298
`events[${index}].commandSucceededEvent`
259299
]);
260-
} else if (expectedEvent.commandFailedEvent && actualEvent instanceof CommandFailedEvent) {
300+
} else if (expectedEvent.commandFailedEvent) {
301+
expect(actualEvent).to.be.instanceOf(CommandFailedEvent);
261302
expect(actualEvent.commandName).to.equal(expectedEvent.commandFailedEvent.commandName);
262-
} else {
263-
expect.fail(`Events must be one of the known types, got ${actualEvent}`);
303+
} else if (expectedEvent.connectionClosedEvent) {
304+
expect(actualEvent).to.be.instanceOf(ConnectionClosedEvent);
305+
if (expectedEvent.connectionClosedEvent.hasServiceId) {
306+
expect(actualEvent).property('serviceId').to.exist;
307+
}
308+
} else if (expectedEvent.poolClearedEvent) {
309+
expect(actualEvent).to.be.instanceOf(ConnectionPoolClearedEvent);
310+
if (expectedEvent.poolClearedEvent.hasServiceId) {
311+
expect(actualEvent).property('serviceId').to.exist;
312+
}
313+
} else if (!validEmptyCmapEvent(expectedEvent, actualEvent)) {
314+
expect.fail(`Events must be one of the known types, got ${inspect(actualEvent)}`);
264315
}
265316
}
266317
}

0 commit comments

Comments
 (0)