UNPKG

7.42 kBJavaScriptView Raw
1'use strict';
2const path = require('path');
3const del = require('del');
4const updateNotifier = require('update-notifier');
5const figures = require('figures');
6const arrify = require('arrify');
7const meow = require('meow');
8const Promise = require('bluebird');
9const isCi = require('is-ci');
10const loadConf = require('./load-config');
11
12// Bluebird specific
13Promise.longStackTraces();
14
15function exit(message) {
16 console.error(`\n${require('./chalk').get().red(figures.cross)} ${message}`);
17 process.exit(1); // eslint-disable-line unicorn/no-process-exit
18}
19
20exports.run = async () => { // eslint-disable-line complexity
21 let conf = {};
22 let confError = null;
23 try {
24 conf = loadConf();
25 } catch (error) {
26 confError = error;
27 }
28
29 const cli = meow(`
30 Usage
31 ava [<file> ...]
32
33 Options
34 --watch, -w Re-run tests when tests and source files change
35 --match, -m Only run tests with matching title (Can be repeated)
36 --update-snapshots, -u Update snapshots
37 --fail-fast Stop after first test failure
38 --timeout, -T Set global timeout (milliseconds or human-readable, e.g. 10s, 2m)
39 --serial, -s Run tests serially
40 --concurrency, -c Max number of test files running at the same time (Default: CPU cores)
41 --verbose, -v Enable verbose output
42 --tap, -t Generate TAP output
43 --color Force color output
44 --no-color Disable color output
45 --reset-cache Reset AVA's compilation cache and exit
46
47 Examples
48 ava
49 ava test.js test2.js
50 ava test-*.js
51 ava test
52
53 The above relies on your shell expanding the glob patterns.
54 Without arguments, AVA uses the following patterns:
55 **/test.js **/test-*.js **/*.spec.js **/*.test.js **/test/**/*.js **/tests/**/*.js **/__tests__/**/*.js
56 `, {
57 flags: {
58 watch: {
59 type: 'boolean',
60 alias: 'w'
61 },
62 match: {
63 type: 'string',
64 alias: 'm',
65 default: conf.match
66 },
67 'update-snapshots': {
68 type: 'boolean',
69 alias: 'u'
70 },
71 'fail-fast': {
72 type: 'boolean',
73 default: conf.failFast
74 },
75 timeout: {
76 type: 'string',
77 alias: 'T',
78 default: conf.timeout
79 },
80 serial: {
81 type: 'boolean',
82 alias: 's',
83 default: conf.serial
84 },
85 concurrency: {
86 type: 'string',
87 alias: 'c',
88 default: conf.concurrency
89 },
90 verbose: {
91 type: 'boolean',
92 alias: 'v',
93 default: conf.verbose
94 },
95 tap: {
96 type: 'boolean',
97 alias: 't',
98 default: conf.tap
99 },
100 color: {
101 type: 'boolean',
102 default: 'color' in conf ? conf.color : require('supports-color').stdout !== false
103 },
104 'reset-cache': {
105 type: 'boolean',
106 default: false
107 },
108 '--': {
109 type: 'string'
110 }
111 }
112 });
113
114 updateNotifier({pkg: cli.pkg}).notify();
115 const chalk = require('./chalk').set({enabled: cli.flags.color});
116
117 if (confError) {
118 if (confError.parent) {
119 exit(`${confError.message}\n\n${chalk.gray((confError.parent && confError.parent.stack) || confError.parent)}`);
120 } else {
121 exit(confError.message);
122 }
123 }
124
125 const {projectDir} = conf;
126 if (cli.flags.resetCache) {
127 const cacheDir = path.join(projectDir, 'node_modules', '.cache', 'ava');
128 try {
129 await del('*', {
130 cwd: cacheDir,
131 nodir: true
132 });
133 console.error(`\n${chalk.green(figures.tick)} Removed AVA cache files in ${cacheDir}`);
134 process.exit(0); // eslint-disable-line unicorn/no-process-exit
135 } catch (error) {
136 exit(`Error removing AVA cache files in ${cacheDir}\n\n${chalk.gray((error && error.stack) || error)}`);
137 }
138
139 return;
140 }
141
142 if (cli.flags.watch && cli.flags.tap && !conf.tap) {
143 exit('The TAP reporter is not available when using watch mode.');
144 }
145
146 if (cli.flags.watch && isCi) {
147 exit('Watch mode is not available in CI, as it prevents AVA from terminating.');
148 }
149
150 if (
151 cli.flags.concurrency === '' ||
152 (cli.flags.concurrency && (!Number.isInteger(Number.parseFloat(cli.flags.concurrency)) || parseInt(cli.flags.concurrency, 10) < 0))
153 ) {
154 exit('The --concurrency or -c flag must be provided with a nonnegative integer.');
155 }
156
157 const ciParallelVars = require('ci-parallel-vars');
158 const Api = require('./api');
159 const VerboseReporter = require('./reporters/verbose');
160 const MiniReporter = require('./reporters/mini');
161 const TapReporter = require('./reporters/tap');
162 const Watcher = require('./watcher');
163 const babelPipeline = require('./babel-pipeline');
164 const normalizeExtensions = require('./extensions');
165 const {normalizeGlobs} = require('./globs');
166 const validateEnvironmentVariables = require('./environment-variables');
167
168 let babelConfig = null;
169 try {
170 babelConfig = babelPipeline.validate(conf.babel);
171 } catch (error) {
172 exit(error.message);
173 }
174
175 let environmentVariables;
176 try {
177 environmentVariables = validateEnvironmentVariables(conf.environmentVariables);
178 } catch (error) {
179 exit(error.message);
180 }
181
182 let extensions;
183 try {
184 extensions = normalizeExtensions(conf.extensions || [], babelConfig);
185 } catch (error) {
186 exit(error.message);
187 }
188
189 let globs;
190 try {
191 globs = normalizeGlobs(conf.files, conf.helpers, conf.sources, extensions.all);
192 } catch (error) {
193 exit(error.message);
194 }
195
196 // Copy resultant cli.flags into conf for use with Api and elsewhere
197 Object.assign(conf, cli.flags);
198
199 let parallelRuns = null;
200 if (isCi && ciParallelVars) {
201 const {index: currentIndex, total: totalRuns} = ciParallelVars;
202 parallelRuns = {currentIndex, totalRuns};
203 }
204
205 const match = arrify(conf.match);
206 const resolveTestsFrom = cli.input.length === 0 ? projectDir : process.cwd();
207 const api = new Api({
208 babelConfig,
209 cacheEnabled: conf.cache !== false,
210 color: conf.color,
211 compileEnhancements: conf.compileEnhancements !== false,
212 concurrency: conf.concurrency ? parseInt(conf.concurrency, 10) : 0,
213 extensions,
214 failFast: conf.failFast,
215 failWithoutAssertions: conf.failWithoutAssertions !== false,
216 globs,
217 environmentVariables,
218 match,
219 parallelRuns,
220 projectDir,
221 ranFromCli: true,
222 require: arrify(conf.require),
223 resolveTestsFrom,
224 serial: conf.serial,
225 snapshotDir: conf.snapshotDir ? path.resolve(projectDir, conf.snapshotDir) : null,
226 timeout: conf.timeout,
227 updateSnapshots: conf.updateSnapshots,
228 workerArgv: cli.flags['--']
229 });
230
231 let reporter;
232 if (conf.tap && !conf.watch) {
233 reporter = new TapReporter({
234 reportStream: process.stdout,
235 stdStream: process.stderr
236 });
237 } else if (conf.verbose || isCi || !process.stdout.isTTY) {
238 reporter = new VerboseReporter({
239 reportStream: process.stdout,
240 stdStream: process.stderr,
241 watching: conf.watch
242 });
243 } else {
244 reporter = new MiniReporter({
245 reportStream: process.stdout,
246 stdStream: process.stderr,
247 watching: conf.watch
248 });
249 }
250
251 api.on('run', plan => {
252 reporter.startRun(plan);
253
254 plan.status.on('stateChange', evt => {
255 if (evt.type === 'interrupt') {
256 reporter.endRun();
257 process.exit(1); // eslint-disable-line unicorn/no-process-exit
258 }
259 });
260 });
261
262 const files = cli.input.map(file => path.relative(resolveTestsFrom, path.resolve(process.cwd(), file)));
263
264 if (conf.watch) {
265 const watcher = new Watcher({
266 api,
267 reporter,
268 files,
269 globs,
270 resolveTestsFrom
271 });
272 watcher.observeStdin(process.stdin);
273 } else {
274 const runStatus = await api.run(files);
275 process.exitCode = runStatus.suggestExitCode({matching: match.length > 0});
276 reporter.endRun();
277 }
278};