Files
stack/packages/cli/tests/owned-child.test.mjs

39 lines
2.4 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {spawn} from 'node:child_process';
import {watch,mkdtempSync,readFileSync,existsSync,rmSync} from 'node:fs';
import {join} from 'node:path';
import {tmpdir} from 'node:os';
import {once} from 'node:events';
// Reproduce the gate's external TERM, not Node's cooperative test-abort hook.
test('external gate termination closes owned detached fixture work before exit', {timeout:10000}, async()=>{
const root=mkdtempSync(join(tmpdir(),'copy-outer-abort-')),ready=join(root,'ready');
const helper=new URL('./fixtures/owned-child.mjs',import.meta.url).href;
let descendant,outer;
const watcher=watch(root);
try{
const announced=new Promise(resolve=>{watcher.on('change',()=>{if(existsSync(ready))resolve()})});
const childCode=`const fs=require('node:fs');fs.writeFileSync(${JSON.stringify(ready+'.tmp')},String(process.pid));fs.renameSync(${JSON.stringify(ready+'.tmp')},${JSON.stringify(ready)});setInterval(()=>{},1000);`;
const code=`import {createOwnedRunner} from ${JSON.stringify(helper)};const t={after(){},signal:new AbortController().signal};const r=createOwnedRunner(t,()=>process.env);await r.run(process.execPath,['-e',${JSON.stringify(childCode)}]);`;
outer=spawn(process.execPath,['--input-type=module','-e',code],{stdio:['ignore','pipe','pipe']});
const exited=once(outer,'close');
await Promise.race([announced,exited.then(()=>{throw Error('outer exited before readiness')})]);
descendant=Number(readFileSync(ready,'utf8'));assert.ok(Number.isSafeInteger(descendant)&&descendant>1);
assert.ok(readFileSync(`/proc/${descendant}/cmdline`,'utf8').includes(ready),'same owned descendant');
outer.kill('SIGTERM');
const [status,signal]=await exited;
assert.equal(existsSync(`/proc/${descendant}`),false,'owned detached child is reaped before outer exit');
assert.equal(status,143);assert.equal(signal,null);
}finally{
watcher.close();
if(descendant&&existsSync(`/proc/${descendant}`)){
// Only this captured owned child group; no process/registry census.
assert.ok(readFileSync(`/proc/${descendant}/cmdline`,'utf8').includes(ready));
try{process.kill(-descendant,'SIGKILL')}catch(e){if(e.code!=='ESRCH')throw e}
}
if(outer&&outer.exitCode===null&&outer.signalCode===null){const done=once(outer,'close');outer.kill('SIGKILL');await done}
rmSync(root,{recursive:true,force:true});
}
});