Skip to content

Commit 4200f3f

Browse files
preliminary tests finished
1 parent 1531bec commit 4200f3f

File tree

4 files changed

+121
-44
lines changed

4 files changed

+121
-44
lines changed

test/integration/node-specific/client_close.test.ts

Lines changed: 72 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { expect } from 'chai';
1+
import sinon = require('sinon');
22
import { TestConfiguration } from '../../tools/runner/config';
3-
import { runScriptAndReturnResourceInfo } from './resource_tracking_script_builder';
3+
import { runScriptAndGetProcessInfo } from './resource_tracking_script_builder';
44

5-
describe.skip('client.close() Integration', () => {
5+
describe('client.close() Integration', () => {
66
let config: TestConfiguration;
77
beforeEach(function () {
88
config = this.configuration;
@@ -12,27 +12,39 @@ describe.skip('client.close() Integration', () => {
1212
describe('when client is being instantiated and reads a long docker file', () => {
1313
// our docker env detection uses fs.access which will not be aborted until after it runs
1414
// fs.access does not support abort signals
15-
it('the file read is not interrupted by client.close', () => {
15+
it.only('the file read is not interrupted by client.close()', async () => {
16+
await runScriptAndGetProcessInfo(
17+
'docker-read',
18+
config,
19+
async function run({ MongoClient, uri }) {
20+
const dockerPath = '.dockerenv';
21+
sinon.stub(fs, 'access').callsFake(async () => await sleep(5000));
22+
await fs.writeFile('.dockerenv', '', { encoding: 'utf8' });
23+
const client = new MongoClient(uri);;
24+
await client.close();
25+
unlink(dockerPath);
26+
});
1627
});
1728
});
1829
describe('when client is connecting and reads a TLS long file', () => {
19-
it('the file read is interrupted by client.close', () => {
30+
it('the file read is interrupted by client.close()', async () => {
31+
2032
});
2133
});
2234
});
2335

2436
describe('MongoClientAuthProviders', () => {
2537
describe('when MongoClientAuthProviders is instantiated and token file read hangs', () => {
26-
it('the file read is interrupted by client.close', () => {
38+
it('the file read is interrupted by client.close()', async () => {
2739
});
2840
});
2941
});
3042

31-
describe('Topology', () => {
43+
describe.only('Topology', () => {
3244
describe('after a Topology is created through client.connect()', () => {
3345
it('server selection timers are cleaned up by client.close()', async () => {
34-
await runScriptAndReturnResourceInfo(
35-
'topology-clean-up',
46+
await runScriptAndGetProcessInfo(
47+
'server-selection-timers',
3648
config,
3749
async function run({ MongoClient, uri }) {
3850
const client = new MongoClient(uri);
@@ -45,64 +57,80 @@ describe.skip('client.close() Integration', () => {
4557
});
4658

4759
describe('SRVPoller', () => {
48-
// SRVPoller is implicitly created after an SRV string's topology transitions to sharded
60+
// TODO: only non-LB mode
4961
describe('after SRVPoller is created', () => {
50-
it('timers are cleaned up by client.close()', () => {
51-
62+
it('timers are cleaned up by client.close()', async () => {
63+
await runScriptAndGetProcessInfo(
64+
'srv-poller',
65+
config,
66+
async function run({ MongoClient, uri }) {
67+
const client = new MongoClient(uri);
68+
await client.connect();
69+
await client.close();
70+
}
71+
);
5272
});
5373
});
5474
});
5575

5676
describe('ClientSession', () => {
5777
describe('after a clientSession is created and used', () => {
58-
it('the server-side ServerSession and transaction are cleaned up by client.close()', () => {
59-
// must send a command to the server
78+
it('the server-side ServerSession and transaction are cleaned up by client.close()', async () => {
79+
await runScriptAndGetProcessInfo(
80+
'client-session',
81+
config,
82+
async function run({ MongoClient, uri }) {
83+
const client = new MongoClient(uri);
84+
await client.connect();
85+
const session = client.startSession();
86+
session.startTransaction();
87+
client.db('db').collection('coll').insertOne({ a: 1 });
88+
await session.endSession();
89+
await client.close();
90+
}
91+
);
6092
});
6193
});
6294
});
6395

6496
describe('StateMachine', () => {
6597
describe('when FLE is enabled and the client has made a KMS request', () => {
66-
it('no sockets remain after client.close', () => {
98+
it('no sockets remain after client.close()', async () => {
6799

68100
});
69101
describe('when the TLS file read hangs', () => {
70-
it('the file read is interrupted by client.close', () => {
102+
it('the file read is interrupted by client.close()', async () => {
71103

72104
});
73105
});
74106
});
75107
});
76108

77-
describe('Server', () => {
78-
79-
});
80-
81109
describe('ConnectionPool', () => {
82110
describe('after new connection pool is created', () => {
83-
it('minPoolSize timer is cleaned up by client.close()', () => {
111+
it('minPoolSize timer is cleaned up by client.close()', async () => {
84112

85113
});
86114
});
87115
});
88116

89117
describe('MonitorInterval', () => {
90118
describe('after a new monitor is made', () => {
91-
it('monitor interval timer is cleaned up by client.close()', () => {
119+
it('monitor interval timer is cleaned up by client.close()', async () => {
92120

93121
});
94122
});
95123

96124
describe('after a heartbeat fails', () => {
97-
it('the new monitor interval timer is cleaned up by client.close()', () => {
125+
it('the new monitor interval timer is cleaned up by client.close()', async () => {
98126

99127
});
100128
});
101129
});
102130

103131
describe('RTTPinger', () => {
104132
describe('after entering monitor streaming mode ', () => {
105-
it('the rtt pinger timer is cleaned up by client.close()', () => {
133+
it('the rtt pinger timer is cleaned up by client.close()', async () => {
106134
// helloReply has a topologyVersion defined
107135
});
108136
});
@@ -111,45 +139,54 @@ describe.skip('client.close() Integration', () => {
111139
describe('Connection', () => {
112140
describe('when connection monitoring is turned on', () => {
113141
// connection monitoring is by default turned on - with the exception of load-balanced mode
114-
it('no sockets remain after client.close', () => {
115-
116-
});
117-
it('no server-side connection threads remain after client.close', () => {
142+
it('no sockets remain after client.close()', async () => {
143+
// TODO: skip for LB mode
144+
await runScriptAndGetProcessInfo(
145+
'connection-monitoring',
146+
config,
147+
async function run({ MongoClient, uri }) {
148+
const client = new MongoClient(uri);
149+
await client.connect();
150+
await client.close();
151+
}
152+
);
153+
});
154+
it('no server-side connection threads remain after client.close()', async () => {
118155

119156
});
120157
});
121158

122159
describe('when rtt monitoring is turned on', () => {
123-
it('no sockets remain after client.close', () => {
160+
it('no sockets remain after client.close()', async () => {
124161

125162
});
126-
it('no server-side connection threads remain after client.close', () => {
163+
it('no server-side connection threads remain after client.close()', async () => {
127164

128165
});
129166
});
130167

131168
describe('after a connection is checked out', () => {
132-
it('no sockets remain after client.close', () => {
169+
it('no sockets remain after client.close()', async () => {
133170

134171
});
135-
it('no server-side connection threads remain after client.close', () => {
172+
it('no server-side connection threads remain after client.close()', async () => {
136173

137174
});
138175
});
139176

140177
describe('after a minPoolSize has been set on the ConnectionPool', () => {
141-
it('no sockets remain after client.close', () => {
178+
it('no sockets remain after client.close()', async () => {
142179

143180
});
144-
it('no server-side connection threads remain after client.close', () => {
181+
it('no server-side connection threads remain after client.close()', async () => {
145182

146183
});
147184
});
148185
});
149186

150187
describe('Cursor', () => {
151188
describe('after cursors are created', () => {
152-
it('all active server-side cursors are closed by client.close()', () => {
189+
it('all active server-side cursors are closed by client.close()', async () => {
153190

154191
});
155192
});

test/integration/node-specific/resource_tracking_script_builder.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,21 @@ import { parseSnapshot } from 'v8-heapsnapshot';
88

99
import { type MongoClient } from '../../mongodb';
1010
import { type TestConfiguration } from '../../tools/runner/config';
11+
import { sleep } from '../../tools/utils';
1112

12-
export type ResourceTestFunction = (options: {
13+
export type ResourceTestFunction = HeapResourceTestFunction | ProcessResourceTestFunction;
14+
15+
export type HeapResourceTestFunction = (options: {
1316
MongoClient: typeof MongoClient;
1417
uri: string;
1518
iteration: number;
1619
}) => Promise<void>;
1720

21+
export type ProcessResourceTestFunction = (options: {
22+
MongoClient: typeof MongoClient;
23+
uri: string;
24+
}) => Promise<void>;
25+
1826
const HEAP_RESOURCE_SCRIPT_PATH = path.resolve(__dirname, '../../tools/fixtures/resource_script.in.js');
1927
const REPORT_RESOURCE_SCRIPT_PATH = path.resolve(__dirname, '../../tools/fixtures/close_resource_script.in.js');
2028
const DRIVER_SRC_PATH = JSON.stringify(path.resolve(__dirname, '../../../lib'));
@@ -40,7 +48,8 @@ export async function testScriptFactory(
4048
}
4149

4250
/**
43-
* A helper for running arbitrary MongoDB Driver scripting code in a resource information collecting script
51+
* A helper for running arbitrary MongoDB Driver scripting code in a resource information collecting script.
52+
* This script uses heap data to collect resource information.
4453
*
4554
* **The provided function is run in an isolated Node.js process**
4655
*
@@ -64,7 +73,7 @@ export async function testScriptFactory(
6473
export async function runScriptAndReturnHeapInfo(
6574
name: string,
6675
config: TestConfiguration,
67-
func: ResourceTestFunction,
76+
func: HeapResourceTestFunction,
6877
{ iterations = 100 } = {}
6978
) {
7079
const scriptName = `${name}.cjs`;
@@ -111,10 +120,31 @@ export async function runScriptAndReturnHeapInfo(
111120
};
112121
}
113122

114-
export async function runScriptAndReturnResourceInfo(
123+
124+
/**
125+
* A helper for running arbitrary MongoDB Driver scripting code in a resource information collecting script.
126+
* This script uses info from node:process to collect resource information.
127+
*
128+
* **The provided function is run in an isolated Node.js process**
129+
*
130+
* A user of this function will likely need to familiarize themselves with the surrounding scripting, but briefly:
131+
* - Every MongoClient you construct should have an asyncResource attached to it like so:
132+
* ```js
133+
* mongoClient.asyncResource = new this.async_hooks.AsyncResource('MongoClient');
134+
* ```
135+
* - You can perform any number of operations and connects/closes of MongoClients
136+
* - This function performs assertions that at the end of the provided function, the js event loop has been exhausted
137+
*
138+
* @param name - the name of the script, this defines the name of the file, it will be cleaned up if the function returns successfully
139+
* @param config - `this.configuration` from your mocha config
140+
* @param func - your javascript function, you can write it inline! this will stringify the function, use the references on the `this` context to get typechecking
141+
* @param options - settings for the script
142+
* @throws Error - if the process exits with failure or if the process' resources are not cleaned up by the provided function.
143+
*/
144+
export async function runScriptAndGetProcessInfo(
115145
name: string,
116146
config: TestConfiguration,
117-
func: ResourceTestFunction
147+
func: ProcessResourceTestFunction
118148
) {
119149

120150
const scriptName = `${name}.cjs`;
@@ -137,15 +167,23 @@ export async function runScriptAndReturnResourceInfo(
137167
const report = await messages.next();
138168
let { finalReport, originalReport } = report.value[0];
139169

140-
// const beforeExit = await messages.next();
170+
const nullishSleepFn = async function () {
171+
await sleep(5000);
172+
return null;
173+
}
174+
175+
// in the event the beforeExit event doesn't fire, set the event value to null after waiting 5 seconds
176+
const beforeExitEvent = await Promise.race([messages.next(), nullishSleepFn()]);
141177

142178
// make sure the process ended
143179
const [exitCode] = await willClose;
144180

181+
await unlink(scriptName);
182+
145183
// assertions about exit status
146184
expect(exitCode, 'process should have exited with zero').to.equal(0);
147185

148186
// assertions about clean-up
149187
expect(originalReport.length).to.equal(finalReport.length);
150-
// expect(beforeExitEventHappened).to.be.true;
188+
expect(beforeExitEvent).to.not.be.null;
151189
}

test/tools/fixtures/close_resource_script.in.js renamed to test/tools/fixtures/process_resource_script.in.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ const process = require('node:process');
1212
const v8 = require('node:v8');
1313
const util = require('node:util');
1414
const timers = require('node:timers');
15+
const fs = require('node:fs');
16+
const sinon = require('sinon');
1517

1618
const run = func;
1719

1820
async function main() {
1921
process.on('beforeExit', (code) => {
20-
console.log('Process beforeExit event with code: ', code);
22+
process.send({ beforeExitCode: code });
2123
});
2224
const originalReport = process.report.getReport().libuv;
23-
await run({ MongoClient, uri });
25+
await run({ MongoClient, uri, fs, sinon });
2426
const finalReport = process.report.getReport().libuv;
2527
process.send({originalReport, finalReport});
2628
}

0 commit comments

Comments
 (0)