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

test(NODE-3049): drivers atlas testing #3805

Merged
merged 3 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"check:unit": "mocha test/unit",
"check:ts": "node ./node_modules/typescript/bin/tsc -v && node ./node_modules/typescript/bin/tsc --noEmit",
"check:atlas": "mocha --config test/manual/mocharc.json test/manual/atlas_connectivity.test.js",
"check:drivers-atlas-testing": "mocha --config test/mocha_mongodb.json test/atlas/drivers_atlas_testing.test.ts",
"check:adl": "mocha --config test/mocha_mongodb.json test/manual/atlas-data-lake-testing",
"check:aws": "nyc mocha --config test/mocha_mongodb.json test/integration/auth/mongodb_aws.test.ts",
"check:oidc": "mocha --config test/mocha_mongodb.json test/manual/mongodb_oidc.prose.test.ts",
Expand Down
10 changes: 10 additions & 0 deletions test/atlas/drivers_atlas_testing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { runUnifiedSuite } from '../tools/unified-spec-runner/runner';

describe('Node Driver Atlas Testing', async function () {
// Astrolabe can, well, take some time. In some cases up to 800s to
// reconfigure clusters.
this.timeout(0);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const spec = JSON.parse(process.env.WORKLOAD_SPECIFICATION!);
runUnifiedSuite([spec]);
});
8 changes: 6 additions & 2 deletions test/tools/reporter/mongodb_reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class MongoDBMochaReporter extends mocha.reporters.Spec {
catchErr(test => this.testEnd(test))
);

process.on('SIGINT', () => this.end(true));
process.prependListener('SIGINT', () => this.end(true));
}

start() {}
Expand Down Expand Up @@ -183,7 +183,11 @@ class MongoDBMochaReporter extends mocha.reporters.Spec {
} catch (error) {
console.error(chalk.red(`Failed to output xunit report! ${error}`));
} finally {
if (ctrlC) process.exit(1);
// Dont exit the process on Astrolabe testing, let it interrupt and
// finish naturally.
if (!process.env.WORKLOAD_SPECIFICATION) {
if (ctrlC) process.exit(1);
}
}
}

Expand Down
15 changes: 15 additions & 0 deletions test/tools/runner/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,18 @@ export class AtlasTestConfiguration extends TestConfiguration {
return process.env.MONGODB_URI!;
}
}

