UNPKG

14.1 kBJavaScriptView Raw
1'use strict'
2
3const tape = require('tape')
4 , child_process = require('child_process')
5 , workerFarm = require('../')
6 , childPath = require.resolve('./child')
7 , fs = require('fs')
8
9function uniq (ar) {
10 let a = [], i, j
11 o: for (i = 0; i < ar.length; ++i) {
12 for (j = 0; j < a.length; ++j) if (a[j] == ar[i]) continue o
13 a[a.length] = ar[i]
14 }
15 return a
16}
17
18
19// a child where module.exports = function ...
20tape('simple, exports=function test', function (t) {
21 t.plan(4)
22
23 let child = workerFarm(childPath)
24 child(0, function (err, pid, rnd) {
25 t.ok(pid > process.pid, 'pid makes sense')
26 t.ok(pid < process.pid + 500, 'pid makes sense')
27 t.ok(rnd >= 0 && rnd < 1, 'rnd result makes sense')
28 })
29
30 workerFarm.end(child, function () {
31 t.ok(true, 'workerFarm ended')
32 })
33})
34
35
36// a child where we have module.exports.fn = function ...
37tape('simple, exports.fn test', function (t) {
38 t.plan(4)
39
40 let child = workerFarm(childPath, [ 'run0' ])
41 child.run0(function (err, pid, rnd) {
42 t.ok(pid > process.pid, 'pid makes sense')
43 t.ok(pid < process.pid + 500, 'pid makes sense')
44 t.ok(rnd >= 0 && rnd < 1, 'rnd result makes sense')
45 })
46
47 workerFarm.end(child, function () {
48 t.ok(true, 'workerFarm ended')
49 })
50})
51
52
53// use the returned pids to check that we're using a single child process
54// when maxConcurrentWorkers = 1
55tape('single worker', function (t) {
56 t.plan(2)
57
58 let child = workerFarm({ maxConcurrentWorkers: 1 }, childPath)
59 , pids = []
60 , i = 10
61
62 while (i--) {
63 child(0, function (err, pid) {
64 pids.push(pid)
65 if (pids.length == 10) {
66 t.equal(1, uniq(pids).length, 'only a single process (by pid)')
67 } else if (pids.length > 10)
68 t.fail('too many callbacks!')
69 })
70 }
71
72 workerFarm.end(child, function () {
73 t.ok(true, 'workerFarm ended')
74 })
75})
76
77
78// use the returned pids to check that we're using two child processes
79// when maxConcurrentWorkers = 2
80tape('two workers', function (t) {
81 t.plan(2)
82
83 let child = workerFarm({ maxConcurrentWorkers: 2 }, childPath)
84 , pids = []
85 , i = 10
86
87 while (i--) {
88 child(0, function (err, pid) {
89 pids.push(pid)
90 if (pids.length == 10) {
91 t.equal(2, uniq(pids).length, 'only two child processes (by pid)')
92 } else if (pids.length > 10)
93 t.fail('too many callbacks!')
94 })
95 }
96
97 workerFarm.end(child, function () {
98 t.ok(true, 'workerFarm ended')
99 })
100})
101
102
103// use the returned pids to check that we're using a child process per
104// call when maxConcurrentWorkers = 10
105tape('many workers', function (t) {
106 t.plan(2)
107
108 let child = workerFarm({ maxConcurrentWorkers: 10 }, childPath)
109 , pids = []
110 , i = 10
111
112 while (i--) {
113 child(1, function (err, pid) {
114 pids.push(pid)
115 if (pids.length == 10) {
116 t.equal(10, uniq(pids).length, 'pids are all the same (by pid)')
117 } else if (pids.length > 10)
118 t.fail('too many callbacks!')
119 })
120 }
121
122 workerFarm.end(child, function () {
123 t.ok(true, 'workerFarm ended')
124 })
125})
126
127
128tape('auto start workers', function (t) {
129 t.plan(4)
130
131 let child = workerFarm({ maxConcurrentWorkers: 3, autoStart: true }, childPath, ['uptime'])
132 , pids = []
133 , i = 3
134 , delay = 150
135
136 setTimeout(function() {
137 while (i--)
138 child.uptime(function (err, uptime) {
139 t.ok(uptime > 10, 'child has been up before the request')
140 })
141
142 workerFarm.end(child, function () {
143 t.ok(true, 'workerFarm ended')
144 })
145 }, delay)
146})
147
148
149// use the returned pids to check that we're using a child process per
150// call when we set maxCallsPerWorker = 1 even when we have maxConcurrentWorkers = 1
151tape('single call per worker', function (t) {
152 t.plan(2)
153
154 let child = workerFarm({ maxConcurrentWorkers: 1, maxCallsPerWorker: 1 }, childPath)
155 , pids = []
156 , i = 10
157
158 while (i--) {
159 child(0, function (err, pid) {
160 pids.push(pid)
161 if (pids.length == 10) {
162 t.equal(10, uniq(pids).length, 'one process for each call (by pid)')
163 workerFarm.end(child, function () {
164 t.ok(true, 'workerFarm ended')
165 })
166 } else if (pids.length > 10)
167 t.fail('too many callbacks!')
168 })
169 }
170})
171
172
173// use the returned pids to check that we're using a child process per
174// two-calls when we set maxCallsPerWorker = 2 even when we have maxConcurrentWorkers = 1
175tape('two calls per worker', function (t) {
176 t.plan(2)
177
178 let child = workerFarm({ maxConcurrentWorkers: 1, maxCallsPerWorker: 2 }, childPath)
179 , pids = []
180 , i = 10
181
182 while (i--) {
183 child(0, function (err, pid) {
184 pids.push(pid)
185 if (pids.length == 10) {
186 t.equal(5, uniq(pids).length, 'one process for each call (by pid)')
187 workerFarm.end(child, function () {
188 t.ok(true, 'workerFarm ended')
189 })
190 } else if (pids.length > 10)
191 t.fail('too many callbacks!')
192 })
193 }
194})
195
196
197// use timing to confirm that one worker will process calls sequentially
198tape('many concurrent calls', function (t) {
199 t.plan(2)
200
201 let child = workerFarm({ maxConcurrentWorkers: 1 }, childPath)
202 , i = 10
203 , cbc = 0
204 , start = Date.now()
205
206 while (i--) {
207 child(100, function () {
208 if (++cbc == 10) {
209 let time = Date.now() - start
210 t.ok(time > 100 && time < 250, 'processed tasks concurrently (' + time + 'ms)')
211 workerFarm.end(child, function () {
212 t.ok(true, 'workerFarm ended')
213 })
214 } else if (cbc > 10)
215 t.fail('too many callbacks!')
216 })
217 }
218})
219
220
221// use timing to confirm that one child processes calls sequentially with
222// maxConcurrentCallsPerWorker = 1
223tape('single concurrent call', function (t) {
224 t.plan(2)
225
226 let child = workerFarm(
227 { maxConcurrentWorkers: 1, maxConcurrentCallsPerWorker: 1 }
228 , childPath
229 )
230 , i = 10
231 , cbc = 0
232 , start = Date.now()
233
234 while (i--) {
235 child(20, function () {
236 if (++cbc == 10) {
237 let time = Date.now() - start
238 t.ok(time > 200 && time < 400, 'processed tasks sequentially (' + time + 'ms)')
239 workerFarm.end(child, function () {
240 t.ok(true, 'workerFarm ended')
241 })
242 } else if (cbc > 10)
243 t.fail('too many callbacks!')
244 })
245 }
246})
247
248
249// use timing to confirm that one child processes *only* 5 calls concurrently
250tape('multiple concurrent calls', function (t) {
251 t.plan(2)
252
253 let child = workerFarm({ maxConcurrentWorkers: 1, maxConcurrentCallsPerWorker: 5 }, childPath)
254 , i = 10
255 , cbc = 0
256 , start = Date.now()
257
258 while (i--) {
259 child(100, function () {
260 if (++cbc == 10) {
261 let time = Date.now() - start
262 t.ok(time > 200 && time < 350, 'processed tasks concurrently (' + time + 'ms)')
263 workerFarm.end(child, function () {
264 t.ok(true, 'workerFarm ended')
265 })
266 } else if (cbc > 10)
267 t.fail('too many callbacks!')
268 })
269 }
270})
271
272
273// call a method that will die with a probability of 0.5 but expect that
274// we'll get results for each of our calls anyway
275tape('durability', function (t) {
276 t.plan(3)
277
278 let child = workerFarm({ maxConcurrentWorkers: 2 }, childPath, [ 'killable' ])
279 , ids = []
280 , pids = []
281 , i = 10
282
283 while (i--) {
284 child.killable(i, function (err, id, pid) {
285 ids.push(id)
286 pids.push(pid)
287 if (ids.length == 10) {
288 t.ok(uniq(pids).length > 2, 'processed by many (' + uniq(pids).length + ') workers, but got there in the end!')
289 t.ok(uniq(ids).length == 10, 'received a single result for each unique call')
290 workerFarm.end(child, function () {
291 t.ok(true, 'workerFarm ended')
292 })
293 } else if (ids.length > 10)
294 t.fail('too many callbacks!')
295 })
296 }
297})
298
299
300// a callback provided to .end() can and will be called (uses "simple, exports=function test" to create a child)
301tape('simple, end callback', function (t) {
302 t.plan(4)
303
304 let child = workerFarm(childPath)
305 child(0, function (err, pid, rnd) {
306 t.ok(pid > process.pid, 'pid makes sense ' + pid + ' vs ' + process.pid)
307 t.ok(pid < process.pid + 500, 'pid makes sense ' + pid + ' vs ' + process.pid)
308 t.ok(rnd >= 0 && rnd < 1, 'rnd result makes sense')
309 })
310
311 workerFarm.end(child, function() {
312 t.pass('an .end() callback was successfully called')
313 })
314})
315
316
317tape('call timeout test', function (t) {
318 t.plan(3 + 3 + 4 + 4 + 4 + 3 + 1)
319
320 let child = workerFarm({ maxCallTime: 250, maxConcurrentWorkers: 1 }, childPath)
321
322 // should come back ok
323 child(50, function (err, pid, rnd) {
324 t.ok(pid > process.pid, 'pid makes sense ' + pid + ' vs ' + process.pid)
325 t.ok(pid < process.pid + 500, 'pid makes sense ' + pid + ' vs ' + process.pid)
326 t.ok(rnd > 0 && rnd < 1, 'rnd result makes sense ' + rnd)
327 })
328
329 // should come back ok
330 child(50, function (err, pid, rnd) {
331 t.ok(pid > process.pid, 'pid makes sense ' + pid + ' vs ' + process.pid)
332 t.ok(pid < process.pid + 500, 'pid makes sense ' + pid + ' vs ' + process.pid)
333 t.ok(rnd > 0 && rnd < 1, 'rnd result makes sense ' + rnd)
334 })
335
336 // should die
337 child(500, function (err, pid, rnd) {
338 t.ok(err, 'got an error')
339 t.equal(err.type, 'TimeoutError', 'correct error type')
340 t.ok(pid === undefined, 'no pid')
341 t.ok(rnd === undefined, 'no rnd')
342 })
343
344 // should die
345 child(1000, function (err, pid, rnd) {
346 t.ok(err, 'got an error')
347 t.equal(err.type, 'TimeoutError', 'correct error type')
348 t.ok(pid === undefined, 'no pid')
349 t.ok(rnd === undefined, 'no rnd')
350 })
351
352 // should die even though it is only a 100ms task, it'll get caught up
353 // in a dying worker
354 setTimeout(function () {
355 child(100, function (err, pid, rnd) {
356 t.ok(err, 'got an error')
357 t.equal(err.type, 'TimeoutError', 'correct error type')
358 t.ok(pid === undefined, 'no pid')
359 t.ok(rnd === undefined, 'no rnd')
360 })
361 }, 200)
362
363 // should be ok, new worker
364 setTimeout(function () {
365 child(50, function (err, pid, rnd) {
366 t.ok(pid > process.pid, 'pid makes sense ' + pid + ' vs ' + process.pid)
367 t.ok(pid < process.pid + 500, 'pid makes sense ' + pid + ' vs ' + process.pid)
368 t.ok(rnd > 0 && rnd < 1, 'rnd result makes sense ' + rnd)
369 })
370 workerFarm.end(child, function () {
371 t.ok(true, 'workerFarm ended')
372 })
373 }, 400)
374})
375
376
377tape('test error passing', function (t) {
378 t.plan(10)
379
380 let child = workerFarm(childPath, [ 'err' ])
381 child.err('Error', 'this is an Error', function (err) {
382 t.ok(err instanceof Error, 'is an Error object')
383 t.equal('Error', err.type, 'correct type')
384 t.equal('this is an Error', err.message, 'correct message')
385 })
386 child.err('TypeError', 'this is a TypeError', function (err) {
387 t.ok(err instanceof Error, 'is a TypeError object')
388 t.equal('TypeError', err.type, 'correct type')
389 t.equal('this is a TypeError', err.message, 'correct message')
390 })
391 child.err('Error', 'this is an Error with custom props', {foo: 'bar', 'baz': 1}, function (err) {
392 t.ok(err instanceof Error, 'is an Error object')
393 t.equal(err.foo, 'bar', 'passes data')
394 t.equal(err.baz, 1, 'passes data')
395 })
396
397 workerFarm.end(child, function () {
398 t.ok(true, 'workerFarm ended')
399 })
400})
401
402
403tape('test maxConcurrentCalls', function (t) {
404 t.plan(10)
405
406 let child = workerFarm({ maxConcurrentCalls: 5 }, childPath)
407
408 child(50, function (err) { t.notOk(err, 'no error') })
409 child(50, function (err) { t.notOk(err, 'no error') })
410 child(50, function (err) { t.notOk(err, 'no error') })
411 child(50, function (err) { t.notOk(err, 'no error') })
412 child(50, function (err) { t.notOk(err, 'no error') })
413 child(50, function (err) {
414 t.ok(err)
415 t.equal(err.type, 'MaxConcurrentCallsError', 'correct error type')
416 })
417 child(50, function (err) {
418 t.ok(err)
419 t.equal(err.type, 'MaxConcurrentCallsError', 'correct error type')
420 })
421
422 workerFarm.end(child, function () {
423 t.ok(true, 'workerFarm ended')
424 })
425})
426
427
428// this test should not keep the process running! if the test process
429// doesn't die then the problem is here
430tape('test timeout kill', function (t) {
431 t.plan(3)
432
433 let child = workerFarm({ maxCallTime: 250, maxConcurrentWorkers: 1 }, childPath, [ 'block' ])
434 child.block(function (err) {
435 t.ok(err, 'got an error')
436 t.equal(err.type, 'TimeoutError', 'correct error type')
437 })
438
439 workerFarm.end(child, function () {
440 t.ok(true, 'workerFarm ended')
441 })
442})
443
444
445tape('test max retries after process terminate', function (t) {
446 t.plan(7)
447
448 // temporary file is used to store the number of retries among terminating workers
449 let filepath1 = '.retries1'
450 let child1 = workerFarm({ maxConcurrentWorkers: 1, maxRetries: 5}, childPath, [ 'stubborn' ])
451 child1.stubborn(filepath1, function (err, result) {
452 t.notOk(err, 'no error')
453 t.equal(result, 12, 'correct result')
454 })
455
456 workerFarm.end(child1, function () {
457 fs.unlinkSync(filepath1)
458 t.ok(true, 'workerFarm ended')
459 })
460
461 let filepath2 = '.retries2'
462 let child2 = workerFarm({ maxConcurrentWorkers: 1, maxRetries: 3}, childPath, [ 'stubborn' ])
463 child2.stubborn(filepath2, function (err, result) {
464 t.ok(err, 'got an error')
465 t.equal(err.type, 'ProcessTerminatedError', 'correct error type')
466 t.equal(err.message, 'cancel after 3 retries!', 'correct message and number of retries')
467 })
468
469 workerFarm.end(child2, function () {
470 fs.unlinkSync(filepath2)
471 t.ok(true, 'workerFarm ended')
472 })
473})
474
475
476tape('ensure --debug/--inspect not propagated to children', function (t) {
477 t.plan(3)
478
479 let script = __dirname + '/debug.js'
480 , debugArg = process.version.replace(/^v(\d+)\..*$/, '$1') >= 8 ? '--inspect' : '--debug=8881'
481 , child = child_process.spawn(process.execPath, [ debugArg, script ])
482 , stdout = ''
483
484 child.stdout.on('data', function (data) {
485 stdout += data.toString()
486 })
487
488 child.on('close', function (code) {
489 t.equal(code, 0, 'exited without error (' + code + ')')
490 t.ok(stdout.indexOf('FINISHED') > -1, 'process finished')
491 t.ok(stdout.indexOf('--debug') === -1, 'child does not receive debug flag')
492 })
493})