Skip to content

refactor(NODE-3717): test reorg part 4 #3086

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

Merged
merged 18 commits into from
Dec 17, 2021
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
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use strict';

const { LEGACY_HELLO_COMMAND } = require('../../src/constants');
const { LEGACY_HELLO_COMMAND } = require('../../../src/constants');

const setupDatabase = require('./shared').setupDatabase,
expect = require('chai').expect;
const { setupDatabase } = require('../shared');
const { expect } = require('chai');

const ignoredCommands = [LEGACY_HELLO_COMMAND, 'endSessions'];
const test = { commands: { started: [], succeeded: [] } };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
'use strict';
const assert = require('assert');
const { Transform, PassThrough } = require('stream');
const { MongoNetworkError } = require('../../src/error');
const { delay, setupDatabase, withClient, withCursor } = require('./shared');
const mock = require('../tools/mongodb-mock/index');
const { EventCollector, getSymbolFrom } = require('../tools/utils');
const { delay, setupDatabase, withClient, withCursor } = require('../shared');
const mock = require('../../tools/mongodb-mock/index');
const { EventCollector, getSymbolFrom } = require('../../tools/utils');
const chai = require('chai');

const expect = chai.expect;
const sinon = require('sinon');
const { ObjectId, Timestamp, Long, ReadPreference } = require('../../src');
const { ObjectId, Timestamp, Long, ReadPreference, MongoNetworkError } = require('../../../src');
const fs = require('fs');
const os = require('os');
const path = require('path');
const crypto = require('crypto');
const { LEGACY_HELLO_COMMAND } = require('../../src/constants');
const { LEGACY_HELLO_COMMAND } = require('../../../src/constants');
chai.use(require('chai-subset'));
const { isHello } = require('../../src/utils');
const { isHello } = require('../../../src/utils');

