concurrently 6.0.2 → 6.2.2

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.
Files changed (36) hide show
  1. package/README.md +16 -10
  2. package/bin/concurrently.js +4 -2
  3. package/bin/concurrently.spec.js +365 -0
  4. package/bin/epilogue.txt +1 -1
  5. package/index.js +3 -2
  6. package/package.json +2 -3
  7. package/src/command-parser/expand-npm-shortcut.spec.js +36 -0
  8. package/src/command-parser/expand-npm-wildcard.js +11 -2
  9. package/src/command-parser/expand-npm-wildcard.spec.js +58 -0
  10. package/src/command-parser/strip-quotes.spec.js +20 -0
  11. package/src/command.js +3 -0
  12. package/src/command.spec.js +183 -0
  13. package/src/completion-listener.js +1 -1
  14. package/src/completion-listener.spec.js +88 -0
  15. package/src/concurrently.js +15 -4
  16. package/src/concurrently.spec.js +186 -0
  17. package/src/defaults.js +1 -1
  18. package/src/flow-control/base-handler.js +16 -0
  19. package/src/flow-control/input-handler.js +16 -5
  20. package/src/flow-control/input-handler.spec.js +113 -0
  21. package/src/flow-control/kill-on-signal.js +17 -12
  22. package/src/flow-control/kill-on-signal.spec.js +79 -0
  23. package/src/flow-control/kill-others.js +7 -4
  24. package/src/flow-control/kill-others.spec.js +66 -0
  25. package/src/flow-control/log-error.js +3 -5
  26. package/src/flow-control/log-error.spec.js +40 -0
  27. package/src/flow-control/log-exit.js +3 -5
  28. package/src/flow-control/log-exit.spec.js +36 -0
  29. package/src/flow-control/log-output.js +3 -5
  30. package/src/flow-control/log-output.spec.js +41 -0
  31. package/src/flow-control/restart-process.js +19 -14
  32. package/src/flow-control/restart-process.spec.js +131 -0
  33. package/src/get-spawn-opts.spec.js +30 -0
  34. package/src/logger.js +4 -3
  35. package/src/logger.spec.js +195 -0
  36. package/src/flow-control/fixtures/fake-command.js +0 -16
@@ -0,0 +1,183 @@
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(data.killed).toBe(false);
64
+ expect(command.process).toBeUndefined();
65
+ done();
66
+ });
67
+
68
+ command.start();
69
+ process.emit('close', 0, null);
70
+ });
71
+
72
+ it('shares closes to the close stream with signal', done => {
73
+ const process = createProcess();
74
+ const command = new Command({ spawn: () => process });
75
+
76
+ command.close.subscribe(data => {
77
+ expect(data.exitCode).toBe('SIGKILL');
78
+ expect(data.killed).toBe(false);
79
+ done();
80
+ });
81
+
82
+ command.start();
83
+ process.emit('close', null, 'SIGKILL');
84
+ });
85
+
86
+ it('shares closes to the close stream with command info and index', done => {
87
+ const process = createProcess();
88
+ const commandInfo = {
89
+ command: 'cmd',
90
+ name: 'name',
91
+ prefixColor: 'green',
92
+ env: { VAR: 'yes' },
93
+ };
94
+ const command = new Command(
95
+ Object.assign({
96
+ index: 1,
97
+ spawn: () => process
98
+ }, commandInfo)
99
+ );
100
+
101
+ command.close.subscribe(data => {
102
+ expect(data.command).toEqual(commandInfo);
103
+ expect(data.killed).toBe(false);
104
+ expect(data.index).toBe(1);
105
+ done();
106
+ });
107
+
108
+ command.start();
109
+ process.emit('close', 0, null);
110
+ });
111
+
112
+ it('shares stdout to the stdout stream', done => {
113
+ const process = createProcessWithIO();
114
+ const command = new Command({ spawn: () => process });
115
+
116
+ command.stdout.subscribe(data => {
117
+ expect(data.toString()).toBe('hello');
118
+ done();
119
+ });
120
+
121
+ command.start();
122
+ process.stdout.emit('data', Buffer.from('hello'));
123
+ });
124
+
125
+ it('shares stderr to the stdout stream', done => {
126
+ const process = createProcessWithIO();
127
+ const command = new Command({ spawn: () => process });
128
+
129
+ command.stderr.subscribe(data => {
130
+ expect(data.toString()).toBe('dang');
131
+ done();
132
+ });
133
+
134
+ command.start();
135
+ process.stderr.emit('data', Buffer.from('dang'));
136
+ });
137
+ });
138
+
139
+ describe('#kill()', () => {
140
+ let process, killProcess, command;
141
+ beforeEach(() => {
142
+ process = createProcess();
143
+ killProcess = jest.fn();
144
+ command = new Command({ spawn: () => process, killProcess });
145
+ });
146
+
147
+ it('kills process', () => {
148
+ command.start();
149
+ command.kill();
150
+
151
+ expect(killProcess).toHaveBeenCalledTimes(1);
152
+ expect(killProcess).toHaveBeenCalledWith(command.pid, undefined);
153
+ });
154
+
155
+ it('kills process with some signal', () => {
156
+ command.start();
157
+ command.kill('SIGKILL');
158
+
159
+ expect(killProcess).toHaveBeenCalledTimes(1);
160
+ expect(killProcess).toHaveBeenCalledWith(command.pid, 'SIGKILL');
161
+ });
162
+
163
+ it('does not try to kill inexistent process', () => {
164
+ command.start();
165
+ process.emit('error');
166
+ command.kill();
167
+
168
+ expect(killProcess).not.toHaveBeenCalled();
169
+ });
170
+
171
+ it('marks the command as killed', done => {
172
+ command.start();
173
+
174
+ command.close.subscribe(data => {
175
+ expect(data.exitCode).toBe(1);
176
+ expect(data.killed).toBe(true);
177
+ done();
178
+ });
179
+
180
+ command.kill();
181
+ process.emit('close', 1, null);
182
+ });
183
+ });
@@ -1,5 +1,5 @@
1
1
  const Rx = require('rxjs');
