1
1
import { autoUnsubscribe , subscribeSpyTo } from '@hirez_io/observer-spy' ;
2
2
import { SpawnOptions } from 'child_process' ;
3
3
import { EventEmitter } from 'events' ;
4
+ import * as Rx from 'rxjs' ;
4
5
import { Readable , Writable } from 'stream' ;
5
6
6
- import { ChildProcess , Command , CommandInfo , KillProcess , SpawnCommand } from './command' ;
7
+ import {
8
+ ChildProcess ,
9
+ CloseEvent ,
10
+ Command ,
11
+ CommandInfo ,
12
+ KillProcess ,
13
+ SpawnCommand ,
14
+ } from './command' ;
15
+
16
+ type CommandValues = { error : unknown ; close : CloseEvent ; timer : unknown [ ] } ;
7
17
8
18
let process : ChildProcess ;
9
19
let spawn : jest . Mocked < SpawnCommand > ;
@@ -34,94 +44,121 @@ beforeEach(() => {
34
44
killProcess = jest . fn ( ) ;
35
45
} ) ;
36
46
37
- const createCommand = ( overrides ?: Partial < CommandInfo > , spawnOpts ?: SpawnOptions ) =>
38
- new Command (
47
+ const createCommand = ( overrides ?: Partial < CommandInfo > , spawnOpts ?: SpawnOptions ) => {
48
+ const command = new Command (
39
49
{ index : 0 , name : '' , command : 'echo foo' , ...overrides } ,
40
50
spawnOpts ,
41
51
spawn ,
42
52
killProcess
43
53
) ;
44
54
55
+ let error : unknown ;
56
+ let close : CloseEvent ;
57
+ const timer = subscribeSpyTo ( command . timer ) ;
58
+ const finished = subscribeSpyTo (
59
+ new Rx . Observable ( ( observer ) => {
60
+ // First event in both subjects means command has finished
61
+ command . error . subscribe ( {
62
+ next : ( value ) => {
63
+ error = value ;
64
+ observer . complete ( ) ;
65
+ } ,
66
+ } ) ;
67
+ command . close . subscribe ( {
68
+ next : ( value ) => {
69
+ close = value ;
70
+ observer . complete ( ) ;
71
+ } ,
72
+ } ) ;
73
+ } )
74
+ ) ;
75
+ const values = async ( ) : Promise < CommandValues > => {
76
+ await finished . onComplete ( ) ;
77
+ return { error, close, timer : timer . getValues ( ) } ;
78
+ } ;
79
+
80
+ return { command, values } ;
81
+ } ;
82
+
45
83
describe ( '#start()' , ( ) => {
46
84
it ( 'spawns process with given command and options' , ( ) => {
47
- const command = createCommand ( { } , { detached : true } ) ;
85
+ const { command } = createCommand ( { } , { detached : true } ) ;
48
86
command . start ( ) ;
49
87
50
88
expect ( spawn ) . toHaveBeenCalledTimes ( 1 ) ;
51
89
expect ( spawn ) . toHaveBeenCalledWith ( command . command , { detached : true } ) ;
52
90
} ) ;
53
91
54
92
it ( 'sets stdin, process and PID' , ( ) => {
55
- const command = createCommand ( ) ;
93
+ const { command } = createCommand ( ) ;
56
94
command . start ( ) ;
57
95
58
96
expect ( command . process ) . toBe ( process ) ;
59
97
expect ( command . pid ) . toBe ( process . pid ) ;
60
98
expect ( command . stdin ) . toBe ( process . stdin ) ;
61
99
} ) ;
62
100
63
- it ( 'shares errors to the error stream' , ( ) => {
64
- const command = createCommand ( ) ;
65
- const observerSpy = subscribeSpyTo ( command . error ) ;
101
+ it ( 'shares errors to the error stream' , async ( ) => {
102
+ const { command, values } = createCommand ( ) ;
66
103
command . start ( ) ;
67
104
process . emit ( 'error' , 'foo' ) ;
105
+ const { error } = await values ( ) ;
68
106
69
- expect ( observerSpy . getFirstValue ( ) ) . toBe ( 'foo' ) ;
107
+ expect ( error ) . toBe ( 'foo' ) ;
70
108
expect ( command . process ) . toBeUndefined ( ) ;
71
109
} ) ;
72
110
73
- it ( 'shares start and close timing events to the timing stream' , ( ) => {
74
- const command = createCommand ( ) ;
75
- const observerSpy = subscribeSpyTo ( command . timer ) ;
111
+ it ( 'shares start and close timing events to the timing stream' , async ( ) => {
112
+ const { command, values } = createCommand ( ) ;
76
113
const startDate = new Date ( ) ;
77
114
const endDate = new Date ( startDate . getTime ( ) + 1000 ) ;
78
115
jest . spyOn ( Date , 'now' )
79
116
. mockReturnValueOnce ( startDate . getTime ( ) )
80
117
. mockReturnValueOnce ( endDate . getTime ( ) ) ;
81
118
command . start ( ) ;
82
119
process . emit ( 'close' , 0 , null ) ;
120
+ const { timer } = await values ( ) ;
83
121
84
- expect ( observerSpy . getValueAt ( 0 ) ) . toEqual ( { startDate, endDate : undefined } ) ;
85
- expect ( observerSpy . getValueAt ( 1 ) ) . toEqual ( { startDate, endDate } ) ;
122
+ expect ( timer [ 0 ] ) . toEqual ( { startDate, endDate : undefined } ) ;
123
+ expect ( timer [ 1 ] ) . toEqual ( { startDate, endDate } ) ;
86
124
} ) ;
87
125
88
- it ( 'shares start and error timing events to the timing stream' , ( ) => {
89
- const command = createCommand ( ) ;
90
- const observerSpy = subscribeSpyTo ( command . timer ) ;
126
+ it ( 'shares start and error timing events to the timing stream' , async ( ) => {
127
+ const { command, values } = createCommand ( ) ;
91
128
const startDate = new Date ( ) ;
92
129
const endDate = new Date ( startDate . getTime ( ) + 1000 ) ;
93
130
jest . spyOn ( Date , 'now' )
94
131
. mockReturnValueOnce ( startDate . getTime ( ) )
95
132
. mockReturnValueOnce ( endDate . getTime ( ) ) ;
96
133
command . start ( ) ;
97
134
process . emit ( 'error' , 0 , null ) ;
135
+ const { timer } = await values ( ) ;
98
136
99
- expect ( observerSpy . getValueAt ( 0 ) ) . toEqual ( { startDate, endDate : undefined } ) ;
100
- expect ( observerSpy . getValueAt ( 1 ) ) . toEqual ( { startDate, endDate } ) ;
137
+ expect ( timer [ 0 ] ) . toEqual ( { startDate, endDate : undefined } ) ;
138
+ expect ( timer [ 1 ] ) . toEqual ( { startDate, endDate } ) ;
101
139
} ) ;
102
140
103
- it ( 'shares closes to the close stream with exit code' , ( ) => {
104
- const command = createCommand ( ) ;
105
- const observerSpy = subscribeSpyTo ( command . close ) ;
141
+ it ( 'shares closes to the close stream with exit code' , async ( ) => {
142
+ const { command, values } = createCommand ( ) ;
106
143
command . start ( ) ;
107
144
process . emit ( 'close' , 0 , null ) ;
145
+ const { close } = await values ( ) ;
108
146
109
- expect ( observerSpy . getFirstValue ( ) ) . toMatchObject ( { exitCode : 0 , killed : false } ) ;
147
+ expect ( close ) . toMatchObject ( { exitCode : 0 , killed : false } ) ;
110
148
expect ( command . process ) . toBeUndefined ( ) ;
111
149
} ) ;
112
150
113
- it ( 'shares closes to the close stream with signal' , ( ) => {
114
- const command = createCommand ( ) ;
115
- const observerSpy = subscribeSpyTo ( command . close ) ;
151
+ it ( 'shares closes to the close stream with signal' , async ( ) => {
152
+ const { command, values } = createCommand ( ) ;
116
153
command . start ( ) ;
117
154
process . emit ( 'close' , null , 'SIGKILL' ) ;
155
+ const { close } = await values ( ) ;
118
156
119
- expect ( observerSpy . getFirstValue ( ) ) . toMatchObject ( { exitCode : 'SIGKILL' , killed : false } ) ;
157
+ expect ( close ) . toMatchObject ( { exitCode : 'SIGKILL' , killed : false } ) ;
120
158
} ) ;
121
159
122
- it ( 'shares closes to the close stream with timing information' , ( ) => {
123
- const command = createCommand ( ) ;
124
- const observerSpy = subscribeSpyTo ( command . close ) ;
160
+ it ( 'shares closes to the close stream with timing information' , async ( ) => {
161
+ const { command, values } = createCommand ( ) ;
125
162
const startDate = new Date ( ) ;
126
163
const endDate = new Date ( startDate . getTime ( ) + 1000 ) ;
127
164
jest . spyOn ( Date , 'now' )
@@ -132,85 +169,86 @@ describe('#start()', () => {
132
169
. mockReturnValueOnce ( [ 1 , 1e8 ] ) ;
133
170
command . start ( ) ;
134
171
process . emit ( 'close' , null , 'SIGKILL' ) ;
172
+ const { close } = await values ( ) ;
135
173
136
- expect ( observerSpy . getFirstValue ( ) . timings ) . toStrictEqual ( {
174
+ expect ( close . timings ) . toStrictEqual ( {
137
175
startDate,
138
176
endDate,
139
177
durationSeconds : 1.1 ,
140
178
} ) ;
141
179
} ) ;
142
180
143
- it ( 'shares closes to the close stream with command info' , ( ) => {
181
+ it ( 'shares closes to the close stream with command info' , async ( ) => {
144
182
const commandInfo = {
145
183
command : 'cmd' ,
146
184
name : 'name' ,
147
185
prefixColor : 'green' ,
148
186
env : { VAR : 'yes' } ,
149
187
} ;
150
- const command = createCommand ( commandInfo ) ;
151
- const observerSpy = subscribeSpyTo ( command . close ) ;
188
+ const { command, values } = createCommand ( commandInfo ) ;
152
189
command . start ( ) ;
153
190
process . emit ( 'close' , 0 , null ) ;
191
+ const { close } = await values ( ) ;
154
192
155
- expect ( observerSpy . getFirstValue ( ) . command ) . toEqual ( expect . objectContaining ( commandInfo ) ) ;
156
- expect ( observerSpy . getFirstValue ( ) . killed ) . toBe ( false ) ;
193
+ expect ( close . command ) . toEqual ( expect . objectContaining ( commandInfo ) ) ;
194
+ expect ( close . killed ) . toBe ( false ) ;
157
195
} ) ;
158
196
159
- it ( 'shares stdout to the stdout stream' , ( ) => {
160
- const command = createCommand ( ) ;
161
- const observerSpy = subscribeSpyTo ( command . stdout ) ;
197
+ it ( 'shares stdout to the stdout stream' , async ( ) => {
198
+ const { command } = createCommand ( ) ;
199
+ const stdout = Rx . firstValueFrom ( command . stdout ) ;
162
200
command . start ( ) ;
163
201
process . stdout . emit ( 'data' , Buffer . from ( 'hello' ) ) ;
164
202
165
- expect ( observerSpy . getFirstValue ( ) . toString ( ) ) . toBe ( 'hello' ) ;
203
+ expect ( ( await stdout ) . toString ( ) ) . toBe ( 'hello' ) ;
166
204
} ) ;
167
205
168
- it ( 'shares stderr to the stdout stream' , ( ) => {
169
- const command = createCommand ( ) ;
170
- const observerSpy = subscribeSpyTo ( command . stderr ) ;
206
+ it ( 'shares stderr to the stdout stream' , async ( ) => {
207
+ const { command } = createCommand ( ) ;
208
+ const stderr = Rx . firstValueFrom ( command . stderr ) ;
171
209
command . start ( ) ;
172
210
process . stderr . emit ( 'data' , Buffer . from ( 'dang' ) ) ;
173
211
174
- expect ( observerSpy . getFirstValue ( ) . toString ( ) ) . toBe ( 'dang' ) ;
212
+ expect ( ( await stderr ) . toString ( ) ) . toBe ( 'dang' ) ;
175
213
} ) ;
176
214
} ) ;
177
215
178
216
describe ( '#kill()' , ( ) => {
179
- let command : Command ;
217
+ let createdCommand : { command : Command ; values : ( ) => Promise < CommandValues > } ;
180
218
beforeEach ( ( ) => {
181
- command = createCommand ( ) ;
219
+ createdCommand = createCommand ( ) ;
182
220
} ) ;
183
221
184
222
it ( 'kills process' , ( ) => {
185
- command . start ( ) ;
186
- command . kill ( ) ;
223
+ createdCommand . command . start ( ) ;
224
+ createdCommand . command . kill ( ) ;
187
225
188
226
expect ( killProcess ) . toHaveBeenCalledTimes ( 1 ) ;
189
- expect ( killProcess ) . toHaveBeenCalledWith ( command . pid , undefined ) ;
227
+ expect ( killProcess ) . toHaveBeenCalledWith ( createdCommand . command . pid , undefined ) ;
190
228
} ) ;
191
229
192
230
it ( 'kills process with some signal' , ( ) => {
193
- command . start ( ) ;
194
- command . kill ( 'SIGKILL' ) ;
231
+ createdCommand . command . start ( ) ;
232
+ createdCommand . command . kill ( 'SIGKILL' ) ;
195
233
196
234
expect ( killProcess ) . toHaveBeenCalledTimes ( 1 ) ;
197
- expect ( killProcess ) . toHaveBeenCalledWith ( command . pid , 'SIGKILL' ) ;
235
+ expect ( killProcess ) . toHaveBeenCalledWith ( createdCommand . command . pid , 'SIGKILL' ) ;
198
236
} ) ;
199
237
200
238
it ( 'does not try to kill inexistent process' , ( ) => {
201
- command . start ( ) ;
239
+ createdCommand . command . start ( ) ;
202
240
process . emit ( 'error' ) ;
203
- command . kill ( ) ;
241
+ createdCommand . command . kill ( ) ;
204
242
205
243
expect ( killProcess ) . not . toHaveBeenCalled ( ) ;
206
244
} ) ;
207
245
208
- it ( 'marks the command as killed' , ( ) => {
209
- command . start ( ) ;
210
- const observerSpy = subscribeSpyTo ( command . close ) ;
211
- command . kill ( ) ;
246
+ it ( 'marks the command as killed' , async ( ) => {
247
+ createdCommand . command . start ( ) ;
248
+ createdCommand . command . kill ( ) ;
212
249
process . emit ( 'close' , 1 , null ) ;
250
+ const { close } = await createdCommand . values ( ) ;
213
251
214
- expect ( observerSpy . getFirstValue ( ) ) . toMatchObject ( { exitCode : 1 , killed : true } ) ;
252
+ expect ( close ) . toMatchObject ( { exitCode : 1 , killed : true } ) ;
215
253
} ) ;
216
254
} ) ;
0 commit comments