import {spawn} from 'node:child_process'; const pools=new Set();let terminating; const terminate=signal=>{ if(terminating)return; // Every close starts synchronously by killing only its captured child groups; // wait for all close events before forwarding the conventional exit status. terminating=Promise.allSettled([...pools].map(close=>close())).then(results=>{ process.exit(results.some(result=>result.status==='rejected')?1:(signal==='SIGTERM'?143:130)); }); }; const term=()=>terminate('SIGTERM'),interrupt=()=>terminate('SIGINT'); function register(close){ if(pools.size===0){process.on('SIGTERM',term);process.on('SIGINT',interrupt)} pools.add(close); } function unregister(close){ pools.delete(close); if(pools.size===0){process.removeListener('SIGTERM',term);process.removeListener('SIGINT',interrupt)} } // Fixture children have their own groups so abort/teardown can stop descendants // without signalling the test runner or another fixture's work. export function createOwnedRunner(t,env){ const children=new Map();let closed=false; const stop=child=>{ if(!child.pid)return; try{process.kill(-child.pid,'SIGKILL')}catch(e){if(e.code!=='ESRCH')throw e} }; const close=async()=>{ closed=true; for(const child of children.keys())stop(child); await Promise.all(children.values()); unregister(close); }; register(close); t.after(close); const run=(command,args=[],options={})=>{ if(closed)throw Error('fixture runner closed'); const child=spawn(command,args,{env:env(),...options,detached:true,stdio:['ignore','pipe','pipe']}); let stdout='',stderr='',error; child.stdout.setEncoding('utf8');child.stderr.setEncoding('utf8'); child.stdout.on('data',chunk=>{stdout+=chunk});child.stderr.on('data',chunk=>{stderr+=chunk}); const abort=()=>stop(child); const done=new Promise(resolve=>{ child.on('error',e=>{error=e}); child.once('close',(status,signal)=>{ children.delete(child);t.signal.removeEventListener('abort',abort); resolve({status,signal,stdout,stderr,...(error?{error}: {})}); }); }); children.set(child,done); t.signal.addEventListener('abort',abort,{once:true}); if(t.signal.aborted)abort(); return done; }; return {run,close}; }