1 | 'use strict';
|
2 | const childProcess = require('child_process');
|
3 | const path = require('path');
|
4 | const fs = require('fs');
|
5 | const Emittery = require('emittery');
|
6 | const {controlFlow} = require('./ipc-flow-control');
|
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 | const workerPath = require.resolve('./worker/subprocess');
|
17 |
|
18 | const useAdvanced = process.versions.node >= '12.17.0';
|
19 |
|
20 | const serializeOptions = useAdvanced ?
|
21 | options => JSON.parse(JSON.stringify(options)) :
|
22 | options => options;
|
23 |
|
24 | module.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 | };
|