Skip to content

Commit

Permalink
refactor(NODE-2992): clean up server selection logic runner (#3201)
Browse files Browse the repository at this point in the history
  • Loading branch information
baileympearson authored Apr 19, 2022
1 parent 68074f9 commit f1c1ca0
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 78 deletions.
77 changes: 0 additions & 77 deletions test/unit/assorted/server_selection.spec.test.js

This file was deleted.

36 changes: 36 additions & 0 deletions test/unit/assorted/server_selection.spec.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { join } from 'path';

import {
collectServerSelectionLogicTests,
runServerSelectionLogicTest
} from './server_selection_logic_spec_utils';

describe('Server Selection Logic (spec)', function () {
beforeEach(function () {
if (this.currentTest.title.match(/Possible/)) {
this.currentTest.skipReason = 'Nodejs driver does not support PossiblePrimary';
this.skip();
}

if (this.currentTest.title.match(/nearest_multiple/i)) {
this.currentTest.skipReason = 'TODO(NODE-4188): localThresholdMS should default to 15ms';
this.skip();
}
});

const selectionSpecDir = join(__dirname, '../../spec/server-selection/server_selection');
const serverSelectionLogicTests = collectServerSelectionLogicTests(selectionSpecDir);
for (const topologyType of Object.keys(serverSelectionLogicTests)) {
describe(topologyType, function () {
for (const subType of Object.keys(serverSelectionLogicTests[topologyType])) {
describe(subType, function () {
for (const test of serverSelectionLogicTests[topologyType][subType]) {
it(test.name, function () {
runServerSelectionLogicTest(test);
});
}
});
}
});
}
});
155 changes: 155 additions & 0 deletions test/unit/assorted/server_selection_logic_spec_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { Document, EJSON } from 'bson';
import { expect } from 'chai';
import { readdirSync, readFileSync, statSync } from 'fs';
import { basename, extname, join } from 'path';

import {
ReadPreference,
ReadPreferenceMode,
ReadPreferenceOptions
} from '../../../src/read_preference';
import { ServerType, TopologyType } from '../../../src/sdam/common';
import { ServerDescription, TagSet } from '../../../src/sdam/server_description';
import {
readPreferenceServerSelector,
writableServerSelector
} from '../../../src/sdam/server_selection';
import { TopologyDescription } from '../../../src/sdam/topology_description';
import { serverDescriptionFromDefinition } from './server_selection_spec_helper';

interface ServerSelectionLogicTestServer {
address: string;
avg_rtt_ms: number;
type: ServerType;
tags?: TagSet;
}
interface ServerSelectionLogicTest {
topology_description: {
type: TopologyType;
servers: ServerSelectionLogicTestServer[];
};
operation: 'read' | 'write';
read_preference: {
mode: ReadPreferenceMode;
tag_sets?: TagSet[];
};
/**
* The spec says we should confirm the list of suitable servers in addition to the list of
* servers in the latency window, if possible. We apply the latency window inside the
* selector so for Node this is not possible.
* https://github.com/mongodb/specifications/tree/master/source/server-selection/tests#server-selection-logic-tests
*/
suitable_servers: never;
in_latency_window: ServerSelectionLogicTestServer[];
}

function readPreferenceFromDefinition(definition) {
const mode = definition.mode
? definition.mode.charAt(0).toLowerCase() + definition.mode.slice(1)
: 'primary';

const options: ReadPreferenceOptions = {};
if (typeof definition.maxStalenessSeconds !== 'undefined')
options.maxStalenessSeconds = definition.maxStalenessSeconds;
const tags = definition.tag_sets ?? [];

return new ReadPreference(mode, tags, options);
}

/**
* Compares two server descriptions and compares all fields that are present
* in the yaml spec tests.
*/
function compareServerDescriptions(s1: ServerDescription, s2: ServerDescription) {
expect(s1.address).to.equal(s2.address);
expect(s1.roundTripTime).to.equal(s2.roundTripTime);
expect(s1.type).to.equal(s2.type);
expect(s1.tags).to.deep.equal(s2.tags);
}

function serverDescriptionsToMap(
descriptions: ServerDescription[]
): Map<string, ServerDescription> {
const descriptionMap = new Map<string, ServerDescription>();

for (const description of descriptions) {
descriptionMap.set(description.address, description);
}

return descriptionMap;
}

/**
* Executes a server selection logic test
* @see https://github.com/mongodb/specifications/tree/master/source/server-selection/tests#server-selection-logic-tests
*/
export function runServerSelectionLogicTest(testDefinition: ServerSelectionLogicTest) {
const allHosts = testDefinition.topology_description.servers.map(({ address }) => address);
const serversInTopology = testDefinition.topology_description.servers.map(s =>
serverDescriptionFromDefinition(s, allHosts)
);
const serverDescriptions = serverDescriptionsToMap(serversInTopology);
const topologyDescription = new TopologyDescription(
testDefinition.topology_description.type,
serverDescriptions
);
const expectedServers = serverDescriptionsToMap(
testDefinition.in_latency_window.map(s => serverDescriptionFromDefinition(s))
);

let selector;
if (testDefinition.operation === 'write') {
selector = writableServerSelector();
} else if (testDefinition.operation === 'read' || testDefinition.read_preference) {
const readPreference = readPreferenceFromDefinition(testDefinition.read_preference);
selector = readPreferenceServerSelector(readPreference);
} else {
expect.fail('test operation was neither read nor write, and no read preference was provided.');
}

const result = selector(topologyDescription, serversInTopology);

expect(result.length).to.equal(expectedServers.size);

for (const server of result) {
const expectedServer = expectedServers.get(server.address);
expect(expectedServer).to.exist;
compareServerDescriptions(server, expectedServer);
expectedServers.delete(server.address);
}

expect(expectedServers.size).to.equal(0);
}

/**
* reads in the server selection logic tests from the provided directory
*/
export function collectServerSelectionLogicTests(specDir) {
const testTypes = readdirSync(specDir).filter(d => statSync(join(specDir, d)).isDirectory());

const tests = {};
for (const testType of testTypes) {
const testsOfType = readdirSync(join(specDir, testType)).filter(d =>
statSync(join(specDir, testType, d)).isDirectory()
);
const result = {};
for (const subType of testsOfType) {
result[subType] = readdirSync(join(specDir, testType, subType))
.filter(f => extname(f) === '.json')
.map(f => {
const fileContents = readFileSync(join(specDir, testType, subType, f), {
encoding: 'utf-8'
});
const test = EJSON.parse(fileContents, { relaxed: true }) as unknown as Document;
test.name = basename(f, '.json');
test.type = testType;
test.subType = subType;
return test;
});
}

tests[testType] = result;
}

return tests;
}
6 changes: 5 additions & 1 deletion test/unit/assorted/server_selection_spec_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ function executeServerSelectionTest(testDefinition, testDone) {
if (testDefinition.error) return done();
return done(e);
}
} else {
return done(
new Error('received neither read nor write, and did not receive a read preference')
);
}

// expectations
Expand Down Expand Up @@ -196,4 +200,4 @@ function executeServerSelectionTest(testDefinition, testDone) {
});
}

module.exports = { executeServerSelectionTest };
module.exports = { executeServerSelectionTest, serverDescriptionFromDefinition };

0 comments on commit f1c1ca0

Please sign in to comment.