UNPKG

3.28 kBJavaScriptView Raw
1'use strict';
2const childProcess = require('child_process');
3const path = require('path');
4const fs = require('fs');
5const Emittery = require('emittery');
6const {controlFlow} = require('./ipc-flow-control');
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
16const workerPath = require.resolve('./worker/subprocess');
17
18const useAdvanced = process.versions.node >= '12.17.0';
19// FIXME: Fix this in api.js or cli.js.
20const serializeOptions = useAdvanced ?
21 options => JSON.parse(JSON.stringify(options)) : // Use JSON serialization to remove non-clonable values.
22 options => options;
23
24module.exports = (file, options, execArgv = process.execArgv) => {
25 let finished = false;
26
27 const emitter = new Emittery();
28 const emitStateChange = evt => {
29 if (!finished) {
30 emitter.emit('stateChange', Object.assign(evt, {testFile: file}));
31 }
32 };
33
34 options = {
35 file,
36 baseDir: process.cwd(),
37 ...options
38 };
39
40 const subprocess = childProcess.fork(workerPath, options.workerArgv, {
41 cwd: options.projectDir,
42 silent: true,
43 env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables, AVA_PATH},
44 execArgv,
45 serialization: useAdvanced ? 'advanced' : 'json'
46 });
47
48 subprocess.stdout.on('data', chunk => {
49 emitStateChange({type: 'worker-stdout', chunk});
50 });
51
52 subprocess.stderr.on('data', chunk => {
53 emitStateChange({type: 'worker-stderr', chunk});
54 });
55
56 const bufferedSend = controlFlow(subprocess);
57
58 let forcedExit = false;
59 const send = evt => {
60 if (!finished && !forcedExit) {
61 bufferedSend({ava: evt});
62 }
63 };
64
65 const promise = new Promise(resolve => {
66 const finish = () => {
67 finished = true;
68 resolve();
69 };
70
71 subprocess.on('message', message => {
72 if (!message.ava) {
73 return;
74 }
75
76 if (message.ava.type === 'ready-for-options') {
77 send({type: 'options', options: serializeOptions(options)});
78 return;
79 }
80
81 if (message.ava.type === 'ping') {
82 send({type: 'pong'});
83 } else {
84 emitStateChange(message.ava);
85 }
86 });
87
88 subprocess.on('error', err => {
89 emitStateChange({type: 'worker-failed', err});
90 finish();
91 });
92
93 subprocess.on('exit', (code, signal) => {
94 if (forcedExit) {
95 emitStateChange({type: 'worker-finished', forcedExit});
96 } else if (code > 0) {
97 emitStateChange({type: 'worker-failed', nonZeroExitCode: code});
98 } else if (code === null && signal) {
99 emitStateChange({type: 'worker-failed', signal});
100 } else {
101 emitStateChange({type: 'worker-finished', forcedExit});
102 }
103
104 finish();
105 });
106 });
107
108 return {
109 exit() {
110 forcedExit = true;
111 subprocess.kill();
112 },
113
114 notifyOfPeerFailure() {
115 send({type: 'peer-failed'});
116 },
117
118 onStateChange(listener) {
119 return emitter.on('stateChange', listener);
120 },
121
122 file,
123 promise
124 };
125};