Skip to content

Commit f1c1ca0

Browse files
refactor(NODE-2992): clean up server selection logic runner (#3201)
1 parent 68074f9 commit f1c1ca0

File tree

4 files changed

+196
-78
lines changed

4 files changed

+196
-78
lines changed

test/unit/assorted/server_selection.spec.test.js

Lines changed: 0 additions & 77 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { join } from 'path';
2+
3+
import {
4+
collectServerSelectionLogicTests,
5+
runServerSelectionLogicTest
6+
} from './server_selection_logic_spec_utils';
7+
8+
describe('Server Selection Logic (spec)', function () {
9+
beforeEach(function () {
10+
if (this.currentTest.title.match(/Possible/)) {
11+
this.currentTest.skipReason = 'Nodejs driver does not support PossiblePrimary';
12+
this.skip();
13+
}
14+
15+
if (this.currentTest.title.match(/nearest_multiple/i)) {
16+
this.currentTest.skipReason = 'TODO(NODE-4188): localThresholdMS should default to 15ms';
17+
this.skip();
18+
}
19+
});
20+
21+
const selectionSpecDir = join(__dirname, '../../spec/server-selection/server_selection');
22+
const serverSelectionLogicTests = collectServerSelectionLogicTests(selectionSpecDir);
23+
for (const topologyType of Object.keys(serverSelectionLogicTests)) {
24+
describe(topologyType, function () {
25+
for (const subType of Object.keys(serverSelectionLogicTests[topologyType])) {
26+
describe(subType, function () {
27+
for (const test of serverSelectionLogicTests[topologyType][subType]) {
28+
it(test.name, function () {
29+
runServerSelectionLogicTest(test);
30+
});
31+
}
32+
});
33+
}
34+
});
35+
}
36+
});
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { Document, EJSON } from 'bson';
2+
import { expect } from 'chai';
3+
import { readdirSync, readFileSync, statSync } from 'fs';
4+
import { basename, extname, join } from 'path';
5+
6+
import {
7+
ReadPreference,
8+
ReadPreferenceMode,
9+
ReadPreferenceOptions
10+
} from '../../../src/read_preference';
11+
import { ServerType, TopologyType } from '../../../src/sdam/common';
12+
import { ServerDescription, TagSet } from '../../../src/sdam/server_description';
13+
import {
14+
readPreferenceServerSelector,
15+
writableServerSelector
16+
} from '../../../src/sdam/server_selection';
17+
import { TopologyDescription } from '../../../src/sdam/topology_description';
18+
import { serverDescriptionFromDefinition } from './server_selection_spec_helper';
19+
20+
interface ServerSelectionLogicTestServer {
21+
address: string;
22+
avg_rtt_ms: number;
23+
type: ServerType;
24+
tags?: TagSet;
25+
}
26+
interface ServerSelectionLogicTest {
27+
topology_description: {
28+
type: TopologyType;
29+
servers: ServerSelectionLogicTestServer[];
30+
};
31+
operation: 'read' | 'write';
32+
read_preference: {
33+
mode: ReadPreferenceMode;
34+
tag_sets?: TagSet[];
35+
};
36+
/**
37+
* The spec says we should confirm the list of suitable servers in addition to the list of
38+
* servers in the latency window, if possible. We apply the latency window inside the
39+
* selector so for Node this is not possible.
40+
* https://github.com/mongodb/specifications/tree/master/source/server-selection/tests#server-selection-logic-tests
41+
*/
42+
suitable_servers: never;
43+
in_latency_window: ServerSelectionLogicTestServer[];
44+
}
45+
46+
function readPreferenceFromDefinition(definition) {
47+
const mode = definition.mode
48+
? definition.mode.charAt(0).toLowerCase() + definition.mode.slice(1)
49+
: 'primary';
50+
51+
const options: ReadPreferenceOptions = {};
52+
if (typeof definition.maxStalenessSeconds !== 'undefined')
53+
options.maxStalenessSeconds = definition.maxStalenessSeconds;
54+
const tags = definition.tag_sets ?? [];
55+
56+
return new ReadPreference(mode, tags, options);
57+
}
58+
59+
/**
60+
* Compares two server descriptions and compares all fields that are present
61+
* in the yaml spec tests.
62+
*/
63+
function compareServerDescriptions(s1: ServerDescription, s2: ServerDescription) {
64+
expect(s1.address).to.equal(s2.address);
65+
expect(s1.roundTripTime).to.equal(s2.roundTripTime);
66+
expect(s1.type).to.equal(s2.type);
67+
expect(s1.tags).to.deep.equal(s2.tags);
68+
}
69+
70+
function serverDescriptionsToMap(
71+
descriptions: ServerDescription[]
72+
): Map<string, ServerDescription> {
73+
const descriptionMap = new Map<string, ServerDescription>();
74+
75+
for (const description of descriptions) {
76+
descriptionMap.set(description.address, description);
77+
}
78+
79+
return descriptionMap;
80+
}
81+
82+
/**
83+
* Executes a server selection logic test
84+
* @see https://github.com/mongodb/specifications/tree/master/source/server-selection/tests#server-selection-logic-tests
85+
*/
86+
export function runServerSelectionLogicTest(testDefinition: ServerSelectionLogicTest) {
87+
const allHosts = testDefinition.topology_description.servers.map(({ address }) => address);
88+
const serversInTopology = testDefinition.topology_description.servers.map(s =>
89+
serverDescriptionFromDefinition(s, allHosts)
90+
);
91+
const serverDescriptions = serverDescriptionsToMap(serversInTopology);
92+
const topologyDescription = new TopologyDescription(
93+
testDefinition.topology_description.type,
94+
serverDescriptions
95+
);
96+
const expectedServers = serverDescriptionsToMap(
97+
testDefinition.in_latency_window.map(s => serverDescriptionFromDefinition(s))
98+
);
99+
100+
let selector;
101+
if (testDefinition.operation === 'write') {
102+
selector = writableServerSelector();
103+
} else if (testDefinition.operation === 'read' || testDefinition.read_preference) {
104+
const readPreference = readPreferenceFromDefinition(testDefinition.read_preference);
105+
selector = readPreferenceServerSelector(readPreference);
106+
} else {
107+
expect.fail('test operation was neither read nor write, and no read preference was provided.');
108+
}
109+
110+
const result = selector(topologyDescription, serversInTopology);
111+
112+
expect(result.length).to.equal(expectedServers.size);
113+
114+
for (const server of result) {
115+
const expectedServer = expectedServers.get(server.address);
116+
expect(expectedServer).to.exist;
117+
compareServerDescriptions(server, expectedServer);
118+
expectedServers.delete(server.address);
119+
}
120+
121+
expect(expectedServers.size).to.equal(0);
122+
}
123+
124+
/**
125+
* reads in the server selection logic tests from the provided directory
126+
*/
127+
export function collectServerSelectionLogicTests(specDir) {
128+
const testTypes = readdirSync(specDir).filter(d => statSync(join(specDir, d)).isDirectory());
129+
130+
const tests = {};
131+
for (const testType of testTypes) {
132+
const testsOfType = readdirSync(join(specDir, testType)).filter(d =>
133+
statSync(join(specDir, testType, d)).isDirectory()
134+
);
135+
const result = {};
136+
for (const subType of testsOfType) {
137+
result[subType] = readdirSync(join(specDir, testType, subType))
138+
.filter(f => extname(f) === '.json')
139+
.map(f => {
140+
const fileContents = readFileSync(join(specDir, testType, subType, f), {
141+
encoding: 'utf-8'
142+
});
143+
const test = EJSON.parse(fileContents, { relaxed: true }) as unknown as Document;
144+
test.name = basename(f, '.json');
145+
test.type = testType;
146+
test.subType = subType;
147+
return test;
148+
});
149+
}
150+
151+
tests[testType] = result;
152+
}
153+
154+
return tests;
155+
}

test/unit/assorted/server_selection_spec_helper.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ function executeServerSelectionTest(testDefinition, testDone) {
132132
if (testDefinition.error) return done();
133133
return done(e);
134134
}
135+
} else {
136+
return done(
137+
new Error('received neither read nor write, and did not receive a read preference')
138+
);
135139
}
136140

137141
// expectations
@@ -196,4 +200,4 @@ function executeServerSelectionTest(testDefinition, testDone) {
196200
});
197201
}
198202

199-
module.exports = { executeServerSelectionTest };
203+
module.exports = { executeServerSelectionTest, serverDescriptionFromDefinition };

0 commit comments

Comments
 (0)