UNPKG

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