85 lines
2.9 KiB
JavaScript
85 lines
2.9 KiB
JavaScript
/**
|
|
* --------------------------------------------------------------------
|
|
* docmd : the zero-config documentation engine.
|
|
*
|
|
* M-11 — `docmd stop` sends SIGTERM first and waits for graceful
|
|
* shutdown before escalating to SIGKILL.
|
|
*
|
|
* Before the fix, `docmd stop` either killed the dev server
|
|
* immediately (SIGKILL on first failure) or relied on SIGTERM
|
|
* landing without checking whether the dev server had time to run
|
|
* its cleanup handlers. After the fix, the dev server installs a
|
|
* graceful-shutdown handler on SIGTERM (same as SIGINT) and `docmd
|
|
* stop` polls for process exit before escalating.
|
|
*
|
|
* Run: `node tests/runner.js --only=stop`
|
|
* --------------------------------------------------------------------
|
|
*/
|
|
|
|
import {
|
|
setup,
|
|
writeFile,
|
|
runTestFile
|
|
} from '../shared.js';
|
|
import { spawn, execSync } from 'node:child_process';
|
|
import { waitForExit } from '../../packages/core/dist/commands/stop.js';
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
const failures = [];
|
|
|
|
function assert(condition, message) {
|
|
if (!condition) {
|
|
failed++;
|
|
failures.push(message);
|
|
console.log(` ❌ ${message}`);
|
|
} else {
|
|
passed++;
|
|
console.log(` ✅ ${message}`);
|
|
}
|
|
}
|
|
|
|
export const test = runTestFile({
|
|
name: 'Stop sends SIGTERM and waits for graceful exit (M-11)',
|
|
emoji: '🛑',
|
|
run: async () => {
|
|
|
|
// waitForExit returns true when the target process exits within
|
|
// the grace window. We spawn `sleep 30`, send SIGTERM after a
|
|
// short delay, and assert waitForExit returns true within 1s.
|
|
{
|
|
const child = spawn('sleep', ['30'], { stdio: 'pipe' });
|
|
setTimeout(() => {
|
|
try { process.kill(child.pid, 'SIGTERM'); } catch { /* ignore */ }
|
|
}, 50);
|
|
const exited = await waitForExit(child.pid, 1000);
|
|
assert(exited, 'M-11: waitForExit returns true after SIGTERM kills a sleep child');
|
|
}
|
|
|
|
// waitForExit returns false when the target process is still
|
|
// alive at the timeout. Spawn `sleep 30`, do NOT signal it,
|
|
// assert waitForExit returns false within 300ms.
|
|
// (Use a short timeout to keep the test fast.)
|
|
{
|
|
const child = spawn('sleep', ['30'], { stdio: 'pipe' });
|
|
const exited = await waitForExit(child.pid, 300);
|
|
assert(exited === false, 'M-11: waitForExit returns false when process is still alive at timeout');
|
|
try { process.kill(child.pid, 'SIGKILL'); } catch { /* ignore */ }
|
|
}
|
|
|
|
// waitForExit returns true immediately for a pid that does not
|
|
// exist (ESRCH). No process spawned.
|
|
{
|
|
// Pick a pid that's very unlikely to exist. Use a large number.
|
|
const fakePid = 999999;
|
|
const exited = await waitForExit(fakePid, 1000);
|
|
assert(exited, 'M-11: waitForExit returns true for a non-existent pid (ESRCH)');
|
|
}
|
|
}
|
|
});
|
|
|
|
export const results = {
|
|
get passed() { return passed; },
|
|
get failed() { return failed; },
|
|
get failures() { return [...failures]; }
|
|
}; |