ava
Version:
279 lines (251 loc) • 7.42 kB
JavaScript
const path = require('path');
const del = require('del');
const updateNotifier = require('update-notifier');
const figures = require('figures');
const arrify = require('arrify');
const meow = require('meow');
const Promise = require('bluebird');
const isCi = require('is-ci');
const loadConf = require('./load-config');
// Bluebird specific
Promise.longStackTraces();
function exit(message) {
console.error(`\n${require('./chalk').get().red(figures.cross)} ${message}`);
process.exit(1); // eslint-disable-line unicorn/no-process-exit
}
exports.run = async () => { // eslint-disable-line complexity
let conf = {};
let confError = null;
try {
conf = loadConf();
} catch (error) {
confError = error;
}
const cli = meow(`
Usage
ava [<file> ...]
Options
--watch, -w Re-run tests when tests and source files change
--match, -m Only run tests with matching title (Can be repeated)
--update-snapshots, -u Update snapshots
--fail-fast Stop after first test failure
--timeout, -T Set global timeout (milliseconds or human-readable, e.g. 10s, 2m)
--serial, -s Run tests serially
--concurrency, -c Max number of test files running at the same time (Default: CPU cores)
--verbose, -v Enable verbose output
--tap, -t Generate TAP output
--color Force color output
--no-color Disable color output
--reset-cache Reset AVA's compilation cache and exit
Examples
ava
ava test.js test2.js
ava test-*.js
ava test
The above relies on your shell expanding the glob patterns.
Without arguments, AVA uses the following patterns:
**/test.js **/test-*.js **/*.spec.js **/*.test.js **/test/**/*.js **/tests/**/*.js **/__tests__/**/*.js
`, {
flags: {
watch: {
type: 'boolean',
alias: 'w'
},
match: {
type: 'string',
alias: 'm',
default: conf.match
},
'update-snapshots': {
type: 'boolean',
alias: 'u'
},
'fail-fast': {
type: 'boolean',
default: conf.failFast
},
timeout: {
type: 'string',
alias: 'T',
default: conf.timeout
},
serial: {
type: 'boolean',
alias: 's',
default: conf.serial
},
concurrency: {
type: 'string',
alias: 'c',
default: conf.concurrency
},
verbose: {
type: 'boolean',
alias: 'v',
default: conf.verbose
},
tap: {
type: 'boolean',
alias: 't',
default: conf.tap
},
color: {
type: 'boolean',
default: 'color' in conf ? conf.color : require('supports-color').stdout !== false
},
'reset-cache': {
type: 'boolean',
default: false
},
'--': {
type: 'string'
}
}
});
updateNotifier({pkg: cli.pkg}).notify();
const chalk = require('./chalk').set({enabled: cli.flags.color});
if (confError) {
if (confError.parent) {
exit(`${confError.message}\n\n${chalk.gray((confError.parent && confError.parent.stack) || confError.parent)}`);
} else {
exit(confError.message);
}
}
const {projectDir} = conf;
if (cli.flags.resetCache) {
const cacheDir = path.join(projectDir, 'node_modules', '.cache', 'ava');
try {
await del('*', {
cwd: cacheDir,
nodir: true
});
console.error(`\n${chalk.green(figures.tick)} Removed AVA cache files in ${cacheDir}`);
process.exit(0); // eslint-disable-line unicorn/no-process-exit
} catch (error) {
exit(`Error removing AVA cache files in ${cacheDir}\n\n${chalk.gray((error && error.stack) || error)}`);
}
return;
}
if (cli.flags.watch && cli.flags.tap && !conf.tap) {
exit('The TAP reporter is not available when using watch mode.');
}
if (cli.flags.watch && isCi) {
exit('Watch mode is not available in CI, as it prevents AVA from terminating.');
}
if (
cli.flags.concurrency === '' ||
(cli.flags.concurrency && (!Number.isInteger(Number.parseFloat(cli.flags.concurrency)) || parseInt(cli.flags.concurrency, 10) < 0))
) {
exit('The --concurrency or -c flag must be provided with a nonnegative integer.');
}
const ciParallelVars = require('ci-parallel-vars');
const Api = require('./api');
const VerboseReporter = require('./reporters/verbose');
const MiniReporter = require('./reporters/mini');
const TapReporter = require('./reporters/tap');
const Watcher = require('./watcher');
const babelPipeline = require('./babel-pipeline');
const normalizeExtensions = require('./extensions');
const {normalizeGlobs} = require('./globs');
const validateEnvironmentVariables = require('./environment-variables');
let babelConfig = null;
try {
babelConfig = babelPipeline.validate(conf.babel);
} catch (error) {
exit(error.message);
}
let environmentVariables;
try {
environmentVariables = validateEnvironmentVariables(conf.environmentVariables);
} catch (error) {
exit(error.message);
}
let extensions;
try {
extensions = normalizeExtensions(conf.extensions || [], babelConfig);
} catch (error) {
exit(error.message);
}
let globs;
try {
globs = normalizeGlobs(conf.files, conf.helpers, conf.sources, extensions.all);
} catch (error) {
exit(error.message);
}
// Copy resultant cli.flags into conf for use with Api and elsewhere
Object.assign(conf, cli.flags);
let parallelRuns = null;
if (isCi && ciParallelVars) {
const {index: currentIndex, total: totalRuns} = ciParallelVars;
parallelRuns = {currentIndex, totalRuns};
}
const match = arrify(conf.match);
const resolveTestsFrom = cli.input.length === 0 ? projectDir : process.cwd();
const api = new Api({
babelConfig,
cacheEnabled: conf.cache !== false,
color: conf.color,
compileEnhancements: conf.compileEnhancements !== false,
concurrency: conf.concurrency ? parseInt(conf.concurrency, 10) : 0,
extensions,
failFast: conf.failFast,
failWithoutAssertions: conf.failWithoutAssertions !== false,
globs,
environmentVariables,
match,
parallelRuns,
projectDir,
ranFromCli: true,
require: arrify(conf.require),
resolveTestsFrom,
serial: conf.serial,
snapshotDir: conf.snapshotDir ? path.resolve(projectDir, conf.snapshotDir) : null,
timeout: conf.timeout,
updateSnapshots: conf.updateSnapshots,
workerArgv: cli.flags['--']
});
let reporter;
if (conf.tap && !conf.watch) {
reporter = new TapReporter({
reportStream: process.stdout,
stdStream: process.stderr
});
} else if (conf.verbose || isCi || !process.stdout.isTTY) {
reporter = new VerboseReporter({
reportStream: process.stdout,
stdStream: process.stderr,
watching: conf.watch
});
} else {
reporter = new MiniReporter({
reportStream: process.stdout,
stdStream: process.stderr,
watching: conf.watch
});
}
api.on('run', plan => {
reporter.startRun(plan);
plan.status.on('stateChange', evt => {
if (evt.type === 'interrupt') {
reporter.endRun();
process.exit(1); // eslint-disable-line unicorn/no-process-exit
}
});
});
const files = cli.input.map(file => path.relative(resolveTestsFrom, path.resolve(process.cwd(), file)));
if (conf.watch) {
const watcher = new Watcher({
api,
reporter,
files,
globs,
resolveTestsFrom
});
watcher.observeStdin(process.stdin);
} else {
const runStatus = await api.run(files);
process.exitCode = runStatus.suggestExitCode({matching: match.length > 0});
reporter.endRun();
}
};
;