blob: 79903327c3dc6003b18a137ff6c1bdf433427fc6 [file] [log] [blame]
Yang Guo4fd355c2019-09-19 08:59:031'use strict';
2
3/**
4 * Definition for Mocha's default ("run tests") command
5 *
6 * @module
7 * @private
8 */
9
Peter Marshall0b95ea12020-07-02 16:50:0410const symbols = require('log-symbols');
11const ansi = require('ansi-colors');
Yang Guo4fd355c2019-09-19 08:59:0312const Mocha = require('../mocha');
13const {
14 createUnsupportedError,
15 createInvalidArgumentValueError,
16 createMissingArgumentError
17} = require('../errors');
18
19const {
20 list,
21 handleRequires,
Peter Marshall4e161df2020-11-10 12:29:3822 validateLegacyPlugin,
Yang Guo4fd355c2019-09-19 08:59:0323 runMocha
24} = require('./run-helpers');
25const {ONE_AND_DONES, ONE_AND_DONE_ARGS} = require('./one-and-dones');
26const debug = require('debug')('mocha:cli:run');
27const defaults = require('../mocharc');
28const {types, aliases} = require('./run-option-metadata');
29
30/**
31 * Logical option groups
32 * @constant
33 */
34const GROUPS = {
35 FILES: 'File Handling',
36 FILTERS: 'Test Filters',
37 NODEJS: 'Node.js & V8',
38 OUTPUT: 'Reporting & Output',
39 RULES: 'Rules & Behavior',
40 CONFIG: 'Configuration'
41};
42
Tim van der Lippe99190c92020-04-07 15:46:3243exports.command = ['$0 [spec..]', 'inspect'];
Yang Guo4fd355c2019-09-19 08:59:0344
45exports.describe = 'Run tests with Mocha';
46
47exports.builder = yargs =>
48 yargs
49 .options({
50 'allow-uncaught': {
51 description: 'Allow uncaught errors to propagate',
52 group: GROUPS.RULES
53 },
54 'async-only': {
55 description:
56 'Require all tests to use a callback (async) or return a Promise',
57 group: GROUPS.RULES
58 },
59 bail: {
60 description: 'Abort ("bail") after first test failure',
61 group: GROUPS.RULES
62 },
63 'check-leaks': {
64 description: 'Check for global variable leaks',
65 group: GROUPS.RULES
66 },
67 color: {
68 description: 'Force-enable color output',
69 group: GROUPS.OUTPUT
70 },
71 config: {
72 config: true,
73 defaultDescription: '(nearest rc file)',
74 description: 'Path to config file',
75 group: GROUPS.CONFIG
76 },
77 delay: {
78 description: 'Delay initial execution of root suite',
79 group: GROUPS.RULES
80 },
81 diff: {
82 default: true,
83 description: 'Show diff on failure',
84 group: GROUPS.OUTPUT
85 },
Tim van der Lippe324d6032021-06-21 15:43:2686 'dry-run': {
87 description: 'Report tests without executing them',
88 group: GROUPS.RULES
89 },
Yang Guo4fd355c2019-09-19 08:59:0390 exit: {
91 description: 'Force Mocha to quit after tests complete',
92 group: GROUPS.RULES
93 },
94 extension: {
95 default: defaults.extension,
Tim van der Lippe99190c92020-04-07 15:46:3296 description: 'File extension(s) to load',
Yang Guo4fd355c2019-09-19 08:59:0397 group: GROUPS.FILES,
98 requiresArg: true,
99 coerce: list
100 },
101 fgrep: {
102 conflicts: 'grep',
103 description: 'Only run tests containing this string',
104 group: GROUPS.FILTERS,
105 requiresArg: true
106 },
107 file: {
108 defaultDescription: '(none)',
109 description:
110 'Specify file(s) to be loaded prior to root suite execution',
111 group: GROUPS.FILES,
112 normalize: true,
113 requiresArg: true
114 },
115 'forbid-only': {
116 description: 'Fail if exclusive test(s) encountered',
117 group: GROUPS.RULES
118 },
119 'forbid-pending': {
120 description: 'Fail if pending test(s) encountered',
121 group: GROUPS.RULES
122 },
123 'full-trace': {
124 description: 'Display full stack traces',
125 group: GROUPS.OUTPUT
126 },
127 global: {
128 coerce: list,
129 description: 'List of allowed global variables',
130 group: GROUPS.RULES,
131 requiresArg: true
132 },
133 grep: {
134 coerce: value => (!value ? null : value),
135 conflicts: 'fgrep',
136 description: 'Only run tests matching this string or regexp',
137 group: GROUPS.FILTERS,
138 requiresArg: true
139 },
140 growl: {
141 description: 'Enable Growl notifications',
142 group: GROUPS.OUTPUT
143 },
144 ignore: {
145 defaultDescription: '(none)',
146 description: 'Ignore file(s) or glob pattern(s)',
147 group: GROUPS.FILES,
148 requiresArg: true
149 },
150 'inline-diffs': {
151 description:
152 'Display actual/expected differences inline within each string',
153 group: GROUPS.OUTPUT
154 },
Yang Guo4fd355c2019-09-19 08:59:03155 invert: {
156 description: 'Inverts --grep and --fgrep matches',
157 group: GROUPS.FILTERS
158 },
Peter Marshall0b95ea12020-07-02 16:50:04159 jobs: {
160 description:
161 'Number of concurrent jobs for --parallel; use 1 to run in serial',
162 defaultDescription: '(number of CPU cores - 1)',
163 requiresArg: true,
164 group: GROUPS.RULES
165 },
Tim van der Lippe99190c92020-04-07 15:46:32166 'list-interfaces': {
167 conflicts: Array.from(ONE_AND_DONE_ARGS),
168 description: 'List built-in user interfaces & exit'
169 },
170 'list-reporters': {
171 conflicts: Array.from(ONE_AND_DONE_ARGS),
172 description: 'List built-in reporters & exit'
173 },
Yang Guo4fd355c2019-09-19 08:59:03174 'no-colors': {
175 description: 'Force-disable color output',
176 group: GROUPS.OUTPUT,
177 hidden: true
178 },
Yang Guo4fd355c2019-09-19 08:59:03179 package: {
180 description: 'Path to package.json for config',
181 group: GROUPS.CONFIG,
182 normalize: true,
183 requiresArg: true
184 },
Peter Marshall0b95ea12020-07-02 16:50:04185 parallel: {
186 description: 'Run tests in parallel',
187 group: GROUPS.RULES
188 },
Yang Guo4fd355c2019-09-19 08:59:03189 recursive: {
190 description: 'Look for tests in subdirectories',
191 group: GROUPS.FILES
192 },
193 reporter: {
194 default: defaults.reporter,
195 description: 'Specify reporter to use',
196 group: GROUPS.OUTPUT,
197 requiresArg: true
198 },
Yang Guo4fd355c2019-09-19 08:59:03199 'reporter-option': {
200 coerce: opts =>
201 list(opts).reduce((acc, opt) => {
202 const pair = opt.split('=');
203
204 if (pair.length > 2 || !pair.length) {
205 throw createInvalidArgumentValueError(
206 `invalid reporter option '${opt}'`,
207 '--reporter-option',
208 opt,
209 'expected "key=value" format'
210 );
211 }
212
213 acc[pair[0]] = pair.length === 2 ? pair[1] : true;
214 return acc;
215 }, {}),
216 description: 'Reporter-specific options (<k=v,[k1=v1,..]>)',
217 group: GROUPS.OUTPUT,
218 requiresArg: true
219 },
220 require: {
221 defaultDescription: '(none)',
222 description: 'Require module',
223 group: GROUPS.FILES,
224 requiresArg: true
225 },
226 retries: {
227 description: 'Retry failed tests this many times',
228 group: GROUPS.RULES
229 },
230 slow: {
231 default: defaults.slow,
232 description: 'Specify "slow" test threshold (in milliseconds)',
233 group: GROUPS.RULES
234 },
235 sort: {
236 description: 'Sort test files',
237 group: GROUPS.FILES
238 },
239 timeout: {
240 default: defaults.timeout,
241 description: 'Specify test timeout threshold (in milliseconds)',
242 group: GROUPS.RULES
243 },
244 ui: {
245 default: defaults.ui,
246 description: 'Specify user interface',
247 group: GROUPS.RULES,
248 requiresArg: true
249 },
250 watch: {
251 description: 'Watch files in the current working directory for changes',
252 group: GROUPS.FILES
Tim van der Lippe99190c92020-04-07 15:46:32253 },
254 'watch-files': {
255 description: 'List of paths or globs to watch',
256 group: GROUPS.FILES,
257 requiresArg: true,
258 coerce: list
259 },
260 'watch-ignore': {
261 description: 'List of paths or globs to exclude from watching',
262 group: GROUPS.FILES,
263 requiresArg: true,
264 coerce: list,
265 default: defaults['watch-ignore']
Yang Guo4fd355c2019-09-19 08:59:03266 }
267 })
268 .positional('spec', {
269 default: ['test'],
270 description: 'One or more files, directories, or globs to test',
271 type: 'array'
272 })
273 .check(argv => {
274 // "one-and-dones"; let yargs handle help and version
275 Object.keys(ONE_AND_DONES).forEach(opt => {
276 if (argv[opt]) {
277 ONE_AND_DONES[opt].call(null, yargs);
278 process.exit();
279 }
280 });
281
282 // yargs.implies() isn't flexible enough to handle this
283 if (argv.invert && !('fgrep' in argv || 'grep' in argv)) {
284 throw createMissingArgumentError(
285 '"--invert" requires one of "--fgrep <str>" or "--grep <regexp>"',
286 '--fgrep|--grep',
287 'string|regexp'
288 );
289 }
290
Peter Marshall0b95ea12020-07-02 16:50:04291 if (argv.parallel) {
292 // yargs.conflicts() can't deal with `--file foo.js --no-parallel`, either
293 if (argv.file) {
294 throw createUnsupportedError(
295 '--parallel runs test files in a non-deterministic order, and is mutually exclusive with --file'
296 );
297 }
298
299 // or this
300 if (argv.sort) {
301 throw createUnsupportedError(
302 '--parallel runs test files in a non-deterministic order, and is mutually exclusive with --sort'
303 );
304 }
305
306 if (argv.reporter === 'progress') {
307 throw createUnsupportedError(
308 '--reporter=progress is mutually exclusive with --parallel'
309 );
310 }
311
312 if (argv.reporter === 'markdown') {
313 throw createUnsupportedError(
314 '--reporter=markdown is mutually exclusive with --parallel'
315 );
316 }
317
318 if (argv.reporter === 'json-stream') {
319 throw createUnsupportedError(
320 '--reporter=json-stream is mutually exclusive with --parallel'
321 );
322 }
323 }
324
Yang Guo4fd355c2019-09-19 08:59:03325 if (argv.compilers) {
326 throw createUnsupportedError(
327 `--compilers is DEPRECATED and no longer supported.
328 See https://git.io/vdcSr for migration information.`
329 );
330 }
331
Peter Marshall0b95ea12020-07-02 16:50:04332 if (argv.opts) {
333 throw createUnsupportedError(
334 `--opts: configuring Mocha via 'mocha.opts' is DEPRECATED and no longer supported.
335 Please use a configuration file instead.`
336 );
337 }
Yang Guo4fd355c2019-09-19 08:59:03338
339 return true;
340 })
Peter Marshall0b95ea12020-07-02 16:50:04341 .middleware(async (argv, yargs) => {
342 // currently a failing middleware does not work nicely with yargs' `fail()`.
343 try {
344 // load requires first, because it can impact "plugin" validation
Peter Marshall4e161df2020-11-10 12:29:38345 const plugins = await handleRequires(argv.require);
346 validateLegacyPlugin(argv, 'reporter', Mocha.reporters);
347 validateLegacyPlugin(argv, 'ui', Mocha.interfaces);
348 Object.assign(argv, plugins);
Peter Marshall0b95ea12020-07-02 16:50:04349 } catch (err) {
350 // this could be a bad --require, bad reporter, ui, etc.
351 console.error(`\n${symbols.error} ${ansi.red('ERROR:')}`, err);
352 yargs.exit(1);
353 }
354 })
Yang Guo4fd355c2019-09-19 08:59:03355 .array(types.array)
356 .boolean(types.boolean)
357 .string(types.string)
358 .number(types.number)
359 .alias(aliases);
360
Tim van der Lippe99190c92020-04-07 15:46:32361exports.handler = async function(argv) {
Yang Guo4fd355c2019-09-19 08:59:03362 debug('post-yargs config', argv);
363 const mocha = new Mocha(argv);
Tim van der Lippe99190c92020-04-07 15:46:32364
365 try {
366 await runMocha(mocha, argv);
367 } catch (err) {
368 console.error('\n' + (err.stack || `Error: ${err.message || err}`));
369 process.exit(1);
370 }
Yang Guo4fd355c2019-09-19 08:59:03371};