// SPDX-License-Identifier: GPL-2.0-or-later // Copyright (C) 2026 Bryan Joshua Pedini /** * Per-test temporary workspaces and TCP port allocation. */ import fs from 'node:fs'; import net from 'node:net'; import path from 'node:path'; import { ALPINE_IMAGE, LABEL_RUN, RESOURCE_PREFIX, runId, workspacesDir } from './env'; import { HarnessError } from './errors'; import { runProcess, toolEnv } from './proc'; export class Workspace { /** Default SQLite path for the test (`/dpsapi.db`); nothing is created there by the harness. */ readonly dbPath: string; constructor(readonly dir: string) { this.dbPath = path.join(dir, 'dpsapi.db'); } /** Creates a fresh workspace directory under the run's workspaces dir. */ static create(key: string): Workspace { fs.mkdirSync(workspacesDir(), { recursive: true }); const dir = fs.mkdtempSync(path.join(workspacesDir(), `${key}-`)); return new Workspace(dir); } /** Absolute path inside the workspace (not created). */ path(...parts: string[]): string { return path.join(this.dir, ...parts); } /** Creates (recursively) and returns a directory inside the workspace. */ mkdir(...parts: string[]): string { const p = this.path(...parts); fs.mkdirSync(p, { recursive: true }); return p; } writeFile(name: string, data: string | Buffer, mode?: number): string { const p = this.path(name); fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, data, mode === undefined ? undefined : { mode }); return p; } /** Removes the workspace, falling back to a throwaway container for root-owned files. */ async remove(): Promise { await forceRemoveDir(this.dir); } } /** * Removes a directory tree. Files written by containers running as root cannot be removed by * the host user; for those, the contents are deleted from a throwaway, labelled alpine container. */ export async function forceRemoveDir(dir: string): Promise { try { fs.rmSync(dir, { recursive: true, force: true }); return; } catch (e) { const code = (e as NodeJS.ErrnoException).code; if (code !== 'EACCES' && code !== 'EPERM' && code !== 'ENOTEMPTY') throw e; } const name = `${RESOURCE_PREFIX}${runId()}-rm-${Math.random().toString(36).slice(2, 10)}`; const r = await runProcess( 'docker', ['run', '--rm', '--name', name, '--label', `${LABEL_RUN}=${runId()}`, '--network', 'none', '-v', `${dir}:/victim`, ALPINE_IMAGE, 'sh', '-c', 'find /victim -mindepth 1 -delete'], { env: toolEnv(), timeoutMs: 60_000, label: 'docker cleanup container' }, ); if (r.exitCode !== 0) throw new HarnessError('infrastructure', `cannot clean ${dir} via container: ${r.stderr}`); fs.rmSync(dir, { recursive: true, force: true }); } /** Lowest port used by the allocator; windows of PORT_WINDOW ports per parallel slot, below the ephemeral range. */ const PORT_BASE = 20_000; const PORT_WINDOW = 500; const PORT_SLOTS = 24; // 20000..31999, below Linux's default ephemeral range (32768+) let portCursor = 0; /** True when `port` can currently be bound on every local address (checked dual-stack). */ export function isPortFree(port: number): Promise { const tryListen = (host: string, ipv6Only: boolean) => new Promise<'free' | 'busy' | 'unsupported'>((resolve) => { const srv = net.createServer(); srv.once('error', (e: NodeJS.ErrnoException) => { resolve(e.code === 'EADDRINUSE' || e.code === 'EACCES' ? 'busy' : 'unsupported'); }); srv.listen({ host, port, ipv6Only, exclusive: true }, () => srv.close(() => resolve('free'))); }); return tryListen('::', false).then(async (r) => { if (r === 'unsupported') return (await tryListen('0.0.0.0', false)) === 'free'; return r === 'free'; }); } /** * Allocates a currently free TCP port. Concurrent workers draw from disjoint windows keyed * by their parallel index, so two running tests never get the same port from the allocator; * the bind check additionally skips ports used by anything else on the host. */ export async function allocatePort(parallelIndex: number): Promise { const slot = parallelIndex % PORT_SLOTS; for (let attempt = 0; attempt < PORT_WINDOW; attempt++) { const port = PORT_BASE + slot * PORT_WINDOW + (portCursor++ % PORT_WINDOW); if (await isPortFree(port)) return port; } throw new HarnessError('infrastructure', `no free port in window of parallel slot ${slot}`); } /** Holds a port busy (listening socket) until close() — for "address already in use" tests. */ export async function occupyPort(port: number, host = '127.0.0.1'): Promise<{ close: () => Promise }> { const srv = net.createServer((s) => s.destroy()); await new Promise((resolve, reject) => { srv.once('error', reject); srv.listen({ host, port, exclusive: true }, () => resolve()); }); return { close: () => new Promise((r) => srv.close(() => r())) }; }