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
package/lib/runner.js
ADDED
|
@@ -0,0 +1,1269 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Module dependencies.
|
|
5
|
+
* @private
|
|
6
|
+
*/
|
|
7
|
+
var EventEmitter = require('events').EventEmitter;
|
|
8
|
+
var Pending = require('./pending');
|
|
9
|
+
var utils = require('./utils');
|
|
10
|
+
var debug = require('debug')('mocha:runner');
|
|
11
|
+
var Runnable = require('./runnable');
|
|
12
|
+
var Suite = require('./suite');
|
|
13
|
+
var HOOK_TYPE_BEFORE_EACH = Suite.constants.HOOK_TYPE_BEFORE_EACH;
|
|
14
|
+
var HOOK_TYPE_AFTER_EACH = Suite.constants.HOOK_TYPE_AFTER_EACH;
|
|
15
|
+
var HOOK_TYPE_AFTER_ALL = Suite.constants.HOOK_TYPE_AFTER_ALL;
|
|
16
|
+
var HOOK_TYPE_BEFORE_ALL = Suite.constants.HOOK_TYPE_BEFORE_ALL;
|
|
17
|
+
var EVENT_ROOT_SUITE_RUN = Suite.constants.EVENT_ROOT_SUITE_RUN;
|
|
18
|
+
var STATE_FAILED = Runnable.constants.STATE_FAILED;
|
|
19
|
+
var STATE_PASSED = Runnable.constants.STATE_PASSED;
|
|
20
|
+
var STATE_PENDING = Runnable.constants.STATE_PENDING;
|
|
21
|
+
var stackFilter = utils.stackTraceFilter();
|
|
22
|
+
var stringify = utils.stringify;
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
createInvalidExceptionError,
|
|
26
|
+
createUnsupportedError,
|
|
27
|
+
createFatalError,
|
|
28
|
+
isMochaError,
|
|
29
|
+
constants: errorConstants
|
|
30
|
+
} = require('./errors');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Non-enumerable globals.
|
|
34
|
+
* @private
|
|
35
|
+
* @readonly
|
|
36
|
+
*/
|
|
37
|
+
var globals = [
|
|
38
|
+
'setTimeout',
|
|
39
|
+
'clearTimeout',
|
|
40
|
+
'setInterval',
|
|
41
|
+
'clearInterval',
|
|
42
|
+
'XMLHttpRequest',
|
|
43
|
+
'Date',
|
|
44
|
+
'setImmediate',
|
|
45
|
+
'clearImmediate'
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
var constants = utils.defineConstants(
|
|
49
|
+
/**
|
|
50
|
+
* {@link Runner}-related constants.
|
|
51
|
+
* @public
|
|
52
|
+
* @memberof Runner
|
|
53
|
+
* @readonly
|
|
54
|
+
* @alias constants
|
|
55
|
+
* @static
|
|
56
|
+
* @enum {string}
|
|
57
|
+
*/
|
|
58
|
+
{
|
|
59
|
+
/**
|
|
60
|
+
* Emitted when {@link Hook} execution begins
|
|
61
|
+
*/
|
|
62
|
+
EVENT_HOOK_BEGIN: 'hook',
|
|
63
|
+
/**
|
|
64
|
+
* Emitted when {@link Hook} execution ends
|
|
65
|
+
*/
|
|
66
|
+
EVENT_HOOK_END: 'hook end',
|
|
67
|
+
/**
|
|
68
|
+
* Emitted when Root {@link Suite} execution begins (all files have been parsed and hooks/tests are ready for execution)
|
|
69
|
+
*/
|
|
70
|
+
EVENT_RUN_BEGIN: 'start',
|
|
71
|
+
/**
|
|
72
|
+
* Emitted when Root {@link Suite} execution has been delayed via `delay` option
|
|
73
|
+
*/
|
|
74
|
+
EVENT_DELAY_BEGIN: 'waiting',
|
|
75
|
+
/**
|
|
76
|
+
* Emitted when delayed Root {@link Suite} execution is triggered by user via `global.run()`
|
|
77
|
+
*/
|
|
78
|
+
EVENT_DELAY_END: 'ready',
|
|
79
|
+
/**
|
|
80
|
+
* Emitted when Root {@link Suite} execution ends
|
|
81
|
+
*/
|
|
82
|
+
EVENT_RUN_END: 'end',
|
|
83
|
+
/**
|
|
84
|
+
* Emitted when {@link Suite} execution begins
|
|
85
|
+
*/
|
|
86
|
+
EVENT_SUITE_BEGIN: 'suite',
|
|
87
|
+
/**
|
|
88
|
+
* Emitted when {@link Suite} execution ends
|
|
89
|
+
*/
|
|
90
|
+
EVENT_SUITE_END: 'suite end',
|
|
91
|
+
/**
|
|
92
|
+
* Emitted when {@link Test} execution begins
|
|
93
|
+
*/
|
|
94
|
+
EVENT_TEST_BEGIN: 'test',
|
|
95
|
+
/**
|
|
96
|
+
* Emitted when {@link Test} execution ends
|
|
97
|
+
*/
|
|
98
|
+
EVENT_TEST_END: 'test end',
|
|
99
|
+
/**
|
|
100
|
+
* Emitted when {@link Test} execution fails
|
|
101
|
+
*/
|
|
102
|
+
EVENT_TEST_FAIL: 'fail',
|
|
103
|
+
/**
|
|
104
|
+
* Emitted when {@link Test} execution succeeds
|
|
105
|
+
*/
|
|
106
|
+
EVENT_TEST_PASS: 'pass',
|
|
107
|
+
/**
|
|
108
|
+
* Emitted when {@link Test} becomes pending
|
|
109
|
+
*/
|
|
110
|
+
EVENT_TEST_PENDING: 'pending',
|
|
111
|
+
/**
|
|
112
|
+
* Emitted when {@link Test} execution has failed, but will retry
|
|
113
|
+
*/
|
|
114
|
+
EVENT_TEST_RETRY: 'retry',
|
|
115
|
+
/**
|
|
116
|
+
* Initial state of Runner
|
|
117
|
+
*/
|
|
118
|
+
STATE_IDLE: 'idle',
|
|
119
|
+
/**
|
|
120
|
+
* State set to this value when the Runner has started running
|
|
121
|
+
*/
|
|
122
|
+
STATE_RUNNING: 'running',
|
|
123
|
+
/**
|
|
124
|
+
* State set to this value when the Runner has stopped
|
|
125
|
+
*/
|
|
126
|
+
STATE_STOPPED: 'stopped'
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
class Runner extends EventEmitter {
|
|
131
|
+
/**
|
|
132
|
+
* Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}.
|
|
133
|
+
*
|
|
134
|
+
* @extends external:EventEmitter
|
|
135
|
+
* @public
|
|
136
|
+
* @class
|
|
137
|
+
* @param {Suite} suite - Root suite
|
|
138
|
+
* @param {Object|boolean} [opts] - Options. If `boolean` (deprecated), whether to delay execution of root suite until ready.
|
|
139
|
+
* @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done.
|
|
140
|
+
* @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready.
|
|
141
|
+
* @param {boolean} [opts.dryRun] - Whether to report tests without running them.
|
|
142
|
+
* @param {boolean} [options.failZero] - Whether to fail test run if zero tests encountered.
|
|
143
|
+
*/
|
|
144
|
+
constructor(suite, opts) {
|
|
145
|
+
super();
|
|
146
|
+
if (opts === undefined) {
|
|
147
|
+
opts = {};
|
|
148
|
+
}
|
|
149
|
+
if (typeof opts === 'boolean') {
|
|
150
|
+
// TODO: remove this
|
|
151
|
+
require('./errors').deprecate(
|
|
152
|
+
'"Runner(suite: Suite, delay: boolean)" is deprecated. Use "Runner(suite: Suite, {delay: boolean})" instead.'
|
|
153
|
+
);
|
|
154
|
+
this._delay = opts;
|
|
155
|
+
opts = {};
|
|
156
|
+
} else {
|
|
157
|
+
this._delay = opts.delay;
|
|
158
|
+
}
|
|
159
|
+
var self = this;
|
|
160
|
+
this._globals = [];
|
|
161
|
+
this._abort = false;
|
|
162
|
+
this.suite = suite;
|
|
163
|
+
this._opts = opts;
|
|
164
|
+
this.state = constants.STATE_IDLE;
|
|
165
|
+
this.total = suite.total();
|
|
166
|
+
this.failures = 0;
|
|
167
|
+
/**
|
|
168
|
+
* @type {Map<EventEmitter,Map<string,Set<EventListener>>>}
|
|
169
|
+
*/
|
|
170
|
+
this._eventListeners = new Map();
|
|
171
|
+
this.on(constants.EVENT_TEST_END, function(test) {
|
|
172
|
+
if (test.type === 'test' && test.retriedTest() && test.parent) {
|
|
173
|
+
var idx =
|
|
174
|
+
test.parent.tests && test.parent.tests.indexOf(test.retriedTest());
|
|
175
|
+
if (idx > -1) test.parent.tests[idx] = test;
|
|
176
|
+
}
|
|
177
|
+
self.checkGlobals(test);
|
|
178
|
+
});
|
|
179
|
+
this.on(constants.EVENT_HOOK_END, function(hook) {
|
|
180
|
+
self.checkGlobals(hook);
|
|
181
|
+
});
|
|
182
|
+
this._defaultGrep = /.*/;
|
|
183
|
+
this.grep(this._defaultGrep);
|
|
184
|
+
this.globals(this.globalProps());
|
|
185
|
+
|
|
186
|
+
this.uncaught = this._uncaught.bind(this);
|
|
187
|
+
this.unhandled = (reason, promise) => {
|
|
188
|
+
if (isMochaError(reason)) {
|
|
189
|
+
debug(
|
|
190
|
+
'trapped unhandled rejection coming out of Mocha; forwarding to uncaught handler:',
|
|
191
|
+
reason
|
|
192
|
+
);
|
|
193
|
+
this.uncaught(reason);
|
|
194
|
+
} else {
|
|
195
|
+
debug(
|
|
196
|
+
'trapped unhandled rejection from (probably) user code; re-emitting on process'
|
|
197
|
+
);
|
|
198
|
+
this._removeEventListener(
|
|
199
|
+
process,
|
|
200
|
+
'unhandledRejection',
|
|
201
|
+
this.unhandled
|
|
202
|
+
);
|
|
203
|
+
try {
|
|
204
|
+
process.emit('unhandledRejection', reason, promise);
|
|
205
|
+
} finally {
|
|
206
|
+
this._addEventListener(process, 'unhandledRejection', this.unhandled);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Wrapper for setImmediate, process.nextTick, or browser polyfill.
|
|
215
|
+
*
|
|
216
|
+
* @param {Function} fn
|
|
217
|
+
* @private
|
|
218
|
+
*/
|
|
219
|
+
Runner.immediately = global.setImmediate || process.nextTick;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Replacement for `target.on(eventName, listener)` that does bookkeeping to remove them when this runner instance is disposed.
|
|
223
|
+
* @param {EventEmitter} target - The `EventEmitter`
|
|
224
|
+
* @param {string} eventName - The event name
|
|
225
|
+
* @param {string} fn - Listener function
|
|
226
|
+
* @private
|
|
227
|
+
*/
|
|
228
|
+
Runner.prototype._addEventListener = function(target, eventName, listener) {
|
|
229
|
+
debug(
|
|
230
|
+
'_addEventListener(): adding for event %s; %d current listeners',
|
|
231
|
+
eventName,
|
|
232
|
+
target.listenerCount(eventName)
|
|
233
|
+
);
|
|
234
|
+
/* istanbul ignore next */
|
|
235
|
+
if (
|
|
236
|
+
this._eventListeners.has(target) &&
|
|
237
|
+
this._eventListeners.get(target).has(eventName) &&
|
|
238
|
+
this._eventListeners
|
|
239
|
+
.get(target)
|
|
240
|
+
.get(eventName)
|
|
241
|
+
.has(listener)
|
|
242
|
+
) {
|
|
243
|
+
debug(
|
|
244
|
+
'warning: tried to attach duplicate event listener for %s',
|
|
245
|
+
eventName
|
|
246
|
+
);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
target.on(eventName, listener);
|
|
250
|
+
const targetListeners = this._eventListeners.has(target)
|
|
251
|
+
? this._eventListeners.get(target)
|
|
252
|
+
: new Map();
|
|
253
|
+
const targetEventListeners = targetListeners.has(eventName)
|
|
254
|
+
? targetListeners.get(eventName)
|
|
255
|
+
: new Set();
|
|
256
|
+
targetEventListeners.add(listener);
|
|
257
|
+
targetListeners.set(eventName, targetEventListeners);
|
|
258
|
+
this._eventListeners.set(target, targetListeners);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Replacement for `target.removeListener(eventName, listener)` that also updates the bookkeeping.
|
|
263
|
+
* @param {EventEmitter} target - The `EventEmitter`
|
|
264
|
+
* @param {string} eventName - The event name
|
|
265
|
+
* @param {function} listener - Listener function
|
|
266
|
+
* @private
|
|
267
|
+
*/
|
|
268
|
+
Runner.prototype._removeEventListener = function(target, eventName, listener) {
|
|
269
|
+
target.removeListener(eventName, listener);
|
|
270
|
+
|
|
271
|
+
if (this._eventListeners.has(target)) {
|
|
272
|
+
const targetListeners = this._eventListeners.get(target);
|
|
273
|
+
if (targetListeners.has(eventName)) {
|
|
274
|
+
const targetEventListeners = targetListeners.get(eventName);
|
|
275
|
+
targetEventListeners.delete(listener);
|
|
276
|
+
if (!targetEventListeners.size) {
|
|
277
|
+
targetListeners.delete(eventName);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (!targetListeners.size) {
|
|
281
|
+
this._eventListeners.delete(target);
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
debug('trying to remove listener for untracked object %s', target);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Removes all event handlers set during a run on this instance.
|
|
290
|
+
* Remark: this does _not_ clean/dispose the tests or suites themselves.
|
|
291
|
+
*/
|
|
292
|
+
Runner.prototype.dispose = function() {
|
|
293
|
+
this.removeAllListeners();
|
|
294
|
+
this._eventListeners.forEach((targetListeners, target) => {
|
|
295
|
+
targetListeners.forEach((targetEventListeners, eventName) => {
|
|
296
|
+
targetEventListeners.forEach(listener => {
|
|
297
|
+
target.removeListener(eventName, listener);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
this._eventListeners.clear();
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Run tests with full titles matching `re`. Updates runner.total
|
|
306
|
+
* with number of tests matched.
|
|
307
|
+
*
|
|
308
|
+
* @public
|
|
309
|
+
* @memberof Runner
|
|
310
|
+
* @param {RegExp} re
|
|
311
|
+
* @param {boolean} invert
|
|
312
|
+
* @return {Runner} Runner instance.
|
|
313
|
+
*/
|
|
314
|
+
Runner.prototype.grep = function(re, invert) {
|
|
315
|
+
debug('grep(): setting to %s', re);
|
|
316
|
+
this._grep = re;
|
|
317
|
+
this._invert = invert;
|
|
318
|
+
this.total = this.grepTotal(this.suite);
|
|
319
|
+
return this;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Returns the number of tests matching the grep search for the
|
|
324
|
+
* given suite.
|
|
325
|
+
*
|
|
326
|
+
* @memberof Runner
|
|
327
|
+
* @public
|
|
328
|
+
* @param {Suite} suite
|
|
329
|
+
* @return {number}
|
|
330
|
+
*/
|
|
331
|
+
Runner.prototype.grepTotal = function(suite) {
|
|
332
|
+
var self = this;
|
|
333
|
+
var total = 0;
|
|
334
|
+
|
|
335
|
+
suite.eachTest(function(test) {
|
|
336
|
+
var match = self._grep.test(test.fullTitle());
|
|
337
|
+
if (self._invert) {
|
|
338
|
+
match = !match;
|
|
339
|
+
}
|
|
340
|
+
if (match) {
|
|
341
|
+
total++;
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
return total;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Return a list of global properties.
|
|
350
|
+
*
|
|
351
|
+
* @return {Array}
|
|
352
|
+
* @private
|
|
353
|
+
*/
|
|
354
|
+
Runner.prototype.globalProps = function() {
|
|
355
|
+
var props = Object.keys(global);
|
|
356
|
+
|
|
357
|
+
// non-enumerables
|
|
358
|
+
for (var i = 0; i < globals.length; ++i) {
|
|
359
|
+
if (~props.indexOf(globals[i])) {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
props.push(globals[i]);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return props;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Allow the given `arr` of globals.
|
|
370
|
+
*
|
|
371
|
+
* @public
|
|
372
|
+
* @memberof Runner
|
|
373
|
+
* @param {Array} arr
|
|
374
|
+
* @return {Runner} Runner instance.
|
|
375
|
+
*/
|
|
376
|
+
Runner.prototype.globals = function(arr) {
|
|
377
|
+
if (!arguments.length) {
|
|
378
|
+
return this._globals;
|
|
379
|
+
}
|
|
380
|
+
debug('globals(): setting to %O', arr);
|
|
381
|
+
this._globals = this._globals.concat(arr);
|
|
382
|
+
return this;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Check for global variable leaks.
|
|
387
|
+
*
|
|
388
|
+
* @private
|
|
389
|
+
*/
|
|
390
|
+
Runner.prototype.checkGlobals = function(test) {
|
|
391
|
+
if (!this.checkLeaks) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
var ok = this._globals;
|
|
395
|
+
|
|
396
|
+
var globals = this.globalProps();
|
|
397
|
+
var leaks;
|
|
398
|
+
|
|
399
|
+
if (test) {
|
|
400
|
+
ok = ok.concat(test._allowedGlobals || []);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (this.prevGlobalsLength === globals.length) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
this.prevGlobalsLength = globals.length;
|
|
407
|
+
|
|
408
|
+
leaks = filterLeaks(ok, globals);
|
|
409
|
+
this._globals = this._globals.concat(leaks);
|
|
410
|
+
|
|
411
|
+
if (leaks.length) {
|
|
412
|
+
var msg = `global leak(s) detected: ${leaks.map(e => `'${e}'`).join(', ')}`;
|
|
413
|
+
this.fail(test, new Error(msg));
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Fail the given `test`.
|
|
419
|
+
*
|
|
420
|
+
* If `test` is a hook, failures work in the following pattern:
|
|
421
|
+
* - If bail, run corresponding `after each` and `after` hooks,
|
|
422
|
+
* then exit
|
|
423
|
+
* - Failed `before` hook skips all tests in a suite and subsuites,
|
|
424
|
+
* but jumps to corresponding `after` hook
|
|
425
|
+
* - Failed `before each` hook skips remaining tests in a
|
|
426
|
+
* suite and jumps to corresponding `after each` hook,
|
|
427
|
+
* which is run only once
|
|
428
|
+
* - Failed `after` hook does not alter execution order
|
|
429
|
+
* - Failed `after each` hook skips remaining tests in a
|
|
430
|
+
* suite and subsuites, but executes other `after each`
|
|
431
|
+
* hooks
|
|
432
|
+
*
|
|
433
|
+
* @private
|
|
434
|
+
* @param {Runnable} test
|
|
435
|
+
* @param {Error} err
|
|
436
|
+
* @param {boolean} [force=false] - Whether to fail a pending test.
|
|
437
|
+
*/
|
|
438
|
+
Runner.prototype.fail = function(test, err, force) {
|
|
439
|
+
force = force === true;
|
|
440
|
+
if (test.isPending() && !force) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (this.state === constants.STATE_STOPPED) {
|
|
444
|
+
if (err.code === errorConstants.MULTIPLE_DONE) {
|
|
445
|
+
throw err;
|
|
446
|
+
}
|
|
447
|
+
throw createFatalError(
|
|
448
|
+
'Test failed after root suite execution completed!',
|
|
449
|
+
err
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
++this.failures;
|
|
454
|
+
debug('total number of failures: %d', this.failures);
|
|
455
|
+
test.state = STATE_FAILED;
|
|
456
|
+
|
|
457
|
+
if (!isError(err)) {
|
|
458
|
+
err = thrown2Error(err);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
err.stack =
|
|
463
|
+
this.fullStackTrace || !err.stack ? err.stack : stackFilter(err.stack);
|
|
464
|
+
} catch (ignore) {
|
|
465
|
+
// some environments do not take kindly to monkeying with the stack
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
this.emit(constants.EVENT_TEST_FAIL, test, err);
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Run hook `name` callbacks and then invoke `fn()`.
|
|
473
|
+
*
|
|
474
|
+
* @private
|
|
475
|
+
* @param {string} name
|
|
476
|
+
* @param {Function} fn
|
|
477
|
+
*/
|
|
478
|
+
|
|
479
|
+
Runner.prototype.hook = function(name, fn) {
|
|
480
|
+
if (this._opts.dryRun) return fn();
|
|
481
|
+
|
|
482
|
+
var suite = this.suite;
|
|
483
|
+
var hooks = suite.getHooks(name);
|
|
484
|
+
var self = this;
|
|
485
|
+
|
|
486
|
+
function next(i) {
|
|
487
|
+
var hook = hooks[i];
|
|
488
|
+
if (!hook) {
|
|
489
|
+
return fn();
|
|
490
|
+
}
|
|
491
|
+
self.currentRunnable = hook;
|
|
492
|
+
|
|
493
|
+
if (name === HOOK_TYPE_BEFORE_ALL) {
|
|
494
|
+
hook.ctx.currentTest = hook.parent.tests[0];
|
|
495
|
+
} else if (name === HOOK_TYPE_AFTER_ALL) {
|
|
496
|
+
hook.ctx.currentTest = hook.parent.tests[hook.parent.tests.length - 1];
|
|
497
|
+
} else {
|
|
498
|
+
hook.ctx.currentTest = self.test;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
setHookTitle(hook);
|
|
502
|
+
|
|
503
|
+
hook.allowUncaught = self.allowUncaught;
|
|
504
|
+
|
|
505
|
+
self.emit(constants.EVENT_HOOK_BEGIN, hook);
|
|
506
|
+
|
|
507
|
+
if (!hook.listeners('error').length) {
|
|
508
|
+
self._addEventListener(hook, 'error', function(err) {
|
|
509
|
+
self.fail(hook, err);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
hook.run(function cbHookRun(err) {
|
|
514
|
+
var testError = hook.error();
|
|
515
|
+
if (testError) {
|
|
516
|
+
self.fail(self.test, testError);
|
|
517
|
+
}
|
|
518
|
+
// conditional skip
|
|
519
|
+
if (hook.pending) {
|
|
520
|
+
if (name === HOOK_TYPE_AFTER_EACH) {
|
|
521
|
+
// TODO define and implement use case
|
|
522
|
+
if (self.test) {
|
|
523
|
+
self.test.pending = true;
|
|
524
|
+
}
|
|
525
|
+
} else if (name === HOOK_TYPE_BEFORE_EACH) {
|
|
526
|
+
if (self.test) {
|
|
527
|
+
self.test.pending = true;
|
|
528
|
+
}
|
|
529
|
+
self.emit(constants.EVENT_HOOK_END, hook);
|
|
530
|
+
hook.pending = false; // activates hook for next test
|
|
531
|
+
return fn(new Error('abort hookDown'));
|
|
532
|
+
} else if (name === HOOK_TYPE_BEFORE_ALL) {
|
|
533
|
+
suite.tests.forEach(function(test) {
|
|
534
|
+
test.pending = true;
|
|
535
|
+
});
|
|
536
|
+
suite.suites.forEach(function(suite) {
|
|
537
|
+
suite.pending = true;
|
|
538
|
+
});
|
|
539
|
+
hooks = [];
|
|
540
|
+
} else {
|
|
541
|
+
hook.pending = false;
|
|
542
|
+
var errForbid = createUnsupportedError('`this.skip` forbidden');
|
|
543
|
+
self.fail(hook, errForbid);
|
|
544
|
+
return fn(errForbid);
|
|
545
|
+
}
|
|
546
|
+
} else if (err) {
|
|
547
|
+
self.fail(hook, err);
|
|
548
|
+
// stop executing hooks, notify callee of hook err
|
|
549
|
+
return fn(err);
|
|
550
|
+
}
|
|
551
|
+
self.emit(constants.EVENT_HOOK_END, hook);
|
|
552
|
+
delete hook.ctx.currentTest;
|
|
553
|
+
setHookTitle(hook);
|
|
554
|
+
next(++i);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
function setHookTitle(hook) {
|
|
558
|
+
hook.originalTitle = hook.originalTitle || hook.title;
|
|
559
|
+
if (hook.ctx && hook.ctx.currentTest) {
|
|
560
|
+
hook.title = `${hook.originalTitle} for "${hook.ctx.currentTest.title}"`;
|
|
561
|
+
} else {
|
|
562
|
+
var parentTitle;
|
|
563
|
+
if (hook.parent.title) {
|
|
564
|
+
parentTitle = hook.parent.title;
|
|
565
|
+
} else {
|
|
566
|
+
parentTitle = hook.parent.root ? '{root}' : '';
|
|
567
|
+
}
|
|
568
|
+
hook.title = `${hook.originalTitle} in "${parentTitle}"`;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
Runner.immediately(function() {
|
|
574
|
+
next(0);
|
|
575
|
+
});
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Run hook `name` for the given array of `suites`
|
|
580
|
+
* in order, and callback `fn(err, errSuite)`.
|
|
581
|
+
*
|
|
582
|
+
* @private
|
|
583
|
+
* @param {string} name
|
|
584
|
+
* @param {Array} suites
|
|
585
|
+
* @param {Function} fn
|
|
586
|
+
*/
|
|
587
|
+
Runner.prototype.hooks = function(name, suites, fn) {
|
|
588
|
+
var self = this;
|
|
589
|
+
var orig = this.suite;
|
|
590
|
+
|
|
591
|
+
function next(suite) {
|
|
592
|
+
self.suite = suite;
|
|
593
|
+
|
|
594
|
+
if (!suite) {
|
|
595
|
+
self.suite = orig;
|
|
596
|
+
return fn();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
self.hook(name, function(err) {
|
|
600
|
+
if (err) {
|
|
601
|
+
var errSuite = self.suite;
|
|
602
|
+
self.suite = orig;
|
|
603
|
+
return fn(err, errSuite);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
next(suites.pop());
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
next(suites.pop());
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Run 'afterEach' hooks from bottom up.
|
|
615
|
+
*
|
|
616
|
+
* @param {String} name
|
|
617
|
+
* @param {Function} fn
|
|
618
|
+
* @private
|
|
619
|
+
*/
|
|
620
|
+
Runner.prototype.hookUp = function(name, fn) {
|
|
621
|
+
var suites = [this.suite].concat(this.parents()).reverse();
|
|
622
|
+
this.hooks(name, suites, fn);
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Run 'beforeEach' hooks from top level down.
|
|
627
|
+
*
|
|
628
|
+
* @param {String} name
|
|
629
|
+
* @param {Function} fn
|
|
630
|
+
* @private
|
|
631
|
+
*/
|
|
632
|
+
Runner.prototype.hookDown = function(name, fn) {
|
|
633
|
+
var suites = [this.suite].concat(this.parents());
|
|
634
|
+
this.hooks(name, suites, fn);
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Return an array of parent Suites from
|
|
639
|
+
* closest to furthest.
|
|
640
|
+
*
|
|
641
|
+
* @return {Array}
|
|
642
|
+
* @private
|
|
643
|
+
*/
|
|
644
|
+
Runner.prototype.parents = function() {
|
|
645
|
+
var suite = this.suite;
|
|
646
|
+
var suites = [];
|
|
647
|
+
while (suite.parent) {
|
|
648
|
+
suite = suite.parent;
|
|
649
|
+
suites.push(suite);
|
|
650
|
+
}
|
|
651
|
+
return suites;
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Run the current test and callback `fn(err)`.
|
|
656
|
+
*
|
|
657
|
+
* @param {Function} fn
|
|
658
|
+
* @private
|
|
659
|
+
*/
|
|
660
|
+
Runner.prototype.runTest = function(fn) {
|
|
661
|
+
if (this._opts.dryRun) return fn();
|
|
662
|
+
|
|
663
|
+
var self = this;
|
|
664
|
+
var test = this.test;
|
|
665
|
+
|
|
666
|
+
if (!test) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (this.asyncOnly) {
|
|
671
|
+
test.asyncOnly = true;
|
|
672
|
+
}
|
|
673
|
+
this._addEventListener(test, 'error', function(err) {
|
|
674
|
+
self.fail(test, err);
|
|
675
|
+
});
|
|
676
|
+
if (this.allowUncaught) {
|
|
677
|
+
test.allowUncaught = true;
|
|
678
|
+
return test.run(fn);
|
|
679
|
+
}
|
|
680
|
+
try {
|
|
681
|
+
test.run(fn);
|
|
682
|
+
} catch (err) {
|
|
683
|
+
fn(err);
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Run tests in the given `suite` and invoke the callback `fn()` when complete.
|
|
689
|
+
*
|
|
690
|
+
* @private
|
|
691
|
+
* @param {Suite} suite
|
|
692
|
+
* @param {Function} fn
|
|
693
|
+
*/
|
|
694
|
+
Runner.prototype.runTests = function(suite, fn) {
|
|
695
|
+
var self = this;
|
|
696
|
+
var tests = suite.tests.slice();
|
|
697
|
+
var test;
|
|
698
|
+
|
|
699
|
+
function hookErr(_, errSuite, after) {
|
|
700
|
+
// before/after Each hook for errSuite failed:
|
|
701
|
+
var orig = self.suite;
|
|
702
|
+
|
|
703
|
+
// for failed 'after each' hook start from errSuite parent,
|
|
704
|
+
// otherwise start from errSuite itself
|
|
705
|
+
self.suite = after ? errSuite.parent : errSuite;
|
|
706
|
+
|
|
707
|
+
if (self.suite) {
|
|
708
|
+
self.hookUp(HOOK_TYPE_AFTER_EACH, function(err2, errSuite2) {
|
|
709
|
+
self.suite = orig;
|
|
710
|
+
// some hooks may fail even now
|
|
711
|
+
if (err2) {
|
|
712
|
+
return hookErr(err2, errSuite2, true);
|
|
713
|
+
}
|
|
714
|
+
// report error suite
|
|
715
|
+
fn(errSuite);
|
|
716
|
+
});
|
|
717
|
+
} else {
|
|
718
|
+
// there is no need calling other 'after each' hooks
|
|
719
|
+
self.suite = orig;
|
|
720
|
+
fn(errSuite);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function next(err, errSuite) {
|
|
725
|
+
// if we bail after first err
|
|
726
|
+
if (self.failures && suite._bail) {
|
|
727
|
+
tests = [];
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (self._abort) {
|
|
731
|
+
return fn();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (err) {
|
|
735
|
+
return hookErr(err, errSuite, true);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// next test
|
|
739
|
+
test = tests.shift();
|
|
740
|
+
|
|
741
|
+
// all done
|
|
742
|
+
if (!test) {
|
|
743
|
+
return fn();
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// grep
|
|
747
|
+
var match = self._grep.test(test.fullTitle());
|
|
748
|
+
if (self._invert) {
|
|
749
|
+
match = !match;
|
|
750
|
+
}
|
|
751
|
+
if (!match) {
|
|
752
|
+
// Run immediately only if we have defined a grep. When we
|
|
753
|
+
// define a grep — It can cause maximum callstack error if
|
|
754
|
+
// the grep is doing a large recursive loop by neglecting
|
|
755
|
+
// all tests. The run immediately function also comes with
|
|
756
|
+
// a performance cost. So we don't want to run immediately
|
|
757
|
+
// if we run the whole test suite, because running the whole
|
|
758
|
+
// test suite don't do any immediate recursive loops. Thus,
|
|
759
|
+
// allowing a JS runtime to breathe.
|
|
760
|
+
if (self._grep !== self._defaultGrep) {
|
|
761
|
+
Runner.immediately(next);
|
|
762
|
+
} else {
|
|
763
|
+
next();
|
|
764
|
+
}
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// static skip, no hooks are executed
|
|
769
|
+
if (test.isPending()) {
|
|
770
|
+
if (self.forbidPending) {
|
|
771
|
+
self.fail(test, new Error('Pending test forbidden'), true);
|
|
772
|
+
} else {
|
|
773
|
+
test.state = STATE_PENDING;
|
|
774
|
+
self.emit(constants.EVENT_TEST_PENDING, test);
|
|
775
|
+
}
|
|
776
|
+
self.emit(constants.EVENT_TEST_END, test);
|
|
777
|
+
return next();
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// execute test and hook(s)
|
|
781
|
+
self.emit(constants.EVENT_TEST_BEGIN, (self.test = test));
|
|
782
|
+
self.hookDown(HOOK_TYPE_BEFORE_EACH, function(err, errSuite) {
|
|
783
|
+
// conditional skip within beforeEach
|
|
784
|
+
if (test.isPending()) {
|
|
785
|
+
if (self.forbidPending) {
|
|
786
|
+
self.fail(test, new Error('Pending test forbidden'), true);
|
|
787
|
+
} else {
|
|
788
|
+
test.state = STATE_PENDING;
|
|
789
|
+
self.emit(constants.EVENT_TEST_PENDING, test);
|
|
790
|
+
}
|
|
791
|
+
self.emit(constants.EVENT_TEST_END, test);
|
|
792
|
+
// skip inner afterEach hooks below errSuite level
|
|
793
|
+
var origSuite = self.suite;
|
|
794
|
+
self.suite = errSuite || self.suite;
|
|
795
|
+
return self.hookUp(HOOK_TYPE_AFTER_EACH, function(e, eSuite) {
|
|
796
|
+
self.suite = origSuite;
|
|
797
|
+
next(e, eSuite);
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
if (err) {
|
|
801
|
+
return hookErr(err, errSuite, false);
|
|
802
|
+
}
|
|
803
|
+
self.currentRunnable = self.test;
|
|
804
|
+
self.runTest(function(err) {
|
|
805
|
+
test = self.test;
|
|
806
|
+
// conditional skip within it
|
|
807
|
+
if (test.pending) {
|
|
808
|
+
if (self.forbidPending) {
|
|
809
|
+
self.fail(test, new Error('Pending test forbidden'), true);
|
|
810
|
+
} else {
|
|
811
|
+
test.state = STATE_PENDING;
|
|
812
|
+
self.emit(constants.EVENT_TEST_PENDING, test);
|
|
813
|
+
}
|
|
814
|
+
self.emit(constants.EVENT_TEST_END, test);
|
|
815
|
+
return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
|
|
816
|
+
} else if (err) {
|
|
817
|
+
var retry = test.currentRetry();
|
|
818
|
+
if (retry < test.retries()) {
|
|
819
|
+
var clonedTest = test.clone();
|
|
820
|
+
clonedTest.currentRetry(retry + 1);
|
|
821
|
+
tests.unshift(clonedTest);
|
|
822
|
+
|
|
823
|
+
self.emit(constants.EVENT_TEST_RETRY, test, err);
|
|
824
|
+
|
|
825
|
+
// Early return + hook trigger so that it doesn't
|
|
826
|
+
// increment the count wrong
|
|
827
|
+
return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
|
|
828
|
+
} else {
|
|
829
|
+
self.fail(test, err);
|
|
830
|
+
}
|
|
831
|
+
self.emit(constants.EVENT_TEST_END, test);
|
|
832
|
+
return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
test.state = STATE_PASSED;
|
|
836
|
+
self.emit(constants.EVENT_TEST_PASS, test);
|
|
837
|
+
self.emit(constants.EVENT_TEST_END, test);
|
|
838
|
+
self.hookUp(HOOK_TYPE_AFTER_EACH, next);
|
|
839
|
+
});
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
this.next = next;
|
|
844
|
+
this.hookErr = hookErr;
|
|
845
|
+
next();
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Run the given `suite` and invoke the callback `fn()` when complete.
|
|
850
|
+
*
|
|
851
|
+
* @private
|
|
852
|
+
* @param {Suite} suite
|
|
853
|
+
* @param {Function} fn
|
|
854
|
+
*/
|
|
855
|
+
Runner.prototype.runSuite = function(suite, fn) {
|
|
856
|
+
var i = 0;
|
|
857
|
+
var self = this;
|
|
858
|
+
var total = this.grepTotal(suite);
|
|
859
|
+
|
|
860
|
+
debug('runSuite(): running %s', suite.fullTitle());
|
|
861
|
+
|
|
862
|
+
if (!total || (self.failures && suite._bail)) {
|
|
863
|
+
debug('runSuite(): bailing');
|
|
864
|
+
return fn();
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
this.emit(constants.EVENT_SUITE_BEGIN, (this.suite = suite));
|
|
868
|
+
|
|
869
|
+
function next(errSuite) {
|
|
870
|
+
if (errSuite) {
|
|
871
|
+
// current suite failed on a hook from errSuite
|
|
872
|
+
if (errSuite === suite) {
|
|
873
|
+
// if errSuite is current suite
|
|
874
|
+
// continue to the next sibling suite
|
|
875
|
+
return done();
|
|
876
|
+
}
|
|
877
|
+
// errSuite is among the parents of current suite
|
|
878
|
+
// stop execution of errSuite and all sub-suites
|
|
879
|
+
return done(errSuite);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (self._abort) {
|
|
883
|
+
return done();
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
var curr = suite.suites[i++];
|
|
887
|
+
if (!curr) {
|
|
888
|
+
return done();
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Avoid grep neglecting large number of tests causing a
|
|
892
|
+
// huge recursive loop and thus a maximum call stack error.
|
|
893
|
+
// See comment in `this.runTests()` for more information.
|
|
894
|
+
if (self._grep !== self._defaultGrep) {
|
|
895
|
+
Runner.immediately(function() {
|
|
896
|
+
self.runSuite(curr, next);
|
|
897
|
+
});
|
|
898
|
+
} else {
|
|
899
|
+
self.runSuite(curr, next);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function done(errSuite) {
|
|
904
|
+
self.suite = suite;
|
|
905
|
+
self.nextSuite = next;
|
|
906
|
+
|
|
907
|
+
// remove reference to test
|
|
908
|
+
delete self.test;
|
|
909
|
+
|
|
910
|
+
self.hook(HOOK_TYPE_AFTER_ALL, function() {
|
|
911
|
+
self.emit(constants.EVENT_SUITE_END, suite);
|
|
912
|
+
fn(errSuite);
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
this.nextSuite = next;
|
|
917
|
+
|
|
918
|
+
this.hook(HOOK_TYPE_BEFORE_ALL, function(err) {
|
|
919
|
+
if (err) {
|
|
920
|
+
return done();
|
|
921
|
+
}
|
|
922
|
+
self.runTests(suite, next);
|
|
923
|
+
});
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Handle uncaught exceptions within runner.
|
|
928
|
+
*
|
|
929
|
+
* This function is bound to the instance as `Runner#uncaught` at instantiation
|
|
930
|
+
* time. It's intended to be listening on the `Process.uncaughtException` event.
|
|
931
|
+
* In order to not leak EE listeners, we need to ensure no more than a single
|
|
932
|
+
* `uncaughtException` listener exists per `Runner`. The only way to do
|
|
933
|
+
* this--because this function needs the context (and we don't have lambdas)--is
|
|
934
|
+
* to use `Function.prototype.bind`. We need strict equality to unregister and
|
|
935
|
+
* _only_ unregister the _one_ listener we set from the
|
|
936
|
+
* `Process.uncaughtException` event; would be poor form to just remove
|
|
937
|
+
* everything. See {@link Runner#run} for where the event listener is registered
|
|
938
|
+
* and unregistered.
|
|
939
|
+
* @param {Error} err - Some uncaught error
|
|
940
|
+
* @private
|
|
941
|
+
*/
|
|
942
|
+
Runner.prototype._uncaught = function(err) {
|
|
943
|
+
// this is defensive to prevent future developers from mis-calling this function.
|
|
944
|
+
// it's more likely that it'd be called with the incorrect context--say, the global
|
|
945
|
+
// `process` object--than it would to be called with a context that is not a "subclass"
|
|
946
|
+
// of `Runner`.
|
|
947
|
+
if (!(this instanceof Runner)) {
|
|
948
|
+
throw createFatalError(
|
|
949
|
+
'Runner#uncaught() called with invalid context',
|
|
950
|
+
this
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
if (err instanceof Pending) {
|
|
954
|
+
debug('uncaught(): caught a Pending');
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
// browser does not exit script when throwing in global.onerror()
|
|
958
|
+
if (this.allowUncaught && !utils.isBrowser()) {
|
|
959
|
+
debug('uncaught(): bubbling exception due to --allow-uncaught');
|
|
960
|
+
throw err;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (this.state === constants.STATE_STOPPED) {
|
|
964
|
+
debug('uncaught(): throwing after run has completed!');
|
|
965
|
+
throw err;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (err) {
|
|
969
|
+
debug('uncaught(): got truthy exception %O', err);
|
|
970
|
+
} else {
|
|
971
|
+
debug('uncaught(): undefined/falsy exception');
|
|
972
|
+
err = createInvalidExceptionError(
|
|
973
|
+
'Caught falsy/undefined exception which would otherwise be uncaught. No stack trace found; try a debugger',
|
|
974
|
+
err
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (!isError(err)) {
|
|
979
|
+
err = thrown2Error(err);
|
|
980
|
+
debug('uncaught(): converted "error" %o to Error', err);
|
|
981
|
+
}
|
|
982
|
+
err.uncaught = true;
|
|
983
|
+
|
|
984
|
+
var runnable = this.currentRunnable;
|
|
985
|
+
|
|
986
|
+
if (!runnable) {
|
|
987
|
+
runnable = new Runnable('Uncaught error outside test suite');
|
|
988
|
+
debug('uncaught(): no current Runnable; created a phony one');
|
|
989
|
+
runnable.parent = this.suite;
|
|
990
|
+
|
|
991
|
+
if (this.state === constants.STATE_RUNNING) {
|
|
992
|
+
debug('uncaught(): failing gracefully');
|
|
993
|
+
this.fail(runnable, err);
|
|
994
|
+
} else {
|
|
995
|
+
// Can't recover from this failure
|
|
996
|
+
debug('uncaught(): test run has not yet started; unrecoverable');
|
|
997
|
+
this.emit(constants.EVENT_RUN_BEGIN);
|
|
998
|
+
this.fail(runnable, err);
|
|
999
|
+
this.emit(constants.EVENT_RUN_END);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
runnable.clearTimeout();
|
|
1006
|
+
|
|
1007
|
+
if (runnable.isFailed()) {
|
|
1008
|
+
debug('uncaught(): Runnable has already failed');
|
|
1009
|
+
// Ignore error if already failed
|
|
1010
|
+
return;
|
|
1011
|
+
} else if (runnable.isPending()) {
|
|
1012
|
+
debug('uncaught(): pending Runnable wound up failing!');
|
|
1013
|
+
// report 'pending test' retrospectively as failed
|
|
1014
|
+
this.fail(runnable, err, true);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// we cannot recover gracefully if a Runnable has already passed
|
|
1019
|
+
// then fails asynchronously
|
|
1020
|
+
if (runnable.isPassed()) {
|
|
1021
|
+
debug('uncaught(): Runnable has already passed; bailing gracefully');
|
|
1022
|
+
this.fail(runnable, err);
|
|
1023
|
+
this.abort();
|
|
1024
|
+
} else {
|
|
1025
|
+
debug('uncaught(): forcing Runnable to complete with Error');
|
|
1026
|
+
return runnable.callback(err);
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Run the root suite and invoke `fn(failures)`
|
|
1032
|
+
* on completion.
|
|
1033
|
+
*
|
|
1034
|
+
* @public
|
|
1035
|
+
* @memberof Runner
|
|
1036
|
+
* @param {Function} fn - Callback when finished
|
|
1037
|
+
* @param {{files: string[], options: Options}} [opts] - For subclasses
|
|
1038
|
+
* @returns {Runner} Runner instance.
|
|
1039
|
+
*/
|
|
1040
|
+
Runner.prototype.run = function(fn, opts = {}) {
|
|
1041
|
+
var rootSuite = this.suite;
|
|
1042
|
+
var options = opts.options || {};
|
|
1043
|
+
|
|
1044
|
+
debug('run(): got options: %O', options);
|
|
1045
|
+
fn = fn || function() {};
|
|
1046
|
+
|
|
1047
|
+
const end = () => {
|
|
1048
|
+
if (!this.total && this._opts.failZero) this.failures = 1;
|
|
1049
|
+
|
|
1050
|
+
debug('run(): root suite completed; emitting %s', constants.EVENT_RUN_END);
|
|
1051
|
+
this.emit(constants.EVENT_RUN_END);
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
const begin = () => {
|
|
1055
|
+
debug('run(): emitting %s', constants.EVENT_RUN_BEGIN);
|
|
1056
|
+
this.emit(constants.EVENT_RUN_BEGIN);
|
|
1057
|
+
debug('run(): emitted %s', constants.EVENT_RUN_BEGIN);
|
|
1058
|
+
|
|
1059
|
+
this.runSuite(rootSuite, end);
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
const prepare = () => {
|
|
1063
|
+
debug('run(): starting');
|
|
1064
|
+
// If there is an `only` filter
|
|
1065
|
+
if (rootSuite.hasOnly()) {
|
|
1066
|
+
rootSuite.filterOnly();
|
|
1067
|
+
debug('run(): filtered exclusive Runnables');
|
|
1068
|
+
}
|
|
1069
|
+
this.state = constants.STATE_RUNNING;
|
|
1070
|
+
if (this._delay) {
|
|
1071
|
+
this.emit(constants.EVENT_DELAY_END);
|
|
1072
|
+
debug('run(): "delay" ended');
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
return begin();
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
// references cleanup to avoid memory leaks
|
|
1079
|
+
if (this._opts.cleanReferencesAfterRun) {
|
|
1080
|
+
this.on(constants.EVENT_SUITE_END, suite => {
|
|
1081
|
+
suite.cleanReferences();
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// callback
|
|
1086
|
+
this.on(constants.EVENT_RUN_END, function() {
|
|
1087
|
+
this.state = constants.STATE_STOPPED;
|
|
1088
|
+
debug('run(): emitted %s', constants.EVENT_RUN_END);
|
|
1089
|
+
fn(this.failures);
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
this._removeEventListener(process, 'uncaughtException', this.uncaught);
|
|
1093
|
+
this._removeEventListener(process, 'unhandledRejection', this.unhandled);
|
|
1094
|
+
this._addEventListener(process, 'uncaughtException', this.uncaught);
|
|
1095
|
+
this._addEventListener(process, 'unhandledRejection', this.unhandled);
|
|
1096
|
+
|
|
1097
|
+
if (this._delay) {
|
|
1098
|
+
// for reporters, I guess.
|
|
1099
|
+
// might be nice to debounce some dots while we wait.
|
|
1100
|
+
this.emit(constants.EVENT_DELAY_BEGIN, rootSuite);
|
|
1101
|
+
rootSuite.once(EVENT_ROOT_SUITE_RUN, prepare);
|
|
1102
|
+
debug('run(): waiting for green light due to --delay');
|
|
1103
|
+
} else {
|
|
1104
|
+
Runner.immediately(prepare);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
return this;
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* Toggle partial object linking behavior; used for building object references from
|
|
1112
|
+
* unique ID's. Does nothing in serial mode, because the object references already exist.
|
|
1113
|
+
* Subclasses can implement this (e.g., `ParallelBufferedRunner`)
|
|
1114
|
+
* @abstract
|
|
1115
|
+
* @param {boolean} [value] - If `true`, enable partial object linking, otherwise disable
|
|
1116
|
+
* @returns {Runner}
|
|
1117
|
+
* @chainable
|
|
1118
|
+
* @public
|
|
1119
|
+
* @example
|
|
1120
|
+
* // this reporter needs proper object references when run in parallel mode
|
|
1121
|
+
* class MyReporter() {
|
|
1122
|
+
* constructor(runner) {
|
|
1123
|
+
* this.runner.linkPartialObjects(true)
|
|
1124
|
+
* .on(EVENT_SUITE_BEGIN, suite => {
|
|
1125
|
+
// this Suite may be the same object...
|
|
1126
|
+
* })
|
|
1127
|
+
* .on(EVENT_TEST_BEGIN, test => {
|
|
1128
|
+
* // ...as the `test.parent` property
|
|
1129
|
+
* });
|
|
1130
|
+
* }
|
|
1131
|
+
* }
|
|
1132
|
+
*/
|
|
1133
|
+
Runner.prototype.linkPartialObjects = function(value) {
|
|
1134
|
+
return this;
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
/*
|
|
1138
|
+
* Like {@link Runner#run}, but does not accept a callback and returns a `Promise` instead of a `Runner`.
|
|
1139
|
+
* This function cannot reject; an `unhandledRejection` event will bubble up to the `process` object instead.
|
|
1140
|
+
* @public
|
|
1141
|
+
* @memberof Runner
|
|
1142
|
+
* @param {Object} [opts] - Options for {@link Runner#run}
|
|
1143
|
+
* @returns {Promise<number>} Failure count
|
|
1144
|
+
*/
|
|
1145
|
+
Runner.prototype.runAsync = async function runAsync(opts = {}) {
|
|
1146
|
+
return new Promise(resolve => {
|
|
1147
|
+
this.run(resolve, opts);
|
|
1148
|
+
});
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* Cleanly abort execution.
|
|
1153
|
+
*
|
|
1154
|
+
* @memberof Runner
|
|
1155
|
+
* @public
|
|
1156
|
+
* @return {Runner} Runner instance.
|
|
1157
|
+
*/
|
|
1158
|
+
Runner.prototype.abort = function() {
|
|
1159
|
+
debug('abort(): aborting');
|
|
1160
|
+
this._abort = true;
|
|
1161
|
+
|
|
1162
|
+
return this;
|
|
1163
|
+
};
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Returns `true` if Mocha is running in parallel mode. For reporters.
|
|
1167
|
+
*
|
|
1168
|
+
* Subclasses should return an appropriate value.
|
|
1169
|
+
* @public
|
|
1170
|
+
* @returns {false}
|
|
1171
|
+
*/
|
|
1172
|
+
Runner.prototype.isParallelMode = function isParallelMode() {
|
|
1173
|
+
return false;
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Configures an alternate reporter for worker processes to use. Subclasses
|
|
1178
|
+
* using worker processes should implement this.
|
|
1179
|
+
* @public
|
|
1180
|
+
* @param {string} path - Absolute path to alternate reporter for worker processes to use
|
|
1181
|
+
* @returns {Runner}
|
|
1182
|
+
* @throws When in serial mode
|
|
1183
|
+
* @chainable
|
|
1184
|
+
* @abstract
|
|
1185
|
+
*/
|
|
1186
|
+
Runner.prototype.workerReporter = function() {
|
|
1187
|
+
throw createUnsupportedError('workerReporter() not supported in serial mode');
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Filter leaks with the given globals flagged as `ok`.
|
|
1192
|
+
*
|
|
1193
|
+
* @private
|
|
1194
|
+
* @param {Array} ok
|
|
1195
|
+
* @param {Array} globals
|
|
1196
|
+
* @return {Array}
|
|
1197
|
+
*/
|
|
1198
|
+
function filterLeaks(ok, globals) {
|
|
1199
|
+
return globals.filter(function(key) {
|
|
1200
|
+
// Firefox and Chrome exposes iframes as index inside the window object
|
|
1201
|
+
if (/^\d+/.test(key)) {
|
|
1202
|
+
return false;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// in firefox
|
|
1206
|
+
// if runner runs in an iframe, this iframe's window.getInterface method
|
|
1207
|
+
// not init at first it is assigned in some seconds
|
|
1208
|
+
if (global.navigator && /^getInterface/.test(key)) {
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// an iframe could be approached by window[iframeIndex]
|
|
1213
|
+
// in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak
|
|
1214
|
+
if (global.navigator && /^\d+/.test(key)) {
|
|
1215
|
+
return false;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Opera and IE expose global variables for HTML element IDs (issue #243)
|
|
1219
|
+
if (/^mocha-/.test(key)) {
|
|
1220
|
+
return false;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
var matched = ok.filter(function(ok) {
|
|
1224
|
+
if (~ok.indexOf('*')) {
|
|
1225
|
+
return key.indexOf(ok.split('*')[0]) === 0;
|
|
1226
|
+
}
|
|
1227
|
+
return key === ok;
|
|
1228
|
+
});
|
|
1229
|
+
return !matched.length && (!global.navigator || key !== 'onerror');
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* Check if argument is an instance of Error object or a duck-typed equivalent.
|
|
1235
|
+
*
|
|
1236
|
+
* @private
|
|
1237
|
+
* @param {Object} err - object to check
|
|
1238
|
+
* @param {string} err.message - error message
|
|
1239
|
+
* @returns {boolean}
|
|
1240
|
+
*/
|
|
1241
|
+
function isError(err) {
|
|
1242
|
+
return err instanceof Error || (err && typeof err.message === 'string');
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
*
|
|
1247
|
+
* Converts thrown non-extensible type into proper Error.
|
|
1248
|
+
*
|
|
1249
|
+
* @private
|
|
1250
|
+
* @param {*} thrown - Non-extensible type thrown by code
|
|
1251
|
+
* @return {Error}
|
|
1252
|
+
*/
|
|
1253
|
+
function thrown2Error(err) {
|
|
1254
|
+
return new Error(
|
|
1255
|
+
`the ${utils.canonicalType(err)} ${stringify(
|
|
1256
|
+
err
|
|
1257
|
+
)} was thrown, throw an Error :)`
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
Runner.constants = constants;
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* Node.js' `EventEmitter`
|
|
1265
|
+
* @external EventEmitter
|
|
1266
|
+
* @see {@link https://nodejs.org/api/events.html#events_class_eventemitter}
|
|
1267
|
+
*/
|
|
1268
|
+
|
|
1269
|
+
module.exports = Runner;
|