concurrently
Version:
Run commands concurrently
99 lines (98 loc) • 4.5 kB
JavaScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createMockInstance } from '../__fixtures__/create-mock-instance.js';
import { createFakeCloseEvent, createFakeProcess, FakeCommand, } from '../__fixtures__/fake-command.js';
import { Logger } from '../logger.js';
import { KillOthers } from './kill-others.js';
let commands;
let logger;
let abortController;
beforeEach(() => {
commands = [new FakeCommand(), new FakeCommand()];
logger = createMockInstance(Logger);
abortController = new AbortController();
});
const createWithConditions = (conditions, opts) => new KillOthers({
logger,
abortController,
conditions,
killSignal: undefined,
...opts,
});
const assignProcess = (command) => {
const process = createFakeProcess(1);
command.pid = process.pid;
command.process = process;
};
const unassignProcess = (command) => {
command.pid = undefined;
command.process = undefined;
};
it('returns same commands', () => {
expect(createWithConditions(['success']).handle(commands)).toMatchObject({ commands });
expect(createWithConditions(['failure']).handle(commands)).toMatchObject({ commands });
});
it('does not kill others if condition does not match', () => {
createWithConditions(['failure']).handle(commands);
assignProcess(commands[1]);
commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
expect(logger.logGlobalEvent).not.toHaveBeenCalled();
expect(commands[0].kill).not.toHaveBeenCalled();
expect(commands[1].kill).not.toHaveBeenCalled();
});
describe.each(['success', 'failure'])('on %s', (condition) => {
const exitCode = condition === 'success' ? 0 : 1;
const inversedCode = exitCode === 1 ? 0 : 1;
it('kills other processes', () => {
createWithConditions([condition]).handle(commands);
assignProcess(commands[1]);
commands[0].close.next(createFakeCloseEvent({ exitCode }));
expect(logger.logGlobalEvent).toHaveBeenCalledExactlyOnceWith('Sending SIGTERM to other processes..');
expect(commands[0].kill).not.toHaveBeenCalled();
expect(commands[1].kill).toHaveBeenCalledWith(undefined);
});
it('kills other processes, with specified signal', () => {
createWithConditions([condition], { killSignal: 'SIGKILL' }).handle(commands);
assignProcess(commands[1]);
commands[0].close.next(createFakeCloseEvent({ exitCode }));
expect(logger.logGlobalEvent).toHaveBeenCalledExactlyOnceWith('Sending SIGKILL to other processes..');
expect(commands[0].kill).not.toHaveBeenCalled();
expect(commands[1].kill).toHaveBeenCalledWith('SIGKILL');
});
it('sends abort signal on condition match', () => {
createWithConditions([condition]).handle(commands);
commands[0].close.next(createFakeCloseEvent({ exitCode }));
expect(abortController.signal.aborted).toBe(true);
});
it('does not send abort signal on condition mismatch', () => {
createWithConditions([condition]).handle(commands);
commands[0].close.next(createFakeCloseEvent({ exitCode: inversedCode }));
expect(abortController.signal.aborted).toBe(false);
});
});
it('does nothing if called without conditions', () => {
createWithConditions([]).handle(commands);
commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
expect(logger.logGlobalEvent).not.toHaveBeenCalled();
expect(commands[0].kill).not.toHaveBeenCalled();
expect(commands[1].kill).not.toHaveBeenCalled();
});
it('does not try to kill processes already dead', () => {
createWithConditions(['failure']).handle(commands);
commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
expect(logger.logGlobalEvent).not.toHaveBeenCalled();
expect(commands[0].kill).not.toHaveBeenCalled();
expect(commands[1].kill).not.toHaveBeenCalled();
});
it('force kills misbehaving processes after a timeout', () => {
vi.useFakeTimers();
commands.push(new FakeCommand());
createWithConditions(['failure'], { timeoutMs: 500 }).handle(commands);
assignProcess(commands[1]);
assignProcess(commands[2]);
commands[2].kill = vi.fn(() => unassignProcess(commands[2]));
commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
vi.advanceTimersByTime(500);
expect(commands[1].kill).toHaveBeenCalledTimes(2);
expect(commands[1].kill).toHaveBeenCalledWith('SIGKILL');
expect(commands[2].kill).toHaveBeenCalledTimes(1);
});