368 lines
10 KiB
JavaScript
368 lines
10 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
/**
|
||
|
* Definition for Mocha's default ("run tests") command
|
||
|
*
|
||
|
* @module
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
const symbols = require('log-symbols');
|
||
|
const ansi = require('ansi-colors');
|
||
|
const Mocha = require('../mocha');
|
||
|
const {
|
||
|
createUnsupportedError,
|
||
|
createInvalidArgumentValueError,
|
||
|
createMissingArgumentError
|
||
|
} = require('../errors');
|
||
|
|
||
|
const {
|
||
|
list,
|
||
|
handleRequires,
|
||
|
validateLegacyPlugin,
|
||
|
runMocha
|
||
|
} = require('./run-helpers');
|
||
|
const {ONE_AND_DONES, ONE_AND_DONE_ARGS} = require('./one-and-dones');
|
||
|
const debug = require('debug')('mocha:cli:run');
|
||
|
const defaults = require('../mocharc');
|
||
|
const {types, aliases} = require('./run-option-metadata');
|
||
|
|
||
|
/**
|
||
|
* Logical option groups
|
||
|
* @constant
|
||
|
*/
|
||
|
const GROUPS = {
|
||
|
FILES: 'File Handling',
|
||
|
FILTERS: 'Test Filters',
|
||
|
NODEJS: 'Node.js & V8',
|
||
|
OUTPUT: 'Reporting & Output',
|
||
|
RULES: 'Rules & Behavior',
|
||
|
CONFIG: 'Configuration'
|
||
|
};
|
||
|
|
||
|
exports.command = ['$0 [spec..]', 'inspect'];
|
||
|
|
||
|
exports.describe = 'Run tests with Mocha';
|
||
|
|
||
|
exports.builder = yargs =>
|
||
|
yargs
|
||
|
.options({
|
||
|
'allow-uncaught': {
|
||
|
description: 'Allow uncaught errors to propagate',
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
'async-only': {
|
||
|
description:
|
||
|
'Require all tests to use a callback (async) or return a Promise',
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
bail: {
|
||
|
description: 'Abort ("bail") after first test failure',
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
'check-leaks': {
|
||
|
description: 'Check for global variable leaks',
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
color: {
|
||
|
description: 'Force-enable color output',
|
||
|
group: GROUPS.OUTPUT
|
||
|
},
|
||
|
config: {
|
||
|
config: true,
|
||
|
defaultDescription: '(nearest rc file)',
|
||
|
description: 'Path to config file',
|
||
|
group: GROUPS.CONFIG
|
||
|
},
|
||
|
delay: {
|
||
|
description: 'Delay initial execution of root suite',
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
diff: {
|
||
|
default: true,
|
||
|
description: 'Show diff on failure',
|
||
|
group: GROUPS.OUTPUT
|
||
|
},
|
||
|
exit: {
|
||
|
description: 'Force Mocha to quit after tests complete',
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
extension: {
|
||
|
default: defaults.extension,
|
||
|
description: 'File extension(s) to load',
|
||
|
group: GROUPS.FILES,
|
||
|
requiresArg: true,
|
||
|
coerce: list
|
||
|
},
|
||
|
fgrep: {
|
||
|
conflicts: 'grep',
|
||
|
description: 'Only run tests containing this string',
|
||
|
group: GROUPS.FILTERS,
|
||
|
requiresArg: true
|
||
|
},
|
||
|
file: {
|
||
|
defaultDescription: '(none)',
|
||
|
description:
|
||
|
'Specify file(s) to be loaded prior to root suite execution',
|
||
|
group: GROUPS.FILES,
|
||
|
normalize: true,
|
||
|
requiresArg: true
|
||
|
},
|
||
|
'forbid-only': {
|
||
|
description: 'Fail if exclusive test(s) encountered',
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
'forbid-pending': {
|
||
|
description: 'Fail if pending test(s) encountered',
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
'full-trace': {
|
||
|
description: 'Display full stack traces',
|
||
|
group: GROUPS.OUTPUT
|
||
|
},
|
||
|
global: {
|
||
|
coerce: list,
|
||
|
description: 'List of allowed global variables',
|
||
|
group: GROUPS.RULES,
|
||
|
requiresArg: true
|
||
|
},
|
||
|
grep: {
|
||
|
coerce: value => (!value ? null : value),
|
||
|
conflicts: 'fgrep',
|
||
|
description: 'Only run tests matching this string or regexp',
|
||
|
group: GROUPS.FILTERS,
|
||
|
requiresArg: true
|
||
|
},
|
||
|
growl: {
|
||
|
description: 'Enable Growl notifications',
|
||
|
group: GROUPS.OUTPUT
|
||
|
},
|
||
|
ignore: {
|
||
|
defaultDescription: '(none)',
|
||
|
description: 'Ignore file(s) or glob pattern(s)',
|
||
|
group: GROUPS.FILES,
|
||
|
requiresArg: true
|
||
|
},
|
||
|
'inline-diffs': {
|
||
|
description:
|
||
|
'Display actual/expected differences inline within each string',
|
||
|
group: GROUPS.OUTPUT
|
||
|
},
|
||
|
invert: {
|
||
|
description: 'Inverts --grep and --fgrep matches',
|
||
|
group: GROUPS.FILTERS
|
||
|
},
|
||
|
jobs: {
|
||
|
description:
|
||
|
'Number of concurrent jobs for --parallel; use 1 to run in serial',
|
||
|
defaultDescription: '(number of CPU cores - 1)',
|
||
|
requiresArg: true,
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
'list-interfaces': {
|
||
|
conflicts: Array.from(ONE_AND_DONE_ARGS),
|
||
|
description: 'List built-in user interfaces & exit'
|
||
|
},
|
||
|
'list-reporters': {
|
||
|
conflicts: Array.from(ONE_AND_DONE_ARGS),
|
||
|
description: 'List built-in reporters & exit'
|
||
|
},
|
||
|
'no-colors': {
|
||
|
description: 'Force-disable color output',
|
||
|
group: GROUPS.OUTPUT,
|
||
|
hidden: true
|
||
|
},
|
||
|
package: {
|
||
|
description: 'Path to package.json for config',
|
||
|
group: GROUPS.CONFIG,
|
||
|
normalize: true,
|
||
|
requiresArg: true
|
||
|
},
|
||
|
parallel: {
|
||
|
description: 'Run tests in parallel',
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
recursive: {
|
||
|
description: 'Look for tests in subdirectories',
|
||
|
group: GROUPS.FILES
|
||
|
},
|
||
|
reporter: {
|
||
|
default: defaults.reporter,
|
||
|
description: 'Specify reporter to use',
|
||
|
group: GROUPS.OUTPUT,
|
||
|
requiresArg: true
|
||
|
},
|
||
|
'reporter-option': {
|
||
|
coerce: opts =>
|
||
|
list(opts).reduce((acc, opt) => {
|
||
|
const pair = opt.split('=');
|
||
|
|
||
|
if (pair.length > 2 || !pair.length) {
|
||
|
throw createInvalidArgumentValueError(
|
||
|
`invalid reporter option '${opt}'`,
|
||
|
'--reporter-option',
|
||
|
opt,
|
||
|
'expected "key=value" format'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
acc[pair[0]] = pair.length === 2 ? pair[1] : true;
|
||
|
return acc;
|
||
|
}, {}),
|
||
|
description: 'Reporter-specific options (<k=v,[k1=v1,..]>)',
|
||
|
group: GROUPS.OUTPUT,
|
||
|
requiresArg: true
|
||
|
},
|
||
|
require: {
|
||
|
defaultDescription: '(none)',
|
||
|
description: 'Require module',
|
||
|
group: GROUPS.FILES,
|
||
|
requiresArg: true
|
||
|
},
|
||
|
retries: {
|
||
|
description: 'Retry failed tests this many times',
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
slow: {
|
||
|
default: defaults.slow,
|
||
|
description: 'Specify "slow" test threshold (in milliseconds)',
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
sort: {
|
||
|
description: 'Sort test files',
|
||
|
group: GROUPS.FILES
|
||
|
},
|
||
|
timeout: {
|
||
|
default: defaults.timeout,
|
||
|
description: 'Specify test timeout threshold (in milliseconds)',
|
||
|
group: GROUPS.RULES
|
||
|
},
|
||
|
ui: {
|
||
|
default: defaults.ui,
|
||
|
description: 'Specify user interface',
|
||
|
group: GROUPS.RULES,
|
||
|
requiresArg: true
|
||
|
},
|
||
|
watch: {
|
||
|
description: 'Watch files in the current working directory for changes',
|
||
|
group: GROUPS.FILES
|
||
|
},
|
||
|
'watch-files': {
|
||
|
description: 'List of paths or globs to watch',
|
||
|
group: GROUPS.FILES,
|
||
|
requiresArg: true,
|
||
|
coerce: list
|
||
|
},
|
||
|
'watch-ignore': {
|
||
|
description: 'List of paths or globs to exclude from watching',
|
||
|
group: GROUPS.FILES,
|
||
|
requiresArg: true,
|
||
|
coerce: list,
|
||
|
default: defaults['watch-ignore']
|
||
|
}
|
||
|
})
|
||
|
.positional('spec', {
|
||
|
default: ['test'],
|
||
|
description: 'One or more files, directories, or globs to test',
|
||
|
type: 'array'
|
||
|
})
|
||
|
.check(argv => {
|
||
|
// "one-and-dones"; let yargs handle help and version
|
||
|
Object.keys(ONE_AND_DONES).forEach(opt => {
|
||
|
if (argv[opt]) {
|
||
|
ONE_AND_DONES[opt].call(null, yargs);
|
||
|
process.exit();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// yargs.implies() isn't flexible enough to handle this
|
||
|
if (argv.invert && !('fgrep' in argv || 'grep' in argv)) {
|
||
|
throw createMissingArgumentError(
|
||
|
'"--invert" requires one of "--fgrep <str>" or "--grep <regexp>"',
|
||
|
'--fgrep|--grep',
|
||
|
'string|regexp'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (argv.parallel) {
|
||
|
// yargs.conflicts() can't deal with `--file foo.js --no-parallel`, either
|
||
|
if (argv.file) {
|
||
|
throw createUnsupportedError(
|
||
|
'--parallel runs test files in a non-deterministic order, and is mutually exclusive with --file'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// or this
|
||
|
if (argv.sort) {
|
||
|
throw createUnsupportedError(
|
||
|
'--parallel runs test files in a non-deterministic order, and is mutually exclusive with --sort'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (argv.reporter === 'progress') {
|
||
|
throw createUnsupportedError(
|
||
|
'--reporter=progress is mutually exclusive with --parallel'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (argv.reporter === 'markdown') {
|
||
|
throw createUnsupportedError(
|
||
|
'--reporter=markdown is mutually exclusive with --parallel'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (argv.reporter === 'json-stream') {
|
||
|
throw createUnsupportedError(
|
||
|
'--reporter=json-stream is mutually exclusive with --parallel'
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (argv.compilers) {
|
||
|
throw createUnsupportedError(
|
||
|
`--compilers is DEPRECATED and no longer supported.
|
||
|
See https://git.io/vdcSr for migration information.`
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (argv.opts) {
|
||
|
throw createUnsupportedError(
|
||
|
`--opts: configuring Mocha via 'mocha.opts' is DEPRECATED and no longer supported.
|
||
|
Please use a configuration file instead.`
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
})
|
||
|
.middleware(async (argv, yargs) => {
|
||
|
// currently a failing middleware does not work nicely with yargs' `fail()`.
|
||
|
try {
|
||
|
// load requires first, because it can impact "plugin" validation
|
||
|
const plugins = await handleRequires(argv.require);
|
||
|
validateLegacyPlugin(argv, 'reporter', Mocha.reporters);
|
||
|
validateLegacyPlugin(argv, 'ui', Mocha.interfaces);
|
||
|
Object.assign(argv, plugins);
|
||
|
} catch (err) {
|
||
|
// this could be a bad --require, bad reporter, ui, etc.
|
||
|
console.error(`\n${symbols.error} ${ansi.red('ERROR:')}`, err);
|
||
|
yargs.exit(1);
|
||
|
}
|
||
|
})
|
||
|
.array(types.array)
|
||
|
.boolean(types.boolean)
|
||
|
.string(types.string)
|
||
|
.number(types.number)
|
||
|
.alias(aliases);
|
||
|
|
||
|
exports.handler = async function(argv) {
|
||
|
debug('post-yargs config', argv);
|
||
|
const mocha = new Mocha(argv);
|
||
|
|
||
|
try {
|
||
|
await runMocha(mocha, argv);
|
||
|
} catch (err) {
|
||
|
console.error('\n' + (err.stack || `Error: ${err.message || err}`));
|
||
|
process.exit(1);
|
||
|
}
|
||
|
};
|