@testmuai/rook 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/rook.test.mjs CHANGED
@@ -434,29 +434,57 @@ describe("rook.cjs signal propagation (real subprocess)", () => {
434
434
  rmSync(childPidFile, { force: true });
435
435
  writeExecutableFixture(
436
436
  tag,
437
- `#!/bin/sh\necho $$ > ${JSON.stringify(childPidFile)}\nsleep 30\n`,
437
+ `#!/bin/sh\necho $$ > ${JSON.stringify(childPidFile)}\nexec sleep 30\n`,
438
438
  );
439
439
 
440
- const trampoline = spawn(process.execPath, [join(BIN_DIR, "rook.cjs")], {
440
+ const readyFile = join(BIN_DIR, "forwarding.ready");
441
+ const preload = join(BIN_DIR, "forwarding-ready.cjs");
442
+ rmSync(readyFile, { force: true });
443
+ writeFileSync(preload, `
444
+ const { writeFileSync } = require("node:fs");
445
+ process.on("newListener", (signal) => {
446
+ if (signal === "SIGTERM") process.nextTick(() => {
447
+ writeFileSync(${JSON.stringify(readyFile)}, "ready");
448
+ });
449
+ });
450
+ `);
451
+
452
+ const trampoline = spawn(process.execPath, ["--require", preload, join(BIN_DIR, "rook.cjs")], {
441
453
  cwd: BIN_DIR,
442
454
  stdio: "ignore",
455
+ // Give this fixture a process group so failures can clean up only its processes.
456
+ detached: true,
443
457
  });
444
458
 
445
- const childPid = await waitForFileContent(childPidFile, 5000);
446
- expect(childPid).toBeTruthy();
447
-
448
- const exited = new Promise((resolve) => trampoline.on("exit", resolve));
449
- // kill(pid) (no leading dash) always targets exactly that one
450
- // process, never its process group — unlike an interactive
451
- // terminal's Ctrl-C, which reaches the whole group and would mask
452
- // this bug even without the fix.
453
- process.kill(trampoline.pid, "SIGTERM");
454
- await exited;
455
- // Give the forwarded signal a moment to actually land before checking.
456
- await new Promise((resolve) => setTimeout(resolve, 300));
457
-
458
- expect(isAlive(Number(childPid))).toBe(false);
459
- });
459
+ try {
460
+ // The child may start before its parent's signal handlers are installed.
461
+ const [pid] = await Promise.all([
462
+ waitForFileContent(childPidFile, 5000),
463
+ waitForFileContent(readyFile, 5000),
464
+ ]);
465
+ const childPid = Number(pid);
466
+ expect(Number.isSafeInteger(childPid) && childPid > 0).toBe(true);
467
+ // Signal only the advertised PID: signalling the group would hide broken forwarding.
468
+ process.kill(trampoline.pid, "SIGTERM");
469
+ await vi.waitFor(() => {
470
+ expect(trampoline.signalCode, "trampoline must preserve SIGTERM").toBe("SIGTERM");
471
+ expect(isAlive(childPid), `fixture child ${childPid} must exit after forwarded SIGTERM`).toBe(false);
472
+ }, { timeout: 5000, interval: 20 });
473
+ } finally {
474
+ if (trampoline.pid) {
475
+ try {
476
+ process.kill(-trampoline.pid, "SIGKILL");
477
+ } catch (error) {
478
+ if (error.code !== "ESRCH") throw error;
479
+ }
480
+ await vi.waitFor(() => {
481
+ expect(trampoline.exitCode !== null || trampoline.signalCode !== null).toBe(true);
482
+ }, { timeout: 1000, interval: 20 });
483
+ }
484
+ rmSync(readyFile, { force: true });
485
+ rmSync(preload, { force: true });
486
+ }
487
+ }, 12000);
460
488
  });
461
489
 
462
490
  function waitForFileContent(path, timeoutMs) {
@@ -464,8 +492,11 @@ function waitForFileContent(path, timeoutMs) {
464
492
  return new Promise((resolve, reject) => {
465
493
  const poll = () => {
466
494
  if (existsSync(path)) {
467
- resolve(readFileSync(path, "utf8").trim());
468
- return;
495
+ const content = readFileSync(path, "utf8").trim();
496
+ if (content) {
497
+ resolve(content);
498
+ return;
499
+ }
469
500
  }
470
501
  if (Date.now() > deadline) {
471
502
  reject(new Error(`timed out waiting for ${path}`));
@@ -481,7 +512,8 @@ function isAlive(pid) {
481
512
  try {
482
513
  process.kill(pid, 0);
483
514
  return true;
484
- } catch {
485
- return false;
515
+ } catch (error) {
516
+ if (error.code === "ESRCH") return false;
517
+ throw error;
486
518
  }
487
519
  }