mocha 9.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1015 -0
- package/LICENSE +22 -0
- package/README.md +70 -0
- package/assets/growl/error.png +0 -0
- package/assets/growl/ok.png +0 -0
- package/bin/_mocha +10 -0
- package/bin/mocha +142 -0
- package/browser-entry.js +216 -0
- package/index.js +3 -0
- package/lib/browser/growl.js +169 -0
- package/lib/browser/highlight-tags.js +39 -0
- package/lib/browser/parse-query.js +24 -0
- package/lib/browser/progress.js +123 -0
- package/lib/browser/template.html +20 -0
- package/lib/cli/cli.js +89 -0
- package/lib/cli/collect-files.js +92 -0
- package/lib/cli/commands.js +13 -0
- package/lib/cli/config.js +105 -0
- package/lib/cli/index.js +3 -0
- package/lib/cli/init.js +36 -0
- package/lib/cli/lookup-files.js +145 -0
- package/lib/cli/node-flags.js +85 -0
- package/lib/cli/one-and-dones.js +69 -0
- package/lib/cli/options.js +261 -0
- package/lib/cli/run-helpers.js +243 -0
- package/lib/cli/run-option-metadata.js +117 -0
- package/lib/cli/run.js +379 -0
- package/lib/cli/watch-run.js +380 -0
- package/lib/context.js +86 -0
- package/lib/errors.js +563 -0
- package/lib/hook.js +89 -0
- package/lib/interfaces/bdd.js +111 -0
- package/lib/interfaces/common.js +193 -0
- package/lib/interfaces/exports.js +60 -0
- package/lib/interfaces/index.js +6 -0
- package/lib/interfaces/qunit.js +98 -0
- package/lib/interfaces/tdd.js +106 -0
- package/lib/mocha.js +1374 -0
- package/lib/mocharc.json +10 -0
- package/lib/nodejs/buffered-worker-pool.js +172 -0
- package/lib/nodejs/esm-utils.js +109 -0
- package/lib/nodejs/file-unloader.js +15 -0
- package/lib/nodejs/growl.js +137 -0
- package/lib/nodejs/parallel-buffered-runner.js +433 -0
- package/lib/nodejs/reporters/parallel-buffered.js +165 -0
- package/lib/nodejs/serializer.js +412 -0
- package/lib/nodejs/worker.js +151 -0
- package/lib/pending.js +16 -0
- package/lib/plugin-loader.js +286 -0
- package/lib/reporters/base.js +537 -0
- package/lib/reporters/doc.js +95 -0
- package/lib/reporters/dot.js +81 -0
- package/lib/reporters/html.js +390 -0
- package/lib/reporters/index.js +19 -0
- package/lib/reporters/json-stream.js +92 -0
- package/lib/reporters/json.js +162 -0
- package/lib/reporters/landing.js +116 -0
- package/lib/reporters/list.js +78 -0
- package/lib/reporters/markdown.js +112 -0
- package/lib/reporters/min.js +52 -0
- package/lib/reporters/nyan.js +276 -0
- package/lib/reporters/progress.js +104 -0
- package/lib/reporters/spec.js +99 -0
- package/lib/reporters/tap.js +293 -0
- package/lib/reporters/xunit.js +217 -0
- package/lib/runnable.js +476 -0
- package/lib/runner.js +1269 -0
- package/lib/stats-collector.js +83 -0
- package/lib/suite.js +695 -0
- package/lib/test.js +113 -0
- package/lib/utils.js +641 -0
- package/mocha-es2018.js +19816 -0
- package/mocha.css +325 -0
- package/mocha.js +30844 -0
- package/mocha.js.map +1 -0
- package/package.json +200 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialization/deserialization classes and functions for communication between a main Mocha process and worker processes.
|
|
3
|
+
* @module serializer
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const {type} = require('../utils');
|
|
10
|
+
const {createInvalidArgumentTypeError} = require('../errors');
|
|
11
|
+
// this is not named `mocha:parallel:serializer` because it's noisy and it's
|
|
12
|
+
// helpful to be able to write `DEBUG=mocha:parallel*` and get everything else.
|
|
13
|
+
const debug = require('debug')('mocha:serializer');
|
|
14
|
+
|
|
15
|
+
const SERIALIZABLE_RESULT_NAME = 'SerializableWorkerResult';
|
|
16
|
+
const SERIALIZABLE_TYPES = new Set(['object', 'array', 'function', 'error']);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The serializable result of a test file run from a worker.
|
|
20
|
+
* @private
|
|
21
|
+
*/
|
|
22
|
+
class SerializableWorkerResult {
|
|
23
|
+
/**
|
|
24
|
+
* Creates instance props; of note, the `__type` prop.
|
|
25
|
+
*
|
|
26
|
+
* Note that the failure count is _redundant_ and could be derived from the
|
|
27
|
+
* list of events; but since we're already doing the work, might as well use
|
|
28
|
+
* it.
|
|
29
|
+
* @param {SerializableEvent[]} [events=[]] - Events to eventually serialize
|
|
30
|
+
* @param {number} [failureCount=0] - Failure count
|
|
31
|
+
*/
|
|
32
|
+
constructor(events = [], failureCount = 0) {
|
|
33
|
+
/**
|
|
34
|
+
* The number of failures in this run
|
|
35
|
+
* @type {number}
|
|
36
|
+
*/
|
|
37
|
+
this.failureCount = failureCount;
|
|
38
|
+
/**
|
|
39
|
+
* All relevant events emitted from the {@link Runner}.
|
|
40
|
+
* @type {SerializableEvent[]}
|
|
41
|
+
*/
|
|
42
|
+
this.events = events;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Symbol-like value needed to distinguish when attempting to deserialize
|
|
46
|
+
* this object (once it's been received over IPC).
|
|
47
|
+
* @type {Readonly<"SerializableWorkerResult">}
|
|
48
|
+
*/
|
|
49
|
+
Object.defineProperty(this, '__type', {
|
|
50
|
+
value: SERIALIZABLE_RESULT_NAME,
|
|
51
|
+
enumerable: true,
|
|
52
|
+
writable: false
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Instantiates a new {@link SerializableWorkerResult}.
|
|
58
|
+
* @param {...any} args - Args to constructor
|
|
59
|
+
* @returns {SerializableWorkerResult}
|
|
60
|
+
*/
|
|
61
|
+
static create(...args) {
|
|
62
|
+
return new SerializableWorkerResult(...args);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Serializes each {@link SerializableEvent} in our `events` prop;
|
|
67
|
+
* makes this object read-only.
|
|
68
|
+
* @returns {Readonly<SerializableWorkerResult>}
|
|
69
|
+
*/
|
|
70
|
+
serialize() {
|
|
71
|
+
this.events.forEach(event => {
|
|
72
|
+
event.serialize();
|
|
73
|
+
});
|
|
74
|
+
return Object.freeze(this);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Deserializes a {@link SerializedWorkerResult} into something reporters can
|
|
79
|
+
* use; calls {@link SerializableEvent.deserialize} on each item in its
|
|
80
|
+
* `events` prop.
|
|
81
|
+
* @param {SerializedWorkerResult} obj
|
|
82
|
+
* @returns {SerializedWorkerResult}
|
|
83
|
+
*/
|
|
84
|
+
static deserialize(obj) {
|
|
85
|
+
obj.events.forEach(event => {
|
|
86
|
+
SerializableEvent.deserialize(event);
|
|
87
|
+
});
|
|
88
|
+
return obj;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns `true` if this is a {@link SerializedWorkerResult} or a
|
|
93
|
+
* {@link SerializableWorkerResult}.
|
|
94
|
+
* @param {*} value - A value to check
|
|
95
|
+
* @returns {boolean} If true, it's deserializable
|
|
96
|
+
*/
|
|
97
|
+
static isSerializedWorkerResult(value) {
|
|
98
|
+
return (
|
|
99
|
+
value instanceof SerializableWorkerResult ||
|
|
100
|
+
(type(value) === 'object' && value.__type === SERIALIZABLE_RESULT_NAME)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Represents an event, emitted by a {@link Runner}, which is to be transmitted
|
|
107
|
+
* over IPC.
|
|
108
|
+
*
|
|
109
|
+
* Due to the contents of the event data, it's not possible to send them
|
|
110
|
+
* verbatim. When received by the main process--and handled by reporters--these
|
|
111
|
+
* objects are expected to contain {@link Runnable} instances. This class
|
|
112
|
+
* provides facilities to perform the translation via serialization and
|
|
113
|
+
* deserialization.
|
|
114
|
+
* @private
|
|
115
|
+
*/
|
|
116
|
+
class SerializableEvent {
|
|
117
|
+
/**
|
|
118
|
+
* Constructs a `SerializableEvent`, throwing if we receive unexpected data.
|
|
119
|
+
*
|
|
120
|
+
* Practically, events emitted from `Runner` have a minumum of zero (0)
|
|
121
|
+
* arguments-- (for example, {@link Runnable.constants.EVENT_RUN_BEGIN}) and a
|
|
122
|
+
* maximum of two (2) (for example,
|
|
123
|
+
* {@link Runnable.constants.EVENT_TEST_FAIL}, where the second argument is an
|
|
124
|
+
* `Error`). The first argument, if present, is a {@link Runnable}. This
|
|
125
|
+
* constructor's arguments adhere to this convention.
|
|
126
|
+
* @param {string} eventName - A non-empty event name.
|
|
127
|
+
* @param {any} [originalValue] - Some data. Corresponds to extra arguments
|
|
128
|
+
* passed to `EventEmitter#emit`.
|
|
129
|
+
* @param {Error} [originalError] - An error, if there's an error.
|
|
130
|
+
* @throws If `eventName` is empty, or `originalValue` is a non-object.
|
|
131
|
+
*/
|
|
132
|
+
constructor(eventName, originalValue, originalError) {
|
|
133
|
+
if (!eventName) {
|
|
134
|
+
throw createInvalidArgumentTypeError(
|
|
135
|
+
'Empty `eventName` string argument',
|
|
136
|
+
'eventName',
|
|
137
|
+
'string'
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The event name.
|
|
142
|
+
* @memberof SerializableEvent
|
|
143
|
+
*/
|
|
144
|
+
this.eventName = eventName;
|
|
145
|
+
const originalValueType = type(originalValue);
|
|
146
|
+
if (originalValueType !== 'object' && originalValueType !== 'undefined') {
|
|
147
|
+
throw createInvalidArgumentTypeError(
|
|
148
|
+
`Expected object but received ${originalValueType}`,
|
|
149
|
+
'originalValue',
|
|
150
|
+
'object'
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* An error, if present.
|
|
155
|
+
* @memberof SerializableEvent
|
|
156
|
+
*/
|
|
157
|
+
Object.defineProperty(this, 'originalError', {
|
|
158
|
+
value: originalError,
|
|
159
|
+
enumerable: false
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The raw value.
|
|
164
|
+
*
|
|
165
|
+
* We don't want this value sent via IPC; making it non-enumerable will do that.
|
|
166
|
+
*
|
|
167
|
+
* @memberof SerializableEvent
|
|
168
|
+
*/
|
|
169
|
+
Object.defineProperty(this, 'originalValue', {
|
|
170
|
+
value: originalValue,
|
|
171
|
+
enumerable: false
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* In case you hated using `new` (I do).
|
|
177
|
+
*
|
|
178
|
+
* @param {...any} args - Args for {@link SerializableEvent#constructor}.
|
|
179
|
+
* @returns {SerializableEvent} A new `SerializableEvent`
|
|
180
|
+
*/
|
|
181
|
+
static create(...args) {
|
|
182
|
+
return new SerializableEvent(...args);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Used internally by {@link SerializableEvent#serialize}.
|
|
187
|
+
* @ignore
|
|
188
|
+
* @param {Array<object|string>} pairs - List of parent/key tuples to process; modified in-place. This JSDoc type is an approximation
|
|
189
|
+
* @param {object} parent - Some parent object
|
|
190
|
+
* @param {string} key - Key to inspect
|
|
191
|
+
* @param {WeakSet<Object>} seenObjects - For avoiding circular references
|
|
192
|
+
*/
|
|
193
|
+
static _serialize(pairs, parent, key, seenObjects) {
|
|
194
|
+
let value = parent[key];
|
|
195
|
+
if (seenObjects.has(value)) {
|
|
196
|
+
parent[key] = Object.create(null);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
let _type = type(value);
|
|
200
|
+
if (_type === 'error') {
|
|
201
|
+
// we need to reference the stack prop b/c it's lazily-loaded.
|
|
202
|
+
// `__type` is necessary for deserialization to create an `Error` later.
|
|
203
|
+
// `message` is apparently not enumerable, so we must handle it specifically.
|
|
204
|
+
value = Object.assign(Object.create(null), value, {
|
|
205
|
+
stack: value.stack,
|
|
206
|
+
message: value.message,
|
|
207
|
+
__type: 'Error'
|
|
208
|
+
});
|
|
209
|
+
parent[key] = value;
|
|
210
|
+
// after this, set the result of type(value) to be `object`, and we'll throw
|
|
211
|
+
// whatever other junk is in the original error into the new `value`.
|
|
212
|
+
_type = 'object';
|
|
213
|
+
}
|
|
214
|
+
switch (_type) {
|
|
215
|
+
case 'object':
|
|
216
|
+
if (type(value.serialize) === 'function') {
|
|
217
|
+
parent[key] = value.serialize();
|
|
218
|
+
} else {
|
|
219
|
+
// by adding props to the `pairs` array, we will process it further
|
|
220
|
+
pairs.push(
|
|
221
|
+
...Object.keys(value)
|
|
222
|
+
.filter(key => SERIALIZABLE_TYPES.has(type(value[key])))
|
|
223
|
+
.map(key => [value, key])
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
case 'function':
|
|
228
|
+
// we _may_ want to dig in to functions for some assertion libraries
|
|
229
|
+
// that might put a usable property on a function.
|
|
230
|
+
// for now, just zap it.
|
|
231
|
+
delete parent[key];
|
|
232
|
+
break;
|
|
233
|
+
case 'array':
|
|
234
|
+
pairs.push(
|
|
235
|
+
...value
|
|
236
|
+
.filter(value => SERIALIZABLE_TYPES.has(type(value)))
|
|
237
|
+
.map((value, index) => [value, index])
|
|
238
|
+
);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Modifies this object *in place* (for theoretical memory consumption &
|
|
245
|
+
* performance reasons); serializes `SerializableEvent#originalValue` (placing
|
|
246
|
+
* the result in `SerializableEvent#data`) and `SerializableEvent#error`.
|
|
247
|
+
* Freezes this object. The result is an object that can be transmitted over
|
|
248
|
+
* IPC.
|
|
249
|
+
* If this quickly becomes unmaintainable, we will want to move towards immutable
|
|
250
|
+
* objects post-haste.
|
|
251
|
+
*/
|
|
252
|
+
serialize() {
|
|
253
|
+
// given a parent object and a key, inspect the value and decide whether
|
|
254
|
+
// to replace it, remove it, or add it to our `pairs` array to further process.
|
|
255
|
+
// this is recursion in loop form.
|
|
256
|
+
const originalValue = this.originalValue;
|
|
257
|
+
const result = Object.assign(Object.create(null), {
|
|
258
|
+
data:
|
|
259
|
+
type(originalValue) === 'object' &&
|
|
260
|
+
type(originalValue.serialize) === 'function'
|
|
261
|
+
? originalValue.serialize()
|
|
262
|
+
: originalValue,
|
|
263
|
+
error: this.originalError
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const pairs = Object.keys(result).map(key => [result, key]);
|
|
267
|
+
const seenObjects = new WeakSet();
|
|
268
|
+
|
|
269
|
+
let pair;
|
|
270
|
+
while ((pair = pairs.shift())) {
|
|
271
|
+
SerializableEvent._serialize(pairs, ...pair, seenObjects);
|
|
272
|
+
seenObjects.add(pair[0]);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
this.data = result.data;
|
|
276
|
+
this.error = result.error;
|
|
277
|
+
|
|
278
|
+
return Object.freeze(this);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Used internally by {@link SerializableEvent.deserialize}; creates an `Error`
|
|
283
|
+
* from an `Error`-like (serialized) object
|
|
284
|
+
* @ignore
|
|
285
|
+
* @param {Object} value - An Error-like value
|
|
286
|
+
* @returns {Error} Real error
|
|
287
|
+
*/
|
|
288
|
+
static _deserializeError(value) {
|
|
289
|
+
const error = new Error(value.message);
|
|
290
|
+
error.stack = value.stack;
|
|
291
|
+
Object.assign(error, value);
|
|
292
|
+
delete error.__type;
|
|
293
|
+
return error;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Used internally by {@link SerializableEvent.deserialize}; recursively
|
|
298
|
+
* deserializes an object in-place.
|
|
299
|
+
* @param {object|Array} parent - Some object or array
|
|
300
|
+
* @param {string|number} key - Some prop name or array index within `parent`
|
|
301
|
+
*/
|
|
302
|
+
static _deserializeObject(parent, key) {
|
|
303
|
+
if (key === '__proto__') {
|
|
304
|
+
delete parent[key];
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const value = parent[key];
|
|
308
|
+
// keys beginning with `$$` are converted into functions returning the value
|
|
309
|
+
// and renamed, stripping the `$$` prefix.
|
|
310
|
+
// functions defined this way cannot be array members!
|
|
311
|
+
if (type(key) === 'string' && key.startsWith('$$')) {
|
|
312
|
+
const newKey = key.slice(2);
|
|
313
|
+
parent[newKey] = () => value;
|
|
314
|
+
delete parent[key];
|
|
315
|
+
key = newKey;
|
|
316
|
+
}
|
|
317
|
+
if (type(value) === 'array') {
|
|
318
|
+
value.forEach((_, idx) => {
|
|
319
|
+
SerializableEvent._deserializeObject(value, idx);
|
|
320
|
+
});
|
|
321
|
+
} else if (type(value) === 'object') {
|
|
322
|
+
if (value.__type === 'Error') {
|
|
323
|
+
parent[key] = SerializableEvent._deserializeError(value);
|
|
324
|
+
} else {
|
|
325
|
+
Object.keys(value).forEach(key => {
|
|
326
|
+
SerializableEvent._deserializeObject(value, key);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Deserialize value returned from a worker into something more useful.
|
|
334
|
+
* Does not return the same object.
|
|
335
|
+
* @todo do this in a loop instead of with recursion (if necessary)
|
|
336
|
+
* @param {SerializedEvent} obj - Object returned from worker
|
|
337
|
+
* @returns {SerializedEvent} Deserialized result
|
|
338
|
+
*/
|
|
339
|
+
static deserialize(obj) {
|
|
340
|
+
if (!obj) {
|
|
341
|
+
throw createInvalidArgumentTypeError('Expected value', obj);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
obj = Object.assign(Object.create(null), obj);
|
|
345
|
+
|
|
346
|
+
if (obj.data) {
|
|
347
|
+
Object.keys(obj.data).forEach(key => {
|
|
348
|
+
SerializableEvent._deserializeObject(obj.data, key);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (obj.error) {
|
|
353
|
+
obj.error = SerializableEvent._deserializeError(obj.error);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return obj;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* "Serializes" a value for transmission over IPC as a message.
|
|
362
|
+
*
|
|
363
|
+
* If value is an object and has a `serialize()` method, call that method; otherwise return the object and hope for the best.
|
|
364
|
+
*
|
|
365
|
+
* @param {*} [value] - A value to serialize
|
|
366
|
+
*/
|
|
367
|
+
exports.serialize = function serialize(value) {
|
|
368
|
+
const result =
|
|
369
|
+
type(value) === 'object' && type(value.serialize) === 'function'
|
|
370
|
+
? value.serialize()
|
|
371
|
+
: value;
|
|
372
|
+
debug('serialized: %O', result);
|
|
373
|
+
return result;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* "Deserializes" a "message" received over IPC.
|
|
378
|
+
*
|
|
379
|
+
* This could be expanded with other objects that need deserialization,
|
|
380
|
+
* but at present time we only care about {@link SerializableWorkerResult} objects.
|
|
381
|
+
*
|
|
382
|
+
* @param {*} [value] - A "message" to deserialize
|
|
383
|
+
*/
|
|
384
|
+
exports.deserialize = function deserialize(value) {
|
|
385
|
+
const result = SerializableWorkerResult.isSerializedWorkerResult(value)
|
|
386
|
+
? SerializableWorkerResult.deserialize(value)
|
|
387
|
+
: value;
|
|
388
|
+
debug('deserialized: %O', result);
|
|
389
|
+
return result;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
exports.SerializableEvent = SerializableEvent;
|
|
393
|
+
exports.SerializableWorkerResult = SerializableWorkerResult;
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* The result of calling `SerializableEvent.serialize`, as received
|
|
397
|
+
* by the deserializer.
|
|
398
|
+
* @private
|
|
399
|
+
* @typedef {Object} SerializedEvent
|
|
400
|
+
* @property {object?} data - Optional serialized data
|
|
401
|
+
* @property {object?} error - Optional serialized `Error`
|
|
402
|
+
*/
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* The result of calling `SerializableWorkerResult.serialize` as received
|
|
406
|
+
* by the deserializer.
|
|
407
|
+
* @private
|
|
408
|
+
* @typedef {Object} SerializedWorkerResult
|
|
409
|
+
* @property {number} failureCount - Number of failures
|
|
410
|
+
* @property {SerializedEvent[]} events - Serialized events
|
|
411
|
+
* @property {"SerializedWorkerResult"} __type - Symbol-like to denote the type of object this is
|
|
412
|
+
*/
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A worker process. Consumes {@link module:reporters/parallel-buffered} reporter.
|
|
3
|
+
* @module worker
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
createInvalidArgumentTypeError,
|
|
11
|
+
createInvalidArgumentValueError
|
|
12
|
+
} = require('../errors');
|
|
13
|
+
const workerpool = require('workerpool');
|
|
14
|
+
const Mocha = require('../mocha');
|
|
15
|
+
const {handleRequires, validateLegacyPlugin} = require('../cli/run-helpers');
|
|
16
|
+
const d = require('debug');
|
|
17
|
+
const debug = d.debug(`mocha:parallel:worker:${process.pid}`);
|
|
18
|
+
const isDebugEnabled = d.enabled(`mocha:parallel:worker:${process.pid}`);
|
|
19
|
+
const {serialize} = require('./serializer');
|
|
20
|
+
const {setInterval, clearInterval} = global;
|
|
21
|
+
|
|
22
|
+
let rootHooks;
|
|
23
|
+
|
|
24
|
+
if (workerpool.isMainThread) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
'This script is intended to be run as a worker (by the `workerpool` package).'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initializes some stuff on the first call to {@link run}.
|
|
32
|
+
*
|
|
33
|
+
* Handles `--require` and `--ui`. Does _not_ handle `--reporter`,
|
|
34
|
+
* as only the `Buffered` reporter is used.
|
|
35
|
+
*
|
|
36
|
+
* **This function only runs once per worker**; it overwrites itself with a no-op
|
|
37
|
+
* before returning.
|
|
38
|
+
*
|
|
39
|
+
* @param {Options} argv - Command-line options
|
|
40
|
+
*/
|
|
41
|
+
let bootstrap = async argv => {
|
|
42
|
+
// globalSetup and globalTeardown do not run in workers
|
|
43
|
+
const plugins = await handleRequires(argv.require, {
|
|
44
|
+
ignoredPlugins: ['mochaGlobalSetup', 'mochaGlobalTeardown']
|
|
45
|
+
});
|
|
46
|
+
validateLegacyPlugin(argv, 'ui', Mocha.interfaces);
|
|
47
|
+
|
|
48
|
+
rootHooks = plugins.rootHooks;
|
|
49
|
+
bootstrap = () => {};
|
|
50
|
+
debug('bootstrap(): finished with args: %O', argv);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Runs a single test file in a worker thread.
|
|
55
|
+
* @param {string} filepath - Filepath of test file
|
|
56
|
+
* @param {string} [serializedOptions] - **Serialized** options. This string will be eval'd!
|
|
57
|
+
* @see https://npm.im/serialize-javascript
|
|
58
|
+
* @returns {Promise<{failures: number, events: BufferedEvent[]}>} - Test
|
|
59
|
+
* failure count and list of events.
|
|
60
|
+
*/
|
|
61
|
+
async function run(filepath, serializedOptions = '{}') {
|
|
62
|
+
if (!filepath) {
|
|
63
|
+
throw createInvalidArgumentTypeError(
|
|
64
|
+
'Expected a non-empty "filepath" argument',
|
|
65
|
+
'file',
|
|
66
|
+
'string'
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
debug('run(): running test file %s', filepath);
|
|
71
|
+
|
|
72
|
+
if (typeof serializedOptions !== 'string') {
|
|
73
|
+
throw createInvalidArgumentTypeError(
|
|
74
|
+
'run() expects second parameter to be a string which was serialized by the `serialize-javascript` module',
|
|
75
|
+
'serializedOptions',
|
|
76
|
+
'string'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
let argv;
|
|
80
|
+
try {
|
|
81
|
+
// eslint-disable-next-line no-eval
|
|
82
|
+
argv = eval('(' + serializedOptions + ')');
|
|
83
|
+
} catch (err) {
|
|
84
|
+
throw createInvalidArgumentValueError(
|
|
85
|
+
'run() was unable to deserialize the options',
|
|
86
|
+
'serializedOptions',
|
|
87
|
+
serializedOptions
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const opts = Object.assign({ui: 'bdd'}, argv, {
|
|
92
|
+
// if this was true, it would cause infinite recursion.
|
|
93
|
+
parallel: false,
|
|
94
|
+
// this doesn't work in parallel mode
|
|
95
|
+
forbidOnly: true,
|
|
96
|
+
// it's useful for a Mocha instance to know if it's running in a worker process.
|
|
97
|
+
isWorker: true
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await bootstrap(opts);
|
|
101
|
+
|
|
102
|
+
opts.rootHooks = rootHooks;
|
|
103
|
+
|
|
104
|
+
const mocha = new Mocha(opts).addFile(filepath);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
await mocha.loadFilesAsync();
|
|
108
|
+
} catch (err) {
|
|
109
|
+
debug('run(): could not load file %s: %s', filepath, err);
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
let debugInterval;
|
|
115
|
+
/* istanbul ignore next */
|
|
116
|
+
if (isDebugEnabled) {
|
|
117
|
+
debugInterval = setInterval(() => {
|
|
118
|
+
debug('run(): still running %s...', filepath);
|
|
119
|
+
}, 5000).unref();
|
|
120
|
+
}
|
|
121
|
+
mocha.run(result => {
|
|
122
|
+
// Runner adds these; if we don't remove them, we'll get a leak.
|
|
123
|
+
process.removeAllListeners('uncaughtException');
|
|
124
|
+
process.removeAllListeners('unhandledRejection');
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const serialized = serialize(result);
|
|
128
|
+
debug(
|
|
129
|
+
'run(): completed run with %d test failures; returning to main process',
|
|
130
|
+
typeof result.failures === 'number' ? result.failures : 0
|
|
131
|
+
);
|
|
132
|
+
resolve(serialized);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
// TODO: figure out exactly what the sad path looks like here.
|
|
135
|
+
// rejection should only happen if an error is "unrecoverable"
|
|
136
|
+
debug('run(): serialization failed; rejecting: %O', err);
|
|
137
|
+
reject(err);
|
|
138
|
+
} finally {
|
|
139
|
+
clearInterval(debugInterval);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// this registers the `run` function.
|
|
146
|
+
workerpool.worker({run});
|
|
147
|
+
|
|
148
|
+
debug('started worker process');
|
|
149
|
+
|
|
150
|
+
// for testing
|
|
151
|
+
exports.run = run;
|
package/lib/pending.js
ADDED