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 |
|
7 | if (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 |
|
12 |
|
13 | const AVA_PATH = path.resolve(__dirname, '..');
|
14 |
|
15 | const workerPath = require.resolve('./worker/subprocess');
|
16 |
|
17 | module.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 |
|
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 | };
|