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

chore: improve test filtering to use mocha hooks #3095

Merged
merged 11 commits into from
Jan 10, 2022
1 change: 1 addition & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
"recursive": true,
"timeout": 60000,
"reporter": "test/tools/reporter/mongodb_reporter.js",
"sort": true,
"color": true
}
241 changes: 241 additions & 0 deletions etc/sdam_viz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-var-requires */

baileympearson marked this conversation as resolved.
Show resolved Hide resolved
// run this file with ts-node:
// npx ts-node etc/sdam_viz.js -h

const { MongoClient } = require('../src');
const { now, calculateDurationInMs, arrayStrictEqual, errorStrictEqual } = require('../src/utils');

const util = require('util');
const chalk = require('chalk');
const argv = require('yargs')
.usage('Usage: $0 [options] <connection string>')
.demandCommand(1)
.help('h')
.describe('workload', 'Simulate a read workload')
.describe('writeWorkload', 'Simulate a write workload')
.describe('writeWorkloadInterval', 'Time interval between write workload write attempts')
.describe('writeWorkloadSampleSize', 'Sample size between status display for write workload')
.describe('legacy', 'Use the legacy topology types')
.alias('l', 'legacy')
.alias('w', 'workload')
.alias('h', 'help').argv;

function print(msg) {
console.log(`${chalk.white(new Date().toISOString())} ${msg}`);
}

const uri = argv._[0];
const client = new MongoClient(uri);

function diff(lhs, rhs, fields, comparator) {
return fields.reduce((diff, field) => {
if ((lhs[field] == null || rhs[field] == null) && field !== 'error') {
return diff;
}

if (!comparator(lhs[field], rhs[field])) {
diff.push(
` ${field}: ${chalk.green(`${util.inspect(lhs[field])}`)} => ${chalk.green(
`${util.inspect(rhs[field])}`
)}`
);
}

return diff;
}, []);
}

function serverDescriptionDiff(lhs, rhs) {
const objectIdFields = ['electionId'];
const arrayFields = ['hosts', 'tags'];
const simpleFields = [
'type',
'minWireVersion',
'me',
'setName',
'setVersion',
'electionId',
'primary',
'logicalSessionTimeoutMinutes'
];

return diff(lhs, rhs, simpleFields, (x, y) => x === y)
.concat(diff(lhs, rhs, ['error'], (x, y) => errorStrictEqual(x, y)))
.concat(diff(lhs, rhs, arrayFields, (x, y) => arrayStrictEqual(x, y)))
.concat(diff(lhs, rhs, objectIdFields, (x, y) => x.equals(y)))
.join(',\n');
}

function topologyDescriptionDiff(lhs, rhs) {
const simpleFields = [
'type',
'setName',
'maxSetVersion',
'stale',
'compatible',
'compatibilityError',
'logicalSessionTimeoutMinutes',
'error',
'commonWireVersion'
];

return diff(lhs, rhs, simpleFields, (x, y) => x === y).join(',\n');
}

function visualizeMonitoringEvents(client) {
function print(msg) {
console.error(`${chalk.white(new Date().toISOString())} ${msg}`);
}

client.on('serverHeartbeatStarted', event =>
print(`${chalk.yellow('heartbeat')} ${chalk.bold('started')} host: '${event.connectionId}`)
);

client.on('serverHeartbeatSucceeded', event =>
print(
`${chalk.yellow('heartbeat')} ${chalk.green('succeeded')} host: '${
event.connectionId
}' ${chalk.gray(`(${event.duration} ms)`)}`
)
);

client.on('serverHeartbeatFailed', event =>
print(
`${chalk.yellow('heartbeat')} ${chalk.red('failed')} host: '${
event.connectionId
}' ${chalk.gray(`(${event.duration} ms)`)}`
)
);

// server information
client.on('serverOpening', event => {
print(
`${chalk.cyan('server')} [${event.address}] ${chalk.bold('opening')} in topology#${
event.topologyId
}`
);
});

client.on('serverClosed', event => {
print(
`${chalk.cyan('server')} [${event.address}] ${chalk.bold('closed')} in topology#${
event.topologyId
}`
);
});

client.on('serverDescriptionChanged', event => {
print(`${chalk.cyan('server')} [${event.address}] changed:`);
console.error(serverDescriptionDiff(event.previousDescription, event.newDescription));
});

// topology information
client.on('topologyOpening', event => {
print(`${chalk.magenta('topology')} adding topology#${event.topologyId}`);
});

client.on('topologyClosed', event => {
print(`${chalk.magenta('topology')} removing topology#${event.topologyId}`);
});

client.on('topologyDescriptionChanged', event => {
const diff = topologyDescriptionDiff(event.previousDescription, event.newDescription);
if (diff !== '') {
print(`${chalk.magenta('topology')} [topology#${event.topologyId}] changed:`);
console.error(diff);
}
});
}

async function run() {
print(`connecting to: ${chalk.bold(uri)}`);

visualizeMonitoringEvents(client);
await client.connect();

if (argv.workload) {
scheduleWorkload(client);
}

if (argv.writeWorkload) {
scheduleWriteWorkload(client);
}
}

