Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for running tests in parallel #4245

Merged
merged 1 commit into from Jun 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 13 additions & 13 deletions .eslintrc.yml
Expand Up @@ -21,22 +21,22 @@ rules:
property: 'assign'
overrides:
- files:
- docs/js/**/*.js
- 'docs/js/**/*.js'
env:
node: false
- files:
- scripts/**/*.js
- package-scripts.js
- karma.conf.js
- .wallaby.js
- .eleventy.js
- bin/*
- lib/cli/**/*.js
- test/node-unit/**/*.js
- test/integration/options/watch.spec.js
- test/integration/helpers.js
- lib/growl.js
- docs/_data/**/*.js
- '.eleventy.js'
- '.wallaby.js'
- 'package-scripts.js'
- 'karma.conf.js'
- 'bin/*'
- 'docs/_data/**/*.js'
- 'lib/cli/**/*.js'
- 'lib/nodejs/**/*.js'
- 'scripts/**/*.js'
- 'test/integration/helpers.js'
- 'test/integration/options/watch.spec.js'
- 'test/node-unit/**/*.js'
parserOptions:
ecmaVersion: 2018
env:
Expand Down
1 change: 1 addition & 0 deletions .mocharc.yml
Expand Up @@ -5,6 +5,7 @@ global:
- 'okGlobalC'
- 'callback*'
timeout: 1000
parallel: true
watch-ignore:
- '.*'
- 'docs/_dist/**'
Expand Down
6 changes: 5 additions & 1 deletion .travis.yml
Expand Up @@ -39,7 +39,8 @@ jobs:
- script: COVERAGE=1 npm start test.node
after_success: npm start coveralls
name: 'Latest Node.js (with coverage)'

- script: MOCHA_PARALLEL=0 npm start test.node.unit
name: 'Latest Node.js (unit tests in serial mode)'
- &node
script: npm start test.node
node_js: '13'
Expand Down Expand Up @@ -95,6 +96,9 @@ jobs:
script: true
name: 'Prime cache'

env:
- 'NODE_OPTIONS="--trace-warnings"'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo this is useful just to leave in.


