Files
smartproxy/test/helpers/port-allocator.ts

71 lines
2.0 KiB
TypeScript

import * as net from 'net';
/**
* Finds `count` free ports by binding to port 0 and reading the OS-assigned port.
* All servers are opened simultaneously to guarantee uniqueness.
* Returns an array of guaranteed-free ports.
*/
export async function findFreePorts(count: number): Promise<number[]> {
const servers: net.Server[] = [];
const ports: number[] = [];
// Open all servers simultaneously on port 0
await Promise.all(
Array.from({ length: count }, () =>
new Promise<void>((resolve, reject) => {
const server = net.createServer();
server.listen(0, '127.0.0.1', () => {
const addr = server.address() as net.AddressInfo;
ports.push(addr.port);
servers.push(server);
resolve();
});
server.on('error', reject);
})
)
);
// Close all servers
await Promise.all(
servers.map(
(server) => new Promise<void>((resolve) => server.close(() => resolve()))
)
);
return ports;
}
/**
* Verifies that all given ports are free (not listening).
* Useful as a cleanup assertion at the end of tests.
* Throws if any port is still in use.
*/
export async function assertPortsFree(ports: number[]): Promise<void> {
const results = await Promise.all(
ports.map(
(port) =>
new Promise<{ port: number; free: boolean }>((resolve) => {
const client = net.connect({ port, host: '127.0.0.1' });
client.on('connect', () => {
client.destroy();
resolve({ port, free: false });
});
client.on('error', () => {
resolve({ port, free: true });
});
client.setTimeout(1000, () => {
client.destroy();
resolve({ port, free: true });
});
})
)
);
const occupied = results.filter((r) => !r.free);
if (occupied.length > 0) {
throw new Error(
`Ports still in use after cleanup: ${occupied.map((r) => r.port).join(', ')}`
);
}
}