diff options
Diffstat (limited to 'node_modules/ava/lib/fork.js')
-rw-r--r-- | node_modules/ava/lib/fork.js | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/node_modules/ava/lib/fork.js b/node_modules/ava/lib/fork.js new file mode 100644 index 000000000..bf918d391 --- /dev/null +++ b/node_modules/ava/lib/fork.js @@ -0,0 +1,178 @@ +'use strict'; +const childProcess = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const Promise = require('bluebird'); +const debug = require('debug')('ava'); +const AvaError = require('./ava-error'); + +if (fs.realpathSync(__filename) !== __filename) { + 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`.'); +} + +let env = process.env; + +// Ensure NODE_PATH paths are absolute +if (env.NODE_PATH) { + env = Object.assign({}, env); + + env.NODE_PATH = env.NODE_PATH + .split(path.delimiter) + .map(x => path.resolve(x)) + .join(path.delimiter); +} + +// In case the test file imports a different AVA install, +// the presence of this variable allows it to require this one instead +env.AVA_PATH = path.resolve(__dirname, '..'); + +module.exports = (file, opts, execArgv) => { + opts = Object.assign({ + file, + baseDir: process.cwd(), + tty: process.stdout.isTTY ? { + columns: process.stdout.columns, + rows: process.stdout.rows + } : false + }, opts); + + const args = [JSON.stringify(opts), opts.color ? '--color' : '--no-color']; + + const ps = childProcess.fork(path.join(__dirname, 'test-worker.js'), args, { + cwd: opts.projectDir, + silent: true, + env, + execArgv: execArgv || process.execArgv + }); + + const relFile = path.relative('.', file); + + let exiting = false; + const send = (name, data) => { + if (!exiting) { + // This seems to trigger a Node bug which kills the AVA master process, at + // least while running AVA's tests. See + // <https://github.com/novemberborn/_ava-tap-crash> for more details. + ps.send({ + name: `ava-${name}`, + data, + ava: true + }); + } + }; + + const testResults = []; + let results; + + const promise = new Promise((resolve, reject) => { + ps.on('error', reject); + + // Emit `test` and `stats` events + ps.on('message', event => { + if (!event.ava) { + return; + } + + event.name = event.name.replace(/^ava-/, ''); + event.data.file = relFile; + + debug('ipc %s:\n%o', event.name, event.data); + + ps.emit(event.name, event.data); + }); + + ps.on('test', props => { + testResults.push(props); + }); + + ps.on('results', data => { + results = data; + data.tests = testResults; + send('teardown'); + }); + + ps.on('exit', (code, signal) => { + if (code > 0) { + return reject(new AvaError(`${relFile} exited with a non-zero exit code: ${code}`)); + } + + if (code === null && signal) { + return reject(new AvaError(`${relFile} exited due to ${signal}`)); + } + + if (results) { + resolve(results); + } else { + reject(new AvaError(`Test results were not received from ${relFile}`)); + } + }); + + ps.on('no-tests', data => { + send('teardown'); + + let message = `No tests found in ${relFile}`; + + if (!data.avaRequired) { + message += ', make sure to import "ava" at the top of your test file'; + } + + reject(new AvaError(message)); + }); + }); + + // Teardown finished, now exit + ps.on('teardown', () => { + send('exit'); + exiting = true; + }); + + // Uncaught exception in fork, need to exit + ps.on('uncaughtException', () => { + send('teardown'); + }); + + ps.stdout.on('data', data => { + ps.emit('stdout', data); + }); + + ps.stderr.on('data', data => { + ps.emit('stderr', data); + }); + + promise.on = function () { + ps.on.apply(ps, arguments); + return promise; + }; + + promise.send = (name, data) => { + send(name, data); + return promise; + }; + + promise.exit = () => { + send('init-exit'); + return promise; + }; + + // Send 'run' event only when fork is listening for it + let isReady = false; + + ps.on('stats', () => { + isReady = true; + }); + + promise.run = options => { + if (isReady) { + send('run', options); + return promise; + } + + ps.on('stats', () => { + send('run', options); + }); + + return promise; + }; + + return promise; +}; |