71 lines
2.0 KiB
TypeScript
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(', ')}`
|
|
);
|
|
}
|
|
}
|