cli-common: cleanup and polish waitForExit
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -122,9 +122,41 @@ describe('run', () => {
|
||||
activeChildren.push(child);
|
||||
// Wait for it to complete
|
||||
await child.waitForExit();
|
||||
await expect(
|
||||
Promise.race([child.waitForExit(), 'pending']),
|
||||
).resolves.not.toBe('pending');
|
||||
// Call waitForExit again - should return immediately
|
||||
await expect(child.waitForExit()).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle multiple simultaneous calls to waitForExit', async () => {
|
||||
const child = run(['node', '--version']);
|
||||
activeChildren.push(child);
|
||||
// Call waitForExit multiple times simultaneously
|
||||
const [result1, result2, result3] = await Promise.all([
|
||||
child.waitForExit(),
|
||||
child.waitForExit(),
|
||||
child.waitForExit(),
|
||||
]);
|
||||
// All should resolve successfully
|
||||
expect(result1).toBeUndefined();
|
||||
expect(result2).toBeUndefined();
|
||||
expect(result3).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle multiple simultaneous calls to waitForExit with error', async () => {
|
||||
const child = run(['node', '--eval', 'process.exit(1)']);
|
||||
activeChildren.push(child);
|
||||
// Call waitForExit multiple times simultaneously
|
||||
const promises = [
|
||||
child.waitForExit(),
|
||||
child.waitForExit(),
|
||||
child.waitForExit(),
|
||||
];
|
||||
// All should reject with the same error
|
||||
for (const promise of promises) {
|
||||
await expect(promise).rejects.toThrow(ExitCodeError);
|
||||
await expect(promise).rejects.toThrow(
|
||||
/Command 'node --eval process\.exit\(1\)' exited with code 1/,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle signal handlers cleanup', async () => {
|
||||
|
||||
@@ -99,49 +99,60 @@ export function run(args: string[], options: RunOptions = {}): RunChildProcess {
|
||||
|
||||
const commandName = args.join(' ');
|
||||
|
||||
let signalHandlersRegistered = false;
|
||||
const handleSignal = () => {
|
||||
if (!child.killed && child.exitCode === null) {
|
||||
child.kill();
|
||||
}
|
||||
};
|
||||
let waitPromise: Promise<void> | undefined;
|
||||
|
||||
child.waitForExit = async (): Promise<void> => {
|
||||
// Register signal handlers to kill child process on SIGINT/SIGTERM
|
||||
if (!signalHandlersRegistered) {
|
||||
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
|
||||
process.on(signal, handleSignal);
|
||||
}
|
||||
signalHandlersRegistered = true;
|
||||
if (waitPromise) {
|
||||
return waitPromise;
|
||||
}
|
||||
|
||||
try {
|
||||
waitPromise = new Promise<void>((resolve, reject) => {
|
||||
if (typeof child.exitCode === 'number') {
|
||||
if (child.exitCode) {
|
||||
throw new ExitCodeError(child.exitCode, commandName);
|
||||
reject(new ExitCodeError(child.exitCode, commandName));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
child.once('error', reject);
|
||||
child.once('exit', code => {
|
||||
if (code) {
|
||||
reject(new ExitCodeError(code, commandName));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
// Clean up signal handlers when done waiting
|
||||
if (signalHandlersRegistered) {
|
||||
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
|
||||
process.removeListener(signal, handleSignal);
|
||||
}
|
||||
signalHandlersRegistered = false;
|
||||
function onError(error: Error) {
|
||||
cleanup();
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
function onExit(code: number | null) {
|
||||
cleanup();
|
||||
if (code) {
|
||||
reject(new ExitCodeError(code, commandName));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
function onSignal() {
|
||||
if (!child.killed && child.exitCode === null) {
|
||||
child.kill();
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
|
||||
process.removeListener(signal, onSignal);
|
||||
}
|
||||
child.removeListener('error', onError);
|
||||
child.removeListener('exit', onExit);
|
||||
}
|
||||
|
||||
child.once('error', onError);
|
||||
child.once('exit', onExit);
|
||||
|
||||
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
|
||||
process.addListener(signal, onSignal);
|
||||
}
|
||||
});
|
||||
|
||||
return waitPromise;
|
||||
};
|
||||
|
||||
return child;
|
||||
|
||||
Reference in New Issue
Block a user