blob: 29f76c61b58c0b7dd1af8a2fcf86d462613bf759 [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 {
20 process.nextTick(function() {
21 console.warn(type + ': ' + msg);
22 });
23 }
24};
25
26/**
27 * Show a deprecation warning. Each distinct message is only displayed once.
28 * Ignores empty messages.
29 *
30 * @param {string} [msg] - Warning to print
31 * @private
32 */
33const deprecate = msg => {
34 msg = String(msg);
35 if (msg && !deprecate.cache[msg]) {
36 deprecate.cache[msg] = true;
37 emitWarning(msg, 'DeprecationWarning');
38 }
39};
40deprecate.cache = {};
41
42/**
43 * Show a generic warning.
44 * Ignores empty messages.
45 *
46 * @param {string} [msg] - Warning to print
47 * @private
48 */
49const warn = msg => {
50 if (msg) {
51 emitWarning(msg);
52 }
53};
54
55/**
Peter Marshall0b95ea12020-07-02 18:50:04 +020056 * When Mocha throw exceptions (or otherwise errors), it attempts to assign a
57 * `code` property to the `Error` object, for easier handling. These are the
58 * potential values of `code`.
59 */
60var constants = {
61 /**
62 * An unrecoverable error.
63 */
64 FATAL: 'ERR_MOCHA_FATAL',
65
66 /**
67 * The type of an argument to a function call is invalid
68 */
69 INVALID_ARG_TYPE: 'ERR_MOCHA_INVALID_ARG_TYPE',
70
71 /**
72 * The value of an argument to a function call is invalid
73 */
74 INVALID_ARG_VALUE: 'ERR_MOCHA_INVALID_ARG_VALUE',
75
76 /**
77 * Something was thrown, but it wasn't an `Error`
78 */
79 INVALID_EXCEPTION: 'ERR_MOCHA_INVALID_EXCEPTION',
80
81 /**
82 * An interface (e.g., `Mocha.interfaces`) is unknown or invalid
83 */
84 INVALID_INTERFACE: 'ERR_MOCHA_INVALID_INTERFACE',
85
86 /**
87 * A reporter (.e.g, `Mocha.reporters`) is unknown or invalid
88 */
89 INVALID_REPORTER: 'ERR_MOCHA_INVALID_REPORTER',
90
91 /**
92 * `done()` was called twice in a `Test` or `Hook` callback
93 */
94 MULTIPLE_DONE: 'ERR_MOCHA_MULTIPLE_DONE',
95
96 /**
97 * No files matched the pattern provided by the user
98 */
99 NO_FILES_MATCH_PATTERN: 'ERR_MOCHA_NO_FILES_MATCH_PATTERN',
100
101 /**
102 * Known, but unsupported behavior of some kind
103 */
104 UNSUPPORTED: 'ERR_MOCHA_UNSUPPORTED',
105
106 /**
Peter Marshall4e161df2020-11-10 13:29:38 +0100107 * Invalid state transition occurring in `Mocha` instance
Peter Marshall0b95ea12020-07-02 18:50:04 +0200108 */
109 INSTANCE_ALREADY_RUNNING: 'ERR_MOCHA_INSTANCE_ALREADY_RUNNING',
110
111 /**
Peter Marshall4e161df2020-11-10 13:29:38 +0100112 * Invalid state transition occurring in `Mocha` instance
Peter Marshall0b95ea12020-07-02 18:50:04 +0200113 */
114 INSTANCE_ALREADY_DISPOSED: 'ERR_MOCHA_INSTANCE_ALREADY_DISPOSED',
115
116 /**
117 * Use of `only()` w/ `--forbid-only` results in this error.
118 */
Peter Marshall4e161df2020-11-10 13:29:38 +0100119 FORBIDDEN_EXCLUSIVITY: 'ERR_MOCHA_FORBIDDEN_EXCLUSIVITY',
120
121 /**
122 * To be thrown when a user-defined plugin implementation (e.g., `mochaHooks`) is invalid
123 */
124 INVALID_PLUGIN_IMPLEMENTATION: 'ERR_MOCHA_INVALID_PLUGIN_IMPLEMENTATION',
125
126 /**
127 * To be thrown when a builtin or third-party plugin definition (the _definition_ of `mochaHooks`) is invalid
128 */
129 INVALID_PLUGIN_DEFINITION: 'ERR_MOCHA_INVALID_PLUGIN_DEFINITION'
Peter Marshall0b95ea12020-07-02 18:50:04 +0200130};
131
Peter Marshall4e161df2020-11-10 13:29:38 +0100132const MOCHA_ERRORS = new Set(Object.values(constants));
133
Peter Marshall0b95ea12020-07-02 18:50:04 +0200134/**
Yang Guo4fd355c2019-09-19 10:59:03 +0200135 * Creates an error object to be thrown when no files to be tested could be found using specified pattern.
136 *
137 * @public
138 * @param {string} message - Error message to be displayed.
139 * @param {string} pattern - User-specified argument value.
140 * @returns {Error} instance detailing the error condition
141 */
142function createNoFilesMatchPatternError(message, pattern) {
143 var err = new Error(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200144 err.code = constants.NO_FILES_MATCH_PATTERN;
Yang Guo4fd355c2019-09-19 10:59:03 +0200145 err.pattern = pattern;
146 return err;
147}
148
149/**
150 * Creates an error object to be thrown when the reporter specified in the options was not found.
151 *
152 * @public
153 * @param {string} message - Error message to be displayed.
154 * @param {string} reporter - User-specified reporter value.
155 * @returns {Error} instance detailing the error condition
156 */
157function createInvalidReporterError(message, reporter) {
158 var err = new TypeError(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200159 err.code = constants.INVALID_REPORTER;
Yang Guo4fd355c2019-09-19 10:59:03 +0200160 err.reporter = reporter;
161 return err;
162}
163
164/**
165 * Creates an error object to be thrown when the interface specified in the options was not found.
166 *
167 * @public
168 * @param {string} message - Error message to be displayed.
169 * @param {string} ui - User-specified interface value.
170 * @returns {Error} instance detailing the error condition
171 */
172function createInvalidInterfaceError(message, ui) {
173 var err = new Error(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200174 err.code = constants.INVALID_INTERFACE;
Yang Guo4fd355c2019-09-19 10:59:03 +0200175 err.interface = ui;
176 return err;
177}
178
179/**
180 * Creates an error object to be thrown when a behavior, option, or parameter is unsupported.
181 *
182 * @public
183 * @param {string} message - Error message to be displayed.
184 * @returns {Error} instance detailing the error condition
185 */
186function createUnsupportedError(message) {
187 var err = new Error(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200188 err.code = constants.UNSUPPORTED;
Yang Guo4fd355c2019-09-19 10:59:03 +0200189 return err;
190}
191
192/**
193 * Creates an error object to be thrown when an argument is missing.
194 *
195 * @public
196 * @param {string} message - Error message to be displayed.
197 * @param {string} argument - Argument name.
198 * @param {string} expected - Expected argument datatype.
199 * @returns {Error} instance detailing the error condition
200 */
201function createMissingArgumentError(message, argument, expected) {
202 return createInvalidArgumentTypeError(message, argument, expected);
203}
204
205/**
206 * Creates an error object to be thrown when an argument did not use the supported type
207 *
208 * @public
209 * @param {string} message - Error message to be displayed.
210 * @param {string} argument - Argument name.
211 * @param {string} expected - Expected argument datatype.
212 * @returns {Error} instance detailing the error condition
213 */
214function createInvalidArgumentTypeError(message, argument, expected) {
215 var err = new TypeError(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200216 err.code = constants.INVALID_ARG_TYPE;
Yang Guo4fd355c2019-09-19 10:59:03 +0200217 err.argument = argument;
218 err.expected = expected;
219 err.actual = typeof argument;
220 return err;
221}
222
223/**
224 * Creates an error object to be thrown when an argument did not use the supported value
225 *
226 * @public
227 * @param {string} message - Error message to be displayed.
228 * @param {string} argument - Argument name.
229 * @param {string} value - Argument value.
230 * @param {string} [reason] - Why value is invalid.
231 * @returns {Error} instance detailing the error condition
232 */
233function createInvalidArgumentValueError(message, argument, value, reason) {
234 var err = new TypeError(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200235 err.code = constants.INVALID_ARG_VALUE;
Yang Guo4fd355c2019-09-19 10:59:03 +0200236 err.argument = argument;
237 err.value = value;
238 err.reason = typeof reason !== 'undefined' ? reason : 'is invalid';
239 return err;
240}
241
242/**
243 * Creates an error object to be thrown when an exception was caught, but the `Error` is falsy or undefined.
244 *
245 * @public
246 * @param {string} message - Error message to be displayed.
247 * @returns {Error} instance detailing the error condition
248 */
249function createInvalidExceptionError(message, value) {
250 var err = new Error(message);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200251 err.code = constants.INVALID_EXCEPTION;
Yang Guo4fd355c2019-09-19 10:59:03 +0200252 err.valueType = typeof value;
253 err.value = value;
254 return err;
255}
256
Peter Marshall0b95ea12020-07-02 18:50:04 +0200257/**
258 * Creates an error object to be thrown when an unrecoverable error occurs.
259 *
260 * @public
261 * @param {string} message - Error message to be displayed.
262 * @returns {Error} instance detailing the error condition
263 */
264function createFatalError(message, value) {
265 var err = new Error(message);
266 err.code = constants.FATAL;
267 err.valueType = typeof value;
268 err.value = value;
269 return err;
270}
271
272/**
273 * Dynamically creates a plugin-type-specific error based on plugin type
274 * @param {string} message - Error message
275 * @param {"reporter"|"interface"} pluginType - Plugin type. Future: expand as needed
276 * @param {string} [pluginId] - Name/path of plugin, if any
277 * @throws When `pluginType` is not known
278 * @public
279 * @returns {Error}
280 */
Peter Marshall4e161df2020-11-10 13:29:38 +0100281function createInvalidLegacyPluginError(message, pluginType, pluginId) {
Peter Marshall0b95ea12020-07-02 18:50:04 +0200282 switch (pluginType) {
283 case 'reporter':
284 return createInvalidReporterError(message, pluginId);
285 case 'interface':
286 return createInvalidInterfaceError(message, pluginId);
287 default:
288 throw new Error('unknown pluginType "' + pluginType + '"');
289 }
290}
291
292/**
Peter Marshall4e161df2020-11-10 13:29:38 +0100293 * **DEPRECATED**. Use {@link createInvalidLegacyPluginError} instead Dynamically creates a plugin-type-specific error based on plugin type
294 * @deprecated
295 * @param {string} message - Error message
296 * @param {"reporter"|"interface"} pluginType - Plugin type. Future: expand as needed
297 * @param {string} [pluginId] - Name/path of plugin, if any
298 * @throws When `pluginType` is not known
299 * @public
300 * @returns {Error}
301 */
302function createInvalidPluginError(...args) {
303 deprecate('Use createInvalidLegacyPluginError() instead');
304 return createInvalidLegacyPluginError(...args);
305}
306
307/**
Peter Marshall0b95ea12020-07-02 18:50:04 +0200308 * Creates an error object to be thrown when a mocha object's `run` method is executed while it is already disposed.
309 * @param {string} message The error message to be displayed.
310 * @param {boolean} cleanReferencesAfterRun the value of `cleanReferencesAfterRun`
311 * @param {Mocha} instance the mocha instance that throw this error
312 */
313function createMochaInstanceAlreadyDisposedError(
314 message,
315 cleanReferencesAfterRun,
316 instance
317) {
318 var err = new Error(message);
319 err.code = constants.INSTANCE_ALREADY_DISPOSED;
320 err.cleanReferencesAfterRun = cleanReferencesAfterRun;
321 err.instance = instance;
322 return err;
323}
324
325/**
326 * Creates an error object to be thrown when a mocha object's `run` method is called while a test run is in progress.
327 * @param {string} message The error message to be displayed.
328 */
329function createMochaInstanceAlreadyRunningError(message, instance) {
330 var err = new Error(message);
331 err.code = constants.INSTANCE_ALREADY_RUNNING;
332 err.instance = instance;
333 return err;
334}
335
336/*
337 * Creates an error object to be thrown when done() is called multiple times in a test
338 *
339 * @public
340 * @param {Runnable} runnable - Original runnable
341 * @param {Error} [originalErr] - Original error, if any
342 * @returns {Error} instance detailing the error condition
343 */
344function createMultipleDoneError(runnable, originalErr) {
345 var title;
346 try {
347 title = format('<%s>', runnable.fullTitle());
348 if (runnable.parent.root) {
349 title += ' (of root suite)';
350 }
351 } catch (ignored) {
352 title = format('<%s> (of unknown suite)', runnable.title);
353 }
354 var message = format(
355 'done() called multiple times in %s %s',
356 runnable.type ? runnable.type : 'unknown runnable',
357 title
358 );
359 if (runnable.file) {
360 message += format(' of file %s', runnable.file);
361 }
362 if (originalErr) {
363 message += format('; in addition, done() received error: %s', originalErr);
364 }
365
366 var err = new Error(message);
367 err.code = constants.MULTIPLE_DONE;
368 err.valueType = typeof originalErr;
369 err.value = originalErr;
370 return err;
371}
372
373/**
374 * Creates an error object to be thrown when `.only()` is used with
375 * `--forbid-only`.
376 * @public
377 * @param {Mocha} mocha - Mocha instance
378 * @returns {Error} Error with code {@link constants.FORBIDDEN_EXCLUSIVITY}
379 */
380function createForbiddenExclusivityError(mocha) {
381 var err = new Error(
382 mocha.isWorker
383 ? '`.only` is not supported in parallel mode'
384 : '`.only` forbidden by --forbid-only'
385 );
386 err.code = constants.FORBIDDEN_EXCLUSIVITY;
387 return err;
388}
389
Peter Marshall4e161df2020-11-10 13:29:38 +0100390/**
391 * Creates an error object to be thrown when a plugin definition is invalid
392 * @param {string} msg - Error message
393 * @param {PluginDefinition} [pluginDef] - Problematic plugin definition
394 * @public
395 * @returns {Error} Error with code {@link constants.INVALID_PLUGIN_DEFINITION}
396 */
397function createInvalidPluginDefinitionError(msg, pluginDef) {
398 const err = new Error(msg);
399 err.code = constants.INVALID_PLUGIN_DEFINITION;
400 err.pluginDef = pluginDef;
401 return err;
402}
403
404/**
405 * Creates an error object to be thrown when a plugin implementation (user code) is invalid
406 * @param {string} msg - Error message
407 * @param {Object} [opts] - Plugin definition and user-supplied implementation
408 * @param {PluginDefinition} [opts.pluginDef] - Plugin Definition
409 * @param {*} [opts.pluginImpl] - Plugin Implementation (user-supplied)
410 * @public
411 * @returns {Error} Error with code {@link constants.INVALID_PLUGIN_DEFINITION}
412 */
413function createInvalidPluginImplementationError(
414 msg,
415 {pluginDef, pluginImpl} = {}
416) {
417 const err = new Error(msg);
418 err.code = constants.INVALID_PLUGIN_IMPLEMENTATION;
419 err.pluginDef = pluginDef;
420 err.pluginImpl = pluginImpl;
421 return err;
422}
423
424/**
425 * Returns `true` if an error came out of Mocha.
426 * _Can suffer from false negatives, but not false positives._
427 * @public
428 * @param {*} err - Error, or anything
429 * @returns {boolean}
430 */
431const isMochaError = err =>
432 Boolean(err && typeof err === 'object' && MOCHA_ERRORS.has(err.code));
433
Yang Guo4fd355c2019-09-19 10:59:03 +0200434module.exports = {
Peter Marshall4e161df2020-11-10 13:29:38 +0100435 constants,
436 createFatalError,
437 createForbiddenExclusivityError,
438 createInvalidArgumentTypeError,
439 createInvalidArgumentValueError,
440 createInvalidExceptionError,
441 createInvalidInterfaceError,
442 createInvalidLegacyPluginError,
443 createInvalidPluginDefinitionError,
444 createInvalidPluginError,
445 createInvalidPluginImplementationError,
446 createInvalidReporterError,
447 createMissingArgumentError,
448 createMochaInstanceAlreadyDisposedError,
449 createMochaInstanceAlreadyRunningError,
450 createMultipleDoneError,
451 createNoFilesMatchPatternError,
452 createUnsupportedError,
453 deprecate,
454 isMochaError,
455 warn
Yang Guo4fd355c2019-09-19 10:59:03 +0200456};