Skip to content

Commit f3a8093

Browse files
committed
test: update SDAM spec tests to support multiple phases
1 parent 863cf67 commit f3a8093

File tree

9 files changed

+125
-96
lines changed

9 files changed

+125
-96
lines changed

test/functional/spec-runner/matcher.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ function generateMatchAndDiffSpecialCase(key, expectedObj, actualObj, metadata)
103103
expected: match ? actual : SYMBOL_DOES_EXIST,
104104
actual
105105
};
106+
} else if (expectedIs42) {
107+
return {
108+
match: true,
109+
expected: SYMBOL_IGNORE,
110+
actual: SYMBOL_IGNORE
111+
};
106112
} else {
107113
// default
108114
return generateMatchAndDiff(expected, actual, metadata);

test/spec/server-discovery-and-monitoring/README.rst

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,17 @@ Format
1717

1818
Each YAML file has the following keys:
1919

20-
- description: Some text.
20+
- description: A textual description of the test.
2121
- uri: A connection string.
2222
- phases: An array of "phase" objects.
23+
A phase of the test optionally sends inputs to the client,
24+
then tests the client's resulting TopologyDescription.
2325

24-
A "phase" of the test sends inputs to the client, then tests the client's
25-
resulting TopologyDescription. Each phase object has two keys:
26+
Each phase object has two keys:
2627

27-
- responses: An array of "response" objects.
28+
- responses: (optional) An array of "response" objects. If not provided,
29+
the test runner should construct the client and perform assertions specified
30+
in the outcome object without processing any responses.
2831
- outcome: An "outcome" object representing the TopologyDescription.
2932

3033
A response is a pair of values:
@@ -37,8 +40,9 @@ A response is a pair of values:
3740
The empty response `{}` indicates a network error
3841
when attempting to call "ismaster".
3942

40-
An "outcome" represents the correct TopologyDescription that results from
41-
processing the responses in the phases so far. It has the following keys:
43+
In non-monitoring tests, an "outcome" represents the correct
44+
TopologyDescription that results from processing the responses in the phases
45+
so far. It has the following keys:
4246

4347
- topologyType: A string like "ReplicaSetNoPrimary".
4448
- setName: A string with the expected replica set name, or null.
@@ -60,6 +64,16 @@ current TopologyDescription. It has the following keys:
6064
- minWireVersion: absent or an integer.
6165
- maxWireVersion: absent or an integer.
6266

67+
In monitoring tests, an "outcome" contains a list of SDAM events that should
68+
have been published by the client as a result of processing ismaster responses
69+
in the current phase. Any SDAM events published by the client during its
70+
construction (that is, prior to processing any of the responses) should be
71+
combined with the events published during processing of ismaster responses
72+
of the first phase of the test. A test MAY explicitly verify events published
73+
during client construction by providing an empty responses array for the
74+
first phase.
75+
76+
6377
Use as unittests
6478
----------------
6579

@@ -71,7 +85,7 @@ without any network I/O, by parsing ismaster responses from the test file
7185
and passing them into the driver code. Parts of the client and monitoring
7286
code may need to be mocked or subclassed to achieve this. `A reference
7387
implementation for PyMongo 3.x is available here
74-
<https://github.com/mongodb/mongo-python-driver/blob/3.0-dev/test/test_discovery_and_monitoring.py>`_.
88+
<https://github.com/mongodb/mongo-python-driver/blob/26d25cd74effc1e7a8d52224eac6c9a95769b371/test/test_discovery_and_monitoring.py>`_.
7589

7690
Initialization
7791
~~~~~~~~~~~~~~
@@ -93,19 +107,29 @@ Set the client's initial TopologyType to ReplicaSetNoPrimary.
93107
(For most clients, parsing a connection string with a "replicaSet" option
94108
automatically sets the TopologyType to ReplicaSetNoPrimary.)
95109

110+
Set up a listener to collect SDAM events published by the client, including
111+
events published during client construction.
112+
96113
Test Phases
97114
~~~~~~~~~~~
98115

99116
For each phase in the file, parse the "responses" array.
100117
Pass in the responses in order to the driver code.
101118
If a response is the empty object `{}`, simulate a network error.
102119

103-
Once all responses are processed, assert that the phase's "outcome" object
120+
For non-monitoring tests,
121+
once all responses are processed, assert that the phase's "outcome" object
104122
is equivalent to the driver's current TopologyDescription.
105123

124+
For monitoring tests, once all responses are processed, assert that the
125+
events collected so far by the SDAM event listener are equivalent to the
126+
events specified in the phase.
127+
106128
Some fields such as "logicalSessionTimeoutMinutes" or "compatible" were added
107129
later and haven't been added to all test files. If these fields are present,
108130
test that they are equivalent to the fields of the driver's current
109131
TopologyDescription.
110132

133+
For monitoring tests, clear the list of events collected so far.
134+
111135
Continue until all phases have been executed.

test/spec/server-discovery-and-monitoring/monitoring/replica_set_with_removal.json

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,7 @@
33
"uri": "mongodb://a,b/",
44
"phases": [
55
{
6-
"responses": [
7-
[
8-
"a:27017",
9-
{
10-
"ok": 1,
11-
"ismaster": true,
12-
"setName": "rs",
13-
"setVersion": 1,
14-
"primary": "a:27017",
15-
"hosts": [
16-
"a:27017"
17-
],
18-
"minWireVersion": 0,
19-
"maxWireVersion": 4
20-
}
21-
],
22-
[
23-
"b:27017",
24-
{
25-
"ok": 1,
26-
"ismaster": true
27-
}
28-
]
29-
],
6+
"responses": [],
307
"outcome": {
318
"events": [
329
{
@@ -73,7 +50,37 @@
7350
"topologyId": "42",
7451
"address": "b:27017"
7552
}
76-
},
53+
}
54+
]
55+
}
56+
},
57+
{
58+
"responses": [
59+
[
60+
"a:27017",
61+
{
62+
"ok": 1,
63+
"ismaster": true,
64+
"setName": "rs",
65+
"setVersion": 1,
66+
"primary": "a:27017",
67+
"hosts": [
68+
"a:27017"
69+
],
70+
"minWireVersion": 0,
71+
"maxWireVersion": 4
72+
}
73+
],
74+
[
75+
"b:27017",
76+
{
77+
"ok": 1,
78+
"ismaster": true
79+
}
80+
]
81+
],
82+
"outcome": {
83+
"events": [
7784
{
7885
"server_description_changed_event": {
7986
"topologyId": "42",

test/spec/server-discovery-and-monitoring/monitoring/replica_set_with_removal.yml

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,7 @@ description: "Monitoring a replica set with non member"
22
uri: "mongodb://a,b/"
33
phases:
44
-
5-
responses:
6-
-
7-
- "a:27017"
8-
- {
9-
ok: 1,
10-
ismaster: true,
11-
setName: "rs",
12-
setVersion: 1.0,
13-
primary: "a:27017",
14-
hosts: [ "a:27017" ],
15-
minWireVersion: 0,
16-
maxWireVersion: 4
17-
}
18-
-
19-
- "b:27017"
20-
- { ok: 1, ismaster: true }
5+
responses: []
216
outcome:
227
events:
238
-
@@ -52,6 +37,25 @@ phases:
5237
server_opening_event:
5338
topologyId: "42"
5439
address: "b:27017"
40+
-
41+
responses:
42+
-
43+
- "a:27017"
44+
- {
45+
ok: 1,
46+
ismaster: true,
47+
setName: "rs",
48+
setVersion: 1.0,
49+
primary: "a:27017",
50+
hosts: [ "a:27017" ],
51+
minWireVersion: 0,
52+
maxWireVersion: 4
53+
}
54+
-
55+
- "b:27017"
56+
- { ok: 1, ismaster: true }
57+
outcome:
58+
events:
5559
-
5660
server_description_changed_event:
5761
topologyId: "42"

test/spec/server-discovery-and-monitoring/monitoring/standalone_debounce_description_updates.json renamed to test/spec/server-discovery-and-monitoring/monitoring/standalone_suppress_equal_description_changes.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"description": "Monitoring a standalone connection - multiple equal description updates are debounced",
2+
"description": "Monitoring a standalone connection - suppress update events for equal server descriptions",
33
"uri": "mongodb://a:27017",
44
"phases": [
55
{

test/spec/server-discovery-and-monitoring/monitoring/standalone_debounce_description_updates.yml renamed to test/spec/server-discovery-and-monitoring/monitoring/standalone_suppress_equal_description_changes.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
description: "Monitoring a standalone connection - multiple equal description updates are debounced"
1+
description: "Monitoring a standalone connection - suppress update events for equal server descriptions"
22
uri: "mongodb://a:27017"
33
phases:
44
-

test/spec/server-discovery-and-monitoring/rs/incompatible_ghost.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@
2323
{
2424
"ok": 1,
2525
"isreplicaset": true,
26-
"setName": "rs",
27-
"hosts": [
28-
"a:27017",
29-
"b:27017"
30-
],
3126
"minWireVersion": 0,
3227
"maxWireVersion": 1
3328
}
@@ -41,7 +36,7 @@
4136
},
4237
"b:27017": {
4338
"type": "RSGhost",
44-
"setName": "rs"
39+
"setName": null
4540
}
4641
},
4742
"topologyType": "ReplicaSetWithPrimary",

test/spec/server-discovery-and-monitoring/rs/incompatible_ghost.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ phases:
1414
- "b:27017"
1515
- ok: 1
1616
isreplicaset: true
17-
setName: "rs"
18-
hosts: ["a:27017", "b:27017"]
1917
minWireVersion: 0
2018
maxWireVersion: 1
2119
outcome:
@@ -25,7 +23,7 @@ phases:
2523
setName: "rs"
2624
"b:27017":
2725
type: "RSGhost"
28-
setName: "rs"
26+
setName:
2927
topologyType: "ReplicaSetWithPrimary"
3028
setName: "rs"
3129
logicalSessionTimeoutMinutes: ~

test/unit/core/sdam_spec.test.js

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
'use strict';
22
const fs = require('fs');
33
const path = require('path');
4-
const chai = require('chai');
5-
const expect = chai.expect;
64
const Topology = require('../../../lib/core/sdam/topology').Topology;
75
const Server = require('../../../lib/core/sdam/server').Server;
86
const ServerDescription = require('../../../lib/core/sdam/server_description').ServerDescription;
97
const monitoring = require('../../../lib/core/sdam/monitoring');
108
const parse = require('../../../lib/core/uri_parser');
11-
chai.use(require('chai-subset'));
129
const sinon = require('sinon');
1310

11+
const chai = require('chai');
12+
chai.use(require('chai-subset'));
13+
chai.use(require('../../functional/spec-runner/matcher').default);
14+
const expect = chai.expect;
15+
1416
const specDir = path.resolve(__dirname, '../../spec/server-discovery-and-monitoring');
1517
function collectTests() {
1618
const testTypes = fs
@@ -115,36 +117,6 @@ function findOmittedFields(expected) {
115117
return result;
116118
}
117119

118-
function replacePlaceholders(actual, expected) {
119-
Object.keys(expected).forEach(key => {
120-
if (expected[key] === 42 || expected[key] === '42') {
121-
expect(actual).to.have.any.keys(key);
122-
expect(actual[key]).to.exist;
123-
actual[key] = expected[key];
124-
}
125-
});
126-
127-
return actual;
128-
}
129-
130-
function convertES6Maps(actual) {
131-
['previousDescription', 'newDescription'].forEach(key => {
132-
if (actual[key] && actual[key].servers) {
133-
const servers = actual[key].servers;
134-
if (servers instanceof Map) {
135-
let obj = Object.create(null);
136-
for (const serverEntry of servers.entries()) {
137-
obj[serverEntry[0]] = serverEntry[1];
138-
}
139-
140-
actual[key].servers = obj;
141-
}
142-
}
143-
});
144-
145-
return actual;
146-
}
147-
148120
function normalizeServerDescription(serverDescription) {
149121
if (serverDescription.type === 'PossiblePrimary') {
150122
// Some single-threaded drivers care a lot about ordering potential primary
@@ -156,6 +128,26 @@ function normalizeServerDescription(serverDescription) {
156128
return serverDescription;
157129
}
158130

131+
function cloneMap(map) {
132+
const result = Object.create(null);
133+
for (let key of map.keys()) {
134+
result[key] = JSON.parse(JSON.stringify(map.get(key)));
135+
}
136+
137+
return result;
138+
}
139+
140+
function cloneForCompare(event) {
141+
const result = JSON.parse(JSON.stringify(event));
142+
['previousDescription', 'newDescription'].forEach(key => {
143+
if (event[key] != null && event[key].servers != null) {
144+
result[key].servers = cloneMap(event[key].servers);
145+
}
146+
});
147+
148+
return result;
149+
}
150+
159151
function executeSDAMTest(testData, testDone) {
160152
parse(testData.uri, (err, parsedUri) => {
161153
if (err) return done(err);
@@ -164,7 +156,7 @@ function executeSDAMTest(testData, testDone) {
164156
const topology = new Topology(parsedUri.hosts, parsedUri.options);
165157

166158
// listen for SDAM monitoring events
167-
const events = [];
159+
let events = [];
168160
[
169161
'serverOpening',
170162
'serverClosed',
@@ -234,8 +226,8 @@ function executeSDAMTest(testData, testDone) {
234226
expect(events).to.have.length(expectedEvents.length);
235227
for (let i = 0; i < events.length; ++i) {
236228
const expectedEvent = expectedEvents[i];
237-
const actualEvent = convertES6Maps(replacePlaceholders(events[i], expectedEvent));
238-
expect(actualEvent).to.containSubset(expectedEvent);
229+
const actualEvent = cloneForCompare(events[i]);
230+
expect(actualEvent).to.matchMongoSpec(expectedEvent);
239231
}
240232

241233
return;
@@ -252,6 +244,9 @@ function executeSDAMTest(testData, testDone) {
252244

253245
// remove error handler
254246
topology.removeListener('error', incompatabilityHandler);
247+
248+
// reset the captured events for each phase
249+
events = [];
255250
});
256251

257252
topology.close(done);

0 commit comments

Comments
 (0)