mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-10-02 00:27:21 +08:00
* test: bind scenario seeds and exercise the library baton in CI * test: run named scenarios through a remote Docker executor * test: bound and clean up the testbed image-load check --------- Co-authored-by: dev-driver <dev-driver@openrig-build> Co-authored-by: dev-qa <dev-qa@openrig-build>
76 lines
3.1 KiB
JavaScript
76 lines
3.1 KiB
JavaScript
// Client-side helpers for the existing scenario scripts; never runs a daemon.
|
|
import { spawn, spawnSync } from 'node:child_process';
|
|
import { writeFileSync } from 'node:fs';
|
|
import { constants } from 'node:os';
|
|
import { resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
export function dockerPlatform(server) {
|
|
if (server?.Os !== 'linux' || !['amd64', 'arm64'].includes(server?.Arch)) {
|
|
throw new Error(`Unsupported Docker server: ${server?.Os}/${server?.Arch}`);
|
|
}
|
|
return `linux/${server.Arch}`;
|
|
}
|
|
|
|
export async function runWithDeadline(argv, { timeoutMs, killAfterMs = 15000 }) {
|
|
if (!argv.length || !Number.isFinite(timeoutMs) || timeoutMs <= 0 || killAfterMs <= 0) {
|
|
throw new Error('Command and positive finite deadline required');
|
|
}
|
|
return new Promise(resolveResult => {
|
|
const child = spawn(argv[0], argv.slice(1), { stdio: 'inherit', detached: true });
|
|
let forced, escalation, finished = false;
|
|
const signalChild = signal => {
|
|
if (!child.pid) return;
|
|
try { process.kill(-child.pid, signal); } catch (error) {
|
|
if (error.code !== 'ESRCH') throw error;
|
|
}
|
|
};
|
|
const terminate = code => {
|
|
if (forced !== undefined) return;
|
|
forced = code;
|
|
signalChild('SIGTERM');
|
|
escalation = setTimeout(() => signalChild('SIGKILL'), killAfterMs);
|
|
};
|
|
const timeout = setTimeout(() => terminate(124), timeoutMs);
|
|
const interrupt = () => terminate(130);
|
|
const shutdown = () => terminate(143);
|
|
process.once('SIGINT', interrupt);
|
|
process.once('SIGTERM', shutdown);
|
|
const finish = code => {
|
|
if (finished) return;
|
|
finished = true;
|
|
if (forced !== undefined) signalChild('SIGKILL');
|
|
clearTimeout(timeout); clearTimeout(escalation);
|
|
process.removeListener('SIGINT', interrupt);
|
|
process.removeListener('SIGTERM', shutdown);
|
|
resolveResult(forced ?? code);
|
|
};
|
|
child.once('error', error => {
|
|
console.error(`Cannot run ${argv[0]}: ${error.message}`);
|
|
finish(127);
|
|
});
|
|
child.once('close', (code, signal) => finish(code ?? (128 + (constants.signals[signal] ?? 0))));
|
|
});
|
|
}
|
|
|
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
try {
|
|
const [operation, ...args] = process.argv.slice(2);
|
|
if (operation === 'platform') {
|
|
const version = spawnSync('docker', ['version', '--format', '{{json .Server}}'], { encoding: 'utf8', timeout: 30000 });
|
|
if (version.error || version.status !== 0) throw new Error(version.error?.message ?? version.stderr);
|
|
const server = JSON.parse(version.stdout);
|
|
const platform = dockerPlatform(server);
|
|
if (args[0]) writeFileSync(args[0], JSON.stringify({ platform, server }, null, 2) + '\n');
|
|
console.log(platform);
|
|
} else if (operation === 'timeout') {
|
|
process.exitCode = await runWithDeadline(args.slice(1), { timeoutMs: Number(args[0]) * 1000 });
|
|
} else {
|
|
throw new Error('Expected platform [receipt-path] or timeout <seconds> <command> [args...]');
|
|
}
|
|
} catch (error) {
|
|
console.error(error.message);
|
|
process.exitCode = 2;
|
|
}
|
|
}
|