diff --git a/.eslintrc.yml b/.eslintrc.yml index cd953505e6..15503b8b54 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -23,12 +23,14 @@ overrides: - karma.conf.js - .wallaby.js - bin/* + - lib/cli/**/*.js + - test/node-unit/**/*.js parserOptions: ecmaVersion: 6 env: browser: no - files: - - test/**/*.js + - test/**/*.{js,mjs} env: mocha: yes globals: @@ -37,4 +39,9 @@ overrides: - doc/**/*.js env: node: no + - files: + - test/**/*.mjs + parserOptions: + ecmaVersion: 6 + sourceType: module diff --git a/.mocharc.yml b/.mocharc.yml new file mode 100644 index 0000000000..bcfa0f5b7b --- /dev/null +++ b/.mocharc.yml @@ -0,0 +1,7 @@ +require: test/setup +ui: bdd +global: + - okGlobalA,okGlobalB + - okGlobalC + - callback* +timeout: 200 diff --git a/.travis.yml b/.travis.yml index 368bc98318..49776cceba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,8 +67,8 @@ jobs: env: null before_install: true install: npm install --production - # `--opts /dev/null` means "ignore test/mocha.opts" - script: ./bin/mocha --opts /dev/null --reporter spec test/sanity/sanity.spec.js + + script: ./bin/mocha --no-config --reporter spec test/sanity/sanity.spec.js cache: directories: - ~/.npm diff --git a/.wallaby.js b/.wallaby.js index 5b85d21486..88620c3ed8 100644 --- a/.wallaby.js +++ b/.wallaby.js @@ -3,26 +3,35 @@ module.exports = () => { return { files: [ - 'index.js', 'lib/**/*.js', 'test/setup.js', 'test/assertions.js', + 'index.js', + 'lib/**/*.{js,json}', + 'test/setup.js', + 'test/assertions.js', { pattern: 'test/node-unit/**/*.fixture.js', instrument: false - }, { + }, + { pattern: 'test/unit/**/*.fixture.js', instrument: false - } + }, + 'package.json', + 'test/opts/mocha.opts', + 'mocharc.yml' ], - filesWithNoCoverageCalculated: ['test/**/*.fixture.js'], - tests: [ - 'test/unit/**/*.spec.js', 'test/node-unit/**/*.spec.js' + filesWithNoCoverageCalculated: [ + 'test/**/*.fixture.js', + 'test/setup.js', + 'test/assertions.js' ], + tests: ['test/unit/**/*.spec.js', 'test/node-unit/**/*.spec.js'], env: { type: 'node', runner: 'node' }, workers: {recycle: true}, testFramework: {type: 'mocha', path: __dirname}, - setup (wallaby) { + setup(wallaby) { // running mocha instance is not the same as mocha under test, // running mocha is the project's source code mocha, mocha under test is instrumented version of the source code const runningMocha = wallaby.testFramework; @@ -33,7 +42,7 @@ module.exports = () => { // to make test/node-unit/color.spec.js pass, we need to run mocha in the project's folder context const childProcess = require('child_process'); const execFile = childProcess.execFile; - childProcess.execFile = function () { + childProcess.execFile = function() { let opts = arguments[2]; if (typeof opts === 'function') { opts = {}; diff --git a/bin/_mocha b/bin/_mocha index 742c5d70d5..2ea8317467 100755 --- a/bin/_mocha +++ b/bin/_mocha @@ -1,652 +1,10 @@ #!/usr/bin/env node 'use strict'; -/* eslint no-unused-vars: off */ - -/** - * Module dependencies. - */ - -const program = require('commander'); -const path = require('path'); -const fs = require('fs'); -const minimatch = require('minimatch'); -const resolve = path.resolve; -const exists = fs.existsSync; -const Mocha = require('../'); -const utils = Mocha.utils; -const interfaceNames = Object.keys(Mocha.interfaces); -const join = path.join; -const cwd = process.cwd(); -const getOptions = require('./options'); -const mocha = new Mocha(); - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -const Date = global.Date; -const setTimeout = global.setTimeout; -const setInterval = global.setInterval; -const clearTimeout = global.clearTimeout; -const clearInterval = global.clearInterval; - -/** - * Exits Mocha when tests + code under test has finished execution (default) - * @param {number} code - Exit code; typically # of failures - */ -const exitLater = code => { - process.on('exit', () => { - process.exit(Math.min(code, 255)); - }); -}; - -/** - * Exits Mocha when Mocha itself has finished execution, regardless of - * what the tests or code under test is doing. - * @param {number} code - Exit code; typically # of failures - */ -const exit = code => { - const clampedCode = Math.min(code, 255); - let draining = 0; - - // Eagerly set the process's exit code in case stream.write doesn't - // execute its callback before the process terminates. - process.exitCode = clampedCode; - - // flush output for Node.js Windows pipe bug - // https://github.com/joyent/node/issues/6247 is just one bug example - // https://github.com/visionmedia/mocha/issues/333 has a good discussion - const done = () => { - if (!draining--) { - process.exit(clampedCode); - } - }; - - const streams = [process.stdout, process.stderr]; - - streams.forEach(stream => { - // submit empty write request and wait for completion - draining += 1; - stream.write('', done); - }); - - done(); -}; - -/** - * Parse list. - */ -const list = str => str.split(/ *, */); - -/** - * Parse multiple flag. - */ -const collect = (val, memo) => memo.concat(val); - -/** - * Hide the cursor. - */ -const hideCursor = () => { - process.stdout.write('\u001b[?25l'); -}; - -/** - * Show the cursor. - */ -const showCursor = () => { - process.stdout.write('\u001b[?25h'); -}; - -/** - * Stop. - */ -const stop = () => { - process.stdout.write('\u001b[2K'); -}; - -/** - * Files. - */ - -let files = []; - -/** - * Globals. - */ - -let globals = []; - -/** - * Requires. - */ - -const requires = []; - /** - * Images. + * This file remains for backwards compatibility only. + * Don't put stuff in this file. + * @see module:lib/cli */ -const images = { - fail: path.join(__dirname, '..', 'assets', 'growl', 'error.png'), - pass: path.join(__dirname, '..', 'assets', 'growl', 'ok.png') -}; - -// options - -program - .version( - JSON.parse( - fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8') - ).version - ) - .usage('[debug] [options] [files]') - .option( - '-A, --async-only', - 'force all tests to take a callback (async) or return a promise' - ) - .option('-c, --colors', 'force enabling of colors') - .option('-C, --no-colors', 'force disabling of colors') - .option('-G, --growl', 'enable growl notification support') - .option('-O, --reporter-options ', 'reporter-specific options') - .option('-R, --reporter ', 'specify the reporter to use', 'spec') - .option('-S, --sort', 'sort test files') - .option('-b, --bail', 'bail after first test failure') - .option('-d, --debug', "enable node's debugger, synonym for node --debug") - .option('-g, --grep ', 'only run tests matching ') - .option('-f, --fgrep ', 'only run tests containing ') - .option('-gc, --expose-gc', 'expose gc extension') - .option('-i, --invert', 'inverts --grep and --fgrep matches') - .option('-r, --require ', 'require the given module', []) - .option( - '-s, --slow ', - 'specify "slow" test threshold in milliseconds', - 75 - ) - .option( - '-t, --timeout ', - 'specify test timeout threshold in milliseconds', - 2000 - ) - .option( - '-u, --ui ', - `specify user-interface (${interfaceNames.join('|')})`, - 'bdd' - ) - .option( - '-w, --watch', - 'watch files in the current working directory for changes' - ) - .option('--check-leaks', 'check for global variable leaks') - .option('--full-trace', 'display the full stack trace') - .option( - '--compilers :,...', - 'use the given module(s) to compile files', - list, - [] - ) - .option('--debug-brk', "enable node's debugger breaking on the first line") - .option( - '--globals ', - 'allow the given comma-delimited global [names]', - list, - [] - ) - .option('--es_staging', 'enable all staged features') - .option( - '--harmony<_classes,_generators,...>', - 'all node --harmony* flags are available' - ) - .option( - '--preserve-symlinks', - 'Instructs the module loader to preserve symbolic links when resolving and caching modules' - ) - .option('--icu-data-dir', 'include ICU data') - .option( - '--inline-diffs', - 'display actual/expected differences inline within each string' - ) - .option('--no-diff', 'do not show a diff on failure') - .option('--inspect', 'activate devtools in chrome') - .option( - '--inspect-brk', - 'activate devtools in chrome and break on the first line' - ) - .option('--interfaces', 'output provided interfaces and exit') - .option('--no-deprecation', 'silence deprecation warnings') - .option( - '--exit', - 'force shutdown of the event loop after test run: mocha will call process.exit' - ) - .option( - '--no-timeouts', - 'disables timeouts, given implicitly with --debug/--inspect' - ) - .option('--no-warnings', 'silence all node process warnings') - .option('--opts ', 'specify opts path', 'test/mocha.opts') - .option('--perf-basic-prof', 'enable perf linux profiler (basic support)') - .option('--napi-modules', 'enable experimental NAPI modules') - .option('--prof', 'log statistical profiling information') - .option('--log-timer-events', 'Time events including external callbacks') - .option('--recursive', 'include sub directories') - .option('--reporters', 'output provided reporters and exit') - .option( - '--retries ', - 'specify number of times to retry a failed test case', - 0 - ) - .option( - '--throw-deprecation', - 'throw an exception anytime a deprecated function is used' - ) - .option('--trace', 'trace function calls') - .option('--trace-deprecation', 'show stack traces on deprecations') - .option('--trace-warnings', 'show stack traces on node process warnings') - .option('--use_strict', 'enforce strict mode') - .option( - '--watch-extensions ,...', - 'specify extensions to monitor with --watch', - list, - ['js'] - ) - .option('--delay', 'wait for async suite definition') - .option('--allow-uncaught', 'enable uncaught errors to propagate') - .option('--forbid-only', 'causes test marked with only to fail the suite') - .option( - '--forbid-pending', - 'causes pending tests and test marked with skip to fail the suite' - ) - .option( - '--file ', - 'adds file be loaded prior to suite execution', - collect, - [] - ) - .option( - '--exclude ', - 'adds file or glob pattern to ignore', - collect, - [] - ); - -program._name = 'mocha'; - -// init command - -program - .command('init ') - .description('initialize a client-side mocha setup at ') - .action(path => { - const mkdir = require('mkdirp'); - mkdir.sync(path); - const css = fs.readFileSync(join(__dirname, '..', 'mocha.css')); - const js = fs.readFileSync(join(__dirname, '..', 'mocha.js')); - const tmpl = fs.readFileSync(join(__dirname, '..', 'lib/template.html')); - fs.writeFileSync(join(path, 'mocha.css'), css); - fs.writeFileSync(join(path, 'mocha.js'), js); - fs.writeFileSync(join(path, 'tests.js'), ''); - fs.writeFileSync(join(path, 'index.html'), tmpl); - process.exit(0); - }); - -// --globals - -program.on('option:globals', val => { - globals = globals.concat(list(val)); -}); - -// --reporters - -program.on('option:reporters', () => { - console.log(); - console.log(' dot - dot matrix'); - console.log(' doc - html documentation'); - console.log(' spec - hierarchical spec list'); - console.log(' json - single json object'); - console.log(' progress - progress bar'); - console.log(' list - spec-style listing'); - console.log(' tap - test-anything-protocol'); - console.log(' landing - unicode landing strip'); - console.log(' xunit - xunit reporter'); - console.log(' min - minimal reporter (great with --watch)'); - console.log(' json-stream - newline delimited json events'); - console.log(' markdown - markdown documentation (github flavour)'); - console.log(' nyan - nyan cat!'); - console.log(); - process.exit(); -}); - -// --interfaces - -program.on('option:interfaces', () => { - console.log(''); - interfaceNames.forEach(interfaceName => { - console.log(` ${interfaceName}`); - }); - console.log(''); - process.exit(); -}); - -// -r, --require - -module.paths.push(cwd, join(cwd, 'node_modules')); - -program.on('option:require', mod => { - const abs = exists(mod) || exists(`${mod}.js`); - if (abs) { - mod = resolve(mod); - } - requires.push(mod); -}); - -// If not already done, load mocha.opts -if (!process.env.LOADED_MOCHA_OPTS) { - getOptions(); -} - -// parse args - -program.parse(process.argv); - -// infinite stack traces - -Error.stackTraceLimit = Infinity; // TODO: config - -// reporter options - -const reporterOptions = {}; -if (program.reporterOptions !== undefined) { - program.reporterOptions.split(',').forEach(opt => { - const L = opt.split('='); - if (L.length > 2 || L.length === 0) { - throw new Error(`invalid reporter option '${opt}'`); - } else if (L.length === 2) { - reporterOptions[L[0]] = L[1]; - } else { - reporterOptions[L[0]] = true; - } - }); -} - -// reporter - -mocha.reporter(program.reporter, reporterOptions); - -// --no-colors - -if (!program.colors) { - mocha.useColors(false); -} - -// --colors - -if (~process.argv.indexOf('--colors') || ~process.argv.indexOf('-c')) { - mocha.useColors(true); -} - -// --inline-diffs - -if (program.inlineDiffs) { - mocha.useInlineDiffs(true); -} - -// --no-diff - -if (process.argv.indexOf('--no-diff') !== -1) { - mocha.hideDiff(true); -} - -// --slow - -if (program.slow) { - mocha.suite.slow(program.slow); -} - -// --no-timeouts - -if (!program.timeouts) { - mocha.enableTimeouts(false); -} - -// --timeout - -if (program.timeout) { - mocha.suite.timeout(program.timeout); -} - -// --bail - -mocha.suite.bail(program.bail); - -// --grep - -if (program.grep) { - mocha.grep(program.grep); -} - -// --fgrep - -if (program.fgrep) { - mocha.fgrep(program.fgrep); -} - -// --invert - -if (program.invert) { - mocha.invert(); -} - -// --check-leaks - -if (program.checkLeaks) { - mocha.checkLeaks(); -} - -// --stack-trace - -if (program.fullTrace) { - mocha.fullTrace(); -} - -// --growl - -if (program.growl) { - mocha.growl(); -} - -// --async-only - -if (program.asyncOnly) { - mocha.asyncOnly(); -} - -// --delay - -if (program.delay) { - mocha.delay(); -} - -// --allow-uncaught - -if (program.allowUncaught) { - mocha.allowUncaught(); -} - -// --globals - -mocha.globals(globals); - -// --retries - -if (program.retries) { - mocha.suite.retries(program.retries); -} - -// --forbid-only - -if (program.forbidOnly) mocha.forbidOnly(); - -// --forbid-pending - -if (program.forbidPending) mocha.forbidPending(); - -// custom compiler support - -if (program.compilers.length > 0) { - require('util').deprecate(() => {}, - '"--compilers" will be removed in a future version of Mocha; see https://git.io/vdcSr for more info')(); -} -const extensions = ['js']; -program.compilers.forEach(c => { - const idx = c.indexOf(':'); - const ext = c.slice(0, idx); - let mod = c.slice(idx + 1); - - if (mod[0] === '.') { - mod = join(process.cwd(), mod); - } - require(mod); - extensions.push(ext); - program.watchExtensions.push(ext); -}); - -// requires - -requires.forEach(mod => { - require(mod); -}); - -// interface - -mocha.ui(program.ui); - -// args - -const args = program.args; - -// default files to test/*.{js,coffee} - -if (!args.length) { - args.push('test'); -} - -args.forEach(arg => { - let newFiles; - try { - newFiles = utils.lookupFiles(arg, extensions, program.recursive); - } catch (err) { - if (err.message.indexOf('cannot resolve path') === 0) { - console.error( - `Warning: Could not find any test files matching pattern: ${arg}` - ); - return; - } - - throw err; - } - - if (typeof newFiles !== 'undefined') { - if (typeof newFiles === 'string') { - newFiles = [newFiles]; - } - newFiles = newFiles.filter(fileName => - program.exclude.every(pattern => !minimatch(fileName, pattern)) - ); - } - - files = files.concat(newFiles); -}); - -if (!files.length) { - console.error('No test files found'); - process.exit(1); -} - -// resolve -let fileArgs = program.file.map(path => resolve(path)); -files = files.map(path => resolve(path)); - -if (program.sort) { - files.sort(); -} - -// add files given through --file to be ran first -files = fileArgs.concat(files); - -// --watch - -let runner; -let loadAndRun; -let purge; -let rerun; - -if (program.watch) { - console.log(); - hideCursor(); - process.on('SIGINT', () => { - showCursor(); - console.log('\n'); - process.exit(130); - }); - - const watchFiles = utils.files(cwd, program.watchExtensions); - let runAgain = false; - - loadAndRun = () => { - try { - mocha.files = files; - runAgain = false; - runner = mocha.run(() => { - runner = null; - if (runAgain) { - rerun(); - } - }); - } catch (e) { - console.log(e.stack); - } - }; - - purge = () => { - watchFiles.forEach(file => { - delete require.cache[file]; - }); - }; - - loadAndRun(); - - rerun = () => { - purge(); - stop(); - if (!program.grep) { - mocha.grep(null); - } - mocha.suite = mocha.suite.clone(); - mocha.suite.ctx = new Mocha.Context(); - mocha.ui(program.ui); - loadAndRun(); - }; - - utils.watch(watchFiles, () => { - runAgain = true; - if (runner) { - runner.abort(); - } else { - rerun(); - } - }); -} else { - // load - - mocha.files = files; - runner = mocha.run(program.exit ? exit : exitLater); -} - -process.on('SIGINT', () => { - runner.abort(); - - // This is a hack: - // Instead of `process.exit(130)`, set runner.failures to 130 (exit code for SIGINT) - // The amount of failures will be emitted as error code later - runner.failures = 130; -}); +require('../lib/cli').main(); diff --git a/bin/mocha b/bin/mocha index 334e8fb0e9..770fb265eb 100755 --- a/bin/mocha +++ b/bin/mocha @@ -3,76 +3,101 @@ 'use strict'; /** - * This tiny wrapper file checks for known node flags and appends them - * when found, before invoking the "real" _mocha(1) executable. + * This wrapper executable checks for known node flags and appends them when found, before invoking the "real" _mocha(1) executable. + * + * @module bin/mocha + * @private */ -const spawn = require('child_process').spawn; -const path = require('path'); -const getOptions = require('./options'); -const args = [path.join(__dirname, '_mocha')]; +const {deprecate} = require('../lib/utils'); +const {spawn} = require('child_process'); +const {loadOptions} = require('../lib/cli/options'); +const {isNodeFlag, impliesNoTimeouts} = require('../lib/cli/node-flags'); +const unparse = require('yargs-unparser'); +const debug = require('debug')('mocha:cli'); +const {aliases} = require('../lib/cli/run-option-metadata'); -// Load mocha.opts into process.argv -// Must be loaded here to handle node-specific options -getOptions(); +const mochaPath = require.resolve('./_mocha'); +const childOpts = {}; +const nodeOpts = {}; -process.argv.slice(2).forEach(arg => { - const flag = arg.split('=')[0]; +const opts = loadOptions(process.argv.slice(2)); +debug('loaded opts', opts); - switch (flag) { - case '-d': - args.unshift('--debug'); - args.push('--no-timeouts'); - break; - case 'debug': - case '--debug': - case '--debug-brk': - case '--inspect': - case '--inspect-brk': - args.unshift(arg); - args.push('--no-timeouts'); - break; - case '-gc': - case '--expose-gc': - args.unshift('--expose-gc'); - break; - case '--gc-global': - case '--es_staging': - case '--experimental-modules': - case '--no-deprecation': - case '--no-warnings': - case '--prof': - case '--log-timer-events': - case '--throw-deprecation': - case '--trace-deprecation': - case '--trace-warnings': - case '--use_strict': - case '--allow-natives-syntax': - case '--perf-basic-prof': - case '--napi-modules': - args.unshift(arg); - break; - default: - if (arg.indexOf('--harmony') === 0) { - args.unshift(arg); - } else if (arg.indexOf('--trace') === 0) { - args.unshift(arg); - } else if (arg.indexOf('--icu-data-dir') === 0) { - args.unshift(arg); - } else if (arg.indexOf('--max-old-space-size') === 0) { - args.unshift(arg); - } else if (arg.indexOf('--preserve-symlinks') === 0) { - args.unshift(arg); - } else { - args.push(arg); - } - break; +Object.keys(opts).forEach(opt => { + if (isNodeFlag(opt)) { + if (/^v8-/.test(opt)) { + opt = opt.slice(2); + } + nodeOpts[opt] = opts[opt]; + if (impliesNoTimeouts(opt)) { + debug(`option "${opt}" disabled timeouts`); + childOpts.timeout = false; + } + } else { + childOpts[opt] = opts[opt]; } }); +// allow --debug to invoke --inspect on Node.js v8 or newer nodeOpts.inspect = childOpts.debug; +if (childOpts.debug) { + childOpts.timeout = false; + delete childOpts.debug; + debug('--debug -> --inspect'); +} else if (childOpts['debug-brk']) { + nodeOpts['inspect-brk'] = childOpts['debug-brk']; + childOpts.timeout = false; + delete childOpts['debug-brk']; + debug('--debug-brk -> --inspect-brk'); +} + +// historical +if (nodeOpts.gc) { + deprecate( + '"-gc" is deprecated and will be removed from a future version of Mocha. Use "--gc-global" instead.' + ); + nodeOpts['gc-global'] = nodeOpts.gc; + delete nodeOpts.gc; +} + +// Native debugger handling +// see https://nodejs.org/api/debugger.html#debugger_debugger +// look for 'debug' or 'inspect' that would launch this debugger, +// remove it from Mocha's opts and prepend it to Node's opts. +// also coerce depending on Node.js version. +if (/^(debug|inspect)$/.test(childOpts._[0])) { + childOpts.timeout = false; + childOpts._.shift(); + // don't conflict with inspector + delete nodeOpts['debug']; + delete nodeOpts['inspect']; + delete nodeOpts['debug-brk']; + delete nodeOpts['inspect-brk']; + nodeOpts._ = [ + parseInt( + process.version + .slice(1) + .split('.') + .shift(), + 10 + ) >= 8 + ? 'inspect' + : 'debug' + ]; +} + +const args = [].concat( + unparse(nodeOpts), + mochaPath, + unparse(childOpts, {alias: aliases}) +); + +debug(`exec ${process.execPath} w/ args:`, args); + const proc = spawn(process.execPath, args, { stdio: 'inherit' }); + proc.on('exit', (code, signal) => { process.on('exit', () => { if (signal) { diff --git a/bin/options.js b/bin/options.js index 69dbe64a77..102fd5a9c1 100644 --- a/bin/options.js +++ b/bin/options.js @@ -1,110 +1,10 @@ 'use strict'; -/** - * Dependencies. +/* + * This module is deprecated and will be removed in a future version of Mocha. + * @deprecated Deprecated in v6.0.0; source moved into {@link module:lib/cli/options lib/cli/options module}. + * @module + * @exports module:lib/cli/options */ -const fs = require('fs'); -const path = require('path'); - -/** - * Export `getOptions`. - */ - -module.exports = getOptions; - -/** - * Default test directory. - * - * @constant - * @type {string} - * @default - */ -const DEFAULT_TEST_DIRECTORY = 'test'; - -/** - * Default filename of run-control file. - * - * @constant - * @type {string} - * @default - */ -const DEFAULT_OPTS_FILENAME = 'mocha.opts'; - -/** - * Reads contents of the run-control file. - * - * @private - * @param {string} pathname - Pathname of run-control file. - * @returns {string} file contents - */ -function readOptionsFile(pathname) { - return fs.readFileSync(pathname, 'utf8'); -} - -/** - * Parses options read from run-control file. - * - * @private - * @param {string} content - Content read from run-control file. - * @returns {string[]} cmdline options (and associated arguments) - */ -function parseOptions(content) { - /* - * Replaces comments with empty strings - * Replaces escaped spaces (e.g., 'xxx\ yyy') with HTML space - * Splits on whitespace, creating array of substrings - * Filters empty string elements from array - * Replaces any HTML space with space - */ - return content - .replace(/^#.*$/gm, '') - .replace(/\\\s/g, '%20') - .split(/\s/) - .filter(Boolean) - .map(value => value.replace(/%20/g, ' ')); -} - -/** - * Prepends options from run-control file to the command line arguments. - * - * @public - * @see {@link https://mochajs.org/#mochaopts|mocha.opts} - */ -function getOptions() { - if ( - process.argv.length === 3 && - (process.argv[2] === '-h' || process.argv[2] === '--help') - ) { - return; - } - - const optsIndex = process.argv.indexOf('--opts'); - const optsPathSpecified = optsIndex !== -1; - const defaultOptsPath = path.join( - DEFAULT_TEST_DIRECTORY, - DEFAULT_OPTS_FILENAME - ); - const optsPath = optsPathSpecified - ? process.argv[optsIndex + 1] - : defaultOptsPath; - - try { - const opts = parseOptions(readOptionsFile(optsPath)); - - if (opts.length > 0) { - process.argv = process.argv - .slice(0, 2) - .concat(opts.concat(process.argv.slice(2))); - } - } catch (err) { - // Default options file may not exist - rethrow anything else - if (optsPathSpecified || err.code !== 'ENOENT') { - console.error(`failed to load Mocha options file: ${optsPath}`); - throw err; - } - } finally { - // Despite its name, signifies loading was attempted and should not be again - process.env.LOADED_MOCHA_OPTS = '1'; - } -} +module.exports = require('../lib/cli/options'); diff --git a/docs/index.md b/docs/index.md index b9ac4edfea..10c05297c5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -66,10 +66,11 @@ Mocha is a feature-rich JavaScript test framework running on [Node.js](https://n - [Dynamically Generating Tests](#dynamically-generating-tests) - [Timeouts](#timeouts) - [Diffs](#diffs) -- [Usage](#usage) +- [Command-Line Usage](#command-line-usage) - [Interfaces](#interfaces) - [Reporters](#reporters) - [Running Mocha in the Browser](#running-mocha-in-the-browser) +- [Configuring Mocha (Node.js)](#configuring-mocha-nodejs) - [`mocha.opts`](#mochaopts) - [The `test/` Directory](#the-test-directory) - [Editor Plugins](#editor-plugins) @@ -809,88 +810,120 @@ Mocha supports the `err.expected` and `err.actual` properties of any thrown `Ass ![string diffs](images/reporter-string-diffs.png?withoutEnlargement&resize=920,9999){:class="screenshot"} -## Usage +## Command-Line Usage ```plain +mocha [spec..] + +Run tests with Mocha + +Commands +mocha debug [spec..] Run tests with Mocha [default] +mocha init create a client-side Mocha setup at + +Rules & Behavior +--allow-uncaught Allow uncaught errors to propagate [boolean] +--async-only, -A Require all tests to use a callback (async) or +return a Promise [boolean] +--bail, -b Abort ("bail") after first test failure [boolean] +--check-leaks Check for global variable leaks [boolean] +--delay Delay initial execution of root suite +--exit Force Mocha to quit after tests complete [boolean] +--forbid-only Fail if exclusive test(s) encountered [boolean] +--forbid-pending Fail if pending test(s) encountered [boolean] +--global, --globals List of allowed global variables [array] +--retries Retry failed tests this many times [number] +--slow, -s Specify "slow" test threshold (in milliseconds) +[number] [default: 75] +--timeout, -t, --timeouts Specify test timeout threshold (in milliseconds) +[number] [default: 2000] +--ui, -u Specify user interface [string] [default: "bdd"] + +Reporting & Output +--color, -c, --colors Force-enable color output [boolean] +--diff Show diff on failure +[boolean] [default: true] +--full-trace Display full stack traces [boolean] +--growl, -G Enable Growl notifications [boolean] +--inline-diffs Display actual/expected differences +inline within each string [boolean] +--reporter, -R Specify reporter to use +[string] [default: "spec"] +--reporter-option, --reporter-options, Reporter-specific options +-O () [array] + +Configuration +--config Path to config file [default: (nearest rc file)] +--opts Path to `mocha.opts` [string] [default: "./test/mocha.opts"] +--package Path to package.json for config [string] + +File Handling +--exclude Ignore file(s) or glob pattern(s) +[array] [default: (none)] +--extension, --watch-extensions File extension(s) to load and/or watch +[array] [default: js] +--file Specify file(s) to be loaded prior to root +suite execution [array] [default: (none)] +--recursive Look for tests in subdirectories [boolean] +--require, -r Require module [array] [default: (none)] +--sort, -S Sort test files +--watch, -w Watch files in the current working directory +for changes [boolean] + +Test Filters +--fgrep, -f Only run tests containing this string [string] +--grep, -g Only run tests matching this string or regexp [string] +--invert, -i Inverts --grep and --fgrep matches [boolean] + +Positional Arguments +spec One or more files, directories, or globs to test +[array] [default: ["test/"]] + +Other Options +--help, -h Show usage information & exit [boolean] +--version, -V Show version number & exit [boolean] +--interfaces List built-in user interfaces & exit +--reporters List built-in reporters & exit + +Mocha Resources +Chat: https://gitter.im/mochajs/mocha/ +GitHub: https://github.com/mochajs/mocha.git +Docs: https://mochajs.org/ +``` -Usage: mocha [debug] [options] [files] - -Options: - -V, --version output the version number - -A, --async-only force all tests to take a callback (async) or return a promise - -c, --colors force enabling of colors - -C, --no-colors force disabling of colors - -G, --growl enable growl notification support - -O, --reporter-options reporter-specific options - -R, --reporter specify the reporter to use (default: "spec") - -S, --sort sort test files - -b, --bail bail after first test failure - -d, --debug enable node's debugger, synonym for node --debug - -g, --grep only run tests matching - -f, --fgrep only run tests containing - -gc, --expose-gc expose gc extension - -i, --invert inverts --grep and --fgrep matches - -r, --require require the given module (default: []) - -s, --slow specify "slow" test threshold in milliseconds (default: 75) - -t, --timeout specify test timeout threshold in milliseconds (default: 2000) - -u, --ui specify user-interface (bdd|tdd|qunit|exports) (default: "bdd") - -w, --watch watch files in the current working directory for changes - --check-leaks check for global variable leaks - --full-trace display the full stack trace - --compilers :,... use the given module(s) to compile files (default: []) - --debug-brk enable node's debugger breaking on the first line - --globals allow the given comma-delimited global [names] (default: []) - --es_staging enable all staged features - --harmony<_classes,_generators,...> all node --harmony* flags are available - --preserve-symlinks Instructs the module loader to preserve symbolic links when resolving and caching modules - --icu-data-dir include ICU data - --inline-diffs display actual/expected differences inline within each string - --no-diff do not show a diff on failure - --inspect activate devtools in chrome - --inspect-brk activate devtools in chrome and break on the first line - --interfaces output provided interfaces and exit - --no-deprecation silence deprecation warnings - --exit force shutdown of the event loop after test run: mocha will call process.exit - --no-timeouts disables timeouts, given implicitly with --debug/--inspect - --no-warnings silence all node process warnings - --opts specify opts path (default: "test/mocha.opts") - --perf-basic-prof enable perf linux profiler (basic support) - --napi-modules enable experimental NAPI modules - --prof log statistical profiling information - --log-timer-events Time events including external callbacks - --recursive include sub directories - --reporters output provided reporters and exit - --retries specify number of times to retry a failed test case (default: 0) - --throw-deprecation throw an exception anytime a deprecated function is used - --trace trace function calls - --trace-deprecation show stack traces on deprecations - --trace-warnings show stack traces on node process warnings - --use_strict enforce strict mode - --watch-extensions ,... specify extensions to monitor with --watch (default: ["js"]) - --delay wait for async suite definition - --allow-uncaught enable uncaught errors to propagate - --forbid-only causes test marked with only to fail the suite - --forbid-pending causes pending tests and test marked with skip to fail the suite - --file adds file be loaded prior to suite execution (default: []) - --exclude adds file or glob pattern to ignore (default: []) - -h, --help output usage information - -Commands: - init initialize a client-side mocha setup at + -``` +### `--allow-uncaught` - +By default, Mocha will attempt to trap uncaught exceptions thrown from running tests and report these as test failures. Use `--allow-uncaught` to disable this behavior and allow uncaught exceptions to propagate. Will typically cause the process to crash. + +This flag is useful when debugging particularly difficult-to-track exceptions. + +### `--async-only, -A` -### `-w, --watch` +Enforce a rule that tests must be written in "async" style, meaning each test provides a `done` callback or returns a `Promise`. Non-compliant tests will be marked as failures. -Executes tests on changes to JavaScript in the CWD, and once initially. +### `--bail, -b` -### `--exit` / `--no-exit` +Causes Mocha to stop running tests after the first test failure it encounters. -> *Updated in Mocha v4.0.0* +`--bail` does *not* imply `--exit`. + +### `--check-leaks` + +Use this option to have Mocha check for global variables that are leaked while running tests. Specify globals that are acceptable via the `--global` option (for example: `--check-leaks --global jQuery --global MyLib`). + +### `--compilers` + +> *`--compilers` was removed in v6.0.0. See [further explanation and workarounds](https://github.com/mochajs/mocha/wiki/compilers-deprecation).* + +### `--exit` + +> *Updated in v4.0.0.* + +TL;DR: If your tests hang after an upgrade to Mocha v4.0.0 or newer, use `--exit` for a quick (though not necessarily recommended) fix. *Prior to* version v4.0.0, *by default*, Mocha would force its own process to exit once it was finished executing all tests. This behavior enables a set of potential problems; it's indicative of tests (or fixtures, harnesses, code under test, etc.) which don't clean up after themselves properly. Ultimately, "dirty" tests can (but not always) lead to *false positive* or *false negative* results. @@ -904,73 +937,209 @@ To ensure your tests aren't leaving messes around, here are some ideas to get st - See the [Node.js guide to debugging](https://nodejs.org/en/docs/inspector/) - Use the new [`async_hooks`](https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md) API ([example](https://git.io/vdlNM)) -- Try something like [why-is-node-running](https://npm.im/why-is-node-running) +- Try something like [wtfnode](https://npm.im/wtfnode) - Use [`.only`](#exclusive-tests) until you find the test that causes Mocha to hang -### `--compilers` +### `--forbid-only` -> *Updated in Mocha v4.0.0* +Enforce a rule that tests may not be exclusive (use of e.g., `describe.only()` or `it.only()` is disallowed). -**`--compilers` is deprecated as of Mocha v4.0.0. See [further explanation and workarounds](https://github.com/mochajs/mocha/wiki/compilers-deprecation).** +`--forbid-only` causes Mocha to fail when an exclusive ("only'd") test or suite is encountered, and it will abort further test execution. -CoffeeScript is no longer supported out of the box. CS and similar transpilers -may be used by mapping the file extensions (for use with `--watch`) and the module -name. For example `--compilers coffee:coffee-script` with CoffeeScript 1.6- or -`--compilers coffee:coffee-script/register` with CoffeeScript 1.7+. +### `--forbid-pending` -#### About Babel +Enforce a rule that tests may not be skipped (use of e.g., `describe.skip()`, `it.skip()`, or `this.skip()` anywhere is disallowed). -If your ES6 modules have extension `.js`, you can `npm install --save-dev babel-register` and use `mocha --require babel-register`; `--compilers` is only necessary if you need to specify a file extension. +`--forbid-pending` causes Mocha to fail when a skipped ("pending") test or suite is encountered, and it will abort further test execution. -### `-b, --bail` +### `--global ` -Only interested in the first exception? use `--bail`! +> *Updated in v6.0.0; the option is `--global` and `--globals` is now an alias.* -### `-d, --debug` +Define a global variable name. For example, suppose your app deliberately exposes a global named `app` and `YUI`, you may want to add `--global app --global YUI`. -Enables node's debugger support, this executes your script(s) with `node debug ` allowing you to step through code and break with the `debugger` statement. Note the difference between `mocha debug` and `mocha --debug`: `mocha debug` will fire up node's built-in debug client, `mocha --debug` will allow you to use a different interface — such as the Blink Developer Tools. Implies `--no-timeouts`. +`--global` accepts wildcards. You could do `--global '*bar'` and it would match `foobar`, `barbar`, etc. You can also simply pass in `'*'` to ignore all globals. -### `--globals ` +`--global` can accept a comma-delimited list; `--global app,YUI` is equivalent to `--global app --global YUI`. -Accepts a comma-delimited list of accepted global variable names. For example, suppose your app deliberately exposes a global named `app` and `YUI`, you may want to add `--globals app,YUI`. It also accepts wildcards. You could do `--globals '*bar'` and it would match `foobar`, `barbar`, etc. You can also simply pass in `'*'` to ignore all globals. +By using this option in conjunction with `--check-leaks`, you can specify a whitelist of known global variables that you *expect* to leak into global scope. -By using this option in conjunction with `--check-leaks`, you can specify a whitelist of known global variables that you would expect to leak into global scope. +### `--retries ` -### `--check-leaks` +Retries failed tests `n` times. + +Mocha does not retry test failures by default. + +### `--slow , -s ` + +Specify the "slow" test threshold in milliseconds. Mocha uses this to highlight test cases that are taking too long. "Slow" tests are not considered failures. + +Note: A test that executes for *half* of the "slow" time will be highlighted *in yellow* with the default `spec` reporter; a test that executes for entire "slow" time will be highlighted *in red*. + +### `--timeout , -t ` + +> *Update in v6.0.0: `--no-timeout` is implied when invoking Mocha using debug flags. It is equivalent to `--timeout 0`. `--timeout 99999999` is no longer needed.* + +Specifies the test case timeout, defaulting to two (2) seconds (2000 milliseconds). Tests taking longer than this amount of time will be marked as failed. + +To override you may pass the timeout in milliseconds, or a value with the `s` suffix, e.g., `--timeout 2s` and `--timeout 2000` are equivalent. + +To disable timeouts, use `--no-timeout`. + +Note: synchronous (blocking) tests are also bound by the timeout, but they will not complete until the code stops blocking. Infinite loops will still be infinite loops! + +### `--ui , -u ` + +The `--ui` option lets you specify the interface to use, defaulting to `bdd`. + +### `--color, -c, --colors` + +> *Updated in v6.0.0. `--colors` is now an alias for `--color`.* + +"Force" color output to be enabled, or alternatively force it to be disabled via `--no-color`. By default, Mocha uses [supports-color](https://npm.im/supports-color) to decide. + +In some cases, color output will be explicitly suppressed by certain reporters outputting in a machine-readable format. + +### `--diff` + +When possible, show the difference between expected and actual values when an assertion failure is encountered. + +This flag is unusual in that it **defaults to `true`**; use `--no-diff` to suppress Mocha's own diff output. + +Some assertion libraries will supply their own diffs, in which case Mocha's will not be used, regardless of the default value. + +Mocha's own diff output does not conform to any known standards, and is designed to be human-readable. + +### `--full-trace` + +Enable "full" stack traces. By default, Mocha attempts to distill stack traces into less noisy (though still useful) output. + +This flag is helpful when debugging a suspected issue within Mocha or Node.js itself. + +### `--growl, -G` + +Enable [Growl](http://growl.info) (or OS-level notifications where available). + +Requires extra software to be installed; see the [growl module's docs](https://npm.im/growl) for more information. + +### `--inline-diffs` + +Enable "inline" diffs, an alternative output for diffing strings. + +Useful when working with large strings. + +Does nothing if an assertion library supplies its own diff output. + +### `--reporter , -R ` + +Specify the reporter that will be used, defaulting to `spec`. + +Allows use of third-party reporters. For example, [mocha-lcov-reporter](https://npm.im/mocha-lcov-reporter) may be used with `--reporter mocha-lcov-reporter` after it has been installed. + +### `--reporter-option