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

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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/. */
     5 'use strict';
     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');
    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';
    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');
    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 };
    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 };
    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 };
    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     });
    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     }
    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     }
    99     child.on('exit', exitHandler);
   100     child.on('close', closeHandler);
   102     function complete () {
   103       child.off('exit', exitHandler);
   104       child.off('close', closeHandler);
   105       done();
   106     }
   107   });
   108 };
   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 };
   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 };
   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 };
   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     });
   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     }
   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     }
   176     child.on('exit', exitHandler);
   177     child.on('close', closeHandler);
   179     function complete () {
   180       child.off('exit', exitHandler);
   181       child.off('close', closeHandler);
   182       done();
   183     }
   184   });
   185 };
   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;
   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   });
   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   }
   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   }
   221   function complete () {
   222     stdoutChild.off('exit', exitHandler);
   223     stdoutChild.off('close', closeHandler);
   224     done();
   225   }
   226 };
   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   });
   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   }
   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   }
   256   function complete () {
   257     stderrChild.off('exit', exitHandler);
   258     stderrChild.off('close', closeHandler);
   259     done();
   260   }
   261 };
   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;
   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   });
   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   }
   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   }
   313   function complete () {
   314     stdoutChild.off('exit', exitHandler);
   315     stdoutChild.off('close', closeHandler);
   316     done();
   317   }
   318 };
   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   });
   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   }
   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   }
   364   function complete () {
   365     stderrChild.off('exit', exitHandler);
   366     stderrChild.off('close', closeHandler);
   367     done();
   368   }
   369 };
   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 };
   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     });
   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 };
   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);
   404   child.stdout.on('data', onData);
   405   child.on('close', onClose);
   407   for (let i = 0; i < REPEAT; i++)
   408     emit(child.stdin, 'data', '12345\n');
   410   emit(child.stdin, 'end');
   412   function onData (data) {
   413     allData += data;
   414   }
   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 };
   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);
   433   emit(child.stdin, 'data', '12345');
   434   emit(child.stdin, 'end');
   436   function onData (data) {
   437     allData += data;
   438   }
   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   });
   462   child.on('error', handleError);
   463   child.on('exit', handleExit);
   464   child.on('close', handleClose);
   466   function handleError (e) {
   467     assert.ok(e, 'error passed into error handler');
   468     if (++handlersCalled === 3) complete();
   469   }
   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   }
   478   function handleExit (code, signal) {
   479     assert.fail('Close event should not be called on init failure');
   480   }
   482   function complete () {
   483     child.off('error', handleError);
   484     child.off('exit', handleExit);
   485     child.off('close', handleClose);
   486     done();
   487   }
   488 };
   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;
   501     envChild = spawn(checkEnv, {
   502       env: { CHILD_PROCESS_ENV_TEST: 'my-value-test' }
   503     });
   504     cwdChild = spawn(checkPwd, { cwd: PROFILE_DIR });
   506     // Do these need to be unbound?
   507     envChild.stdout.on('data', data => envStdout += data);
   508     cwdChild.stdout.on('data', data => cwdStdout += data);
   510     envChild.on('close', envClose);
   511     cwdChild.on('close', cwdClose);
   512   });
   514   function envClose () {
   515     assert.equal(envStdout.trim(), 'my-value-test', 'spawn correctly passed in ENV');
   516     if (++count === 2) complete();
   517   }
   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   }
   528   function complete () {
   529     envChild.off('close', envClose);
   530     cwdChild.off('close', cwdClose);
   531     done();
   532   }
   533 };
   535 exports.testFork = function (assert) {
   536   assert.throws(function () {
   537     fork();
   538   }, /not currently supported/, 'fork() correctly throws an unsupported error');
   539 };
   541 after(exports, cleanUp);
   543 require("test").run(exports);
   545 // Test disabled because of bug 979675
   546 module.exports = {};

mercurial