/**
* Test configuration specific to Astrolabe testing.
*/
export class AstrolabeTestConfiguration extends TestConfiguration {
override newClient(): MongoClient {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return new MongoClient(process.env.DRIVERS_ATLAS_TESTING_URI!);
}

override url(): string {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return process.env.DRIVERS_ATLAS_TESTING_URI!;
}
}
6 changes: 5 additions & 1 deletion test/tools/runner/hooks/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require('source-map-support').install({
const path = require('path');
const fs = require('fs');
const { MongoClient } = require('../../../mongodb');
const { TestConfiguration } = require('../config');
const { AstrolabeTestConfiguration, TestConfiguration } = require('../config');
const { getEnvironmentalOptions } = require('../../utils');
const mock = require('../../mongodb-mock/index');
const { inspect } = require('util');
Expand Down Expand Up @@ -105,6 +105,10 @@ const skipBrokenAuthTestBeforeEachHook = function ({ skippedTests } = { skippedT
};

const testConfigBeforeHook = async function () {
if (process.env.DRIVERS_ATLAS_TESTING_URI) {
this.configuration = new AstrolabeTestConfiguration(process.env.DRIVERS_ATLAS_TESTING_URI, {});
return;
}
// TODO(NODE-5035): Implement OIDC support. Creating the MongoClient will fail
// with "MongoInvalidArgumentError: AuthMechanism 'MONGODB-OIDC' not supported"
// as is expected until that ticket goes in. Then this condition gets removed.
Expand Down
44 changes: 44 additions & 0 deletions test/tools/unified-spec-runner/astrolabe_results_writer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { writeFile } from 'node:fs/promises';

import * as path from 'path';

import type { EntitiesMap } from './entities';
import { trace } from './runner';

/**
* Writes the entities saved from the loop operations run in the
* Astrolabe workload executor to the required files.
*/
export class AstrolabeResultsWriter {
constructor(private entities: EntitiesMap) {
this.entities = entities;
}

async write(): Promise<void> {
// Write the events.json to the execution directory.
const errors = this.entities.getEntity('errors', 'errors', false);
const failures = this.entities.getEntity('failures', 'failures', false);
const events = this.entities.getEntity('events', 'events', false);
const iterations = this.entities.getEntity('iterations', 'iterations', false);
const successes = this.entities.getEntity('successes', 'successes', false);

// Write the events.json to the execution directory.
trace('writing events.json');
await writeFile(
path.join(process.env.OUTPUT_DIRECTORY ?? '', 'events.json'),
JSON.stringify({ events: events ?? [], errors: errors ?? [], failures: failures ?? [] })
);

// Write the results.json to the execution directory.
trace('writing results.json');
await writeFile(
path.join(process.env.OUTPUT_DIRECTORY ?? '', 'results.json'),
JSON.stringify({
numErrors: errors?.length ?? 0,
numFailures: failures?.length ?? 0,
numSuccesses: successes ?? 0,
numIterations: iterations ?? 0
})
);
}
}
34 changes: 23 additions & 11 deletions test/tools/unified-spec-runner/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { EventEmitter } from 'events';
import {
AbstractCursor,
ChangeStream,
ClientEncryption,
ClientSession,
Collection,
type CommandFailedEvent,
Expand Down Expand Up @@ -37,13 +38,9 @@ import {
} from '../../mongodb';
import { ejson, getEnvironmentalOptions } from '../../tools/utils';
import type { TestConfiguration } from '../runner/config';
import { EntityEventRegistry } from './entity_event_registry';
import { trace } from './runner';
import type {
ClientEncryption,
ClientEntity,
EntityDescription,
ExpectedLogMessage
} from './schema';
import type { ClientEntity, EntityDescription, ExpectedLogMessage } from './schema';
import {
createClientEncryption,
makeConnectionString,
Expand Down Expand Up @@ -357,9 +354,10 @@ export type Entity =
| AbstractCursor
| UnifiedChangeStream
| GridFSBucket
| Document
| ClientEncryption
| TopologyDescription // From recordTopologyDescription operation
| Document; // Results from operations
| number;

export type EntityCtor =
| typeof UnifiedMongoClient
Expand All @@ -370,7 +368,7 @@ export type EntityCtor =
| typeof AbstractCursor
| typeof GridFSBucket
| typeof UnifiedThread
| ClientEncryption;
| typeof ClientEncryption;

export type EntityTypeId =
| 'client'
Expand All @@ -381,18 +379,26 @@ export type EntityTypeId =
| 'thread'
| 'cursor'
| 'stream'
| 'clientEncryption';
| 'clientEncryption'
| 'errors'
| 'failures'
| 'events'
| 'iterations'
| 'successes';

const ENTITY_CTORS = new Map<EntityTypeId, EntityCtor>();
ENTITY_CTORS.set('client', UnifiedMongoClient);
ENTITY_CTORS.set('db', Db);
ENTITY_CTORS.set('clientEncryption', ClientEncryption);
Copy link
Member Author

Choose a reason for hiding this comment

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

Since we have ClientEncryption in the driver now, I fixed this while I was in the area.

ENTITY_CTORS.set('collection', Collection);
ENTITY_CTORS.set('session', ClientSession);
ENTITY_CTORS.set('bucket', GridFSBucket);
ENTITY_CTORS.set('thread', UnifiedThread);
ENTITY_CTORS.set('cursor', AbstractCursor);
ENTITY_CTORS.set('stream', ChangeStream);

const NO_INSTANCE_CHECK = ['errors', 'failures', 'events', 'successes', 'iterations'];

export class EntitiesMap<E = Entity> extends Map<string, E> {
failPoints: FailPointMap;

Expand Down Expand Up @@ -435,15 +441,20 @@ export class EntitiesMap<E = Entity> extends Map<string, E> {
getEntity(type: 'thread', key: string, assertExists?: boolean): UnifiedThread;
getEntity(type: 'cursor', key: string, assertExists?: boolean): AbstractCursor;
getEntity(type: 'stream', key: string, assertExists?: boolean): UnifiedChangeStream;
getEntity(type: 'iterations', key: string, assertExists?: boolean): number;
getEntity(type: 'successes', key: string, assertExists?: boolean): number;
getEntity(type: 'errors', key: string, assertExists?: boolean): Document[];
getEntity(type: 'failures', key: string, assertExists?: boolean): Document[];
getEntity(type: 'events', key: string, assertExists?: boolean): Document[];
getEntity(type: 'clientEncryption', key: string, assertExists?: boolean): ClientEncryption;
getEntity(type: EntityTypeId, key: string, assertExists = true): Entity | undefined {
const entity = this.get(key);
if (!entity) {
if (assertExists) throw new Error(`Entity '${key}' does not exist`);
return;
}
if (type === 'clientEncryption') {
// we do not have instanceof checking here since csfle might not be installed
if (NO_INSTANCE_CHECK.includes(type)) {
// Skip constructor checks for interfaces.
return entity;
}
const ctor = ENTITY_CTORS.get(type);
Expand Down Expand Up @@ -499,6 +510,7 @@ export class EntitiesMap<E = Entity> extends Map<string, E> {
entity.client.uriOptions
);
const client = new UnifiedMongoClient(uri, entity.client);
new EntityEventRegistry(client, entity.client, map).register();
try {
await client.connect();
} catch (error) {
Expand Down
76 changes: 76 additions & 0 deletions test/tools/unified-spec-runner/entity_event_registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
COMMAND_FAILED,
COMMAND_STARTED,
COMMAND_SUCCEEDED,
CONNECTION_CHECK_OUT_FAILED,
CONNECTION_CHECK_OUT_STARTED,
CONNECTION_CHECKED_IN,
CONNECTION_CHECKED_OUT,
CONNECTION_CLOSED,
CONNECTION_CREATED,
CONNECTION_POOL_CLEARED,
CONNECTION_POOL_CLOSED,
CONNECTION_POOL_CREATED,
CONNECTION_POOL_READY,
CONNECTION_READY
} from '../../mongodb';
import { type EntitiesMap, type UnifiedMongoClient } from './entities';
import { type ClientEntity } from './schema';

/**
* Maps the names of the events the unified runner passes and maps
* them to the names of the events emitted in the driver.
*/
const MAPPINGS = {
PoolCreatedEvent: CONNECTION_POOL_CREATED,
PoolReadyEvent: CONNECTION_POOL_READY,
PoolClearedEvent: CONNECTION_POOL_CLEARED,
PoolClosedEvent: CONNECTION_POOL_CLOSED,
ConnectionCreatedEvent: CONNECTION_CREATED,
ConnectionReadyEvent: CONNECTION_READY,
ConnectionClosedEvent: CONNECTION_CLOSED,
ConnectionCheckOutStartedEvent: CONNECTION_CHECK_OUT_STARTED,
ConnectionCheckOutFailedEvent: CONNECTION_CHECK_OUT_FAILED,
ConnectionCheckedOutEvent: CONNECTION_CHECKED_OUT,
ConnectionCheckedInEvent: CONNECTION_CHECKED_IN,
CommandStartedEvent: COMMAND_STARTED,
CommandSucceededEvent: COMMAND_SUCCEEDED,
CommandFailedEvent: COMMAND_FAILED
};

/**
* Registers events that need to be stored in the entities map, since
* the UnifiedMongoClient does not contain a cyclical dependency on the
* entities map itself.
*/
export class EntityEventRegistry {
constructor(
private client: UnifiedMongoClient,
private clientEntity: ClientEntity,
private entitiesMap: EntitiesMap
) {
this.client = client;
this.clientEntity = clientEntity;
this.entitiesMap = entitiesMap;
}

/**
* Connect the event listeners on the client and the entities map.
*/
register(): void {
if (this.clientEntity.storeEventsAsEntities) {
for (const { id, events } of this.clientEntity.storeEventsAsEntities) {
this.entitiesMap.set(id, []);
for (const eventName of events) {
// Need to map the event names to the Node event names.
this.client.on(MAPPINGS[eventName], () => {
this.entitiesMap.getEntity('events', id).push({
name: eventName,
observedAt: Date.now()
});
});
}
}
}
}
}
Loading