function withChangeStream(dbName, collectionName, callback) {
if (arguments.length === 1) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
'use strict';

const path = require('path');
const chai = require('chai');
const { loadSpecTests } = require('../spec');
const { runUnifiedSuite } = require('../tools/unified-spec-runner/runner');
const { expect } = require('chai');
const { loadSpecTests } = require('../../spec');
const { runUnifiedSuite } = require('../../tools/unified-spec-runner/runner');
const camelCase = require('lodash.camelcase');
const { setupDatabase } = require('./shared');
const { delay } = require('./shared');

const expect = chai.expect;
const { LEGACY_HELLO_COMMAND } = require('../../src/constants');
const { LEGACY_HELLO_COMMAND } = require('../../../src/constants');
const { delay, setupDatabase } = require('../shared');

describe('Change Streams Spec - Unified', function () {
runUnifiedSuite(loadSpecTests(path.join('change-streams', 'unified')));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';

const { expect } = require('chai');
const { loadSpecTests } = require('../spec/index');
const { runUnifiedTest } = require('../tools/unified-spec-runner/runner');
const { loadSpecTests } = require('../../spec/index');
const { runUnifiedTest } = require('../../tools/unified-spec-runner/runner');

describe('Collection management unified spec tests', function () {
for (const collectionManagementTest of loadSpecTests('collection-management')) {
Expand Down
295 changes: 295 additions & 0 deletions test/integration/command-monitoring/command_monitoring.spec.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
'use strict';

const { setupDatabase, filterOutCommands } = require('../shared');
const { loadSpecTests } = require('../../spec');
const { LEGACY_HELLO_COMMAND } = require('../../../src/constants');
const { expect } = require('chai');
const { runUnifiedTest } = require('../../tools/unified-spec-runner/runner');

describe('Command Monitoring spec tests', function () {
describe('command monitoring legacy spec tests', function () {
before(function () {
return setupDatabase(this.configuration);
});

// TODO: The worst part about this custom validation method is that it does not
// provide the rich context of failure location that chai gives us out of
// the box. I investigated extending chai, however their internal implementation
// does not reuse other internal methods, so we'd have to bring lodash in.
// It may be worth seeing if we can improve on this, as we might need the
// behavior in other future YAML tests.
const maybeLong = val => (typeof val.equals === 'function' ? val.toNumber() : val);
function apmExpect(actual, expected, parentKey) {
Object.keys(expected).forEach(key => {
expect(actual).to.include.key(key);

// TODO: This is a workaround that works because all sorts in the specs
// are objects with one key; ideally we'd want to adjust the spec definitions
// to indicate whether order matters for any given key and set general
// expectations accordingly (see NODE-3235)
if (key === 'sort') {
expect(actual[key]).to.be.instanceOf(Map);
expect(Object.keys(expected[key])).to.have.lengthOf(1);
expect(actual[key].size).to.equal(1);
expect(actual[key].get(Object.keys(expected[key])[0])).to.equal(
Object.values(expected[key])[0]
);
return;
}

if (Array.isArray(expected[key])) {
expect(actual[key]).to.be.instanceOf(Array);
expect(actual[key]).to.have.lengthOf(expected[key].length);
for (let i = 0; i < expected[key].length; ++i) {
apmExpect(actual[key][i], expected[key][i], key);
}

return;
}

if (expected[key] === 42 || expected[key] === '42' || expected[key] === '') {
if (key === 'code' && expected[key] === 42) {
expect(actual[key]).to.be.greaterThan(0);
}

if (key === 'errmsg' && expected[key] === '') {
expect(actual[key]).to.have.lengthOf.at.least(1); // >= 1
}

if (key === 'getmore' || (parentKey === 'cursor' && key === 'id')) {
expect(maybeLong(actual[key])).to.be.greaterThan(0);
}

return;
}

// cheap isPlainObject clone
if (Object.prototype.toString.call(expected[key]) === '[object Object]') {
apmExpect(actual[key], expected[key], key);
return;
}

// otherwise compare the values
expect(maybeLong(actual[key]), key).to.deep.equal(expected[key]);
});
}

function validateCommandStartedEvent(expected, event) {
expect(event.commandName).to.equal(expected.command_name);
expect(event.databaseName).to.equal(expected.database_name);
apmExpect(event.command, expected.command);
}

function validateCommandSucceededEvent(expected, event) {
expect(event.commandName).to.equal(expected.command_name);
apmExpect(event.reply, expected.reply);
}

function validateCommandFailedEvent(expected, event) {
expect(event.commandName).to.equal(expected.command_name);
}

function validateExpectations(expectation, results) {
if (expectation.command_started_event) {
validateCommandStartedEvent(expectation.command_started_event, results.starts.shift());
} else if (expectation.command_succeeded_event) {
validateCommandSucceededEvent(
expectation.command_succeeded_event,
results.successes.shift()
);
} else if (expectation.command_failed_event) {
validateCommandFailedEvent(expectation.command_failed_event, results.failures.shift());
}
}

function executeOperation(client, scenario, test) {
// Get the operation
const operation = test.operation;
// Get the command name
const commandName = operation.name;
// Get the arguments
const args = operation.arguments || {};
// Get the database instance
const db = client.db(scenario.database_name);
// Get the collection
const collection = db.collection(scenario.collection_name);
// Parameters
const params = [];
// Options
let options = null;
// Get the data
const data = scenario.data;
// Command Monitoring context
const monitoringResults = {
successes: [],
failures: [],
starts: []
};

// Drop the collection
return collection
.drop()
.catch(err => {
// potentially skip this error
if (!err.message.match(/ns not found/)) throw err;
})
.then(() => collection.insertMany(data))
.then(r => {
expect(data).to.have.length(Object.keys(r.insertedIds).length);

// Set up the listeners
client.on(
'commandStarted',
filterOutCommands([LEGACY_HELLO_COMMAND, 'endSessions'], monitoringResults.starts)
);
client.on(
'commandFailed',
filterOutCommands([LEGACY_HELLO_COMMAND, 'endSessions'], monitoringResults.failures)
);
client.on(
'commandSucceeded',
filterOutCommands([LEGACY_HELLO_COMMAND, 'endSessions'], monitoringResults.successes)
);

// Unpack the operation
if (args.options) options = args.options;
if (args.filter) params.push(args.filter);
if (args.deletes) params.push(args.deletes);
if (args.document) params.push(args.document);
if (args.documents) params.push(args.documents);
if (args.update) params.push(args.update);
if (args.requests) {
if (operation.name !== 'bulkWrite') {
params.push(args.requests);
} else {
params.push(
args.requests.map(r => {
return { [r.name]: r.arguments.document || r.arguments };
})
);
}
}

if (args.writeConcern) {
options = options || {};
options.writeConcern = args.writeConcern;
}

if (typeof args.ordered === 'boolean') {
if (options == null) {
options = { ordered: args.ordered };
} else {
options.ordered = args.ordered;
}
}

if (typeof args.upsert === 'boolean') {
if (options == null) {
options = { upsert: args.upsert };
} else {
options.upsert = args.upsert;
}
}

// Find command is special needs to executed using toArray
if (operation.name === 'find') {
let cursor = collection[commandName]();

// Set the options
if (args.filter) cursor = cursor.filter(args.filter);
if (args.batchSize) cursor = cursor.batchSize(args.batchSize);
if (args.limit) cursor = cursor.limit(args.limit);
if (args.skip) cursor = cursor.skip(args.skip);
if (args.sort) cursor = cursor.sort(args.sort);

// Set any modifiers
if (args.modifiers) {
for (let modifier in args.modifiers) {
cursor.addQueryModifier(modifier, args.modifiers[modifier]);
}
}

// Execute find
return cursor
.toArray()
.catch(() => {} /* ignore */)
.then(() =>
test.expectations.forEach(expectation =>
validateExpectations(expectation, monitoringResults)
)
);
}
// Add options if they exists
if (options) params.push(options);

// Execute the operation
const coll = operation.collectionOptions
? db.collection(scenario.collection_name, operation.collectionOptions)
: db.collection(scenario.collection_name);

const promise = coll[commandName].apply(coll, params);
return promise
.catch(() => {} /* ignore */)
.then(() =>
test.expectations.forEach(expectation =>
validateExpectations(expectation, monitoringResults)
)
);
});
}

loadSpecTests('command-monitoring/legacy').forEach(scenario => {
if (scenario.name === 'command') return; // FIXME(NODE-3074): remove when `count` spec tests have been fixed
describe(scenario.name, function () {
scenario.tests.forEach(test => {
const requirements = { topology: ['single', 'replicaset', 'sharded'] };
if (test.ignore_if_server_version_greater_than) {
requirements.mongodb = `<${test.ignore_if_server_version_greater_than}`;
} else if (test.ignore_if_server_version_less_than) {
requirements.mongodb = `>${test.ignore_if_server_version_less_than}`;
}

if (test.ignore_if_topology_type) {
requirements.topology = requirements.topology.filter(
top => test.ignore_if_topology_type.indexOf(top) < 0
);
}

it(test.description, {
metadata: { requires: requirements },
test: function () {
if (
test.description ===
'A successful find event with a getmore and the server kills the cursor'
) {
this.skip();
}

const client = this.configuration.newClient({}, { monitorCommands: true });
return client.connect().then(client => {
expect(client).to.exist;
return executeOperation(client, scenario, test).then(() => client.close());
});
}
});
});
});
});
});

describe('command monitoring unified spec tests', () => {
for (const loadedSpec of loadSpecTests('command-monitoring/unified')) {
expect(loadedSpec).to.include.all.keys(['description', 'tests']);
context(String(loadedSpec.description), function () {
for (const test of loadedSpec.tests) {
it(String(test.description), {
metadata: { sessions: { skipLeakTests: true } },
test: async function () {
await runUnifiedTest(this, loadedSpec, test);
}
});
}
});
}
});
});
Loading