Skip to content

Commit

Permalink
Ensuring completion-listener.ts listen to a single close event emitter (
Browse files Browse the repository at this point in the history
  • Loading branch information
odeadglaz authored Mar 24, 2024
1 parent 7207279 commit bae6fe5
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 123 deletions.
1 change: 1 addition & 0 deletions src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface CloseEvent {
* The exit code or signal for the command.
*/
exitCode: string | number;

timings: {
startDate: Date;
endDate: Date;
Expand Down
297 changes: 180 additions & 117 deletions src/completion-listener.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,186 +24,249 @@ const createController = (successCondition?: SuccessCondition) =>
const emitFakeCloseEvent = (command: FakeCommand, event?: Partial<CloseEvent>) =>
command.close.next(createFakeCloseEvent({ ...event, command, index: command.index }));

describe('with default success condition set', () => {
it('succeeds if all processes exited with code 0', () => {
const result = createController().listen(commands);
const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0));

describe('listen', () => {
it('check for success once all commands have emitted at least a single close event', async () => {
const finallyCallback = jest.fn();
const result = createController().listen(commands).finally(finallyCallback);

// Emitting multiple close events to mimic calling command `kill/start` APIs.
emitFakeCloseEvent(commands[0]);
emitFakeCloseEvent(commands[0]);
emitFakeCloseEvent(commands[0]);

scheduler.flush();
// A broken implementantion will have called finallyCallback only after flushing promises
await flushPromises();
expect(finallyCallback).not.toHaveBeenCalled();

commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[1].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[2].close.next(createFakeCloseEvent({ exitCode: 0 }));
emitFakeCloseEvent(commands[1]);
emitFakeCloseEvent(commands[2]);

scheduler.flush();

return expect(result).resolves.toEqual(expect.anything());
await expect(result).resolves.toEqual(expect.anything());
expect(finallyCallback).toHaveBeenCalled();
});

it('fails if one of the processes exited with non-0 code', () => {
it('takes last event emitted from each command', async () => {
const result = createController().listen(commands);

commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[1].close.next(createFakeCloseEvent({ exitCode: 1 }));
commands[2].close.next(createFakeCloseEvent({ exitCode: 0 }));
emitFakeCloseEvent(commands[0], { exitCode: 0 });
emitFakeCloseEvent(commands[0], { exitCode: 1 });
emitFakeCloseEvent(commands[1], { exitCode: 0 });
emitFakeCloseEvent(commands[2], { exitCode: 0 });

scheduler.flush();

return expect(result).rejects.toEqual(expect.anything());
await expect(result).rejects.toEqual(expect.anything());
});
});

describe('with success condition set to first', () => {
it('succeeds if first process to exit has code 0', () => {
const result = createController('first').listen(commands);
it('waits for manually restarted events to close', async () => {
const finallyCallback = jest.fn();
const result = createController().listen(commands).finally(finallyCallback);

commands[1].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
commands[2].close.next(createFakeCloseEvent({ exitCode: 1 }));
emitFakeCloseEvent(commands[0]);
commands[0].state = 'started';
emitFakeCloseEvent(commands[1]);
emitFakeCloseEvent(commands[2]);

scheduler.flush();
// A broken implementantion will have called finallyCallback only after flushing promises
await flushPromises();
expect(finallyCallback).not.toHaveBeenCalled();

commands[0].state = 'exited';
emitFakeCloseEvent(commands[0]);
scheduler.flush();

return expect(result).resolves.toEqual(expect.anything());
await expect(result).resolves.toEqual(expect.anything());
expect(finallyCallback).toHaveBeenCalled();
});
});

it('fails if first process to exit has non-0 code', () => {
const result = createController('first').listen(commands);
describe('Detect commands exit conditions', () => {
describe('with default success condition set', () => {
it('succeeds if all processes exited with code 0', () => {
const result = createController().listen(commands);

commands[1].close.next(createFakeCloseEvent({ exitCode: 1 }));
commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[2].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[1].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[2].close.next(createFakeCloseEvent({ exitCode: 0 }));

scheduler.flush();
scheduler.flush();

return expect(result).rejects.toEqual(expect.anything());
});
});
return expect(result).resolves.toEqual(expect.anything());
});

describe('with success condition set to last', () => {
it('succeeds if last process to exit has code 0', () => {
const result = createController('last').listen(commands);
it('fails if one of the processes exited with non-0 code', () => {
const result = createController().listen(commands);

commands[1].close.next(createFakeCloseEvent({ exitCode: 1 }));
commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[2].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[1].close.next(createFakeCloseEvent({ exitCode: 1 }));
commands[2].close.next(createFakeCloseEvent({ exitCode: 0 }));

scheduler.flush();
scheduler.flush();

return expect(result).resolves.toEqual(expect.anything());
return expect(result).rejects.toEqual(expect.anything());
});
});

it('fails if last process to exit has non-0 code', () => {
const result = createController('last').listen(commands);
describe('with success condition set to first', () => {
it('succeeds if first process to exit has code 0', () => {
const result = createController('first').listen(commands);

commands[1].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
commands[2].close.next(createFakeCloseEvent({ exitCode: 1 }));
commands[1].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
commands[2].close.next(createFakeCloseEvent({ exitCode: 1 }));

scheduler.flush();
scheduler.flush();

return expect(result).rejects.toEqual(expect.anything());
});
});
return expect(result).resolves.toEqual(expect.anything());
});

describe.each([
// Use the middle command for both cases to make it more difficult to make a mess up
// in the implementation cause false passes.
['command-bar' as const, 'bar'],
['command-1' as const, 1],
])('with success condition set to %s', (condition, nameOrIndex) => {
it(`succeeds if command ${nameOrIndex} exits with code 0`, () => {
const result = createController(condition).listen(commands);
it('fails if first process to exit has non-0 code', () => {
const result = createController('first').listen(commands);

emitFakeCloseEvent(commands[0], { exitCode: 1 });
emitFakeCloseEvent(commands[1], { exitCode: 0 });
emitFakeCloseEvent(commands[2], { exitCode: 1 });
commands[1].close.next(createFakeCloseEvent({ exitCode: 1 }));
commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[2].close.next(createFakeCloseEvent({ exitCode: 0 }));

scheduler.flush();
scheduler.flush();

return expect(result).resolves.toEqual(expect.anything());
return expect(result).rejects.toEqual(expect.anything());
});
});

it(`succeeds if all commands ${nameOrIndex} exit with code 0`, () => {
commands = [commands[0], commands[1], commands[1]];
const result = createController(condition).listen(commands);
describe('with success condition set to last', () => {
it('succeeds if last process to exit has code 0', () => {
const result = createController('last').listen(commands);

emitFakeCloseEvent(commands[0], { exitCode: 1 });
emitFakeCloseEvent(commands[1], { exitCode: 0 });
emitFakeCloseEvent(commands[2], { exitCode: 0 });
commands[1].close.next(createFakeCloseEvent({ exitCode: 1 }));
commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[2].close.next(createFakeCloseEvent({ exitCode: 0 }));

scheduler.flush();
scheduler.flush();

return expect(result).resolves.toEqual(expect.anything());
});
return expect(result).resolves.toEqual(expect.anything());
});

it(`fails if command ${nameOrIndex} exits with non-0 code`, () => {
const result = createController(condition).listen(commands);
it('fails if last process to exit has non-0 code', () => {
const result = createController('last').listen(commands);

emitFakeCloseEvent(commands[0], { exitCode: 0 });
emitFakeCloseEvent(commands[1], { exitCode: 1 });
emitFakeCloseEvent(commands[2], { exitCode: 0 });
commands[1].close.next(createFakeCloseEvent({ exitCode: 0 }));
commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
commands[2].close.next(createFakeCloseEvent({ exitCode: 1 }));

scheduler.flush();
scheduler.flush();

return expect(result).rejects.toEqual(expect.anything());
return expect(result).rejects.toEqual(expect.anything());
});
});

it(`fails if some commands ${nameOrIndex} exit with non-0 code`, () => {
commands = [commands[0], commands[1], commands[1]];
const result = createController(condition).listen(commands);
describe.each([
// Use the middle command for both cases to make it more difficult to make a mess up
// in the implementation cause false passes.
['command-bar' as const, 'bar'],
['command-1' as const, 1],
])('with success condition set to %s', (condition, nameOrIndex) => {
it(`succeeds if command ${nameOrIndex} exits with code 0`, () => {
const result = createController(condition).listen(commands);

emitFakeCloseEvent(commands[0], { exitCode: 1 });
emitFakeCloseEvent(commands[1], { exitCode: 0 });
emitFakeCloseEvent(commands[2], { exitCode: 1 });
emitFakeCloseEvent(commands[0], { exitCode: 1 });
emitFakeCloseEvent(commands[1], { exitCode: 0 });
emitFakeCloseEvent(commands[2], { exitCode: 1 });

scheduler.flush();
scheduler.flush();

return expect(result).resolves.toEqual(expect.anything());
});
return expect(result).resolves.toEqual(expect.anything());
});

it(`fails if command ${nameOrIndex} doesn't exist`, () => {
const result = createController(condition).listen([commands[0]]);
it(`succeeds if all commands ${nameOrIndex} exit with code 0`, () => {
commands = [commands[0], commands[1], commands[1]];
const result = createController(condition).listen(commands);

emitFakeCloseEvent(commands[0], { exitCode: 0 });
scheduler.flush();
emitFakeCloseEvent(commands[0], { exitCode: 1 });
emitFakeCloseEvent(commands[1], { exitCode: 0 });
emitFakeCloseEvent(commands[2], { exitCode: 0 });

return expect(result).rejects.toEqual(expect.anything());
});
});
scheduler.flush();

describe.each([
// Use the middle command for both cases to make it more difficult to make a mess up
// in the implementation cause false passes.
['!command-bar' as const, 'bar'],
['!command-1' as const, 1],
])('with success condition set to %s', (condition, nameOrIndex) => {
it(`succeeds if all commands but ${nameOrIndex} exit with code 0`, () => {
const result = createController(condition).listen(commands);
return expect(result).resolves.toEqual(expect.anything());
});

emitFakeCloseEvent(commands[0], { exitCode: 0 });
emitFakeCloseEvent(commands[1], { exitCode: 1 });
emitFakeCloseEvent(commands[2], { exitCode: 0 });
it(`fails if command ${nameOrIndex} exits with non-0 code`, () => {
const result = createController(condition).listen(commands);

scheduler.flush();
emitFakeCloseEvent(commands[0], { exitCode: 0 });
emitFakeCloseEvent(commands[1], { exitCode: 1 });
emitFakeCloseEvent(commands[2], { exitCode: 0 });

return expect(result).resolves.toEqual(expect.anything());
});
scheduler.flush();

it(`fails if any commands but ${nameOrIndex} exit with non-0 code`, () => {
const result = createController(condition).listen(commands);
return expect(result).rejects.toEqual(expect.anything());
});

emitFakeCloseEvent(commands[0], { exitCode: 1 });
emitFakeCloseEvent(commands[1], { exitCode: 1 });
emitFakeCloseEvent(commands[2], { exitCode: 0 });
it(`fails if some commands ${nameOrIndex} exit with non-0 code`, () => {
const result = createController(condition).listen(commands);

scheduler.flush();
emitFakeCloseEvent(commands[0], { exitCode: 1 });
emitFakeCloseEvent(commands[1], { exitCode: 0 });
emitFakeCloseEvent(commands[2], { exitCode: 1 });

scheduler.flush();

return expect(result).resolves.toEqual(expect.anything());
});

it(`fails if command ${nameOrIndex} doesn't exist`, () => {
const result = createController(condition).listen([commands[0]]);

return expect(result).rejects.toEqual(expect.anything());
emitFakeCloseEvent(commands[0], { exitCode: 0 });
scheduler.flush();

return expect(result).rejects.toEqual(expect.anything());
});
});

it(`succeeds if command ${nameOrIndex} doesn't exist`, () => {
const result = createController(condition).listen([commands[0]]);
describe.each([
// Use the middle command for both cases to make it more difficult to make a mess up
// in the implementation cause false passes.
['!command-bar' as const, 'bar'],
['!command-1' as const, 1],
])('with success condition set to %s', (condition, nameOrIndex) => {
it(`succeeds if all commands but ${nameOrIndex} exit with code 0`, () => {
const result = createController(condition).listen(commands);

emitFakeCloseEvent(commands[0], { exitCode: 0 });
scheduler.flush();
emitFakeCloseEvent(commands[0], { exitCode: 0 });
emitFakeCloseEvent(commands[1], { exitCode: 1 });
emitFakeCloseEvent(commands[2], { exitCode: 0 });

scheduler.flush();

return expect(result).resolves.toEqual(expect.anything());
});

it(`fails if any commands but ${nameOrIndex} exit with non-0 code`, () => {
const result = createController(condition).listen(commands);

emitFakeCloseEvent(commands[0], { exitCode: 1 });
emitFakeCloseEvent(commands[1], { exitCode: 1 });
emitFakeCloseEvent(commands[2], { exitCode: 0 });

scheduler.flush();

return expect(result).rejects.toEqual(expect.anything());
});

it(`succeeds if command ${nameOrIndex} doesn't exist`, () => {
const result = createController(condition).listen([commands[0]]);

emitFakeCloseEvent(commands[0], { exitCode: 0 });
scheduler.flush();

return expect(result).resolves.toEqual(expect.anything());
return expect(result).resolves.toEqual(expect.anything());
});
});
});
Loading

0 comments on commit bae6fe5

Please sign in to comment.