2
- const { bufferCount, map, switchMap, take } = require('rxjs/operators');
2
+ const { bufferCount, switchMap, take } = require('rxjs/operators');
3
3
 
4
4
  module.exports = class CompletionListener {
5
5
  constructor({ successCondition, scheduler }) {
@@ -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
+ });
@@ -49,10 +49,17 @@ module.exports = (commands, options) => {
49
49
  ))
50
50
  .value();
51
51
 
52
- commands = options.controllers.reduce(
53
- (prevCommands, controller) => controller.handle(prevCommands),
54
- commands
52
+ const handleResult = options.controllers.reduce(
53
+ ({ commands: prevCommands, onFinishCallbacks }, controller) => {
54
+ const { commands, onFinish } = controller.handle(prevCommands);
55
+ return {
56
+ commands,
57
+ onFinishCallbacks: _.concat(onFinishCallbacks, onFinish ? [onFinish] : [])
58
+ };
59
+ },
60
+ { commands, onFinishCallbacks: [] }
55
61
  );
62
+ commands = handleResult.commands;
56
63
 
57
64
  const commandsLeft = commands.slice();
58
65
  const maxProcesses = Math.max(1, Number(options.maxProcesses) || commandsLeft.length);
@@ -60,7 +67,11 @@ module.exports = (commands, options) => {
60
67
  maybeRunMore(commandsLeft);
61
68
  }
62
69
 
63
- return new CompletionListener({ successCondition: options.successCondition }).listen(commands);
70
+ return new CompletionListener({ successCondition: options.successCondition })
71
+ .listen(commands)
72
+ .finally(() => {
73
+ handleResult.onFinishCallbacks.forEach((onFinish) => onFinish());
74
+ });
64
75
  };
65
76
 
