mocha 7.2.0 → 8.1.1
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 +116 -0
- package/bin/mocha +17 -2
- package/browser-entry.js +26 -9
- package/lib/browser/growl.js +2 -1
- package/lib/browser/highlight-tags.js +39 -0
- package/lib/browser/parse-query.js +24 -0
- package/lib/browser/progress.js +4 -0
- package/lib/browser/template.html +7 -5
- package/lib/cli/cli.js +2 -2
- package/lib/cli/collect-files.js +15 -9
- package/lib/cli/config.js +0 -1
- package/lib/cli/init.js +1 -2
- package/lib/cli/lookup-files.js +145 -0
- package/lib/cli/node-flags.js +2 -2
- package/lib/cli/options.js +11 -87
- package/lib/cli/run-helpers.js +54 -16
- package/lib/cli/run-option-metadata.js +4 -2
- package/lib/cli/run.js +61 -14
- package/lib/cli/watch-run.js +211 -51
- package/lib/context.js +0 -15
- package/lib/errors.js +26 -3
- package/lib/esm-utils.js +11 -6
- package/lib/hook.js +24 -0
- package/lib/interfaces/common.js +19 -11
- package/lib/mocha.js +137 -121
- package/lib/mocharc.json +0 -1
- package/lib/nodejs/buffered-worker-pool.js +174 -0
- package/lib/{growl.js → nodejs/growl.js} +3 -2
- package/lib/nodejs/parallel-buffered-runner.js +295 -0
- package/lib/nodejs/reporters/parallel-buffered.js +133 -0
- package/lib/nodejs/serializer.js +404 -0
- package/lib/nodejs/worker.js +154 -0
- package/lib/pending.js +4 -0
- package/lib/reporters/base.js +25 -12
- package/lib/reporters/landing.js +3 -3
- package/lib/reporters/tap.js +1 -2
- package/lib/reporters/xunit.js +3 -2
- package/lib/runnable.js +18 -30
- package/lib/runner.js +58 -64
- package/lib/suite.js +32 -24
- package/lib/test.js +28 -1
- package/lib/utils.js +19 -206
- package/mocha.js +25522 -18248
- package/mocha.js.map +1 -0
- package/package.json +52 -42
- package/lib/browser/tty.js +0 -13
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A test Runner that uses a {@link module:buffered-worker-pool}.
|
|
3
|
+
* @module parallel-buffered-runner
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const allSettled = require('promise.allsettled');
|
|
10
|
+
const Runner = require('../runner');
|
|
11
|
+
const {EVENT_RUN_BEGIN, EVENT_RUN_END} = Runner.constants;
|
|
12
|
+
const debug = require('debug')('mocha:parallel:parallel-buffered-runner');
|
|
13
|
+
const {BufferedWorkerPool} = require('./buffered-worker-pool');
|
|
14
|
+
const {setInterval, clearInterval} = global;
|
|
15
|
+
const {createMap} = require('../utils');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Outputs a debug statement with worker stats
|
|
19
|
+
* @param {BufferedWorkerPool} pool - Worker pool
|
|
20
|
+
*/
|
|
21
|
+
/* istanbul ignore next */
|
|
22
|
+
const debugStats = pool => {
|
|
23
|
+
const {totalWorkers, busyWorkers, idleWorkers, pendingTasks} = pool.stats();
|
|
24
|
+
debug(
|
|
25
|
+
'%d/%d busy workers; %d idle; %d tasks queued',
|
|
26
|
+
busyWorkers,
|
|
27
|
+
totalWorkers,
|
|
28
|
+
idleWorkers,
|
|
29
|
+
pendingTasks
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The interval at which we will display stats for worker processes in debug mode
|
|
35
|
+
*/
|
|
36
|
+
const DEBUG_STATS_INTERVAL = 5000;
|
|
37
|
+
|
|
38
|
+
const ABORTED = 'ABORTED';
|
|
39
|
+
const IDLE = 'IDLE';
|
|
40
|
+
const ABORTING = 'ABORTING';
|
|
41
|
+
const RUNNING = 'RUNNING';
|
|
42
|
+
const BAILING = 'BAILING';
|
|
43
|
+
const BAILED = 'BAILED';
|
|
44
|
+
const COMPLETE = 'COMPLETE';
|
|
45
|
+
|
|
46
|
+
const states = createMap({
|
|
47
|
+
[IDLE]: new Set([RUNNING, ABORTING]),
|
|
48
|
+
[RUNNING]: new Set([COMPLETE, BAILING, ABORTING]),
|
|
49
|
+
[COMPLETE]: new Set(),
|
|
50
|
+
[ABORTED]: new Set(),
|
|
51
|
+
[ABORTING]: new Set([ABORTED]),
|
|
52
|
+
[BAILING]: new Set([BAILED, ABORTING]),
|
|
53
|
+
[BAILED]: new Set([COMPLETE, ABORTING])
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* This `Runner` delegates tests runs to worker threads. Does not execute any
|
|
58
|
+
* {@link Runnable}s by itself!
|
|
59
|
+
* @private
|
|
60
|
+
*/
|
|
61
|
+
class ParallelBufferedRunner extends Runner {
|
|
62
|
+
constructor(...args) {
|
|
63
|
+
super(...args);
|
|
64
|
+
|
|
65
|
+
let state = IDLE;
|
|
66
|
+
Object.defineProperty(this, '_state', {
|
|
67
|
+
get() {
|
|
68
|
+
return state;
|
|
69
|
+
},
|
|
70
|
+
set(newState) {
|
|
71
|
+
if (states[state].has(newState)) {
|
|
72
|
+
state = newState;
|
|
73
|
+
} else {
|
|
74
|
+
throw new Error(`invalid state transition: ${state} => ${newState}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
this.once(Runner.constants.EVENT_RUN_END, () => {
|
|
80
|
+
this._state = COMPLETE;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns a mapping function to enqueue a file in the worker pool and return results of its execution.
|
|
86
|
+
* @param {BufferedWorkerPool} pool - Worker pool
|
|
87
|
+
* @param {Options} options - Mocha options
|
|
88
|
+
* @returns {FileRunner} Mapping function
|
|
89
|
+
*/
|
|
90
|
+
_createFileRunner(pool, options) {
|
|
91
|
+
return async file => {
|
|
92
|
+
debug('run(): enqueueing test file %s', file);
|
|
93
|
+
try {
|
|
94
|
+
const {failureCount, events} = await pool.run(file, options);
|
|
95
|
+
if (this._state === BAILED) {
|
|
96
|
+
// short-circuit after a graceful bail. if this happens,
|
|
97
|
+
// some other worker has bailed.
|
|
98
|
+
// TODO: determine if this is the desired behavior, or if we
|
|
99
|
+
// should report the events of this run anyway.
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
debug(
|
|
103
|
+
'run(): completed run of file %s; %d failures / %d events',
|
|
104
|
+
file,
|
|
105
|
+
failureCount,
|
|
106
|
+
events.length
|
|
107
|
+
);
|
|
108
|
+
this.failures += failureCount; // can this ever be non-numeric?
|
|
109
|
+
let event = events.shift();
|
|
110
|
+
while (event) {
|
|
111
|
+
this.emit(event.eventName, event.data, event.error);
|
|
112
|
+
if (
|
|
113
|
+
this._state !== BAILING &&
|
|
114
|
+
event.data &&
|
|
115
|
+
event.data._bail &&
|
|
116
|
+
(failureCount || event.error)
|
|
117
|
+
) {
|
|
118
|
+
debug('run(): nonzero failure count & found bail flag');
|
|
119
|
+
// we need to let the events complete for this file, as the worker
|
|
120
|
+
// should run any cleanup hooks
|
|
121
|
+
this._state = BAILING;
|
|
122
|
+
}
|
|
123
|
+
event = events.shift();
|
|
124
|
+
}
|
|
125
|
+
if (this._state === BAILING) {
|
|
126
|
+
debug('run(): terminating pool due to "bail" flag');
|
|
127
|
+
this._state = BAILED;
|
|
128
|
+
await pool.terminate();
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
if (this._state === BAILED || this._state === ABORTING) {
|
|
132
|
+
debug(
|
|
133
|
+
'run(): worker pool terminated with intent; skipping file %s',
|
|
134
|
+
file
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
// this is an uncaught exception
|
|
138
|
+
debug('run(): encountered uncaught exception: %O', err);
|
|
139
|
+
if (this.allowUncaught) {
|
|
140
|
+
// still have to clean up
|
|
141
|
+
this._state = ABORTING;
|
|
142
|
+
await pool.terminate(true);
|
|
143
|
+
}
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
} finally {
|
|
147
|
+
debug('run(): done running file %s', file);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Listen on `Process.SIGINT`; terminate pool if caught.
|
|
154
|
+
* Returns the listener for later call to `process.removeListener()`.
|
|
155
|
+
* @param {BufferedWorkerPool} pool - Worker pool
|
|
156
|
+
* @returns {SigIntListener} Listener
|
|
157
|
+
*/
|
|
158
|
+
_bindSigIntListener(pool) {
|
|
159
|
+
const sigIntListener = async () => {
|
|
160
|
+
debug('run(): caught a SIGINT');
|
|
161
|
+
this._state = ABORTING;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
debug('run(): force-terminating worker pool');
|
|
165
|
+
await pool.terminate(true);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.error(
|
|
168
|
+
`Error while attempting to force-terminate worker pool: ${err}`
|
|
169
|
+
);
|
|
170
|
+
process.exitCode = 1;
|
|
171
|
+
} finally {
|
|
172
|
+
process.nextTick(() => {
|
|
173
|
+
debug('run(): imminent death');
|
|
174
|
+
this._state = ABORTED;
|
|
175
|
+
process.kill(process.pid, 'SIGINT');
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
process.once('SIGINT', sigIntListener);
|
|
181
|
+
|
|
182
|
+
return sigIntListener;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Runs Mocha tests by creating a thread pool, then delegating work to the
|
|
187
|
+
* worker threads.
|
|
188
|
+
*
|
|
189
|
+
* Each worker receives one file, and as workers become available, they take a
|
|
190
|
+
* file from the queue and run it. The worker thread execution is treated like
|
|
191
|
+
* an RPC--it returns a `Promise` containing serialized information about the
|
|
192
|
+
* run. The information is processed as it's received, and emitted to a
|
|
193
|
+
* {@link Reporter}, which is likely listening for these events.
|
|
194
|
+
*
|
|
195
|
+
* @param {Function} callback - Called with an exit code corresponding to
|
|
196
|
+
* number of test failures.
|
|
197
|
+
* @param {{files: string[], options: Options}} opts - Files to run and
|
|
198
|
+
* command-line options, respectively.
|
|
199
|
+
*/
|
|
200
|
+
run(callback, {files, options} = {}) {
|
|
201
|
+
/**
|
|
202
|
+
* Listener on `Process.SIGINT` which tries to cleanly terminate the worker pool.
|
|
203
|
+
*/
|
|
204
|
+
let sigIntListener;
|
|
205
|
+
// This function should _not_ return a `Promise`; its parent (`Runner#run`)
|
|
206
|
+
// returns this instance, so this should do the same. However, we want to make
|
|
207
|
+
// use of `async`/`await`, so we use this IIFE.
|
|
208
|
+
|
|
209
|
+
(async () => {
|
|
210
|
+
/**
|
|
211
|
+
* This is an interval that outputs stats about the worker pool every so often
|
|
212
|
+
*/
|
|
213
|
+
let debugInterval;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @type {BufferedWorkerPool}
|
|
217
|
+
*/
|
|
218
|
+
let pool;
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
pool = BufferedWorkerPool.create({maxWorkers: options.jobs});
|
|
222
|
+
|
|
223
|
+
sigIntListener = this._bindSigIntListener(pool);
|
|
224
|
+
|
|
225
|
+
/* istanbul ignore next */
|
|
226
|
+
debugInterval = setInterval(
|
|
227
|
+
() => debugStats(pool),
|
|
228
|
+
DEBUG_STATS_INTERVAL
|
|
229
|
+
).unref();
|
|
230
|
+
|
|
231
|
+
// this is set for uncaught exception handling in `Runner#uncaught`
|
|
232
|
+
// TODO: `Runner` should be using a state machine instead.
|
|
233
|
+
this.started = true;
|
|
234
|
+
this._state = RUNNING;
|
|
235
|
+
|
|
236
|
+
this.emit(EVENT_RUN_BEGIN);
|
|
237
|
+
|
|
238
|
+
const results = await allSettled(
|
|
239
|
+
files.map(this._createFileRunner(pool, options))
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// note that pool may already be terminated due to --bail
|
|
243
|
+
await pool.terminate();
|
|
244
|
+
|
|
245
|
+
results
|
|
246
|
+
.filter(({status}) => status === 'rejected')
|
|
247
|
+
.forEach(({reason}) => {
|
|
248
|
+
if (this.allowUncaught) {
|
|
249
|
+
// yep, just the first one.
|
|
250
|
+
throw reason;
|
|
251
|
+
}
|
|
252
|
+
// "rejected" will correspond to uncaught exceptions.
|
|
253
|
+
// unlike the serial runner, the parallel runner can always recover.
|
|
254
|
+
this.uncaught(reason);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (this._state === ABORTING) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
this.emit(EVENT_RUN_END);
|
|
261
|
+
debug('run(): completing with failure count %d', this.failures);
|
|
262
|
+
callback(this.failures);
|
|
263
|
+
} catch (err) {
|
|
264
|
+
// this `nextTick` takes us out of the `Promise` scope, so the
|
|
265
|
+
// exception will not be caught and returned as a rejected `Promise`,
|
|
266
|
+
// which would lead to an `unhandledRejection` event.
|
|
267
|
+
process.nextTick(() => {
|
|
268
|
+
debug('run(): re-throwing uncaught exception');
|
|
269
|
+
throw err;
|
|
270
|
+
});
|
|
271
|
+
} finally {
|
|
272
|
+
clearInterval(debugInterval);
|
|
273
|
+
process.removeListener('SIGINT', sigIntListener);
|
|
274
|
+
}
|
|
275
|
+
})();
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
module.exports = ParallelBufferedRunner;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Listener function intended to be bound to `Process.SIGINT` event
|
|
284
|
+
* @private
|
|
285
|
+
* @callback SigIntListener
|
|
286
|
+
* @returns {Promise<void>}
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* A function accepting a test file path and returning the results of a test run
|
|
291
|
+
* @private
|
|
292
|
+
* @callback FileRunner
|
|
293
|
+
* @param {string} filename - File to run
|
|
294
|
+
* @returns {Promise<SerializedWorkerResult>}
|
|
295
|
+
*/
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Buffered" reporter used internally by a worker process when running in parallel mode.
|
|
3
|
+
* @module reporters/parallel-buffered
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Module dependencies.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
EVENT_SUITE_BEGIN,
|
|
15
|
+
EVENT_SUITE_END,
|
|
16
|
+
EVENT_TEST_FAIL,
|
|
17
|
+
EVENT_TEST_PASS,
|
|
18
|
+
EVENT_TEST_PENDING,
|
|
19
|
+
EVENT_TEST_BEGIN,
|
|
20
|
+
EVENT_TEST_END,
|
|
21
|
+
EVENT_TEST_RETRY,
|
|
22
|
+
EVENT_DELAY_BEGIN,
|
|
23
|
+
EVENT_DELAY_END,
|
|
24
|
+
EVENT_HOOK_BEGIN,
|
|
25
|
+
EVENT_HOOK_END,
|
|
26
|
+
EVENT_RUN_END
|
|
27
|
+
} = require('../../runner').constants;
|
|
28
|
+
const {SerializableEvent, SerializableWorkerResult} = require('../serializer');
|
|
29
|
+
const debug = require('debug')('mocha:reporters:buffered');
|
|
30
|
+
const Base = require('../../reporters/base');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* List of events to listen to; these will be buffered and sent
|
|
34
|
+
* when `Mocha#run` is complete (via {@link ParallelBuffered#done}).
|
|
35
|
+
*/
|
|
36
|
+
const EVENT_NAMES = [
|
|
37
|
+
EVENT_SUITE_BEGIN,
|
|
38
|
+
EVENT_SUITE_END,
|
|
39
|
+
EVENT_TEST_BEGIN,
|
|
40
|
+
EVENT_TEST_PENDING,
|
|
41
|
+
EVENT_TEST_FAIL,
|
|
42
|
+
EVENT_TEST_PASS,
|
|
43
|
+
EVENT_TEST_RETRY,
|
|
44
|
+
EVENT_TEST_END,
|
|
45
|
+
EVENT_HOOK_BEGIN,
|
|
46
|
+
EVENT_HOOK_END
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Like {@link EVENT_NAMES}, except we expect these events to only be emitted
|
|
51
|
+
* by the `Runner` once.
|
|
52
|
+
*/
|
|
53
|
+
const ONCE_EVENT_NAMES = [EVENT_DELAY_BEGIN, EVENT_DELAY_END];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The `ParallelBuffered` reporter is for use by concurrent runs. Instead of outputting
|
|
57
|
+
* to `STDOUT`, etc., it retains a list of events it receives and hands these
|
|
58
|
+
* off to the callback passed into {@link Mocha#run}. That callback will then
|
|
59
|
+
* return the data to the main process.
|
|
60
|
+
* @private
|
|
61
|
+
*/
|
|
62
|
+
class ParallelBuffered extends Base {
|
|
63
|
+
/**
|
|
64
|
+
* Listens for {@link Runner} events and retains them in an `events` instance prop.
|
|
65
|
+
* @param {Runner} runner
|
|
66
|
+
*/
|
|
67
|
+
constructor(runner, opts) {
|
|
68
|
+
super(runner, opts);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Retained list of events emitted from the {@link Runner} instance.
|
|
72
|
+
* @type {BufferedEvent[]}
|
|
73
|
+
* @memberOf Buffered
|
|
74
|
+
*/
|
|
75
|
+
const events = (this.events = []);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* mapping of event names to listener functions we've created,
|
|
79
|
+
* so we can cleanly _remove_ them from the runner once it's completed.
|
|
80
|
+
*/
|
|
81
|
+
const listeners = new Map();
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Creates a listener for event `eventName` and adds it to the `listeners`
|
|
85
|
+
* map. This is a defensive measure, so that we don't a) leak memory or b)
|
|
86
|
+
* remove _other_ listeners that may not be associated with this reporter.
|
|
87
|
+
* @param {string} eventName - Event name
|
|
88
|
+
*/
|
|
89
|
+
const createListener = eventName =>
|
|
90
|
+
listeners
|
|
91
|
+
.set(eventName, (runnable, err) => {
|
|
92
|
+
events.push(SerializableEvent.create(eventName, runnable, err));
|
|
93
|
+
})
|
|
94
|
+
.get(eventName);
|
|
95
|
+
|
|
96
|
+
EVENT_NAMES.forEach(evt => {
|
|
97
|
+
runner.on(evt, createListener(evt));
|
|
98
|
+
});
|
|
99
|
+
ONCE_EVENT_NAMES.forEach(evt => {
|
|
100
|
+
runner.once(evt, createListener(evt));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
runner.once(EVENT_RUN_END, () => {
|
|
104
|
+
debug('received EVENT_RUN_END');
|
|
105
|
+
listeners.forEach((listener, evt) => {
|
|
106
|
+
runner.removeListener(evt, listener);
|
|
107
|
+
listeners.delete(evt);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Calls the {@link Mocha#run} callback (`callback`) with the test failure
|
|
114
|
+
* count and the array of {@link BufferedEvent} objects. Resets the array.
|
|
115
|
+
* @param {number} failures - Number of failed tests
|
|
116
|
+
* @param {Function} callback - The callback passed to {@link Mocha#run}.
|
|
117
|
+
*/
|
|
118
|
+
done(failures, callback) {
|
|
119
|
+
callback(SerializableWorkerResult.create(this.events, failures));
|
|
120
|
+
this.events = []; // defensive
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Serializable event data from a `Runner`. Keys of the `data` property
|
|
126
|
+
* beginning with `__` will be converted into a function which returns the value
|
|
127
|
+
* upon deserialization.
|
|
128
|
+
* @typedef {Object} BufferedEvent
|
|
129
|
+
* @property {string} name - Event name
|
|
130
|
+
* @property {object} data - Event parameters
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
module.exports = ParallelBuffered;
|