Files

59 lines
2.2 KiB
JavaScript

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