concurrently 5.3.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,167 @@
1
+ const EventEmitter = require('events');
2
+ const Command = require('./command');
3
+
4
+ const createProcess = () => {
5
+ const process = new EventEmitter();
6
+ process.pid = 1;
7
+ return process;
8
+ };
9
+
10
+ const createProcessWithIO = () => {
11
+ const process = createProcess();
12
+ return Object.assign(process, {
13
+ stdout: new EventEmitter(),
14
+ stderr: new EventEmitter(),
15
+ stdin: new EventEmitter()
16
+ });
17
+ };
18
+
19
+ describe('#start()', () => {
20
+ it('spawns process with given command and options', () => {
21
+ const spawn = jest.fn().mockReturnValue(createProcess());
22
+ const command = new Command({
23
+ spawn,
24
+ spawnOpts: { bla: true },
25
+ command: 'echo foo',
26
+ });
27
+ command.start();
28
+
29
+ expect(spawn).toHaveBeenCalledTimes(1);
30
+ expect(spawn).toHaveBeenCalledWith(command.command, { bla: true });
31
+ });
32
+
33
+ it('sets stdin, process and PID', () => {
34
+ const process = createProcessWithIO();
35
+ const command = new Command({ spawn: () => process });
36
+
37
+ command.start();
38
+ expect(command.process).toBe(process);
39
+ expect(command.pid).toBe(process.pid);
40
+ expect(command.stdin).toBe(process.stdin);
41
+ });
42
+
43
+ it('shares errors to the error stream', done => {
44
+ const process = createProcess();
45
+ const command = new Command({ spawn: () => process });
46
+
47
+ command.error.subscribe(data => {
48
+ expect(data).toBe('foo');
49
+ expect(command.process).toBeUndefined();
50
+ done();
51
+ });
52
+
53
+ command.start();
54
+ process.emit('error', 'foo');
55
+ });
56
+
57
+ it('shares closes to the close stream with exit code', done => {
58
+ const process = createProcess();
59
+ const command = new Command({ spawn: () => process });
60
+
61
+ command.close.subscribe(data => {
62
+ expect(data.exitCode).toBe(0);
63
+ expect(command.process).toBeUndefined();
64
+ done();
65
+ });
66
+
67
+ command.start();
68
+ process.emit('close', 0, null);
69
+ });
70
+
71
+ it('shares closes to the close stream with signal', done => {
72
+ const process = createProcess();
73
+ const command = new Command({ spawn: () => process });
74
+
75
+ command.close.subscribe(data => {
76
+ expect(data.exitCode).toBe('SIGKILL');
77
+ done();
78
+ });
79
+
80
+ command.start();
81
+ process.emit('close', null, 'SIGKILL');
82
+ });
83
+
84
+ it('shares closes to the close stream with command info and index', done => {
85
+ const process = createProcess();
86
+ const commandInfo = {
87
+ command: 'cmd',
88
+ name: 'name',
89
+ prefixColor: 'green',
90
+ env: { VAR: 'yes' },
91
+ };
92
+ const command = new Command(
93
+ Object.assign({
94
+ index: 1,
95
+ spawn: () => process
96
+ }, commandInfo)
97
+ );
98
+
99
+ command.close.subscribe(data => {
100
+ expect(data.command).toEqual(commandInfo);
101
+ expect(data.index).toBe(1);
102
+ done();
103
+ });
104
+
105
+ command.start();
106
+ process.emit('close', 0, null);
107
+ });
108
+
109
+ it('shares stdout to the stdout stream', done => {
110
+ const process = createProcessWithIO();
111
+ const command = new Command({ spawn: () => process });
112
+
113
+ command.stdout.subscribe(data => {
114
+ expect(data.toString()).toBe('hello');
115
+ done();
116
+ });
117
+
118
+ command.start();
119
+ process.stdout.emit('data', Buffer.from('hello'));
120
+ });
121
+
122
+ it('shares stderr to the stdout stream', done => {
123
+ const process = createProcessWithIO();
124
+ const command = new Command({ spawn: () => process });
125
+
126
+ command.stderr.subscribe(data => {
127
+ expect(data.toString()).toBe('dang');
128
+ done();
129
+ });
130
+
131
+ command.start();
132
+ process.stderr.emit('data', Buffer.from('dang'));
133
+ });
134
+ });
135
+
136
+ describe('#kill()', () => {
137
+ let process, killProcess, command;
138
+ beforeEach(() => {
139
+ process = createProcess();
140
+ killProcess = jest.fn();
141
+ command = new Command({ spawn: () => process, killProcess });
142
+ });
143
+
144
+ it('kills process', () => {
145
+ command.start();
146
+ command.kill();
147
+
148
+ expect(killProcess).toHaveBeenCalledTimes(1);
149
+ expect(killProcess).toHaveBeenCalledWith(command.pid, undefined);
150
+ });
151
+
152
+ it('kills process with some signal', () => {
153
+ command.start();
154
+ command.kill('SIGKILL');
155
+
156
+ expect(killProcess).toHaveBeenCalledTimes(1);
157
+ expect(killProcess).toHaveBeenCalledWith(command.pid, 'SIGKILL');
158
+ });
159
+
160
+ it('does not try to kill inexistent process', () => {
161
+ command.start();
162
+ process.emit('error');
163
+ command.kill();
164
+
165
+ expect(killProcess).not.toHaveBeenCalled();
166
+ });
167
+ });
@@ -0,0 +1,88 @@
1
+ const { TestScheduler } = require('rxjs/testing');
2
+
3
+ const createFakeCommand = require('./flow-control/fixtures/fake-command');
4
+ const CompletionListener = require('./completion-listener');
5
+
6
+ let commands, scheduler;
7
+ beforeEach(() => {
8
+ commands = [createFakeCommand('foo'), createFakeCommand('bar')];
9
+ scheduler = new TestScheduler();
10
+ });
11
+
12
+ const createController = successCondition =>
13
+ new CompletionListener({
14
+ successCondition,
15
+ scheduler
16
+ });
17
+
18
+ describe('with default success condition set', () => {
19
+ it('succeeds if all processes exited with code 0', () => {
20
+ const result = createController().listen(commands);
21
+
22
+ commands[0].close.next({ exitCode: 0 });
23
+ commands[1].close.next({ exitCode: 0 });
24
+
25
+ scheduler.flush();
26
+
27
+ return expect(result).resolves.toEqual([{ exitCode: 0 }, { exitCode: 0 }]);
28
+ });
29
+
30
+ it('fails if one of the processes exited with non-0 code', () => {
31
+ const result = createController().listen(commands);
32
+
33
+ commands[0].close.next({ exitCode: 0 });
34
+ commands[1].close.next({ exitCode: 1 });
35
+
36
+ scheduler.flush();
37
+
38
+ expect(result).rejects.toEqual([{ exitCode: 0 }, { exitCode: 1 }]);
39
+ });
40
+ });
41
+
42
+ describe('with success condition set to first', () => {
43
+ it('succeeds if first process to exit has code 0', () => {
44
+ const result = createController('first').listen(commands);
45
+
46
+ commands[1].close.next({ exitCode: 0 });
47
+ commands[0].close.next({ exitCode: 1 });
48
+
49
+ scheduler.flush();
50
+
51
+ return expect(result).resolves.toEqual([{ exitCode: 0 }, { exitCode: 1 }]);
52
+ });
53
+
54
+ it('fails if first process to exit has non-0 code', () => {
55
+ const result = createController('first').listen(commands);
56
+
57
+ commands[1].close.next({ exitCode: 1 });
58
+ commands[0].close.next({ exitCode: 0 });
59
+
60
+ scheduler.flush();
61
+
62
+ return expect(result).rejects.toEqual([{ exitCode: 1 }, { exitCode: 0 }]);
63
+ });
64
+ });
65
+
66
+ describe('with success condition set to last', () => {
67
+ it('succeeds if last process to exit has code 0', () => {
68
+ const result = createController('last').listen(commands);
69
+
70
+ commands[1].close.next({ exitCode: 1 });
71
+ commands[0].close.next({ exitCode: 0 });
72
+
73
+ scheduler.flush();
74
+
75
+ return expect(result).resolves.toEqual([{ exitCode: 1 }, { exitCode: 0 }]);
76
+ });
77
+
78
+ it('fails if last process to exit has non-0 code', () => {
79
+ const result = createController('last').listen(commands);
80
+
81
+ commands[1].close.next({ exitCode: 0 });
82
+ commands[0].close.next({ exitCode: 1 });
83
+
84
+ scheduler.flush();
85
+
86
+ return expect(result).rejects.toEqual([{ exitCode: 0 }, { exitCode: 1 }]);
87
+ });
88
+ });
@@ -16,7 +16,8 @@ const defaults = {
16
16
  spawn,
17
17
  kill: treeKill,
18
18
  raw: false,
19
- controllers: []
19
+ controllers: [],
20
+ cwd: undefined,
20
21
  };
