addon-sdk/source/test/test-child_process.js

branch
TOR_BUG_9701
changeset 10
ac0c01689b40
equal deleted inserted replaced
-1:000000000000 0:098378435bbf
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 'use strict';
6
7 const { spawn, exec, execFile, fork } = require('sdk/system/child_process');
8 const { env, platform, pathFor } = require('sdk/system');
9 const { isNumber } = require('sdk/lang/type');
10 const { after } = require('sdk/test/utils');
11 const { emit } = require('sdk/event/core');
12 const PROFILE_DIR= pathFor('ProfD');
13 const isWindows = platform.toLowerCase().indexOf('win') === 0;
14 const { getScript, cleanUp } = require('./fixtures/child-process-scripts');
15
16 // We use direct paths to these utilities as we currently cannot
17 // call non-absolute paths to utilities in subprocess.jsm
18 const CAT_PATH = isWindows ? 'C:\\Windows\\System32\\more.com' : '/bin/cat';
19
20 exports.testExecCallbackSuccess = function (assert, done) {
21 exec(isWindows ? 'DIR /A-D' : 'ls -al', {
22 cwd: PROFILE_DIR
23 }, function (err, stdout, stderr) {
24 assert.ok(!err, 'no errors found');
25 assert.equal(stderr, '', 'stderr is empty');
26 assert.ok(/extensions\.ini/.test(stdout), 'stdout output of `ls -al` finds files');
27
28 if (isWindows) {
29 // `DIR /A-D` does not display directories on WIN
30 assert.ok(!/<DIR>/.test(stdout),
31 'passing arguments in `exec` works');
32 }
33 else {
34 // `ls -al` should list all the priviledge information on Unix
35 assert.ok(/d(r[-|w][-|x]){3}/.test(stdout),
36 'passing arguments in `exec` works');
37 }
38 done();
39 });
40 };
41
42 exports.testExecCallbackError = function (assert, done) {
43 exec('not-real-command', { cwd: PROFILE_DIR }, function (err, stdout, stderr) {
44 assert.ok(/not-real-command/.test(err.toString()),
45 'error contains error message');
46 assert.ok(err.lineNumber >= 0, 'error contains lineNumber');
47 assert.ok(/resource:\/\//.test(err.fileName), 'error contains fileName');
48 assert.ok(err.code && isNumber(err.code), 'non-zero error code property on error');
49 assert.equal(err.signal, null,
50 'null signal property when not manually terminated');
51 assert.equal(stdout, '', 'stdout is empty');
52 assert.ok(/not-real-command/.test(stderr), 'stderr contains error message');
53 done();
54 });
55 };
56
57 exports.testExecOptionsEnvironment = function (assert, done) {
58 getScript('check-env').then(envScript => {
59 exec(envScript, {
60 cwd: PROFILE_DIR,
61 env: { CHILD_PROCESS_ENV_TEST: 'my-value-test' }
62 }, function (err, stdout, stderr) {
63 assert.equal(stderr, '', 'stderr is empty');
64 assert.ok(!err, 'received `cwd` option');
65 assert.ok(/my-value-test/.test(stdout),
66 'receives environment option');
67 done();
68 });
69 });
70 };
71
72 exports.testExecOptionsTimeout = function (assert, done) {
73 let count = 0;
74 getScript('wait').then(script => {
75 let child = exec(script, { timeout: 100 }, (err, stdout, stderr) => {
76 assert.equal(err.killed, true, 'error has `killed` property as true');
77 assert.equal(err.code, null, 'error has `code` as null');
78 assert.equal(err.signal, 'SIGTERM',
79 'error has `signal` as SIGTERM by default');
80 assert.equal(stdout, '', 'stdout is empty');
81 assert.equal(stderr, '', 'stderr is empty');
82 if (++count === 3) complete();
83 });
84
85 function exitHandler (code, signal) {
86 assert.equal(code, null, 'error has `code` as null');
87 assert.equal(signal, 'SIGTERM',
88 'error has `signal` as SIGTERM by default');
89 if (++count === 3) complete();
90 }
91
92 function closeHandler (code, signal) {
93 assert.equal(code, null, 'error has `code` as null');
94 assert.equal(signal, 'SIGTERM',
95 'error has `signal` as SIGTERM by default');
96 if (++count === 3) complete();
97 }
98
99 child.on('exit', exitHandler);
100 child.on('close', closeHandler);
101
102 function complete () {
103 child.off('exit', exitHandler);
104 child.off('close', closeHandler);
105 done();
106 }
107 });
108 };
109
110 exports.testExecFileCallbackSuccess = function (assert, done) {
111 getScript('args').then(script => {
112 execFile(script, ['--myargs', '-j', '-s'], { cwd: PROFILE_DIR }, function (err, stdout, stderr) {
113 assert.ok(!err, 'no errors found');
114 assert.equal(stderr, '', 'stderr is empty');
115 // Trim output since different systems have different new line output
116 assert.equal(stdout.trim(), '--myargs -j -s'.trim(), 'passes in correct arguments');
117 done();
118 });
119 });
120 };
121
122 exports.testExecFileCallbackError = function (assert, done) {
123 execFile('not-real-command', { cwd: PROFILE_DIR }, function (err, stdout, stderr) {
124 assert.ok(/NS_ERROR_FILE_UNRECOGNIZED_PATH/.test(err.message),
125 'error contains error message');
126 assert.ok(err.lineNumber >= 0, 'error contains lineNumber');
127 assert.ok(/resource:\/\//.test(err.fileName), 'error contains fileName');
128 assert.equal(stdout, '', 'stdout is empty');
129 assert.equal(stderr, '', 'stdout is empty');
130 done();
131 });
132 };
133
134 exports.testExecFileOptionsEnvironment = function (assert, done) {
135 getScript('check-env').then(script => {
136 execFile(script, {
137 cwd: PROFILE_DIR,
138 env: { CHILD_PROCESS_ENV_TEST: 'my-value-test' }
139 }, function (err, stdout, stderr) {
140 assert.equal(stderr, '', 'stderr is empty');
141 assert.ok(!err, 'received `cwd` option');
142 assert.ok(/my-value-test/.test(stdout),
143 'receives environment option');
144 done();
145 });
146 });
147 };
148
149 exports.testExecFileOptionsTimeout = function (assert, done) {
150 let count = 0;
151 getScript('wait').then(script => {
152 let child = execFile(script, { timeout: 100 }, (err, stdout, stderr) => {
153 assert.equal(err.killed, true, 'error has `killed` property as true');
154 assert.equal(err.code, null, 'error has `code` as null');
155 assert.equal(err.signal, 'SIGTERM',
156 'error has `signal` as SIGTERM by default');
157 assert.equal(stdout, '', 'stdout is empty');
158 assert.equal(stderr, '', 'stderr is empty');
159 if (++count === 3) complete();
160 });
161
162 function exitHandler (code, signal) {
163 assert.equal(code, null, 'error has `code` as null');
164 assert.equal(signal, 'SIGTERM',
165 'error has `signal` as SIGTERM by default');
166 if (++count === 3) complete();
167 }
168
169 function closeHandler (code, signal) {
170 assert.equal(code, null, 'error has `code` as null');
171 assert.equal(signal, 'SIGTERM',
172 'error has `signal` as SIGTERM by default');
173 if (++count === 3) complete();
174 }
175
176 child.on('exit', exitHandler);
177 child.on('close', closeHandler);
178
179 function complete () {
180 child.off('exit', exitHandler);
181 child.off('close', closeHandler);
182 done();
183 }
184 });
185 };
186
187 /**
188 * Not necessary to test for both `exec` and `execFile`, but
189 * it is necessary to test both when the buffer is larger
190 * and smaller than buffer size used by the subprocess library (1024)
191 */
192 exports.testExecFileOptionsMaxBufferLargeStdOut = function (assert, done) {
193 let count = 0;
194 let stdoutChild;
195
196 // Creates a buffer of 2000 to stdout, greater than 1024
197 getScript('large-out').then(script => {
198 stdoutChild = execFile(script, ['10000'], { maxBuffer: 50 }, (err, stdout, stderr) => {
199 assert.ok(/stdout maxBuffer exceeded/.test(err.toString()),
200 'error contains stdout maxBuffer exceeded message');
201 assert.ok(stdout.length >= 50, 'stdout has full buffer');
202 assert.equal(stderr, '', 'stderr is empty');
203 if (++count === 3) complete();
204 });
205 stdoutChild.on('exit', exitHandler);
206 stdoutChild.on('close', closeHandler);
207 });
208
209 function exitHandler (code, signal) {
210 assert.equal(code, null, 'Exit code is null in exit handler');
211 assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in exit handler');
212 if (++count === 3) complete();
213 }
214
215 function closeHandler (code, signal) {
216 assert.equal(code, null, 'Exit code is null in close handler');
217 assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in close handler');
218 if (++count === 3) complete();
219 }
220
221 function complete () {
222 stdoutChild.off('exit', exitHandler);
223 stdoutChild.off('close', closeHandler);
224 done();
225 }
226 };
227
228 exports.testExecFileOptionsMaxBufferLargeStdOErr = function (assert, done) {
229 let count = 0;
230 let stderrChild;
231 // Creates a buffer of 2000 to stderr, greater than 1024
232 getScript('large-err').then(script => {
233 stderrChild = execFile(script, ['10000'], { maxBuffer: 50 }, (err, stdout, stderr) => {
234 assert.ok(/stderr maxBuffer exceeded/.test(err.toString()),
235 'error contains stderr maxBuffer exceeded message');
236 assert.ok(stderr.length >= 50, 'stderr has full buffer');
237 assert.equal(stdout, '', 'stdout is empty');
238 if (++count === 3) complete();
239 });
240 stderrChild.on('exit', exitHandler);
241 stderrChild.on('close', closeHandler);
242 });
243
244 function exitHandler (code, signal) {
245 assert.equal(code, null, 'Exit code is null in exit handler');
246 assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in exit handler');
247 if (++count === 3) complete();
248 }
249
250 function closeHandler (code, signal) {
251 assert.equal(code, null, 'Exit code is null in close handler');
252 assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in close handler');
253 if (++count === 3) complete();
254 }
255
256 function complete () {
257 stderrChild.off('exit', exitHandler);
258 stderrChild.off('close', closeHandler);
259 done();
260 }
261 };
262
263 /**
264 * When total buffer is < process buffer (1024), the process will exit
265 * and not get a chance to be killed for violating the maxBuffer,
266 * although the error will still be sent through (node behaviour)
267 */
268 exports.testExecFileOptionsMaxBufferSmallStdOut = function (assert, done) {
269 let count = 0;
270 let stdoutChild;
271
272 // Creates a buffer of 60 to stdout, less than 1024
273 getScript('large-out').then(script => {
274 stdoutChild = execFile(script, ['60'], { maxBuffer: 50 }, (err, stdout, stderr) => {
275 assert.ok(/stdout maxBuffer exceeded/.test(err.toString()),
276 'error contains stdout maxBuffer exceeded message');
277 assert.ok(stdout.length >= 50, 'stdout has full buffer');
278 assert.equal(stderr, '', 'stderr is empty');
279 if (++count === 3) complete();
280 });
281 stdoutChild.on('exit', exitHandler);
282 stdoutChild.on('close', closeHandler);
283 });
284
285 function exitHandler (code, signal) {
286 // Sometimes the buffer limit is hit before the process closes successfully
287 // on both OSX/Windows
288 if (code === null) {
289 assert.equal(code, null, 'Exit code is null in exit handler');
290 assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in exit handler');
291 }
292 else {
293 assert.equal(code, 0, 'Exit code is 0 in exit handler');
294 assert.equal(signal, null, 'Signal is null in exit handler');
295 }
296 if (++count === 3) complete();
297 }
298
299 function closeHandler (code, signal) {
300 // Sometimes the buffer limit is hit before the process closes successfully
301 // on both OSX/Windows
302 if (code === null) {
303 assert.equal(code, null, 'Exit code is null in close handler');
304 assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in close handler');
305 }
306 else {
307 assert.equal(code, 0, 'Exit code is 0 in close handler');
308 assert.equal(signal, null, 'Signal is null in close handler');
309 }
310 if (++count === 3) complete();
311 }
312
313 function complete () {
314 stdoutChild.off('exit', exitHandler);
315 stdoutChild.off('close', closeHandler);
316 done();
317 }
318 };
319
320 exports.testExecFileOptionsMaxBufferSmallStdErr = function (assert, done) {
321 let count = 0;
322 let stderrChild;
323 // Creates a buffer of 60 to stderr, less than 1024
324 getScript('large-err').then(script => {
325 stderrChild = execFile(script, ['60'], { maxBuffer: 50 }, (err, stdout, stderr) => {
326 assert.ok(/stderr maxBuffer exceeded/.test(err.toString()),
327 'error contains stderr maxBuffer exceeded message');
328 assert.ok(stderr.length >= 50, 'stderr has full buffer');
329 assert.equal(stdout, '', 'stdout is empty');
330 if (++count === 3) complete();
331 });
332 stderrChild.on('exit', exitHandler);
333 stderrChild.on('close', closeHandler);
334 });
335
336 function exitHandler (code, signal) {
337 // Sometimes the buffer limit is hit before the process closes successfully
338 // on both OSX/Windows
339 if (code === null) {
340 assert.equal(code, null, 'Exit code is null in exit handler');
341 assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in exit handler');
342 }
343 else {
344 assert.equal(code, 0, 'Exit code is 0 in exit handler');
345 assert.equal(signal, null, 'Signal is null in exit handler');
346 }
347 if (++count === 3) complete();
348 }
349
350 function closeHandler (code, signal) {
351 // Sometimes the buffer limit is hit before the process closes successfully
352 // on both OSX/Windows
353 if (code === null) {
354 assert.equal(code, null, 'Exit code is null in close handler');
355 assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in close handler');
356 }
357 else {
358 assert.equal(code, 0, 'Exit code is 0 in close handler');
359 assert.equal(signal, null, 'Signal is null in close handler');
360 }
361 if (++count === 3) complete();
362 }
363
364 function complete () {
365 stderrChild.off('exit', exitHandler);
366 stderrChild.off('close', closeHandler);
367 done();
368 }
369 };
370
371 exports.testChildExecFileKillSignal = function (assert, done) {
372 getScript('wait').then(script => {
373 execFile(script, {
374 killSignal: 'beepbeep',
375 timeout: 10
376 }, function (err, stdout, stderr) {
377 assert.equal(err.signal, 'beepbeep', 'correctly used custom killSignal');
378 done();
379 });
380 });
381 };
382
383 exports.testChildProperties = function (assert, done) {
384 getScript('check-env').then(script => {
385 let child = spawn(script, {
386 env: { CHILD_PROCESS_ENV_TEST: 'my-value-test' }
387 });
388
389 if (isWindows)
390 assert.ok(true, 'Windows environment does not have `pid`');
391 else
392 assert.ok(child.pid > 0, 'Child has a pid');
393 done();
394 });
395 };
396
397 exports.testChildStdinStreamLarge = function (assert, done) {
398 let REPEAT = 2000;
399 let allData = '';
400 // Use direct paths to more/cat, as we do not currently support calling non-files
401 // from subprocess.jsm
402 let child = spawn(CAT_PATH);
403
404 child.stdout.on('data', onData);
405 child.on('close', onClose);
406
407 for (let i = 0; i < REPEAT; i++)
408 emit(child.stdin, 'data', '12345\n');
409
410 emit(child.stdin, 'end');
411
412 function onData (data) {
413 allData += data;
414 }
415
416 function onClose (code, signal) {
417 child.stdout.off('data', onData);
418 child.off('close', onClose);
419 assert.equal(code, 0, 'exited succesfully');
420 assert.equal(signal, null, 'no kill signal given');
421 assert.equal(allData.replace(/\W/g, '').length, '12345'.length * REPEAT,
422 'all data processed from stdin');
423 done();
424 }
425 };
426
427 exports.testChildStdinStreamSmall = function (assert, done) {
428 let allData = '';
429 let child = spawn(CAT_PATH);
430 child.stdout.on('data', onData);
431 child.on('close', onClose);
432
433 emit(child.stdin, 'data', '12345');
434 emit(child.stdin, 'end');
435
436 function onData (data) {
437 allData += data;
438 }
439
440 function onClose (code, signal) {
441 child.stdout.off('data', onData);
442 child.off('close', onClose);
443 assert.equal(code, 0, 'exited succesfully');
444 assert.equal(signal, null, 'no kill signal given');
445 assert.equal(allData.trim(), '12345', 'all data processed from stdin');
446 done();
447 }
448 };
449 /*
450 * This tests failures when an error is thrown attempting to
451 * spawn the process, like an invalid command
452 */
453 exports.testChildEventsSpawningError= function (assert, done) {
454 let handlersCalled = 0;
455 let child = execFile('i-do-not-exist', (err, stdout, stderr) => {
456 assert.ok(err, 'error was passed into callback');
457 assert.equal(stdout, '', 'stdout is empty')
458 assert.equal(stderr, '', 'stderr is empty');
459 if (++handlersCalled === 3) complete();
460 });
461
462 child.on('error', handleError);
463 child.on('exit', handleExit);
464 child.on('close', handleClose);
465
466 function handleError (e) {
467 assert.ok(e, 'error passed into error handler');
468 if (++handlersCalled === 3) complete();
469 }
470
471 function handleClose (code, signal) {
472 assert.equal(code, -1,
473 'process was never spawned, therefore exit code is -1');
474 assert.equal(signal, null, 'signal should be null');
475 if (++handlersCalled === 3) complete();
476 }
477
478 function handleExit (code, signal) {
479 assert.fail('Close event should not be called on init failure');
480 }
481
482 function complete () {
483 child.off('error', handleError);
484 child.off('exit', handleExit);
485 child.off('close', handleClose);
486 done();
487 }
488 };
489
490 exports.testSpawnOptions = function (assert, done) {
491 let count = 0;
492 let envStdout = '';
493 let cwdStdout = '';
494 let checkEnv, checkPwd, envChild, cwdChild;
495 getScript('check-env').then(script => {
496 checkEnv = script;
497 return getScript('check-pwd');
498 }).then(script => {
499 checkPwd = script;
500
501 envChild = spawn(checkEnv, {
502 env: { CHILD_PROCESS_ENV_TEST: 'my-value-test' }
503 });
504 cwdChild = spawn(checkPwd, { cwd: PROFILE_DIR });
505
506 // Do these need to be unbound?
507 envChild.stdout.on('data', data => envStdout += data);
508 cwdChild.stdout.on('data', data => cwdStdout += data);
509
510 envChild.on('close', envClose);
511 cwdChild.on('close', cwdClose);
512 });
513
514 function envClose () {
515 assert.equal(envStdout.trim(), 'my-value-test', 'spawn correctly passed in ENV');
516 if (++count === 2) complete();
517 }
518
519 function cwdClose () {
520 // Check for PROFILE_DIR in the output because
521 // some systems resolve symbolic links, and on OSX
522 // /var -> /private/var
523 let isCorrectPath = ~cwdStdout.trim().indexOf(PROFILE_DIR);
524 assert.ok(isCorrectPath, 'spawn correctly passed in cwd');
525 if (++count === 2) complete();
526 }
527
528 function complete () {
529 envChild.off('close', envClose);
530 cwdChild.off('close', cwdClose);
531 done();
532 }
533 };
534
535 exports.testFork = function (assert) {
536 assert.throws(function () {
537 fork();
538 }, /not currently supported/, 'fork() correctly throws an unsupported error');
539 };
540
541 after(exports, cleanUp);
542
543 require("test").run(exports);
544
545 // Test disabled because of bug 979675
546 module.exports = {};

mercurial