notifications:
email: false
webhooks:
Expand Down
19 changes: 17 additions & 2 deletions bin/mocha
Expand Up @@ -130,8 +130,23 @@ if (Object.keys(nodeArgs).length) {

// terminate children.
process.on('SIGINT', () => {
proc.kill('SIGINT'); // calls runner.abort()
proc.kill('SIGTERM'); // if that didn't work, we're probably in an infinite loop, so make it die.
// XXX: a previous comment said this would abort the runner, but I can't see that it does
// anything with the default runner.
debug('main process caught SIGINT');
proc.kill('SIGINT');
// if running in parallel mode, we will have a proper SIGINT handler, so the below won't
// be needed.
if (!args.parallel || args.jobs < 2) {
nicojs marked this conversation as resolved.
Show resolved Hide resolved
// win32 does not support SIGTERM, so use next best thing.
if (require('os').platform() === 'win32') {
proc.kill('SIGKILL');
} else {
// using SIGKILL won't cleanly close the output streams, which can result
// in cut-off text or a befouled terminal.
debug('sending SIGTERM to child process');
proc.kill('SIGTERM');
}
}
});
} else {
debug('running Mocha in-process');
Expand Down
11 changes: 8 additions & 3 deletions karma.conf.js
Expand Up @@ -30,13 +30,18 @@ module.exports = config => {
browserify: {
debug: true,
configure: function configure(b) {
b.ignore('./lib/cli/*.js')
.ignore('chokidar')
b.ignore('chokidar')
.ignore('fs')
.ignore('glob')
.ignore('./lib/esm-utils.js')
.ignore('path')
.ignore('supports-color')
.ignore('./lib/esm-utils.js')
.ignore('./lib/cli/*.js')
.ignore('./lib/nodejs/serializer.js')
.ignore('./lib/nodejs/worker.js')
.ignore('./lib/nodejs/buffered-worker-pool.js')
.ignore('./lib/nodejs/parallel-buffered-runner.js')
.ignore('./lib/nodejs/reporters/parallel-buffered.js')
.on('bundled', (err, content) => {
if (err) {
throw err;
Expand Down
3 changes: 2 additions & 1 deletion lib/browser/growl.js
Expand Up @@ -11,6 +11,7 @@
var Date = global.Date;
var setTimeout = global.setTimeout;
var EVENT_RUN_END = require('../runner').constants.EVENT_RUN_END;
var isBrowser = require('../utils').isBrowser;

/**
* Checks if browser notification support exists.
Expand All @@ -25,7 +26,7 @@ var EVENT_RUN_END = require('../runner').constants.EVENT_RUN_END;
exports.isCapable = function() {
var hasNotificationSupport = 'Notification' in window;
var hasPromiseSupport = typeof Promise === 'function';
return process.browser && hasNotificationSupport && hasPromiseSupport;
return isBrowser() && hasNotificationSupport && hasPromiseSupport;
};

/**
Expand Down
19 changes: 12 additions & 7 deletions lib/cli/collect-files.js
Expand Up @@ -17,13 +17,7 @@ const {NO_FILES_MATCH_PATTERN} = require('../errors').constants;

/**
* Smash together an array of test files in the correct order
* @param {Object} opts - Options
* @param {string[]} opts.extension - File extensions to use
* @param {string[]} opts.spec - Files, dirs, globs to run
* @param {string[]} opts.ignore - Files, dirs, globs to ignore
* @param {string[]} opts.file - List of additional files to include
* @param {boolean} opts.recursive - Find files recursively
* @param {boolean} opts.sort - Sort test files
* @param {FileCollectionOptions} [opts] - Options
* @returns {string[]} List of files to test
* @private
*/
Expand Down Expand Up @@ -84,3 +78,14 @@ module.exports = ({ignore, extension, file, recursive, sort, spec} = {}) => {

return files;
};

/**
* An object to configure how Mocha gathers test files
* @typedef {Object} FileCollectionOptions
* @property {string[]} extension - File extensions to use
* @property {string[]} spec - Files, dirs, globs to run
* @property {string[]} ignore - Files, dirs, globs to ignore
* @property {string[]} file - List of additional files to include
* @property {boolean} recursive - Find files recursively
* @property {boolean} sort - Sort test files
*/
51 changes: 41 additions & 10 deletions lib/cli/run-helpers.js
Expand Up @@ -10,7 +10,7 @@
const fs = require('fs');
const path = require('path');
const debug = require('debug')('mocha:cli:run:helpers');
const watchRun = require('./watch-run');
const {watchRun, watchParallelRun} = require('./watch-run');
const collectFiles = require('./collect-files');
const {type} = require('../utils');
const {format} = require('util');
Expand Down Expand Up @@ -151,24 +151,52 @@ const singleRun = async (mocha, {exit}, fileCollectParams) => {
};

/**
* Actually run tests
* Collect files and run tests (using `BufferedRunner`).
*
* This is `async` for consistency.
*
* @param {Mocha} mocha - Mocha instance
* @param {Object} opts - Command line options
* @param {Options} options - Command line options
* @param {Object} fileCollectParams - Parameters that control test
* file collection. See `lib/cli/collect-files.js`.
* @returns {Promise<BufferedRunner>}
* @ignore
* @private
* @returns {Promise}
*/
const parallelRun = async (mocha, options, fileCollectParams) => {
const files = collectFiles(fileCollectParams);
debug(
'executing %d test file(s) across %d concurrent jobs',
files.length,
options.jobs
);
mocha.files = files;

// note that we DO NOT load any files here; this is handled by the worker
return mocha.run(options.exit ? exitMocha : exitMochaLater);
};

/**
* Actually run tests. Delegates to one of four different functions:
* - `singleRun`: run tests in serial & exit
* - `watchRun`: run tests in serial, rerunning as files change
* - `parallelRun`: run tests in parallel & exit
* - `watchParallelRun`: run tests in parallel, rerunning as files change
* @param {Mocha} mocha - Mocha instance
* @param {Options} opts - Command line options
* @private
* @returns {Promise<Runner>}
*/
exports.runMocha = async (mocha, options) => {
const {
watch = false,
extension = [],
exit = false,
ignore = [],
file = [],
parallel = false,
recursive = false,
sort = false,
spec = [],
watchFiles,
watchIgnore
spec = []
} = options;

const fileCollectParams = {
Expand All @@ -180,11 +208,14 @@ exports.runMocha = async (mocha, options) => {
spec
};

let run;
if (watch) {
watchRun(mocha, {watchFiles, watchIgnore}, fileCollectParams);
run = parallel ? watchParallelRun : watchRun;
} else {
await singleRun(mocha, {exit}, fileCollectParams);
run = parallel ? parallelRun : singleRun;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my assumption is that people tend not to love nested ternaries. but if someone wants it, I will abide

return run(mocha, options, fileCollectParams);
};

/**
Expand Down
5 changes: 4 additions & 1 deletion lib/cli/run-option-metadata.js
Expand Up @@ -42,11 +42,12 @@ exports.types = {
'list-interfaces',
'list-reporters',
'no-colors',
'parallel',
'recursive',
'sort',
'watch'
],
number: ['retries'],
number: ['retries', 'jobs'],
string: [
'config',
'fgrep',
Expand Down Expand Up @@ -75,7 +76,9 @@ exports.aliases = {
growl: ['G'],
ignore: ['exclude'],
invert: ['i'],
jobs: ['j'],
'no-colors': ['C'],
parallel: ['p'],
reporter: ['R'],
'reporter-option': ['reporter-options', 'O'],
require: ['r'],
Expand Down
45 changes: 45 additions & 0 deletions lib/cli/run.js
Expand Up @@ -151,6 +151,13 @@ exports.builder = yargs =>
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'
Expand All @@ -170,6 +177,10 @@ exports.builder = yargs =>
normalize: true,
requiresArg: true
},
parallel: {
description: 'Run tests in parallel',
group: GROUPS.RULES
},
recursive: {
description: 'Look for tests in subdirectories',
group: GROUPS.FILES
Expand Down Expand Up @@ -272,6 +283,40 @@ exports.builder = yargs =>
);
}

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.
Expand Down