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 |
|
8 | if (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 |
|
13 |
|
14 | const AVA_PATH = path.resolve(__dirname, '..');
|
15 |
|
16 |
|
17 | let NODE_PATH;
|
18 |
|
19 | if (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 |
|
26 | const describeTTY = tty => ({
|
27 | colorDepth: tty.getColorDepth ? tty.getColorDepth() : undefined,
|
28 | columns: tty.columns || 80,
|
29 | rows: tty.rows
|
30 | });
|
31 |
|
32 | const workerPath = require.resolve('./worker/subprocess');
|
33 |
|
34 | module.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 | };
|