Skip to content
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

test(NODE-6626): implement integration tests for improved client.close() - server-side #4367

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Changes from 13 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
225 changes: 199 additions & 26 deletions test/integration/node-specific/client_close.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { expect } from 'chai';

import { type Collection, type FindCursor, type MongoClient } from '../../mongodb';
import { type TestConfiguration } from '../../tools/runner/config';
import { runScriptAndGetProcessInfo } from './resource_tracking_script_builder';

describe.skip('MongoClient.close() Integration', () => {
describe('MongoClient.close() Integration', () => {
// note: these tests are set-up in accordance of the resource ownership tree

let config: TestConfiguration;
Expand All @@ -13,7 +16,7 @@ describe.skip('MongoClient.close() Integration', () => {

describe('Node.js resource: TLS File read', () => {
describe('when client is connecting and reads an infinite TLS file', () => {
it('the file read is interrupted by client.close()', async function () {
it.skip('the file read is interrupted by client.close()', async function () {
await runScriptAndGetProcessInfo(
'tls-file-read',
config,
Expand Down Expand Up @@ -49,7 +52,7 @@ describe.skip('MongoClient.close() Integration', () => {
});

describe('when MongoClientAuthProviders is instantiated and token file read hangs', () => {
it('the file read is interrupted by client.close()', async () => {
it.skip('the file read is interrupted by client.close()', async () => {
await runScriptAndGetProcessInfo(
'token-file-read',
config,
Expand All @@ -76,8 +79,7 @@ describe.skip('MongoClient.close() Integration', () => {
describe('Node.js resource: Server Selection Timer', () => {
describe('after a Topology is created through client.connect()', () => {
const metadata: MongoDBMetadataUI = { requires: { topology: 'replicaset' } };

it('server selection timers are cleaned up by client.close()', metadata, async () => {
it.skip('server selection timers are cleaned up by client.close()', metadata, async () => {
const run = async function ({ MongoClient, uri, expect, sleep, mongodb, getTimerCount }) {
const serverSelectionTimeoutMS = 2222;
const client = new MongoClient(uri, {
Expand Down Expand Up @@ -116,7 +118,7 @@ describe.skip('MongoClient.close() Integration', () => {
describe('MonitorInterval', () => {
describe('Node.js resource: Timer', () => {
describe('after a new monitor is made', () => {
it(
it.skip(
'monitor interval timer is cleaned up by client.close()',
metadata,
async function () {
Expand Down Expand Up @@ -149,7 +151,7 @@ describe.skip('MongoClient.close() Integration', () => {
});

describe('after a heartbeat fails', () => {
it(
it.skip(
'the new monitor interval timer is cleaned up by client.close()',
metadata,
async () => {
Expand All @@ -159,7 +161,6 @@ describe.skip('MongoClient.close() Integration', () => {
const willBeHeartbeatFailed = once(client, 'serverHeartbeatFailed');
client.connect();
await willBeHeartbeatFailed;

function getMonitorTimer(servers) {
for (const [, server] of servers) {
return server?.monitor.monitorId.timerId;
Expand All @@ -182,7 +183,7 @@ describe.skip('MongoClient.close() Integration', () => {

describe('Monitoring Connection', () => {
describe('Node.js resource: Socket', () => {
it('no sockets remain after client.close()', metadata, async function () {
it.skip('no sockets remain after client.close()', metadata, async function () {
const run = async function ({ MongoClient, uri, expect, getSocketEndpoints }) {
const client = new MongoClient(uri);
await client.connect();
Expand Down Expand Up @@ -210,7 +211,7 @@ describe.skip('MongoClient.close() Integration', () => {
describe('RTT Pinger', () => {
describe('Node.js resource: Timer', () => {
describe('after entering monitor streaming mode ', () => {
it(
it.skip(
'the rtt pinger timer is cleaned up by client.close()',
metadata,
async function () {
Expand Down Expand Up @@ -246,8 +247,8 @@ describe.skip('MongoClient.close() Integration', () => {
describe('Connection', () => {
describe('Node.js resource: Socket', () => {
describe('when rtt monitoring is turned on', () => {
it('no sockets remain after client.close()', metadata, async () => {
const run = async ({ MongoClient, uri, expect, getSockets, once, log }) => {
it.skip('no sockets remain after client.close()', metadata, async () => {
const run = async ({ MongoClient, uri, expect, getSockets, once }) => {
const heartbeatFrequencyMS = 500;
const client = new MongoClient(uri, {
serverMonitoringMode: 'stream',
Expand All @@ -264,7 +265,6 @@ describe.skip('MongoClient.close() Integration', () => {

while (heartbeatOccurredSet.size < servers.size) {
const ev = await once(client, 'serverHeartbeatSucceeded');
log({ ev: ev[0] });
heartbeatOccurredSet.add(ev[0].connectionId);
}

Expand All @@ -280,8 +280,6 @@ describe.skip('MongoClient.close() Integration', () => {

// close the client
await client.close();

log({ socketsAfterClose: getSockets() });
// upon close, assert rttPinger sockets are cleaned up
const activeSocketsAfterClose = activeSocketsAfterHeartbeat();
expect(activeSocketsAfterClose).to.have.lengthOf(0);
Expand All @@ -298,7 +296,7 @@ describe.skip('MongoClient.close() Integration', () => {
describe('ConnectionPool', () => {
describe('Node.js resource: minPoolSize timer', () => {
describe('after new connection pool is created', () => {
it('the minPoolSize timer is cleaned up by client.close()', async function () {
it.skip('the minPoolSize timer is cleaned up by client.close()', async function () {
const run = async function ({ MongoClient, uri, expect, getTimerCount }) {
const client = new MongoClient(uri, { minPoolSize: 1 });
let minPoolSizeTimerCreated = false;
Expand Down Expand Up @@ -356,7 +354,7 @@ describe.skip('MongoClient.close() Integration', () => {
await utilClient.close();
});

it('the wait queue timer is cleaned up by client.close()', async function () {
it.skip('the wait queue timer is cleaned up by client.close()', async function () {
const run = async function ({ MongoClient, uri, expect, getTimerCount, once }) {
const waitQueueTimeoutMS = 1515;

Expand Down Expand Up @@ -398,7 +396,7 @@ describe.skip('MongoClient.close() Integration', () => {
describe('Connection', () => {
describe('Node.js resource: Socket', () => {
describe('after a minPoolSize has been set on the ConnectionPool', () => {
it('no sockets remain after client.close()', async function () {
it.skip('no sockets remain after client.close()', async function () {
const run = async function ({ MongoClient, uri, expect, getSockets }) {
// assert no sockets to start with
expect(getSockets()).to.have.lengthOf(0);
Expand Down Expand Up @@ -430,13 +428,17 @@ describe.skip('MongoClient.close() Integration', () => {
const metadata: MongoDBMetadataUI = { requires: { topology: 'sharded' } };

describe('after SRVPoller is created', () => {
it('timers are cleaned up by client.close()', metadata, async () => {
it.skip('timers are cleaned up by client.close()', metadata, async () => {
const run = async function ({ MongoClient, expect, getTimerCount }) {
const SRV_CONNECTION_STRING = `mongodb+srv://test1.test.build.10gen.cc`;
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved

// 27018 localhost.test.build.10gen.cc.
// 27017 localhost.test.build.10gen.cc.

const client = new MongoClient(SRV_CONNECTION_STRING);
const client = new MongoClient(SRV_CONNECTION_STRING, {
serverSelectionTimeoutMS: 2000,
tls: false
});
await client.connect();
// the current expected behavior is that _timeout is set to undefined until SRV polling starts
// then _timeout is set to undefined again when SRV polling stops
Expand All @@ -452,29 +454,143 @@ describe.skip('MongoClient.close() Integration', () => {
});

describe('ClientSession (Implicit)', () => {
let idleSessionsBeforeClose;
let idleSessionsAfterClose;
let client;
let utilClient;
let session;

const metadata: MongoDBMetadataUI = {
requires: {
topology: ['replicaset', 'sharded'],
mongodb: '>=4.2'
}
};

beforeEach(async function () {
client = this.configuration.newClient();
utilClient = this.configuration.newClient();
await client.connect();
await client
.db('db')
.collection('collection')
?.drop()
.catch(() => null);
const collection = await client.db('db').createCollection('collection');
session = client.startSession({ explicit: false });
session.startTransaction();
await collection.insertOne({ x: 1 }, { session });

const opBefore = await utilClient.db().admin().command({ currentOp: 1 });
idleSessionsBeforeClose = opBefore.inprog.filter(s => s.type === 'idleSession');

await client.close();

const opAfter = await utilClient.db().admin().command({ currentOp: 1 });
idleSessionsAfterClose = opAfter.inprog.filter(s => s.type === 'idleSession');

await utilClient.close();
});

afterEach(async function () {
await utilClient?.close();
await session?.endSession();
await client?.close();
});

describe('Server resource: LSID/ServerSession', () => {
describe('after a clientSession is implicitly created and used', () => {
it.skip('the server-side ServerSession is cleaned up by client.close()', async function () {});
it(
'the server-side ServerSession is cleaned up by client.close()',
metadata,
async function () {
expect(idleSessionsBeforeClose).to.not.be.empty;
expect(idleSessionsAfterClose).to.be.empty;
}
);
});
});

describe('Server resource: Transactions', () => {
describe('after a clientSession is implicitly created and used', () => {
it.skip('the server-side transaction is cleaned up by client.close()', async function () {});
it(
'the server-side transaction is cleaned up by client.close()',
metadata,
async function () {
expect(idleSessionsBeforeClose[0].transaction.txnNumber).to.not.null;
expect(idleSessionsAfterClose).to.be.empty;
}
);
});
});
});

describe('ClientSession (Explicit)', () => {
let idleSessionsBeforeClose;
let idleSessionsAfterClose;
let client;
let utilClient;
let session;

const metadata: MongoDBMetadataUI = {
requires: {
topology: ['replicaset', 'sharded'],
mongodb: '>=4.2'
}
};

beforeEach(async function () {
client = this.configuration.newClient();
utilClient = this.configuration.newClient();
await client.connect();
await client
.db('db')
.collection('collection')
?.drop()
.catch(() => null);
const collection = await client.db('db').createCollection('collection');
session = client.startSession();
session.startTransaction();
await collection.insertOne({ x: 1 }, { session });

const opBefore = await utilClient.db().admin().command({ currentOp: 1 });
idleSessionsBeforeClose = opBefore.inprog.filter(s => s.type === 'idleSession');

await client.close();

const opAfter = await utilClient.db().admin().command({ currentOp: 1 });
idleSessionsAfterClose = opAfter.inprog.filter(s => s.type === 'idleSession');
});

afterEach(async function () {
await utilClient?.close();
await session?.endSession();
await client?.close();
});

describe('Server resource: LSID/ServerSession', () => {
describe('after a clientSession is created and used', () => {
it.skip('the server-side ServerSession is cleaned up by client.close()', async function () {});
it(
'the server-side ServerSession is cleaned up by client.close()',
metadata,
async function () {
expect(idleSessionsBeforeClose).to.not.be.empty;
expect(idleSessionsAfterClose).to.be.empty;
}
);
});
});

describe('Server resource: Transactions', () => {
describe('after a clientSession is created and used', () => {
it.skip('the server-side transaction is cleaned up by client.close()', async function () {});
it(
'the server-side transaction is cleaned up by client.close()',
metadata,
async function () {
expect(idleSessionsBeforeClose[0].transaction.txnNumber).to.not.null;
expect(idleSessionsAfterClose).to.be.empty;
}
);
});
});
});
Expand All @@ -490,7 +606,7 @@ describe.skip('MongoClient.close() Integration', () => {
describe('KMS Request', () => {
describe('Node.js resource: TLS file read', () => {
describe('when KMSRequest reads an infinite TLS file', () => {
it('the file read is interrupted by client.close()', metadata, async () => {
it.skip('the file read is interrupted by client.close()', metadata, async () => {
await runScriptAndGetProcessInfo(
'tls-file-read-auto-encryption',
config,
Expand Down Expand Up @@ -584,7 +700,64 @@ describe.skip('MongoClient.close() Integration', () => {

describe('Server resource: Cursor', () => {
describe('after cursors are created', () => {
it.skip('all active server-side cursors are closed by client.close()', async function () {});
let client: MongoClient;
let coll: Collection;
let cursor: FindCursor;
let utilClient: MongoClient;

beforeEach(async function () {
client = this.configuration.newClient();
utilClient = this.configuration.newClient();
await client.connect();
await client
.db('db')
.collection('coll')
?.drop()
.catch(() => null);
coll = await client
.db('db')
.createCollection('coll', { capped: true, size: 1_000, max: 4 });
await coll.insertMany([{ a: 1 }, { b: 2 }, { c: 3 }]);
});

afterEach(async function () {
await utilClient?.close();
await client?.close();
await cursor?.close();
});

it('all active server-side cursors are closed by client.close()', async function () {
const getCursors = async () => {
const res = await utilClient
.db()
.admin()
.command({
baileympearson marked this conversation as resolved.
Show resolved Hide resolved
aggregate: 1,
cursor: {},
pipeline: [{ $currentOp: { idleCursors: true } }]
});
return res.cursor.firstBatch.filter(
r => r.type === 'idleCursor' || (r.type === 'op' && r.desc === 'getMore')
);
};

cursor = coll.find(
{},
{
tailable: true,
awaitData: true
}
);
await cursor.next();

// assert creation
expect(await getCursors()).to.not.be.empty;
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved

await client.close();

// assert clean-up
baileympearson marked this conversation as resolved.
Show resolved Hide resolved
expect(await getCursors()).to.be.empty;
});
});
});
});