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}); } });