/
worker.js
136 lines (118 loc) · 3.91 KB
/
worker.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
'use strict';
const {
createInvalidArgumentTypeError,
createInvalidArgumentValueError
} = require('./errors');
const workerpool = require('workerpool');
const Mocha = require('./mocha');
const {handleRequires, validatePlugin} = require('./cli/run-helpers');
const debug = require('debug')(`mocha:parallel:worker:${process.pid}`);
const {serialize} = require('./serializer');
const {setInterval, clearInterval} = global;
const BUFFERED_REPORTER_PATH = require.resolve('./reporters/buffered');
let rootHooks;
if (workerpool.isMainThread) {
throw new Error(
'This script is intended to be run as a worker (by the `workerpool` package).'
);
}
/**
* Initializes some stuff on the first call to {@link run}.
*
* Handles `--require` and `--ui`. Does _not_ handle `--reporter`,
* as only the `Buffered` reporter is used.
*
* **This function only runs once per worker**; it overwrites itself with a no-op
* before returning.
*
* @param {Options} argv - Command-line options
*/
let bootstrap = async argv => {
handleRequires(argv.require);
// const rawRootHooks = handleRequires(argv.require);
// rootHooks = await loadRootHooks(rawRootHooks);
validatePlugin(argv, 'ui', Mocha.interfaces);
bootstrap = () => {};
debug('bootstrap(): finished with args: %O', argv);
};
/**
* Runs a single test file in a worker thread.
* @param {string} filepath - Filepath of test file
* @param {string} [serializedOptions] - **Serialized** options. This string will be eval'd!
* @see https://npm.im/serialize-javascript
* @returns {Promise<{failures: number, events: BufferedEvent[]}>} - Test
* failure count and list of events.
*/
async function run(filepath, serializedOptions = '{}') {
if (!filepath) {
throw createInvalidArgumentTypeError(
'Expected a non-empty "filepath" argument',
'file',
'string'
);
}
debug('run(): running test file %s', filepath);
if (typeof serializedOptions !== 'string') {
throw createInvalidArgumentTypeError(
'run() expects second parameter to be a string which was serialized by the `serialize-javascript` module',
'serializedOptions',
'string'
);
}
let argv;
try {
// eslint-disable-next-line no-eval
argv = eval('(' + serializedOptions + ')');
} catch (err) {
throw createInvalidArgumentValueError(
'run() was unable to deserialize the options',
'serializedOptions',
serializedOptions
);
}
debug('run(): deserialized options to %O', argv);
const opts = Object.assign({ui: 'bdd'}, argv, {
// workers only use the `Buffered` reporter.
reporter: BUFFERED_REPORTER_PATH,
// if this was true, it would cause infinite recursion.
parallel: false
});
await bootstrap(opts);
opts.rootHooks = rootHooks;
const mocha = new Mocha(opts).addFile(filepath);
try {
await mocha.loadFilesAsync();
} catch (err) {
debug('run(): could not load file %s: %s', filepath, err);
throw err;
}
return new Promise((resolve, reject) => {
const t = setInterval(() => {
debug('run(): still running %s...', filepath);
}, 5000).unref();
mocha.run(result => {
// Runner adds these; if we don't remove them, we'll get a leak.
process.removeAllListeners('uncaughtException');
try {
const serialized = serialize(result);
debug(
'run(): completed run with %d test failures; returning to main process',
typeof result.failures === 'number' ? result.failures : 0
);
resolve(serialized);
} catch (err) {
// TODO: figure out exactly what the sad path looks like here.
// rejection should only happen if an error is "unrecoverable"
debug('run(): serialization failed; rejecting: %O', err);
reject(err);
} finally {
clearInterval(t);
}
});
});
}
// this registers the `run` function.
workerpool.worker({run});
debug('started worker process');
// for testing
exports.run = run;