21
22
 
22
23
  module.exports = (commands, options) => {
@@ -37,7 +38,11 @@ module.exports = (commands, options) => {
37
38
  .map((command, index) => new Command(
38
39
  Object.assign({
39
40
  index,
40
- spawnOpts: getSpawnOpts({ raw: options.raw, env: command.env }),
41
+ spawnOpts: getSpawnOpts({
42
+ raw: options.raw,
43
+ env: command.env,
44
+ cwd: command.cwd || options.cwd,
45
+ }),
41
46
  killProcess: options.kill,
42
47
  spawn: options.spawn,
43
48
  }, command)
@@ -64,6 +69,7 @@ function mapToCommandInfo(command) {
64
69
  name: command.name || '',
65
70
  prefixColor: command.prefixColor || '',
66
71
  env: command.env || {},
72
+ cwd: command.cwd || '',
67
73
  };
68
74
  }
69
75
 
@@ -0,0 +1,167 @@
1
+ const EventEmitter = require('events');
2
+
3
+ const createFakeCommand = require('./flow-control/fixtures/fake-command');
4
+ const concurrently = require('./concurrently');
5
+
6
+ let spawn, kill, controllers, processes = [];
7
+ const create = (commands, options = {}) => concurrently(
8
+ commands,
9
+ Object.assign(options, { controllers, spawn, kill })
10
+ );
11
+
12
+ beforeEach(() => {
13
+ processes = [];
14
+ spawn = jest.fn(() => {
15
+ const process = new EventEmitter();
16
+ processes.push(process);
17
+ process.pid = processes.length;
18
+ return process;
19
+ });
20
+ kill = jest.fn();
21
+ controllers = [{ handle: jest.fn(arg => arg) }, { handle: jest.fn(arg => arg) }];
22
+ });
23
+
24
+ it('fails if commands is not an array', () => {
25
+ const bomb = () => create('foo');
26
+ expect(bomb).toThrowError();
27
+ });
28
+
29
+ it('fails if no commands were provided', () => {
30
+ const bomb = () => create([]);
31
+ expect(bomb).toThrowError();
32
+ });
33
+
34
+ it('spawns all commands', () => {
35
+ create(['echo', 'kill']);
36
+ expect(spawn).toHaveBeenCalledTimes(2);
37
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({}));
38
+ expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({}));
39
+ });
40
+
41
+ it('spawns commands up to configured limit at once', () => {
42
+ create(['foo', 'bar', 'baz', 'qux'], { maxProcesses: 2 });
43
+ expect(spawn).toHaveBeenCalledTimes(2);
44
+ expect(spawn).toHaveBeenCalledWith('foo', expect.objectContaining({}));
45
+ expect(spawn).toHaveBeenCalledWith('bar', expect.objectContaining({}));
46
+
47
+ // Test out of order completion picking up new processes in-order
48
+ processes[1].emit('close', 1, null);
49
+ expect(spawn).toHaveBeenCalledTimes(3);
50
+ expect(spawn).toHaveBeenCalledWith('baz', expect.objectContaining({}));
51
+
52
+ processes[0].emit('close', null, 'SIGINT');
53
+ expect(spawn).toHaveBeenCalledTimes(4);
54
+ expect(spawn).toHaveBeenCalledWith('qux', expect.objectContaining({}));
55
+
56
+ // Shouldn't attempt to spawn anything else.
57
+ processes[2].emit('close', 1, null);
58
+ expect(spawn).toHaveBeenCalledTimes(4);
59
+ });
60
+
61
+ it('runs controllers with the commands', () => {
62
+ create(['echo', '"echo wrapped"']);
63
+
64
+ controllers.forEach(controller => {
65
+ expect(controller.handle).toHaveBeenCalledWith([
66
+ expect.objectContaining({ command: 'echo', index: 0 }),
67
+ expect.objectContaining({ command: 'echo wrapped', index: 1 }),
68
+ ]);
69
+ });
70
+ });
71
+
72
+ it('runs commands with a name or prefix color', () => {
73
+ create([
74
+ { command: 'echo', prefixColor: 'red', name: 'foo' },
75
+ 'kill'
76
+ ]);
77
+
78
+ controllers.forEach(controller => {
79
+ expect(controller.handle).toHaveBeenCalledWith([
80
+ expect.objectContaining({ command: 'echo', index: 0, name: 'foo', prefixColor: 'red' }),
81
+ expect.objectContaining({ command: 'kill', index: 1, name: '', prefixColor: '' }),
82
+ ]);
83
+ });
84
+ });
85
+
86
+ it('passes commands wrapped from a controller to the next one', () => {
87
+ const fakeCommand = createFakeCommand('banana', 'banana');
88
+ controllers[0].handle.mockReturnValue([fakeCommand]);
89
+
90
+ create(['echo']);
91
+
92
+ expect(controllers[0].handle).toHaveBeenCalledWith([
93
+ expect.objectContaining({ command: 'echo', index: 0 })
94
+ ]);
95
+
96
+ expect(controllers[1].handle).toHaveBeenCalledWith([fakeCommand]);
97
+
98
+ expect(fakeCommand.start).toHaveBeenCalledTimes(1);
99
+ });
100
+
101
+ it('merges extra env vars into each command', () => {
102
+ create([
103
+ { command: 'echo', env: { foo: 'bar' } },
104
+ { command: 'echo', env: { foo: 'baz' } },
105
+ 'kill'
106
+ ]);
107
+
108
+ expect(spawn).toHaveBeenCalledTimes(3);
109
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
110
+ env: expect.objectContaining({ foo: 'bar' })
111
+ }));
112
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
113
+ env: expect.objectContaining({ foo: 'baz' })
114
+ }));
115
+ expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
116
+ env: expect.not.objectContaining({ foo: expect.anything() })
117
+ }));
118
+ });
119
+
120
+ it('uses cwd from options for each command', () => {
121
+ create(
122
+ [
123
+ { command: 'echo', env: { foo: 'bar' } },
124
+ { command: 'echo', env: { foo: 'baz' } },
125
+ 'kill'
126
+ ],
127
+ {
128
+ cwd: 'foobar',
129
+ }
130
+ );
131
+
132
+ expect(spawn).toHaveBeenCalledTimes(3);
133
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
134
+ env: expect.objectContaining({ foo: 'bar' }),
135
+ cwd: 'foobar',
136
+ }));
137
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
138
+ env: expect.objectContaining({ foo: 'baz' }),
139
+ cwd: 'foobar',
140
+ }));
141
+ expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
142
+ env: expect.not.objectContaining({ foo: expect.anything() }),
143
+ cwd: 'foobar',
144
+ }));
145
+ });
146
+
147
+ it('uses overridden cwd option for each command if specified', () => {
148
+ create(
149
+ [
150
+ { command: 'echo', env: { foo: 'bar' }, cwd: 'baz' },
151
+ { command: 'echo', env: { foo: 'baz' } },
152
+ ],
153
+ {
154
+ cwd: 'foobar',
155
+ }
156
+ );
157
+
158
+ expect(spawn).toHaveBeenCalledTimes(2);
159
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
160
+ env: expect.objectContaining({ foo: 'bar' }),
161
+ cwd: 'baz',
162
+ }));
163
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
164
+ env: expect.objectContaining({ foo: 'baz' }),
165
+ cwd: 'foobar',
166
+ }));
167
+ });
package/src/defaults.js CHANGED
@@ -14,7 +14,7 @@ module.exports = {
14
14
  // Which prefix style to use when logging processes output.
15
15
  prefix: '',
16
16
  // Refer to https://www.npmjs.com/package/chalk
17
- prefixColors: 'gray.dim',
17
+ prefixColors: 'reset',
18
18
  // How many bytes we'll show on the command prefix
19
19
  prefixLength: 10,
20
20
  raw: false,
@@ -25,5 +25,7 @@ module.exports = {
25
25
  // Condition of success for concurrently itself.
26
26
  success: 'all',
27
27
  // Refer to https://date-fns.org/v2.0.1/docs/format
28
- timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS'
28
+ timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS',
29
+ // Current working dir passed as option to spawn command. Default: process.cwd()
30
+ cwd: undefined
29
31
  };
@@ -18,7 +18,7 @@ module.exports = class InputHandler {
18
18
  Rx.fromEvent(this.inputStream, 'data')
19
19
  .pipe(map(data => data.toString()))
20
20
  .subscribe(data => {
21
- let [targetId, input] = data.split(':', 2);
21
+ let [targetId, input] = data.split(/:(.+)/);
22
22
  targetId = input ? targetId : this.defaultInputTarget;
23
23
  input = input || data;
24
24
 
@@ -0,0 +1,91 @@
1
+ const EventEmitter = require('events');
2
+ const { createMockInstance } = require('jest-create-mock-instance');
3
+
4
+ const Logger = require('../logger');
5
+ const createFakeCommand = require('./fixtures/fake-command');
6
+ const InputHandler = require('./input-handler');
7
+
8
+ let commands, controller, inputStream, logger;
9
+
10
+ beforeEach(() => {
11
+ commands = [
12
+ createFakeCommand('foo', 'echo foo', 0),
13
+ createFakeCommand('bar', 'echo bar', 1),
14
+ ];
15
+ inputStream = new EventEmitter();
16
+ logger = createMockInstance(Logger);
17
+ controller = new InputHandler({
18
+ defaultInputTarget: 0,
19
+ inputStream,
20
+ logger
21
+ });
22
+ });
23
+
24
+ it('returns same commands', () => {
25
+ expect(controller.handle(commands)).toBe(commands);
26
+
27
+ controller = new InputHandler({ logger });
28
+ expect(controller.handle(commands)).toBe(commands);
29
+ });
30
+
31
+ it('forwards input stream to default target ID', () => {
32
+ controller.handle(commands);
33
+
34
+ inputStream.emit('data', Buffer.from('something'));
35
+
36
+ expect(commands[0].stdin.write).toHaveBeenCalledTimes(1);
37
+ expect(commands[0].stdin.write).toHaveBeenCalledWith('something');
38
+ expect(commands[1].stdin.write).not.toHaveBeenCalled();
39
+ });
40
+
41
+ it('forwards input stream to target index specified in input', () => {
42
+ controller.handle(commands);
43
+
44
+ inputStream.emit('data', Buffer.from('1:something'));
45
+
46
+ expect(commands[0].stdin.write).not.toHaveBeenCalled();
47
+ expect(commands[1].stdin.write).toHaveBeenCalledTimes(1);
48
+ expect(commands[1].stdin.write).toHaveBeenCalledWith('something');
49
+ });
50
+
51
+ it('forwards input stream to target index specified in input when input contains colon', () => {
52
+ controller.handle(commands);
53
+
54
+ inputStream.emit('data', Buffer.from('1::something'));
55
+ inputStream.emit('data', Buffer.from('1:some:thing'));
56
+
57
+ expect(commands[0].stdin.write).not.toHaveBeenCalled();
58
+ expect(commands[1].stdin.write).toHaveBeenCalledTimes(2);
59
+ expect(commands[1].stdin.write).toHaveBeenCalledWith(':something');
60
+ expect(commands[1].stdin.write).toHaveBeenCalledWith('some:thing');
61
+ });
62
+
63
+ it('forwards input stream to target name specified in input', () => {
64
+ controller.handle(commands);
65
+
66
+ inputStream.emit('data', Buffer.from('bar:something'));
67
+
68
+ expect(commands[0].stdin.write).not.toHaveBeenCalled();
69
+ expect(commands[1].stdin.write).toHaveBeenCalledTimes(1);
70
+ expect(commands[1].stdin.write).toHaveBeenCalledWith('something');
71
+ });
72
+
73
+ it('logs error if command has no stdin open', () => {
74
+ commands[0].stdin = null;
75
+ controller.handle(commands);
76
+
77
+ inputStream.emit('data', Buffer.from('something'));
78
+
79
+ expect(commands[1].stdin.write).not.toHaveBeenCalled();
80
+ expect(logger.logGlobalEvent).toHaveBeenCalledWith('Unable to find command 0, or it has no stdin open\n');
81
+ });
82
+
83
+ it('logs error if command is not found', () => {
84
+ controller.handle(commands);
85
+
86
+ inputStream.emit('data', Buffer.from('foobar:something'));
87
+
88
+ expect(commands[0].stdin.write).not.toHaveBeenCalled();
89
+ expect(commands[1].stdin.write).not.toHaveBeenCalled();
90
+ expect(logger.logGlobalEvent).toHaveBeenCalledWith('Unable to find command foobar, or it has no stdin open\n');
91
+ });
@@ -0,0 +1,79 @@
1
+ const EventEmitter = require('events');
2
+
3
+ const createFakeCommand = require('./fixtures/fake-command');
4
+ const KillOnSignal = require('./kill-on-signal');
5
+
6
+ let commands, controller, process;
7
+ beforeEach(() => {
8
+ process = new EventEmitter();
9
+ commands = [
10
+ createFakeCommand(),
11
+ createFakeCommand(),
12
+ ];
13
+ controller = new KillOnSignal({ process });
14
+ });
15
+
16
+ it('returns commands that keep non-close streams from original commands', () => {
17
+ const newCommands = controller.handle(commands);
18
+ newCommands.forEach((newCommand, i) => {
19
+ expect(newCommand.close).not.toBe(commands[i].close);
20
+ expect(newCommand.error).toBe(commands[i].error);
21
+ expect(newCommand.stdout).toBe(commands[i].stdout);
22
+ expect(newCommand.stderr).toBe(commands[i].stderr);
23
+ });
24
+ });
25
+
26
+ it('returns commands that map SIGINT to exit code 0', () => {
27
+ const newCommands = controller.handle(commands);
28
+ expect(newCommands).not.toBe(commands);
29
+ expect(newCommands).toHaveLength(commands.length);
30
+
31
+ const callback = jest.fn();
32
+ newCommands[0].close.subscribe(callback);
33
+ process.emit('SIGINT');
34
+
35
+ // A fake command's .kill() call won't trigger a close event automatically...
36
+ commands[0].close.next({ exitCode: 1 });
37
+
38
+ expect(callback).not.toHaveBeenCalledWith({ exitCode: 'SIGINT' });
39
+ expect(callback).toHaveBeenCalledWith({ exitCode: 0 });
40
+ });
41
+
42
+ it('returns commands that keep non-SIGINT exit codes', () => {
43
+ const newCommands = controller.handle(commands);
44
+ expect(newCommands).not.toBe(commands);
45
+ expect(newCommands).toHaveLength(commands.length);
46
+
47
+ const callback = jest.fn();
48
+ newCommands[0].close.subscribe(callback);
49
+ commands[0].close.next({ exitCode: 1 });
50
+
51
+ expect(callback).toHaveBeenCalledWith({ exitCode: 1 });
52
+ });
53
+
54
+ it('kills all commands on SIGINT', () => {
55
+ controller.handle(commands);
56
+ process.emit('SIGINT');
57
+
58
+ expect(process.listenerCount('SIGINT')).toBe(1);
59
+ expect(commands[0].kill).toHaveBeenCalledWith('SIGINT');
60
+ expect(commands[1].kill).toHaveBeenCalledWith('SIGINT');
61
+ });
62
+
63
+ it('kills all commands on SIGTERM', () => {
64
+ controller.handle(commands);
65
+ process.emit('SIGTERM');
66
+
67
+ expect(process.listenerCount('SIGTERM')).toBe(1);
68
+ expect(commands[0].kill).toHaveBeenCalledWith('SIGTERM');
69
+ expect(commands[1].kill).toHaveBeenCalledWith('SIGTERM');
70
+ });
71
+
72
+ it('kills all commands on SIGHUP', () => {
73
+ controller.handle(commands);
74
+ process.emit('SIGHUP');
75
+
76
+ expect(process.listenerCount('SIGHUP')).toBe(1);
77
+ expect(commands[0].kill).toHaveBeenCalledWith('SIGHUP');
78
+ expect(commands[1].kill).toHaveBeenCalledWith('SIGHUP');
79
+ });