66
77
  function mapToCommandInfo(command) {
@@ -0,0 +1,186 @@
1
+ const EventEmitter = require('events');
2
+
3
+ const createFakeCommand = require('./flow-control/fixtures/fake-command');
4
+ const FakeHandler = require('./flow-control/fixtures/fake-handler');
5
+ const concurrently = require('./concurrently');
6
+
7
+ let spawn, kill, controllers, processes = [];
8
+ const create = (commands, options = {}) => concurrently(
9
+ commands,
10
+ Object.assign(options, { controllers, spawn, kill })
11
+ );
12
+
13
+ beforeEach(() => {
14
+ processes = [];
15
+ spawn = jest.fn(() => {
16
+ const process = new EventEmitter();
17
+ processes.push(process);
18
+ process.pid = processes.length;
19
+ return process;
20
+ });
21
+ kill = jest.fn();
22
+ controllers = [new FakeHandler(), new FakeHandler()];
23
+ });
24
+
25
+ it('fails if commands is not an array', () => {
26
+ const bomb = () => create('foo');
27
+ expect(bomb).toThrowError();
28
+ });
29
+
30
+ it('fails if no commands were provided', () => {
31
+ const bomb = () => create([]);
32
+ expect(bomb).toThrowError();
33
+ });
34
+
35
+ it('spawns all commands', () => {
36
+ create(['echo', 'kill']);
37
+ expect(spawn).toHaveBeenCalledTimes(2);
38
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({}));
39
+ expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({}));
40
+ });
41
+
42
+ it('spawns commands up to configured limit at once', () => {
43
+ create(['foo', 'bar', 'baz', 'qux'], { maxProcesses: 2 });
44
+ expect(spawn).toHaveBeenCalledTimes(2);
45
+ expect(spawn).toHaveBeenCalledWith('foo', expect.objectContaining({}));
46
+ expect(spawn).toHaveBeenCalledWith('bar', expect.objectContaining({}));
47
+
48
+ // Test out of order completion picking up new processes in-order
49
+ processes[1].emit('close', 1, null);
50
+ expect(spawn).toHaveBeenCalledTimes(3);
51
+ expect(spawn).toHaveBeenCalledWith('baz', expect.objectContaining({}));
52
+
53
+ processes[0].emit('close', null, 'SIGINT');
54
+ expect(spawn).toHaveBeenCalledTimes(4);
55
+ expect(spawn).toHaveBeenCalledWith('qux', expect.objectContaining({}));
56
+
57
+ // Shouldn't attempt to spawn anything else.
58
+ processes[2].emit('close', 1, null);
59
+ expect(spawn).toHaveBeenCalledTimes(4);
60
+ });
61
+
62
+ it('runs controllers with the commands', () => {
63
+ create(['echo', '"echo wrapped"']);
64
+
65
+ controllers.forEach(controller => {
66
+ expect(controller.handle).toHaveBeenCalledWith([
67
+ expect.objectContaining({ command: 'echo', index: 0 }),
68
+ expect.objectContaining({ command: 'echo wrapped', index: 1 }),
69
+ ]);
70
+ });
71
+ });
72
+
73
+ it('runs commands with a name or prefix color', () => {
74
+ create([
75
+ { command: 'echo', prefixColor: 'red', name: 'foo' },
76
+ 'kill'
77
+ ]);
78
+
79
+ controllers.forEach(controller => {
80
+ expect(controller.handle).toHaveBeenCalledWith([
81
+ expect.objectContaining({ command: 'echo', index: 0, name: 'foo', prefixColor: 'red' }),
82
+ expect.objectContaining({ command: 'kill', index: 1, name: '', prefixColor: '' }),
83
+ ]);
84
+ });
85
+ });
86
+
87
+ it('passes commands wrapped from a controller to the next one', () => {
88
+ const fakeCommand = createFakeCommand('banana', 'banana');
89
+ controllers[0].handle.mockReturnValue({ commands: [fakeCommand] });
90
+
91
+ create(['echo']);
92
+
93
+ expect(controllers[0].handle).toHaveBeenCalledWith([
94
+ expect.objectContaining({ command: 'echo', index: 0 })
95
+ ]);
96
+
97
+ expect(controllers[1].handle).toHaveBeenCalledWith([fakeCommand]);
98
+
99
+ expect(fakeCommand.start).toHaveBeenCalledTimes(1);
100
+ });
101
+
102
+ it('merges extra env vars into each command', () => {
103
+ create([
104
+ { command: 'echo', env: { foo: 'bar' } },
105
+ { command: 'echo', env: { foo: 'baz' } },
106
+ 'kill'
107
+ ]);
108
+
109
+ expect(spawn).toHaveBeenCalledTimes(3);
110
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
111
+ env: expect.objectContaining({ foo: 'bar' })
112
+ }));
113
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
114
+ env: expect.objectContaining({ foo: 'baz' })
115
+ }));
116
+ expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
117
+ env: expect.not.objectContaining({ foo: expect.anything() })
118
+ }));
119
+ });
120
+
121
+ it('uses cwd from options for each command', () => {
122
+ create(
123
+ [
124
+ { command: 'echo', env: { foo: 'bar' } },
125
+ { command: 'echo', env: { foo: 'baz' } },
126
+ 'kill'
127
+ ],
128
+ {
129
+ cwd: 'foobar',
130
+ }
131
+ );
132
+
133
+ expect(spawn).toHaveBeenCalledTimes(3);
134
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
135
+ env: expect.objectContaining({ foo: 'bar' }),
136
+ cwd: 'foobar',
137
+ }));
138
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
139
+ env: expect.objectContaining({ foo: 'baz' }),
140
+ cwd: 'foobar',
141
+ }));
142
+ expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
143
+ env: expect.not.objectContaining({ foo: expect.anything() }),
144
+ cwd: 'foobar',
145
+ }));
146
+ });
147
+
148
+ it('uses overridden cwd option for each command if specified', () => {
149
+ create(
150
+ [
151
+ { command: 'echo', env: { foo: 'bar' }, cwd: 'baz' },
152
+ { command: 'echo', env: { foo: 'baz' } },
153
+ ],
154
+ {
155
+ cwd: 'foobar',
156
+ }
157
+ );
158
+
159
+ expect(spawn).toHaveBeenCalledTimes(2);
160
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
161
+ env: expect.objectContaining({ foo: 'bar' }),
162
+ cwd: 'baz',
163
+ }));
164
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
165
+ env: expect.objectContaining({ foo: 'baz' }),
166
+ cwd: 'foobar',
167
+ }));
168
+ });
169
+
170
+ it('runs onFinish hook after all commands run', async () => {
171
+ const promise = create(['foo', 'bar'], { maxProcesses: 1 });
172
+ expect(spawn).toHaveBeenCalledTimes(1);
173
+ expect(controllers[0].onFinish).not.toHaveBeenCalled();
174
+ expect(controllers[1].onFinish).not.toHaveBeenCalled();
175
+
176
+ processes[0].emit('close', 0, null);
177
+ expect(spawn).toHaveBeenCalledTimes(2);
178
+ expect(controllers[0].onFinish).not.toHaveBeenCalled();
179
+ expect(controllers[1].onFinish).not.toHaveBeenCalled();
180
+
181
+ processes[1].emit('close', 0, null);
182
+ await promise;
183
+
184
+ expect(controllers[0].onFinish).toHaveBeenCalled();
185
+ expect(controllers[1].onFinish).toHaveBeenCalled();
186
+ });
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,
@@ -0,0 +1,16 @@
1
+ module.exports = class BaseHandler {
2
+ constructor(options = {}) {
3
+ const { logger } = options;
4
+
5
+ this.logger = logger;
6
+ }
7
+
8
+ handle(commands) {
9
+ return {
10
+ commands,
11
+ // an optional callback to call when all commands have finished
12
+ // (either successful or not)
13
+ onFinish: null,
14
+ };
15
+ }
16
+ };
@@ -2,17 +2,20 @@ const Rx = require('rxjs');
2
2
  const { map } = require('rxjs/operators');
