fork.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. 'use strict';
  2. const childProcess = require('child_process');
  3. const path = require('path');
  4. const fs = require('fs');
  5. const Promise = require('bluebird');
  6. const Emittery = require('./emittery');
  7. if (fs.realpathSync(__filename) !== __filename) {
  8. console.warn('WARNING: `npm link ava` and the `--preserve-symlink` flag are incompatible. We have detected that AVA is linked via `npm link`, and that you are using either an early version of Node 6, or the `--preserve-symlink` flag. This breaks AVA. You should upgrade to Node 6.2.0+, avoid the `--preserve-symlink` flag, or avoid using `npm link ava`.');
  9. }
  10. const env = Object.assign({NODE_ENV: 'test'}, process.env);
  11. // Ensure NODE_PATH paths are absolute
  12. if (env.NODE_PATH) {
  13. env.NODE_PATH = env.NODE_PATH
  14. .split(path.delimiter)
  15. .map(x => path.resolve(x))
  16. .join(path.delimiter);
  17. }
  18. // In case the test file imports a different AVA install,
  19. // the presence of this variable allows it to require this one instead
  20. env.AVA_PATH = path.resolve(__dirname, '..');
  21. const workerPath = require.resolve('./worker/subprocess');
  22. module.exports = (file, opts, execArgv) => {
  23. let finished = false;
  24. const emitter = new Emittery();
  25. const emitStateChange = evt => {
  26. if (!finished) {
  27. emitter.emit('stateChange', Object.assign(evt, {testFile: file}));
  28. }
  29. };
  30. opts = Object.assign({
  31. file,
  32. baseDir: process.cwd(),
  33. tty: process.stdout.isTTY ? {
  34. columns: process.stdout.columns || 80,
  35. rows: process.stdout.rows
  36. } : false
  37. }, opts);
  38. const args = [JSON.stringify(opts), opts.color ? '--color' : '--no-color'].concat(opts.workerArgv);
  39. const subprocess = childProcess.fork(workerPath, args, {
  40. cwd: opts.projectDir,
  41. silent: true,
  42. env,
  43. execArgv: execArgv || process.execArgv
  44. });
  45. subprocess.stdout.on('data', chunk => {
  46. emitStateChange({type: 'worker-stdout', chunk});
  47. });
  48. subprocess.stderr.on('data', chunk => {
  49. emitStateChange({type: 'worker-stderr', chunk});
  50. });
  51. let forcedExit = false;
  52. const send = evt => {
  53. if (subprocess.connected && !finished && !forcedExit) {
  54. subprocess.send({ava: evt});
  55. }
  56. };
  57. const promise = new Promise(resolve => {
  58. const finish = () => {
  59. finished = true;
  60. resolve();
  61. };
  62. subprocess.on('message', message => {
  63. if (!message.ava) {
  64. return;
  65. }
  66. if (message.ava.type === 'ping') {
  67. send({type: 'pong'});
  68. } else {
  69. emitStateChange(message.ava);
  70. }
  71. });
  72. subprocess.on('error', err => {
  73. emitStateChange({type: 'worker-failed', err});
  74. finish();
  75. });
  76. subprocess.on('exit', (code, signal) => {
  77. if (forcedExit) {
  78. emitStateChange({type: 'worker-finished', forcedExit});
  79. } else if (code > 0) {
  80. emitStateChange({type: 'worker-failed', nonZeroExitCode: code});
  81. } else if (code === null && signal) {
  82. emitStateChange({type: 'worker-failed', signal});
  83. } else {
  84. emitStateChange({type: 'worker-finished', forcedExit});
  85. }
  86. finish();
  87. });
  88. });
  89. return {
  90. exit() {
  91. forcedExit = true;
  92. subprocess.kill();
  93. },
  94. notifyOfPeerFailure() {
  95. send({type: 'peer-failed'});
  96. },
  97. onStateChange(listener) {
  98. return emitter.on('stateChange', listener);
  99. },
  100. file,
  101. promise
  102. };
  103. };