From 7fa8620782d2b0e8b957492bfd198d555877509d Mon Sep 17 00:00:00 2001 From: Christopher Hiller Date: Thu, 29 Nov 2018 11:55:00 -0800 Subject: [PATCH] use yargs for option parsing and support config files ## BREAKING CHANGES - `-d` is no longer an alias for `--debug` - `--grep` and `--fgrep` are now mutually exclusive - The option formerly known as `--compilers` is now removed from Mocha - `lib/template.html` moved to `lib/browser/template.html` - An error is thrown if a path to `mocha.opts` is specified by the user and the file was not found ## Soft Deprecations ### CLI - `-gc` users should use `--gc-global` ### Programmatic API - Public API method `getOptions()` in `bin/options.js` is deprecated and currently has no alternative - Users of the `enableTimeouts` option of the `Mocha` constructor should use `timeout` instead. Specify `false` for no timeout - Users of the `useColors` option of the `Mocha` constructor should use `color` instead ## Enhancements - Mocha now supports JS, JSON, YAML, or `package.json`-based config. - Any/all configuration, including `mocha.opts` can be used in addition to command-line arguments, and everything will be merged as applicable - Node/V8 flag support: - Support of all available `node` flags on a *per-version* basis - Support of any V8 flag by prepending `--v8-` to the option - These are supported within `mocha.opts` or config files - More flexible command-line parsing including negation of any boolean flag by prepending `--no-` to the name - Better `--help` - Descriptions of interfaces in `--help` text - A useful `Mocha` constructor - Debug-related flags (e.g., `--inspect`) now *imply* `--no-timeouts` ## Fixes - Many bug fixes around CLI option handling, e.g., closes #3475 - Fixes #3363, #2576 - `--no-timeouts` works ## Documentation - Added new, updated, or rewrote documentation for all non-self-explanatory options - Updated usage text, TOC - Added section on configuration - Added example configuration files in `example/config/` ## Etc. - Updated many dev deps, mostly within existing semver ranges - Removed `commander` - Added production deps: - ansi-colors - color terminal output - findup-sync - find package.json and other config files - js-yaml - parse YAML - log-symbols - visual symbols for success/fail etc. - node-environment-flags - per-version allowed Node.js env flags - object.assign - for IE11 - strip-json-comments - allow comments in JSON config files - wide-align - terminal formatting - yargs - option parser - yargs-parser - for compatible, lightweight parsing of config files - yargs-unparser - for passing config to `bin/_mocha` from `bin/mocha` --- .eslintrc.yml | 9 +- .mocharc.yml | 7 + .travis.yml | 4 +- .wallaby.js | 25 +- bin/_mocha | 649 +- bin/mocha | 135 +- bin/options.js | 87 +- docs/index.md | 550 +- example/config/README.md | 7 + example/config/mocharc.js | 16 + example/config/mocharc.json | 14 + example/config/mocharc.yml | 47 + karma.conf.js | 42 +- lib/{ => browser}/template.html | 0 lib/cli/cli.js | 59 + lib/cli/commands.js | 11 + lib/cli/config.js | 73 + lib/cli/index.js | 3 + lib/cli/init.js | 34 + lib/cli/node-flags.js | 41 + lib/cli/one-and-dones.js | 62 + lib/cli/options.js | 273 + lib/cli/run-args.js | 60 + lib/cli/run-helpers.js | 328 + lib/cli/run.js | 278 + lib/interfaces/bdd.js | 2 + lib/interfaces/exports.js | 2 + lib/interfaces/qunit.js | 2 + lib/interfaces/tdd.js | 3 + lib/mocha.js | 85 +- lib/mocharc.json | 10 + lib/reporters/base.js | 2 + lib/reporters/doc.js | 2 + lib/reporters/dot.js | 2 + lib/reporters/html.js | 4 +- lib/reporters/json-stream.js | 2 + lib/reporters/json.js | 2 + lib/reporters/landing.js | 4 +- lib/reporters/list.js | 2 + lib/reporters/markdown.js | 2 + lib/reporters/min.js | 2 + lib/reporters/nyan.js | 2 + lib/reporters/progress.js | 2 + lib/reporters/spec.js | 2 + lib/reporters/tap.js | 2 + lib/reporters/xunit.js | 2 + lib/runner.js | 6 +- lib/utils.js | 3 +- package-lock.json | 11912 ++++++++-------- package-scripts.js | 57 +- package.json | 54 +- scripts/docs-update-toc.js | 3 +- test/assertions.js | 135 +- test/browser-fixtures/bdd.fixture.js | 8 - test/browser-fixtures/esm.fixture.html | 7 - test/browser-fixtures/exports.fixture.js | 8 - test/browser-fixtures/qunit.fixture.js | 8 - test/browser-fixtures/tdd.fixture.js | 8 - .../{esm.spec.js => esm.spec.mjs} | 2 +- .../browser-specific/fixtures/esm.fixture.mjs | 2 + test/browser-specific/mocha.opts | 6 + test/browser-specific/setup.js | 9 +- test/integration/config.spec.js | 18 + test/integration/deprecate.spec.js | 21 +- test/integration/fixtures/config/mocharc.js | 9 + test/integration/fixtures/config/mocharc.json | 7 + test/integration/fixtures/config/mocharc.yaml | 7 + test/integration/glob.spec.js | 2 +- test/integration/helpers.js | 129 +- test/integration/hooks.spec.js | 2 +- test/integration/init.spec.js | 58 + test/integration/options.spec.js | 145 +- test/integration/regression.spec.js | 16 +- test/integration/retries.spec.js | 158 +- test/integration/show-plugins.spec.js | 76 + test/integration/suite.spec.js | 44 +- test/node-unit/cli/config.spec.js | 131 + test/node-unit/cli/node-flags.spec.js | 77 + test/node-unit/cli/options.spec.js | 612 + test/node-unit/cli/run-helpers.spec.js | 28 + test/{ => opts}/mocha.opts | 5 +- test/opts/opts.spec.js | 11 + test/reporters/base.spec.js | 6 +- test/setup.js | 2 +- test/unit/context.spec.js | 4 +- test/unit/mocha.spec.js | 127 +- test/unit/suite.spec.js | 39 +- 87 files changed, 9535 insertions(+), 7379 deletions(-) create mode 100644 .mocharc.yml create mode 100644 example/config/README.md create mode 100644 example/config/mocharc.js create mode 100644 example/config/mocharc.json create mode 100644 example/config/mocharc.yml rename lib/{ => browser}/template.html (100%) create mode 100755 lib/cli/cli.js create mode 100644 lib/cli/commands.js create mode 100644 lib/cli/config.js create mode 100644 lib/cli/index.js create mode 100644 lib/cli/init.js create mode 100644 lib/cli/node-flags.js create mode 100644 lib/cli/one-and-dones.js create mode 100644 lib/cli/options.js create mode 100644 lib/cli/run-args.js create mode 100644 lib/cli/run-helpers.js create mode 100644 lib/cli/run.js create mode 100644 lib/mocharc.json delete mode 100644 test/browser-fixtures/bdd.fixture.js delete mode 100644 test/browser-fixtures/esm.fixture.html delete mode 100644 test/browser-fixtures/exports.fixture.js delete mode 100644 test/browser-fixtures/qunit.fixture.js delete mode 100644 test/browser-fixtures/tdd.fixture.js rename test/browser-specific/{esm.spec.js => esm.spec.mjs} (74%) create mode 100644 test/browser-specific/fixtures/esm.fixture.mjs create mode 100644 test/browser-specific/mocha.opts create mode 100644 test/integration/config.spec.js create mode 100644 test/integration/fixtures/config/mocharc.js create mode 100644 test/integration/fixtures/config/mocharc.json create mode 100644 test/integration/fixtures/config/mocharc.yaml create mode 100644 test/integration/init.spec.js create mode 100644 test/integration/show-plugins.spec.js create mode 100644 test/node-unit/cli/config.spec.js create mode 100644 test/node-unit/cli/node-flags.spec.js create mode 100644 test/node-unit/cli/options.spec.js create mode 100644 test/node-unit/cli/run-helpers.spec.js rename test/{ => opts}/mocha.opts (73%) create mode 100644 test/opts/opts.spec.js 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..fea9a89dea 100755 --- a/bin/_mocha +++ b/bin/_mocha @@ -1,652 +1,9 @@ #!/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. */ -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..528337a75a 100755 --- a/bin/mocha +++ b/bin/mocha @@ -7,69 +7,90 @@ * when found, before invoking the "real" _mocha(1) executable. */ -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-args'); -// 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' }); diff --git a/bin/options.js b/bin/options.js index 0c27ae5fa9..77070a07b1 100644 --- a/bin/options.js +++ b/bin/options.js @@ -1,88 +1,5 @@ 'use strict'; -/** - * Dependencies. - */ +// This file is deprecated and will be removed in a future version of Mocha -const fs = require('fs'); - -/** - * Export `getOptions`. - */ - -module.exports = getOptions; - -/** - * Default pathname for run-control file. - * - * @constant - * @type {string} - * @default - */ -const defaultPathname = 'test/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 optsPath = - process.argv.indexOf('--opts') === -1 - ? defaultPathname - : process.argv[process.argv.indexOf('--opts') + 1]; - - try { - const opts = parseOptions(readOptionsFile(optsPath)); - - process.argv = process.argv - .slice(0, 2) - .concat(opts.concat(process.argv.slice(2))); - } catch (ignore) { - // NOTE: should console.error() and throw the error - } - - process.env.LOADED_MOCHA_OPTS = true; -} +module.exports = require('../lib/cli/options'); diff --git a/docs/index.md b/docs/index.md index fd4ac9aa94..1450c50596 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) @@ -133,8 +134,8 @@ Set up a test script in package.json: ```json "scripts": { - "test": "mocha" - } + "test": "mocha" +} ``` Then run tests with: @@ -777,82 +778,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 - -```console -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 +## 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/ ``` -### `-w, --watch` + + +### `--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` + +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. + +### `--bail, -b` + +Causes Mocha to stop running tests after the first test failure it encounters. -Executes tests on changes to JavaScript in the CWD, and once initially. +`--bail` does *not* imply `--exit`. -### `--exit` / `--no-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).* -> *Updated in Mocha v4.0.0* +### `--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. @@ -866,73 +905,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