let workloadTimer;
let workloadCounter = 0;
let workloadInterrupt = false;
async function scheduleWorkload(client) {
if (!workloadInterrupt) {
// immediately reschedule work
workloadTimer = setTimeout(() => scheduleWorkload(client), 7000);
}

const currentWorkload = workloadCounter++;

try {
print(`${chalk.yellow(`workload#${currentWorkload}`)} issuing find...`);
const result = await client
.db('test')
.collection('test')
.find({}, { socketTimeoutMS: 2000 })
.limit(1)
.toArray();

print(
`${chalk.yellow(`workload#${currentWorkload}`)} find completed: ${JSON.stringify(result)}`
);
} catch (e) {
print(`${chalk.yellow(`workload#${currentWorkload}`)} find failed: ${e.message}`);
}
}

let writeWorkloadTimer;
let writeWorkloadCounter = 0;
let averageWriteMS = 0;
let completedWriteWorkloads = 0;
const writeWorkloadSampleSize = argv.writeWorkloadSampleSize || 100;
const writeWorkloadInterval = argv.writeWorkloadInterval || 100;
async function scheduleWriteWorkload(client) {
if (!workloadInterrupt) {
// immediately reschedule work
writeWorkloadTimer = setTimeout(() => scheduleWriteWorkload(client), writeWorkloadInterval);
}

const currentWriteWorkload = writeWorkloadCounter++;

try {
const start = now();
await client.db('test').collection('test').insertOne({ a: 42 });
averageWriteMS = 0.2 * calculateDurationInMs(start) + 0.8 * averageWriteMS;

completedWriteWorkloads++;
if (completedWriteWorkloads % writeWorkloadSampleSize === 0) {
print(
`${chalk.yellow(
`workload#${currentWriteWorkload}`
)} completed ${completedWriteWorkloads} writes with average time: ${averageWriteMS}`
);
}
} catch (e) {
print(`${chalk.yellow(`workload#${currentWriteWorkload}`)} write failed: ${e.message}`);
}
}

let exitRequestCount = 0;
process.on('SIGINT', async function () {
exitRequestCount++;
if (exitRequestCount > 3) {
console.log('force quitting...');
process.exit(1);
}

workloadInterrupt = true;
clearTimeout(workloadTimer);
clearTimeout(writeWorkloadTimer);
await client.close();
});

run().catch(error => console.log('Caught', error));
Original file line number Diff line number Diff line change
Expand Up @@ -552,14 +552,16 @@ describe('Client Side Encryption Prose Tests', function () {
const limitsKey = loadLimits('limits-key.json');
const limitsDoc = loadLimits('limits-doc.json');

before(function () {
// First, perform the setup.
let firstTimeSetup = true;
beforeEach(async function () {
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved
if (firstTimeSetup) {
firstTimeSetup = false;
dariakp marked this conversation as resolved.
Show resolved Hide resolved
// First, perform the setup.

// #. Create a MongoClient without encryption enabled (referred to as ``client``).
this.client = this.configuration.newClient();
// #. Create a MongoClient without encryption enabled (referred to as ``client``).
this.client = this.configuration.newClient();

return (
this.client
await this.client
.connect()
// #. Using ``client``, drop and create the collection ``db.coll`` configured with the included JSON schema `limits/limits-schema.json <../limits/limits-schema.json>`_.
.then(() => dropCollection(this.client.db(dataDbName), dataCollName))
Expand All @@ -575,11 +577,9 @@ describe('Client Side Encryption Prose Tests', function () {
.db(keyVaultDbName)
.collection(keyVaultCollName)
.insertOne(limitsKey, { writeConcern: { w: 'majority' } });
})
);
});
});
}

beforeEach(function () {
// #. Create a MongoClient configured with auto encryption (referred to as ``client_encrypted``)
// Configure with the ``local`` KMS provider as follows:
// .. code:: javascript
Expand Down
13 changes: 13 additions & 0 deletions test/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ about the types of tests and how to run them.
- [Running the Tests in Evergreen](#running-the-tests-in-evergreen)
- [Using a Pre-Release Version of a Dependent Library](#using-a-pre-release-version-of-a-dependent-library)
- [Manually Testing the Driver](#manually-testing-the-driver)
- [Writing Tests](#writing-tests)
- [Testing with Special Environments](#testing-with-special-environments)

## About the Tests
Expand Down Expand Up @@ -143,6 +144,18 @@ modify the steps to work with existing Node projects.

> **Note:** When making driver changes, you will need to run `npm run build:ts` with each change in order for it to take effect.

## Writing Tests

> TODO: flesh this section out more
dariakp marked this conversation as resolved.
Show resolved Hide resolved

We use mocha to construct our test suites and chai to assert expectations.

Some special notes on how mocha works with our testing setup:

- `before` hooks will run even if a test is skipped by the environment it runs on.
- So, for example, if your before hook does logic that can only run on a certain server version you can't depend on your test block metadata to filter for that.
- `after` hooks cannot be used to clean up clients because the session leak checker currently runs its afterEach hook first.
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved

## Testing with Special Environments

In order to test some features, you will need to generate and set a specialized group of environment variables. The subsections below will walk you through how to generate and set the environment variables for these features.
Expand Down
28 changes: 0 additions & 28 deletions test/tools/deprecate_warning_test_program.js

This file was deleted.

Loading