blob: 4289676edc4153d53a0cd2c8e610260da5061f4b [file] [log] [blame]
Yang Guo4fd355c2019-09-19 10:59:03 +02001'use strict';
Peter Marshall0b95ea12020-07-02 18:50:04 +02002
Peter Marshall4e161df2020-11-10 13:29:38 +01003const {format} = require('util');
Peter Marshall0b95ea12020-07-02 18:50:04 +02004
Yang Guo4fd355c2019-09-19 10:59:03 +02005/**
Peter Marshall4e161df2020-11-10 13:29:38 +01006 * Contains error codes, factory functions to create throwable error objects,
7 * and warning/deprecation functions.
8 * @module
Yang Guo4fd355c2019-09-19 10:59:03 +02009 */
10
11/**
Peter Marshall4e161df2020-11-10 13:29:38 +010012 * process.emitWarning or a polyfill
13 * @see https://nodejs.org/api/process.html#process_process_emitwarning_warning_options
14 * @ignore
15 */
16const emitWarning = (msg, type) => {
17 if (process.emitWarning) {
18 process.emitWarning(msg, type);
19 } else {
Tim van der Lippe6d109a92021-02-16 16:00:32 +000020 /* istanbul ignore next */
Peter Marshall4e161df2020-11-10 13:29:38 +010021 process.nextTick(function() {
22 console.warn(type + ': ' + msg);
23 });
24 }
25};
26
27/**
28 * Show a deprecation warning. Each distinct message is only displayed once.
29 * Ignores empty messages.
30 *
31 * @param {string} [msg] - Warning to print
32 * @private
33 */
34const deprecate = msg => {
35 msg = String(msg);
36 if (msg && !deprecate.cache[msg]) {
37 deprecate.cache[msg] = true;
38 emitWarning(msg, 'DeprecationWarning');
39 }
40};
41deprecate.cache = {};
42
43/**
44 * Show a generic warning.
45 * Ignores empty messages.
46 *
47 * @param {string} [msg] - Warning to print
48 * @private
49 */
50const warn = msg => {
51 if (msg) {
52 emitWarning(msg);
53 }
54};
55
56/**
Tim van der Lippe6d109a92021-02-16 16:00:32 +000057 * When Mocha throws exceptions (or rejects `Promise`s), it attempts to assign a `code` property to the `Error` object, for easier handling. These are the potential values of `code`.
58 * @public
59 * @namespace
60 * @memberof module:lib/errors
Peter Marshall0b95ea12020-07-02 18:50:04 +020061 */
62var constants = {
63 /**
64 * An unrecoverable error.
Tim van der Lippe6d109a92021-02-16 16:00:32 +000065 * @constant
66 * @default
Peter Marshall0b95ea12020-07-02 18:50:04 +020067 */
68 FATAL: 'ERR_MOCHA_FATAL',
69
70 /**
71 * The type of an argument to a function call is invalid
Tim van der Lippe6d109a92021-02-16 16:00:32 +000072 * @constant
73 * @default
Peter Marshall0b95ea12020-07-02 18:50:04 +020074 */
75 INVALID_ARG_TYPE: 'ERR_MOCHA_INVALID_ARG_TYPE',
76
77 /**
78 * The value of an argument to a function call is invalid
Tim van der Lippe6d109a92021-02-16 16:00:32 +000079 * @constant
80 * @default
Peter Marshall0b95ea12020-07-02 18:50:04 +020081 */
82 INVALID_ARG_VALUE: 'ERR_MOCHA_INVALID_ARG_VALUE',
83
84 /**
85 * Something was thrown, but it wasn't an `Error`
Tim van der Lippe6d109a92021-02-16 16:00:32 +000086 * @constant
87 * @default
Peter Marshall0b95ea12020-07-02 18:50:04 +020088 */
89 INVALID_EXCEPTION: 'ERR_MOCHA_INVALID_EXCEPTION',
90
91 /**
92 * An interface (e.g., `Mocha.interfaces`) is unknown or invalid
Tim van der Lippe6d109a92021-02-16 16:00:32 +000093 * @constant
94 * @default
Peter Marshall0b95ea12020-07-02 18:50:04 +020095 */
96 INVALID_INTERFACE: 'ERR_MOCHA_INVALID_INTERFACE',
97
98 /**
99 * A reporter (.e.g, `Mocha.reporters`) is unknown or invalid
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000100 * @constant
101 * @default
Peter Marshall0b95ea12020-07-02 18:50:04 +0200102 */
103 INVALID_REPORTER: 'ERR_MOCHA_INVALID_REPORTER',
104
105 /**
106 * `done()` was called twice in a `Test` or `Hook` callback
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000107 * @constant
108 * @default
Peter Marshall0b95ea12020-07-02 18:50:04 +0200109 */
110 MULTIPLE_DONE: 'ERR_MOCHA_MULTIPLE_DONE',
111
112 /**
113 * No files matched the pattern provided by the user
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000114 * @constant
115 * @default
Peter Marshall0b95ea12020-07-02 18:50:04 +0200116 */
117 NO_FILES_MATCH_PATTERN: 'ERR_MOCHA_NO_FILES_MATCH_PATTERN',
118
119 /**
120 * Known, but unsupported behavior of some kind
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000121 * @constant
122 * @default
Peter Marshall0b95ea12020-07-02 18:50:04 +0200123 */
124 UNSUPPORTED: 'ERR_MOCHA_UNSUPPORTED',
125
126 /**
Peter Marshall4e161df2020-11-10 13:29:38 +0100127 * Invalid state transition occurring in `Mocha` instance
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000128 * @constant
129 * @default
Peter Marshall0b95ea12020-07-02 18:50:04 +0200130 */
131 INSTANCE_ALREADY_RUNNING: 'ERR_MOCHA_INSTANCE_ALREADY_RUNNING',
132
133 /**
Peter Marshall4e161df2020-11-10 13:29:38 +0100134 * Invalid state transition occurring in `Mocha` instance
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000135 * @constant
136 * @default
Peter Marshall0b95ea12020-07-02 18:50:04 +0200137 */
138 INSTANCE_ALREADY_DISPOSED: 'ERR_MOCHA_INSTANCE_ALREADY_DISPOSED',
139
140 /**
141 * Use of `only()` w/ `--forbid-only` results in this error.
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000142 * @constant
143 * @default
Peter Marshall0b95ea12020-07-02 18:50:04 +0200144 */
Peter Marshall4e161df2020-11-10 13:29:38 +0100145 FORBIDDEN_EXCLUSIVITY: 'ERR_MOCHA_FORBIDDEN_EXCLUSIVITY',
146
147 /**
148 * To be thrown when a user-defined plugin implementation (e.g., `mochaHooks`) is invalid
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000149 * @constant
150 * @default
Peter Marshall4e161df2020-11-10 13:29:38 +0100151 */
152 INVALID_PLUGIN_IMPLEMENTATION: 'ERR_MOCHA_INVALID_PLUGIN_IMPLEMENTATION',
153
154 /**
155 * To be thrown when a builtin or third-party plugin definition (the _definition_ of `mochaHooks`) is invalid
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000156 * @constant
157 * @default
Peter Marshall4e161df2020-11-10 13:29:38 +0100158 */
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000159 INVALID_PLUGIN_DEFINITION: 'ERR_MOCHA_INVALID_PLUGIN_DEFINITION',
160
161 /**
162 * When a runnable exceeds its allowed run time.
163 * @constant
164 * @default
165 */
166 TIMEOUT: 'ERR_MOCHA_TIMEOUT'
Peter Marshall0b95ea12020-07-02 18:50:04 +0200167};
168
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000169/**
170 * A set containing all string values of all Mocha error constants, for use by {@link isMochaError}.
171 * @private
172 */
Peter Marshall4e161df2020-11-10 13:29:38 +0100173const MOCHA_ERRORS = new Set(Object.values(constants));
174
Peter Marshall0b95ea12020-07-02 18:50:04 +0200175/**
Yang Guo4fd355c2019-09-19 10:59:03 +0200176 * Creates an error object to be thrown when no files to be tested could be found using specified pattern.
177 *
178 * @public
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000179 * @static
Yang Guo4fd355c2019-09-19 10:59:03 +0200180 * @param {string} message - Error message to be displayed.
181 * @param {string} pattern - User-specified argument value.
182 * @returns {Error} instance detailing the error condition
183 */
184function createNoFilesMatchPatternError(message, pattern) {
185 var err = new Error(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200186 err.code = constants.NO_FILES_MATCH_PATTERN;
Yang Guo4fd355c2019-09-19 10:59:03 +0200187 err.pattern = pattern;
188 return err;
189}
190
191/**
192 * Creates an error object to be thrown when the reporter specified in the options was not found.
193 *
194 * @public
195 * @param {string} message - Error message to be displayed.
196 * @param {string} reporter - User-specified reporter value.
197 * @returns {Error} instance detailing the error condition
198 */
199function createInvalidReporterError(message, reporter) {
200 var err = new TypeError(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200201 err.code = constants.INVALID_REPORTER;
Yang Guo4fd355c2019-09-19 10:59:03 +0200202 err.reporter = reporter;
203 return err;
204}
205
206/**
207 * Creates an error object to be thrown when the interface specified in the options was not found.
208 *
209 * @public
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000210 * @static
Yang Guo4fd355c2019-09-19 10:59:03 +0200211 * @param {string} message - Error message to be displayed.
212 * @param {string} ui - User-specified interface value.
213 * @returns {Error} instance detailing the error condition
214 */
215function createInvalidInterfaceError(message, ui) {
216 var err = new Error(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200217 err.code = constants.INVALID_INTERFACE;
Yang Guo4fd355c2019-09-19 10:59:03 +0200218 err.interface = ui;
219 return err;
220}
221
222/**
223 * Creates an error object to be thrown when a behavior, option, or parameter is unsupported.
224 *
225 * @public
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000226 * @static
Yang Guo4fd355c2019-09-19 10:59:03 +0200227 * @param {string} message - Error message to be displayed.
228 * @returns {Error} instance detailing the error condition
229 */
230function createUnsupportedError(message) {
231 var err = new Error(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200232 err.code = constants.UNSUPPORTED;
Yang Guo4fd355c2019-09-19 10:59:03 +0200233 return err;
234}
235
236/**
237 * Creates an error object to be thrown when an argument is missing.
238 *
239 * @public
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000240 * @static
Yang Guo4fd355c2019-09-19 10:59:03 +0200241 * @param {string} message - Error message to be displayed.
242 * @param {string} argument - Argument name.
243 * @param {string} expected - Expected argument datatype.
244 * @returns {Error} instance detailing the error condition
245 */
246function createMissingArgumentError(message, argument, expected) {
247 return createInvalidArgumentTypeError(message, argument, expected);
248}
249
250/**
251 * Creates an error object to be thrown when an argument did not use the supported type
252 *
253 * @public
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000254 * @static
Yang Guo4fd355c2019-09-19 10:59:03 +0200255 * @param {string} message - Error message to be displayed.
256 * @param {string} argument - Argument name.
257 * @param {string} expected - Expected argument datatype.
258 * @returns {Error} instance detailing the error condition
259 */
260function createInvalidArgumentTypeError(message, argument, expected) {
261 var err = new TypeError(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200262 err.code = constants.INVALID_ARG_TYPE;
Yang Guo4fd355c2019-09-19 10:59:03 +0200263 err.argument = argument;
264 err.expected = expected;
265 err.actual = typeof argument;
266 return err;
267}
268
269/**
270 * Creates an error object to be thrown when an argument did not use the supported value
271 *
272 * @public
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000273 * @static
Yang Guo4fd355c2019-09-19 10:59:03 +0200274 * @param {string} message - Error message to be displayed.
275 * @param {string} argument - Argument name.
276 * @param {string} value - Argument value.
277 * @param {string} [reason] - Why value is invalid.
278 * @returns {Error} instance detailing the error condition
279 */
280function createInvalidArgumentValueError(message, argument, value, reason) {
281 var err = new TypeError(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200282 err.code = constants.INVALID_ARG_VALUE;
Yang Guo4fd355c2019-09-19 10:59:03 +0200283 err.argument = argument;
284 err.value = value;
285 err.reason = typeof reason !== 'undefined' ? reason : 'is invalid';
286 return err;
287}
288
289/**
290 * Creates an error object to be thrown when an exception was caught, but the `Error` is falsy or undefined.
291 *
292 * @public
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000293 * @static
Yang Guo4fd355c2019-09-19 10:59:03 +0200294 * @param {string} message - Error message to be displayed.
295 * @returns {Error} instance detailing the error condition
296 */
297function createInvalidExceptionError(message, value) {
298 var err = new Error(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200299 err.code = constants.INVALID_EXCEPTION;
Yang Guo4fd355c2019-09-19 10:59:03 +0200300 err.valueType = typeof value;
301 err.value = value;
302 return err;
303}
304
Peter Marshall0b95ea12020-07-02 18:50:04 +0200305/**
306 * Creates an error object to be thrown when an unrecoverable error occurs.
307 *
308 * @public
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000309 * @static
Peter Marshall0b95ea12020-07-02 18:50:04 +0200310 * @param {string} message - Error message to be displayed.
311 * @returns {Error} instance detailing the error condition
312 */
313function createFatalError(message, value) {
314 var err = new Error(message);
315 err.code = constants.FATAL;
316 err.valueType = typeof value;
317 err.value = value;
318 return err;
319}
320
321/**
322 * Dynamically creates a plugin-type-specific error based on plugin type
323 * @param {string} message - Error message
324 * @param {"reporter"|"interface"} pluginType - Plugin type. Future: expand as needed
325 * @param {string} [pluginId] - Name/path of plugin, if any
326 * @throws When `pluginType` is not known
327 * @public
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000328 * @static
Peter Marshall0b95ea12020-07-02 18:50:04 +0200329 * @returns {Error}
330 */
Peter Marshall4e161df2020-11-10 13:29:38 +0100331function createInvalidLegacyPluginError(message, pluginType, pluginId) {
Peter Marshall0b95ea12020-07-02 18:50:04 +0200332 switch (pluginType) {
333 case 'reporter':
334 return createInvalidReporterError(message, pluginId);
335 case 'interface':
336 return createInvalidInterfaceError(message, pluginId);
337 default:
338 throw new Error('unknown pluginType "' + pluginType + '"');
339 }
340}
341
342/**
Peter Marshall4e161df2020-11-10 13:29:38 +0100343 * **DEPRECATED**. Use {@link createInvalidLegacyPluginError} instead Dynamically creates a plugin-type-specific error based on plugin type
344 * @deprecated
345 * @param {string} message - Error message
346 * @param {"reporter"|"interface"} pluginType - Plugin type. Future: expand as needed
347 * @param {string} [pluginId] - Name/path of plugin, if any
348 * @throws When `pluginType` is not known
349 * @public
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000350 * @static
Peter Marshall4e161df2020-11-10 13:29:38 +0100351 * @returns {Error}
352 */
353function createInvalidPluginError(...args) {
354 deprecate('Use createInvalidLegacyPluginError() instead');
355 return createInvalidLegacyPluginError(...args);
356}
357
358/**
Peter Marshall0b95ea12020-07-02 18:50:04 +0200359 * Creates an error object to be thrown when a mocha object's `run` method is executed while it is already disposed.
360 * @param {string} message The error message to be displayed.
361 * @param {boolean} cleanReferencesAfterRun the value of `cleanReferencesAfterRun`
362 * @param {Mocha} instance the mocha instance that throw this error
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000363 * @static
Peter Marshall0b95ea12020-07-02 18:50:04 +0200364 */
365function createMochaInstanceAlreadyDisposedError(
366 message,
367 cleanReferencesAfterRun,
368 instance
369) {
370 var err = new Error(message);
371 err.code = constants.INSTANCE_ALREADY_DISPOSED;
372 err.cleanReferencesAfterRun = cleanReferencesAfterRun;
373 err.instance = instance;
374 return err;
375}
376
377/**
378 * Creates an error object to be thrown when a mocha object's `run` method is called while a test run is in progress.
379 * @param {string} message The error message to be displayed.
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000380 * @static
381 * @public
Peter Marshall0b95ea12020-07-02 18:50:04 +0200382 */
383function createMochaInstanceAlreadyRunningError(message, instance) {
384 var err = new Error(message);
385 err.code = constants.INSTANCE_ALREADY_RUNNING;
386 err.instance = instance;
387 return err;
388}
389
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000390/**
Peter Marshall0b95ea12020-07-02 18:50:04 +0200391 * Creates an error object to be thrown when done() is called multiple times in a test
392 *
393 * @public
394 * @param {Runnable} runnable - Original runnable
395 * @param {Error} [originalErr] - Original error, if any
396 * @returns {Error} instance detailing the error condition
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000397 * @static
Peter Marshall0b95ea12020-07-02 18:50:04 +0200398 */
399function createMultipleDoneError(runnable, originalErr) {
400 var title;
401 try {
402 title = format('<%s>', runnable.fullTitle());
403 if (runnable.parent.root) {
404 title += ' (of root suite)';
405 }
406 } catch (ignored) {
407 title = format('<%s> (of unknown suite)', runnable.title);
408 }
409 var message = format(
410 'done() called multiple times in %s %s',
411 runnable.type ? runnable.type : 'unknown runnable',
412 title
413 );
414 if (runnable.file) {
415 message += format(' of file %s', runnable.file);
416 }
417 if (originalErr) {
418 message += format('; in addition, done() received error: %s', originalErr);
419 }
420
421 var err = new Error(message);
422 err.code = constants.MULTIPLE_DONE;
423 err.valueType = typeof originalErr;
424 err.value = originalErr;
425 return err;
426}
427
428/**
429 * Creates an error object to be thrown when `.only()` is used with
430 * `--forbid-only`.
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000431 * @static
Peter Marshall0b95ea12020-07-02 18:50:04 +0200432 * @public
433 * @param {Mocha} mocha - Mocha instance
434 * @returns {Error} Error with code {@link constants.FORBIDDEN_EXCLUSIVITY}
435 */
436function createForbiddenExclusivityError(mocha) {
437 var err = new Error(
438 mocha.isWorker
439 ? '`.only` is not supported in parallel mode'
440 : '`.only` forbidden by --forbid-only'
441 );
442 err.code = constants.FORBIDDEN_EXCLUSIVITY;
443 return err;
444}
445
Peter Marshall4e161df2020-11-10 13:29:38 +0100446/**
447 * Creates an error object to be thrown when a plugin definition is invalid
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000448 * @static
Peter Marshall4e161df2020-11-10 13:29:38 +0100449 * @param {string} msg - Error message
450 * @param {PluginDefinition} [pluginDef] - Problematic plugin definition
451 * @public
452 * @returns {Error} Error with code {@link constants.INVALID_PLUGIN_DEFINITION}
453 */
454function createInvalidPluginDefinitionError(msg, pluginDef) {
455 const err = new Error(msg);
456 err.code = constants.INVALID_PLUGIN_DEFINITION;
457 err.pluginDef = pluginDef;
458 return err;
459}
460
461/**
462 * Creates an error object to be thrown when a plugin implementation (user code) is invalid
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000463 * @static
Peter Marshall4e161df2020-11-10 13:29:38 +0100464 * @param {string} msg - Error message
465 * @param {Object} [opts] - Plugin definition and user-supplied implementation
466 * @param {PluginDefinition} [opts.pluginDef] - Plugin Definition
467 * @param {*} [opts.pluginImpl] - Plugin Implementation (user-supplied)
468 * @public
469 * @returns {Error} Error with code {@link constants.INVALID_PLUGIN_DEFINITION}
470 */
471function createInvalidPluginImplementationError(
472 msg,
473 {pluginDef, pluginImpl} = {}
474) {
475 const err = new Error(msg);
476 err.code = constants.INVALID_PLUGIN_IMPLEMENTATION;
477 err.pluginDef = pluginDef;
478 err.pluginImpl = pluginImpl;
479 return err;
480}
481
482/**
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000483 * Creates an error object to be thrown when a runnable exceeds its allowed run time.
484 * @static
485 * @param {string} msg - Error message
486 * @param {number} [timeout] - Timeout in ms
487 * @param {string} [file] - File, if given
488 * @returns {MochaTimeoutError}
489 */
490function createTimeoutError(msg, timeout, file) {
491 const err = new Error(msg);
492 err.code = constants.TIMEOUT;
493 err.timeout = timeout;
494 err.file = file;
495 return err;
496}
497
498/**
Peter Marshall4e161df2020-11-10 13:29:38 +0100499 * Returns `true` if an error came out of Mocha.
500 * _Can suffer from false negatives, but not false positives._
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000501 * @static
Peter Marshall4e161df2020-11-10 13:29:38 +0100502 * @public
503 * @param {*} err - Error, or anything
504 * @returns {boolean}
505 */
506const isMochaError = err =>
507 Boolean(err && typeof err === 'object' && MOCHA_ERRORS.has(err.code));
508
Yang Guo4fd355c2019-09-19 10:59:03 +0200509module.exports = {
Peter Marshall4e161df2020-11-10 13:29:38 +0100510 constants,
511 createFatalError,
512 createForbiddenExclusivityError,
513 createInvalidArgumentTypeError,
514 createInvalidArgumentValueError,
515 createInvalidExceptionError,
516 createInvalidInterfaceError,
517 createInvalidLegacyPluginError,
518 createInvalidPluginDefinitionError,
519 createInvalidPluginError,
520 createInvalidPluginImplementationError,
521 createInvalidReporterError,
522 createMissingArgumentError,
523 createMochaInstanceAlreadyDisposedError,
524 createMochaInstanceAlreadyRunningError,
525 createMultipleDoneError,
526 createNoFilesMatchPatternError,
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000527 createTimeoutError,
Peter Marshall4e161df2020-11-10 13:29:38 +0100528 createUnsupportedError,
529 deprecate,
530 isMochaError,
531 warn
Yang Guo4fd355c2019-09-19 10:59:03 +0200532};
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000533
534/**
535 * The error thrown when a Runnable times out
536 * @memberof module:lib/errors
537 * @typedef {Error} MochaTimeoutError
538 * @property {constants.TIMEOUT} code - Error code
539 * @property {number?} timeout Timeout in ms
540 * @property {string?} file Filepath, if given
541 */