3
3
 
4
4
  const defaults = require('../defaults');
5
+ const BaseHandler = require('./base-handler');
6
+
7
+ module.exports = class InputHandler extends BaseHandler {
8
+ constructor({ defaultInputTarget, inputStream, pauseInputStreamOnFinish, logger }) {
9
+ super({ logger });
5
10
 
6
- module.exports = class InputHandler {
7
- constructor({ defaultInputTarget, inputStream, logger }) {
8
11
  this.defaultInputTarget = defaultInputTarget || defaults.defaultInputTarget;
9
12
  this.inputStream = inputStream;
10
- this.logger = logger;
13
+ this.pauseInputStreamOnFinish = pauseInputStreamOnFinish !== false;
11
14
  }
12
15
 
13
16
  handle(commands) {
14
17
  if (!this.inputStream) {
15
- return commands;
18
+ return { commands };
16
19
  }
17
20
 
18
21
  Rx.fromEvent(this.inputStream, 'data')
@@ -34,6 +37,14 @@ module.exports = class InputHandler {
34
37
  }
35
38
  });
36
39
 
37
- return commands;
40
+ return {
41
+ commands,
42
+ onFinish: () => {
43
+ if (this.pauseInputStreamOnFinish) {
44
+ // https://github.com/kimmobrunfeldt/concurrently/issues/252
45
+ this.inputStream.pause();
46
+ }
47
+ },
48
+ };
38
49
  }
39
50
  };
@@ -0,0 +1,113 @@
1
+ const stream = require('stream');
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 stream.PassThrough();
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)).toMatchObject({ commands });
26
+
27
+ controller = new InputHandler({ logger });
28
+ expect(controller.handle(commands)).toMatchObject({ commands });
29
+ });
30
+
31
+ it('forwards input stream to default target ID', () => {
32
+ controller.handle(commands);
33
+
34
+ inputStream.write('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.write('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.write('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.write('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.write('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
+ });
92
+
93
+ it('pauses input stream when finished', () => {
94
+ expect(inputStream.readableFlowing).toBeNull();
95
+
96
+ const { onFinish } = controller.handle(commands);
97
+ expect(inputStream.readableFlowing).toBe(true);
98
+
99
+ onFinish();
100
+ expect(inputStream.readableFlowing).toBe(false);
101
+ });
102
+
103
+ it('does not pause input stream when pauseInputStreamOnFinish is set to false', () => {
104
+ controller = new InputHandler({ inputStream, pauseInputStreamOnFinish: false });
105
+
106
+ expect(inputStream.readableFlowing).toBeNull();
107
+
108
+ const { onFinish } = controller.handle(commands);
109
+ expect(inputStream.readableFlowing).toBe(true);
110
+
111
+ onFinish();
112
+ expect(inputStream.readableFlowing).toBe(true);
113
+ });