1 | 'use strict';
|
2 | const path = require('path');
|
3 | const del = require('del');
|
4 | const updateNotifier = require('update-notifier');
|
5 | const figures = require('figures');
|
6 | const arrify = require('arrify');
|
7 | const meow = require('meow');
|
8 | const Promise = require('bluebird');
|
9 | const isCi = require('is-ci');
|
10 | const loadConf = require('./load-config');
|
11 |
|
12 |
|
13 | Promise.longStackTraces();
|
14 |
|
15 | function exit(message) {
|
16 | console.error(`\n${require('./chalk').get().red(figures.cross)} ${message}`);
|
17 | process.exit(1);
|
18 | }
|
19 |
|
20 | exports.run = async () => {
|
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);
|
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 |
|
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);
|
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 | };
|