Skip to content

Commit 631ec28

Browse files
committed
feat(getport): add experimental "EXP_NET0LISTEN" option
this changes the behavior to use "net.listen(0)" to create a random port re #827
1 parent c065147 commit 631ec28

File tree

4 files changed

+68
-10
lines changed

4 files changed

+68
-10
lines changed

docs/api/config-options.md

+12
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,18 @@ Also see [ARCHIVE_NAME](#archive_name).
239239
Keep in mind that downloaded binaries will never be automatically deleted.
240240
:::
241241

242+
### EXP_NET0LISTEN
243+
244+
| Environment Variable | PackageJson |
245+
| :------------------: | :---------: |
246+
| `MONGOMS_EXP_NET0LISTEN` | `expNet0Listen` |
247+
248+
Option `EXP_NET0LISTEN` is used to use the experimental (non-predictable) port generation of `net.listen`
249+
250+
This is a experimental option, it maybe removed, renamed or have changed behavior in the future.
251+
252+
Default: `false`
253+
242254
## How to use them in the package.json
243255

244256
To use the config options in the `package.json`, they need to be camelCased (and without `_`), and need to be in the property `config.mongodbMemoryServer`

packages/mongodb-memory-server-core/src/util/getport/__tests__/getport.test.ts

+36-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import resolveConfig, {
2+
ResolveConfigVariables,
3+
defaultValues,
4+
envToBool,
5+
setDefaultValue,
6+
} from '../../resolveConfig';
17
import * as getPort from '../index';
28
import * as net from 'node:net';
39

@@ -26,22 +32,28 @@ describe('getport', () => {
2632

2733
describe('tryPort', () => {
2834
it('should return "true" on unused port', async () => {
29-
await expect(getPort.tryPort(20000)).resolves.toStrictEqual(true);
35+
await expect(getPort.tryPort(20000)).resolves.toStrictEqual(20000);
3036
});
3137

32-
it('should return "false" on used port', async () => {
38+
it('should return "-1" on used port', async () => {
3339
const testPort = 30000;
3440
const blockingServer = net.createServer();
3541
blockingServer.unref();
3642
blockingServer.listen(testPort);
37-
await expect(getPort.tryPort(testPort)).resolves.toStrictEqual(false);
43+
await expect(getPort.tryPort(testPort)).resolves.toStrictEqual(-1);
3844
});
3945
});
4046

4147
describe('getFreePort', () => {
48+
const originalResolve = new Map(defaultValues.entries());
4249
beforeEach(() => {
4350
// reset cache to be more consistent in tests
4451
getPort.resetPortsCache();
52+
defaultValues.clear();
53+
54+
for (const [key, val] of originalResolve.entries()) {
55+
defaultValues.set(key, val);
56+
}
4557
});
4658

4759
it('should give a free port from default', async () => {
@@ -60,6 +72,8 @@ describe('getport', () => {
6072
});
6173

6274
it('port should be predictable', async () => {
75+
expect(envToBool(resolveConfig(ResolveConfigVariables.EXP_NET0LISTEN))).toStrictEqual(false);
76+
6377
const testPort = 23232;
6478
await expect(getPort.getFreePort(testPort)).resolves.toStrictEqual(testPort);
6579

@@ -74,5 +88,24 @@ describe('getport', () => {
7488

7589
server.close();
7690
});
91+
92+
it('EXP_NET0LISTEN should not be predictable', async () => {
93+
setDefaultValue(ResolveConfigVariables.EXP_NET0LISTEN, 'true');
94+
expect(envToBool(resolveConfig(ResolveConfigVariables.EXP_NET0LISTEN))).toStrictEqual(true);
95+
96+
const testPort = 23232;
97+
await expect(getPort.getFreePort(testPort)).resolves.toStrictEqual(testPort);
98+
99+
const server = await new Promise<net.Server>((res) => {
100+
const server = net.createServer();
101+
server.unref();
102+
server.listen(testPort, () => res(server));
103+
});
104+
105+
const foundPort = await getPort.getFreePort(testPort);
106+
expect(foundPort).not.toStrictEqual(testPort); // not predictable port, so not testable to be a exact number
107+
108+
server.close();
109+
});
77110
});
78111
});

packages/mongodb-memory-server-core/src/util/getport/index.ts

+19-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import resolveConfig, { ResolveConfigVariables, envToBool } from '../resolveConfig';
12
import * as net from 'node:net';
23

34
/** Linux min port that does not require root permissions */
@@ -59,8 +60,15 @@ export async function getFreePort(
5960
while (tries <= max_tries) {
6061
tries += 1;
6162

62-
// use "startPort" at first try, otherwise increase from last number
63-
const nextPort = tries === 1 ? firstPort : validPort(PORTS_CACHE.lastNumber + tries);
63+
let nextPort: number;
64+
65+
if (envToBool(resolveConfig(ResolveConfigVariables.EXP_NET0LISTEN))) {
66+
// "0" means to use ".listen" random port
67+
nextPort = tries === 1 ? firstPort : 0;
68+
} else {
69+
// use "startPort" at first try, otherwise increase from last number
70+
nextPort = tries === 1 ? firstPort : validPort(PORTS_CACHE.lastNumber + tries);
71+
}
6472

6573
// try next port, because it is already in the cache
6674
if (PORTS_CACHE.ports.has(nextPort)) {
@@ -71,8 +79,10 @@ export async function getFreePort(
7179
// only set "lastNumber" if the "nextPort" was not in the cache
7280
PORTS_CACHE.lastNumber = nextPort;
7381

74-
if (await tryPort(nextPort)) {
75-
return nextPort;
82+
const triedPort = await tryPort(nextPort);
83+
84+
if (triedPort > 0) {
85+
return triedPort;
7686
}
7787
}
7888

@@ -99,7 +109,7 @@ export function validPort(port: number): number {
99109
* @returns "true" if the port is not in use, "false" if in use
100110
* @throws The error given if the code is not "EADDRINUSE"
101111
*/
102-
export function tryPort(port: number): Promise<boolean> {
112+
export function tryPort(port: number): Promise<number> {
103113
return new Promise((res, rej) => {
104114
const server = net.createServer();
105115

@@ -113,12 +123,14 @@ export function tryPort(port: number): Promise<boolean> {
113123
rej(err);
114124
}
115125

116-
res(false);
126+
res(-1);
117127
});
118128
server.listen(port, () => {
129+
const address = server.address();
130+
const port = (address as net.AddressInfo).port;
119131
server.close();
120132

121-
res(true);
133+
res(port);
122134
});
123135
});
124136
}

packages/mongodb-memory-server-core/src/util/resolveConfig.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export enum ResolveConfigVariables {
2727
USE_ARCHIVE_NAME_FOR_BINARY_NAME = 'USE_ARCHIVE_NAME_FOR_BINARY_NAME',
2828
MAX_REDIRECTS = 'MAX_REDIRECTS',
2929
DISTRO = 'DISTRO',
30+
EXP_NET0LISTEN = 'EXP_NET0LISTEN',
3031
}
3132

3233
/** The Prefix for Environmental values */

0 commit comments

Comments
 (0)