UNPKG

3.48 kBJavaScriptView Raw
1'use strict';
2const childProcess = require('child_process');
3const path = require('path');
4const fs = require('fs');
5const Promise = require('bluebird');
6const Emittery = require('emittery');
7
8if (fs.realpathSync(__filename) !== __filename) {
9 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`.');
10}
11
12// In case the test file imports a different AVA install,
13// the presence of this variable allows it to require this one instead
14const AVA_PATH = path.resolve(__dirname, '..');
15
16// Ensure NODE_PATH paths are absolute
17let NODE_PATH;
18
19if (process.env.NODE_PATH) {
20 NODE_PATH = process.env.NODE_PATH
21 .split(path.delimiter)
22 .map(x => path.resolve(x))
23 .join(path.delimiter);
24}
25
26const describeTTY = tty => ({
27 colorDepth: tty.getColorDepth ? tty.getColorDepth() : undefined,
28 columns: tty.columns || 80,
29 rows: tty.rows
30});
31
32const workerPath = require.resolve('./worker/subprocess');
33
34module.exports = (file, opts, execArgv) => {
35 let finished = false;
36
37 const emitter = new Emittery();
38 const emitStateChange = evt => {
39 if (!finished) {
40 emitter.emit('stateChange', Object.assign(evt, {testFile: file}));
41 }
42 };
43
44 opts = {
45 file,
46 baseDir: process.cwd(),
47 tty: {
48 stderr: process.stderr.isTTY ? describeTTY(process.stderr) : false,
49 stdout: process.stdout.isTTY ? describeTTY(process.stdout) : false
50 },
51 ...opts
52 };
53
54 const args = [opts.color ? '--color' : '--no-color'].concat(opts.workerArgv);
55
56 const subprocess = childProcess.fork(workerPath, args, {
57 cwd: opts.projectDir,
58 silent: true,
59 env: {NODE_ENV: 'test', ...process.env, ...opts.environmentVariables, AVA_PATH, NODE_PATH},
60 execArgv: execArgv || process.execArgv
61 });
62
63 subprocess.stdout.on('data', chunk => {
64 emitStateChange({type: 'worker-stdout', chunk});
65 });
66
67 subprocess.stderr.on('data', chunk => {
68 emitStateChange({type: 'worker-stderr', chunk});
69 });
70
71 let forcedExit = false;
72 const send = evt => {
73 if (subprocess.connected && !finished && !forcedExit) {
74 subprocess.send({ava: evt});
75 }
76 };
77
78 const promise = new Promise(resolve => {
79 const finish = () => {
80 finished = true;
81 resolve();
82 };
83
84 subprocess.on('message', message => {
85 if (!message.ava) {
86 return;
87 }
88
89 if (message.ava.type === 'ready-for-options') {
90 send({type: 'options', options: opts});
91 return;
92 }
93
94 if (message.ava.type === 'ping') {
95 send({type: 'pong'});
96 } else {
97 emitStateChange(message.ava);
98 }
99 });
100
101 subprocess.on('error', err => {
102 emitStateChange({type: 'worker-failed', err});
103 finish();
104 });
105
106 subprocess.on('exit', (code, signal) => {
107 if (forcedExit) {
108 emitStateChange({type: 'worker-finished', forcedExit});
109 } else if (code > 0) {
110 emitStateChange({type: 'worker-failed', nonZeroExitCode: code});
111 } else if (code === null && signal) {
112 emitStateChange({type: 'worker-failed', signal});
113 } else {
114 emitStateChange({type: 'worker-finished', forcedExit});
115 }
116
117 finish();
118 });
119 });
120
121 return {
122 exit() {
123 forcedExit = true;
124 subprocess.kill();
125 },
126
127 notifyOfPeerFailure() {
128 send({type: 'peer-failed'});
129 },
130
131 onStateChange(listener) {
132 return emitter.on('stateChange', listener);
133 },
134
135 file,
136 promise
137 };
138};