test(ci): pin how a chunk's SIGKILL cause is reported
Drives real subprocesses so the 137 under test is a genuine SIGKILL rather than a hand-written constant: `kill -9 $$` for the OOM-killer shape, and a watchdog-killed `sleep` for the timeout shape.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { describeChunkFailure } from "./ci-test-ts.ts";
|
||||
|
||||
// The two ways a chunk reaches SIGKILL are indistinguishable by exit code, so
|
||||
// these drive real subprocesses to produce a genuine 137 rather than asserting
|
||||
// against a hand-written constant.
|
||||
async function spawnExitCode(script: string): Promise<number> {
|
||||
const proc = Bun.spawn(["sh", "-c", script], { stdout: "ignore", stderr: "ignore" });
|
||||
return await proc.exited;
|
||||
}
|
||||
|
||||
// Re-hosts the sequential runner's failure tail: spawn, watchdog, attribute.
|
||||
// `runTestCommand` itself is not injectable (it builds argv from the repo
|
||||
// layout), so the decision under test is driven directly.
|
||||
async function runWithWatchdog(script: string, timeoutMs: number): Promise<string> {
|
||||
const proc = Bun.spawn(["sh", "-c", script], { stdout: "ignore", stderr: "ignore" });
|
||||
let timedOut = false;
|
||||
const killTimer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
proc.kill("SIGKILL");
|
||||
}, timeoutMs);
|
||||
const exitCode = await proc.exited;
|
||||
clearTimeout(killTimer);
|
||||
return describeChunkFailure(exitCode, timedOut);
|
||||
}
|
||||
|
||||
describe("describeChunkFailure", () => {
|
||||
test("a real SIGKILL that the watchdog did not cause is attributed to the OOM killer", async () => {
|
||||
const exitCode = await spawnExitCode("kill -9 $$");
|
||||
expect(exitCode).toBe(137);
|
||||
|
||||
const message = describeChunkFailure(exitCode, false);
|
||||
expect(message).toContain("OOM killer");
|
||||
expect(message).toContain("chunkSize");
|
||||
// The old wording carried no cause at all; it must not come back.
|
||||
expect(message).not.toBe("failed with exit code 137");
|
||||
});
|
||||
|
||||
test("a watchdog kill is attributed to the watchdog, not to memory", async () => {
|
||||
const message = await runWithWatchdog("sleep 30", 150);
|
||||
expect(message).toContain("chunk watchdog");
|
||||
expect(message).toContain("OMP_TEST_CHUNK_TIMEOUT");
|
||||
expect(message).not.toContain("OOM killer");
|
||||
});
|
||||
|
||||
test("the two SIGKILL causes produce different messages from the same exit code", async () => {
|
||||
const oomKilled = describeChunkFailure(137, false);
|
||||
const watchdogKilled = describeChunkFailure(137, true);
|
||||
expect(oomKilled).not.toBe(watchdogKilled);
|
||||
});
|
||||
|
||||
test("an ordinary test failure keeps the plain wording", async () => {
|
||||
const exitCode = await spawnExitCode("exit 1");
|
||||
expect(exitCode).toBe(1);
|
||||
expect(describeChunkFailure(exitCode, false)).toBe("failed with exit code 1");
|
||||
});
|
||||
|
||||
test("a bun crash exit keeps the plain wording so the retry log still reads naturally", () => {
|
||||
expect(describeChunkFailure(134, false)).toBe("failed with exit code 134");
|
||||
expect(describeChunkFailure(139, false)).toBe("failed with exit code 139");
|
||||
});
|
||||
|
||||
test("the watchdog message reports the configured timeout", () => {
|
||||
const previous = Bun.env.OMP_TEST_CHUNK_TIMEOUT;
|
||||
Bun.env.OMP_TEST_CHUNK_TIMEOUT = "42";
|
||||
try {
|
||||
expect(describeChunkFailure(137, true)).toContain("42s");
|
||||
} finally {
|
||||
if (previous === undefined) delete Bun.env.OMP_TEST_CHUNK_TIMEOUT;
|
||||
else Bun.env.OMP_TEST_CHUNK_TIMEOUT = previous;
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user