From 2786ccecdc6b8aeff55cc25b3eb4b1c595060daa Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Tue, 14 Jul 2026 13:51:01 +0100 Subject: [PATCH] fix(mongodb-runner): don't leave a dangling --port flag on secondaries removePortArg() only removed the value after --port, not the flag itself, so replset secondaries got both the original --port and a second --port 0 appended, causing mongod to reject the duplicate flag. Also fixes two places that only checked args.includes('--port') and therefore missed the --port=X form: the mongos random-port retry logic and the default --port 0 injection. Extracted a shared hasPortArg() helper in util.ts to avoid duplicating this check. --- .../mongodb-runner/src/mongocluster.spec.ts | 50 +++++++++++++++++++ packages/mongodb-runner/src/mongocluster.ts | 10 +--- packages/mongodb-runner/src/mongoserver.ts | 5 +- packages/mongodb-runner/src/util.ts | 5 ++ 4 files changed, 60 insertions(+), 10 deletions(-) diff --git a/packages/mongodb-runner/src/mongocluster.spec.ts b/packages/mongodb-runner/src/mongocluster.spec.ts index 1e95da1f..10aa1124 100644 --- a/packages/mongodb-runner/src/mongocluster.spec.ts +++ b/packages/mongodb-runner/src/mongocluster.spec.ts @@ -173,6 +173,34 @@ describe('MongoCluster', function () { expect(+hello.passives.length + +hello.hosts.length).to.equal(3); }); + it('can spawn a 8.x replset with a pre-specified port for the primary', async function () { + cluster = await MongoCluster.start({ + version: '8.x', + topology: 'replset', + tmpDir, + args: ['--port', '50080'], + }); + expect(cluster.connectionString).to.include(`:50080`); + const hello = await cluster.withClient(async (client) => { + return await client.db('admin').command({ hello: 1 }); + }); + expect(+hello.passives.length + +hello.hosts.length).to.equal(3); + }); + + it('can spawn a 8.x replset with a pre-specified --port= for the primary', async function () { + cluster = await MongoCluster.start({ + version: '8.x', + topology: 'replset', + tmpDir, + args: ['--port=50081'], + }); + expect(cluster.connectionString).to.include(`:50081`); + const hello = await cluster.withClient(async (client) => { + return await client.db('admin').command({ hello: 1 }); + }); + expect(+hello.passives.length + +hello.hosts.length).to.equal(3); + }); + it('can spawn a 8.x replset with specific number of arbiters and secondaries', async function () { cluster = await MongoCluster.start({ version: '8.x', @@ -699,6 +727,28 @@ describe('MongoCluster', function () { }); }); + it('can spawn a sharded cluster with a pre-specified port for mongos (--port x)', async function () { + cluster = await MongoCluster.start({ + version: '8.x', + topology: 'sharded', + tmpDir, + secondaries: 0, + mongosArgs: [['--port', '50082']], + }); + expect(cluster.connectionString).to.include(`:50082`); + }); + + it('can spawn a sharded cluster with a pre-specified port for mongos (--port=x)', async function () { + cluster = await MongoCluster.start({ + version: '8.x', + topology: 'sharded', + tmpDir, + secondaries: 0, + mongosArgs: [['--port=50083']], + }); + expect(cluster.connectionString).to.include(`:50083`); + }); + it('can add authentication options and verify them after serialization', async function () { cluster = await MongoCluster.start({ version: '8.x', diff --git a/packages/mongodb-runner/src/mongocluster.ts b/packages/mongodb-runner/src/mongocluster.ts index c71e574e..6f8d24a8 100644 --- a/packages/mongodb-runner/src/mongocluster.ts +++ b/packages/mongodb-runner/src/mongocluster.ts @@ -19,6 +19,7 @@ import { debugVerbose, makeConnectionString, safePromiseAll, + hasPortArg, } from './util'; import { OIDCMockProviderProcess } from './oidc'; import { EventEmitter } from 'events'; @@ -166,7 +167,7 @@ export type MongoClusterEvents = { function removePortArg([...args]: string[]): string[] { let portArgIndex = -1; if ((portArgIndex = args.indexOf('--port')) !== -1) { - args.splice(portArgIndex + 1, 1); + args.splice(portArgIndex, 2); } else if ( (portArgIndex = args.findIndex((arg) => arg.startsWith('--port='))) !== -1 ) { @@ -175,13 +176,6 @@ function removePortArg([...args]: string[]): string[] { return args; } -function hasPortArg(args: string[] | undefined): boolean { - if (!args) return false; - return ( - args.includes('--port') || args.some((arg) => arg.startsWith('--port=')) - ); -} - function processRSMembers(options: MongoClusterOptions): { rsMembers: RSMemberOptions[]; replSetName: string; diff --git a/packages/mongodb-runner/src/mongoserver.ts b/packages/mongodb-runner/src/mongoserver.ts index 5c098380..4efc1d9e 100644 --- a/packages/mongodb-runner/src/mongoserver.ts +++ b/packages/mongodb-runner/src/mongoserver.ts @@ -22,6 +22,7 @@ import { makeConnectionString, sleep, eventually, + hasPortArg, } from './util'; /** @@ -187,7 +188,7 @@ export class MongoServer extends EventEmitter { } static async start({ ...options }: MongoServerOptions): Promise { - if (options.binary === 'mongos' && !options.args?.includes('--port')) { + if (options.binary === 'mongos' && !hasPortArg(options.args)) { // SERVER-78384: mongos before 7.1.0 does not understand `--port 0` ... // Just pick a random port in [1024, 49152), try to listen, and continue until // we find a free one. @@ -265,7 +266,7 @@ export class MongoServer extends EventEmitter { } commandline.push(...(options.args ?? [])); - if (!options.args?.includes('--port')) commandline.push('--port', '0'); + if (!hasPortArg(options.args)) commandline.push('--port', '0'); if (!options.args?.includes('--dbpath') && options.binary === 'mongod') commandline.push('--dbpath', options.docker ? '/tmp' : srv.dbPath!); if ( diff --git a/packages/mongodb-runner/src/util.ts b/packages/mongodb-runner/src/util.ts index eaafe9c1..24f249c1 100644 --- a/packages/mongodb-runner/src/util.ts +++ b/packages/mongodb-runner/src/util.ts @@ -10,6 +10,11 @@ export const sleep = (ms: number): Promise => new Promise((r) => setTimeout(r, ms)); export const range = (n: number): number[] => [...Array(n).keys()]; +export function hasPortArg(args: string[] | undefined): boolean { + if (!args) return false; + return args.some((arg) => arg === '--port' || arg.startsWith('--port=')); +} + /** * This function iterates `iterable` and applies `fn` to each item that * `iterable` produces. `fn` is not awaited on each iteration of `iterable`.