mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
test(rex): wait for a timed-out run's orphaned child to be reaped instead of checking once (#1954)
## What this fixes `rex-transport.test.ts` › "ends a run that does not answer in time, with its process group" failed intermittently on macOS CI runners (for example run 40141827784, attempt 1). The failing assertion was the last one: an instant check that the script's backgrounded `sleep` was gone (`expect(() => process.kill(child, 0)).toThrow()`). The deadline and timing assertions before it passed. `stopGroup` returns once the run's process group is empty and the direct child is reaped. The orphaned `sleep` is reaped by the system on its own schedule, so on a loaded runner it can still be listed for a moment after the run settles. An empty pid file would also have read as pid 0, and `process.kill(0, 0)` signals the test's own process group. ## The change (test only) - **The orphan check:** wait up to five seconds for it to go (`expect.poll`), instead of reading the process table at one instant. - **The pid:** assert the pid read from the file is a real one (an integer above 1). - **The deadline:** one second instead of 300 ms, so the script's start fits well inside it on a loaded runner. - **The elapsed bound:** under two seconds, which is the deadline plus `stopGroup`'s one-second grace. It still fails if the deadline stops ending the group at once. No product code changes. ## Proof (local, macOS) - **The test alone:** 8 of 8 repeated runs pass, and the whole file passes 24 of 24. - **Without the immediate group kill at the deadline:** the test fails (2,050 ms against the 2,000 ms bound). - **Without the deadline:** the test fails (it times out at 20 s).
This commit is contained in:
1 parent
c3c514f2a0
commit
54864d331f
1 file changed
+11
-4
@@ -197,12 +197,19 @@ describe("spawnRex", () => {
|
||||
try {
|
||||
const failure = vi.fn();
|
||||
const started = performance.now();
|
||||
await spawnRex([file], "{}", 300).catch(failure);
|
||||
expect(performance.now() - started).toBeLessThan(1_000);
|
||||
// A deadline the script's start fits well inside, even on a loaded runner.
|
||||
await spawnRex([file], "{}", 1_000).catch(failure);
|
||||
// Ended at the deadline: not after stopGroup's one-second grace, and not when the sleep ends 30 s later.
|
||||
expect(performance.now() - started).toBeLessThan(2_000);
|
||||
expect(failure).toHaveBeenCalledOnce();
|
||||
expect(failure.mock.calls[0]![0]).toMatchObject({ exitCode: null, message: "did not answer within 300 ms" });
|
||||
expect(failure.mock.calls[0]![0]).toMatchObject({ exitCode: null, message: "did not answer within 1000 ms" });
|
||||
// An empty file would read as pid 0, which names this test's own process group.
|
||||
const child = Number(fs.readFileSync(path.join(path.dirname(file), "child.pid"), "utf8"));
|
||||
expect(() => process.kill(child, 0)).toThrow();
|
||||
expect(Number.isInteger(child) && child > 1, `backgrounded pid ${child}`).toBe(true);
|
||||
// The backgrounded sleep was ended with the run's group. Its parent is gone, so the system reaps it on its own
|
||||
// schedule: wait a bounded moment for that instead of reading the process table at one instant.
|
||||
await expect.poll(() => { try { process.kill(child, 0); return true; } catch { return false; } }, { timeout: 5_000, interval: 20 })
|
||||
.toBe(false);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user