diff --git a/cli.js b/cli.js index 38f7f61..95020b1 100755 --- a/cli.js +++ b/cli.js @@ -13,187 +13,16 @@ var localCLI = resolveCwd('ava/cli'); // see https://github.com/nodejs/node/issues/6624 if (localCLI && path.relative(localCLI, __filename) !== '') { debug('Using local install of AVA'); - require(localCLI); - return; -} - -if (debug.enabled) { - require('time-require'); -} - -var updateNotifier = require('update-notifier'); -var figures = require('figures'); -var arrify = require('arrify'); -var meow = require('meow'); -var Promise = require('bluebird'); -var pkgConf = require('pkg-conf'); -var isCi = require('is-ci'); -var hasFlag = require('has-flag'); -var colors = require('./lib/colors'); -var verboseReporter = require('./lib/reporters/verbose'); -var miniReporter = require('./lib/reporters/mini'); -var tapReporter = require('./lib/reporters/tap'); -var Logger = require('./lib/logger'); -var Watcher = require('./lib/watcher'); -var babelConfig = require('./lib/babel-config'); -var Api = require('./api'); - -// Bluebird specific -Promise.longStackTraces(); - -var conf = pkgConf.sync('ava'); - -var pkgDir = path.dirname(pkgConf.filepath(conf)); - -try { - conf.babel = babelConfig.validate(conf.babel); -} catch (err) { - console.log('\n ' + err.message); - process.exit(1); -} - -var cli = meow([ - 'Usage', - ' ava [ ...]', - '', - 'Options', - ' --init Add AVA to your project', - ' --fail-fast Stop after first test failure', - ' --serial, -s Run tests serially', - ' --tap, -t Generate TAP output', - ' --verbose, -v Enable verbose output', - ' --no-cache Disable the transpiler cache', - ' --no-power-assert Disable Power Assert', - ' --match, -m Only run tests with matching title (Can be repeated)', - ' --watch, -w Re-run tests when tests and source files change', - ' --source, -S Pattern to match source files so tests can be re-run (Can be repeated)', - ' --timeout, -T Set global timeout', - ' --concurrency, -c Maximum number of test files running at the same time (EXPERIMENTAL)', - '', - 'Examples', - ' ava', - ' ava test.js test2.js', - ' ava test-*.js', - ' ava test', - ' ava --init', - ' ava --init foo.js', - '', - 'Default patterns when no arguments:', - 'test.js test-*.js test/**/*.js **/__tests__/**/*.js **/*.test.js' -], { - string: [ - '_', - 'timeout', - 'source', - 'match', - 'concurrency' - ], - boolean: [ - 'fail-fast', - 'verbose', - 'serial', - 'tap', - 'watch' - ], - default: conf, - alias: { - t: 'tap', - v: 'verbose', - s: 'serial', - m: 'match', - w: 'watch', - S: 'source', - T: 'timeout', - c: 'concurrency' - } -}); - -updateNotifier({pkg: cli.pkg}).notify(); - -if (cli.flags.init) { - require('ava-init')(); - return; -} - -if ( - ((hasFlag('--watch') || hasFlag('-w')) && (hasFlag('--tap') || hasFlag('-t'))) || - (conf.watch && conf.tap) -) { - console.error(' ' + colors.error(figures.cross) + ' The TAP reporter is not available when using watch mode.'); - process.exit(1); -} - -if (hasFlag('--require') || hasFlag('-r')) { - console.error(' ' + colors.error(figures.cross) + ' The --require and -r flags are deprecated. Requirements should be configured in package.json - see documentation.'); - process.exit(1); -} - -var api = new Api({ - failFast: cli.flags.failFast, - serial: cli.flags.serial, - require: arrify(conf.require), - cacheEnabled: cli.flags.cache !== false, - powerAssert: cli.flags.powerAssert !== false, - explicitTitles: cli.flags.watch, - match: arrify(cli.flags.match), - babelConfig: conf.babel, - resolveTestsFrom: cli.input.length === 0 ? pkgDir : process.cwd(), - pkgDir: pkgDir, - timeout: cli.flags.timeout, - concurrency: cli.flags.concurrency ? parseInt(cli.flags.concurrency, 10) : 0 -}); - -var reporter; - -if (cli.flags.tap && !cli.flags.watch) { - reporter = tapReporter(); -} else if (cli.flags.verbose || isCi) { - reporter = verboseReporter(); + require(localCLI); // eslint-disable-line import/no-dynamic-require } else { - reporter = miniReporter({watching: cli.flags.watch}); -} - -reporter.api = api; -var logger = new Logger(reporter); - -logger.start(); - -api.on('test-run', function (runStatus) { - reporter.api = runStatus; - runStatus.on('test', logger.test); - runStatus.on('error', logger.unhandledError); - - runStatus.on('stdout', logger.stdout); - runStatus.on('stderr', logger.stderr); -}); - -var files = cli.input.length ? cli.input : arrify(conf.files); + if (debug.enabled) { + require('time-require'); // eslint-disable-line import/no-unassigned-import + } -if (cli.flags.watch) { try { - var watcher = new Watcher(logger, api, files, arrify(cli.flags.source)); - watcher.observeStdin(process.stdin); + require('./lib/cli').run(); } catch (err) { - if (err.name === 'AvaError') { - // An AvaError may be thrown if chokidar is not installed. Log it nicely. - console.error(' ' + colors.error(figures.cross) + ' ' + err.message); - logger.exit(1); - } else { - // Rethrow so it becomes an uncaught exception. - throw err; - } + console.error('\n ' + err.message); + process.exit(1); } -} else { - api.run(files) - .then(function (runStatus) { - logger.finish(runStatus); - logger.exit(runStatus.failCount > 0 || runStatus.rejectionCount > 0 || runStatus.exceptionCount > 0 ? 1 : 0); - }) - .catch(function (err) { - // Don't swallow exceptions. Note that any expected error should already - // have been logged. - setImmediate(function () { - throw err; - }); - }); } diff --git a/lib/cli.js b/lib/cli.js new file mode 100644 index 0000000..31e66ac --- /dev/null +++ b/lib/cli.js @@ -0,0 +1,170 @@ +'use strict'; +var path = require('path'); +var updateNotifier = require('update-notifier'); +var figures = require('figures'); +var arrify = require('arrify'); +var meow = require('meow'); +var Promise = require('bluebird'); +var pkgConf = require('pkg-conf'); +var isCi = require('is-ci'); +var hasFlag = require('has-flag'); +var Api = require('../api'); +var colors = require('./colors'); +var verboseReporter = require('./reporters/verbose'); +var miniReporter = require('./reporters/mini'); +var tapReporter = require('./reporters/tap'); +var Logger = require('./logger'); +var Watcher = require('./watcher'); +var babelConfig = require('./babel-config'); + +// Bluebird specific +Promise.longStackTraces(); + +exports.run = function () { + var conf = pkgConf.sync('ava'); + var pkgDir = path.dirname(pkgConf.filepath(conf)); + + var cli = meow([ + 'Usage', + ' ava [ ...]', + '', + 'Options', + ' --init Add AVA to your project', + ' --fail-fast Stop after first test failure', + ' --serial, -s Run tests serially', + ' --tap, -t Generate TAP output', + ' --verbose, -v Enable verbose output', + ' --no-cache Disable the transpiler cache', + ' --no-power-assert Disable Power Assert', + ' --match, -m Only run tests with matching title (Can be repeated)', + ' --watch, -w Re-run tests when tests and source files change', + ' --source, -S Pattern to match source files so tests can be re-run (Can be repeated)', + ' --timeout, -T Set global timeout', + ' --concurrency, -c Maximum number of test files running at the same time (EXPERIMENTAL)', + '', + 'Examples', + ' ava', + ' ava test.js test2.js', + ' ava test-*.js', + ' ava test', + ' ava --init', + ' ava --init foo.js', + '', + 'Default patterns when no arguments:', + 'test.js test-*.js test/**/*.js **/__tests__/**/*.js **/*.test.js' + ], { + string: [ + '_', + 'timeout', + 'source', + 'match', + 'concurrency' + ], + boolean: [ + 'fail-fast', + 'verbose', + 'serial', + 'tap', + 'watch' + ], + default: conf, + alias: { + t: 'tap', + v: 'verbose', + s: 'serial', + m: 'match', + w: 'watch', + S: 'source', + T: 'timeout', + c: 'concurrency' + } + }); + + updateNotifier({pkg: cli.pkg}).notify(); + + if (cli.flags.init) { + require('ava-init')(); + return; + } + + if ( + ((hasFlag('--watch') || hasFlag('-w')) && (hasFlag('--tap') || hasFlag('-t'))) || + (conf.watch && conf.tap) + ) { + throw new Error(colors.error(figures.cross) + ' The TAP reporter is not available when using watch mode.'); + } + + if (hasFlag('--require') || hasFlag('-r')) { + throw new Error(colors.error(figures.cross) + ' The --require and -r flags are deprecated. Requirements should be configured in package.json - see documentation.'); + } + + var api = new Api({ + failFast: cli.flags.failFast, + serial: cli.flags.serial, + require: arrify(conf.require), + cacheEnabled: cli.flags.cache !== false, + powerAssert: cli.flags.powerAssert !== false, + explicitTitles: cli.flags.watch, + match: arrify(cli.flags.match), + babelConfig: babelConfig.validate(conf.babel), + resolveTestsFrom: cli.input.length === 0 ? pkgDir : process.cwd(), + pkgDir: pkgDir, + timeout: cli.flags.timeout, + concurrency: cli.flags.concurrency ? parseInt(cli.flags.concurrency, 10) : 0 + }); + + var reporter; + + if (cli.flags.tap && !cli.flags.watch) { + reporter = tapReporter(); + } else if (cli.flags.verbose || isCi) { + reporter = verboseReporter(); + } else { + reporter = miniReporter({watching: cli.flags.watch}); + } + + reporter.api = api; + var logger = new Logger(reporter); + + logger.start(); + + api.on('test-run', function (runStatus) { + reporter.api = runStatus; + runStatus.on('test', logger.test); + runStatus.on('error', logger.unhandledError); + + runStatus.on('stdout', logger.stdout); + runStatus.on('stderr', logger.stderr); + }); + + var files = cli.input.length ? cli.input : arrify(conf.files); + + if (cli.flags.watch) { + try { + var watcher = new Watcher(logger, api, files, arrify(cli.flags.source)); + watcher.observeStdin(process.stdin); + } catch (err) { + if (err.name === 'AvaError') { + // An AvaError may be thrown if chokidar is not installed. Log it nicely. + console.error(' ' + colors.error(figures.cross) + ' ' + err.message); + logger.exit(1); + } else { + // Rethrow so it becomes an uncaught exception. + throw err; + } + } + } else { + api.run(files) + .then(function (runStatus) { + logger.finish(runStatus); + logger.exit(runStatus.failCount > 0 || runStatus.rejectionCount > 0 || runStatus.exceptionCount > 0 ? 1 : 0); + }) + .catch(function (err) { + // Don't swallow exceptions. Note that any expected error should already + // have been logged. + setImmediate(function () { + throw err; + }); + }); + } +}; diff --git a/lib/logger.js b/lib/logger.js index c790126..14d705d 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -98,6 +98,6 @@ Logger.prototype.exit = function (code) { // timeout required to correctly flush IO on Node.js 0.10 on Windows setTimeout(function () { - process.exit(code); // eslint-disable-line xo/no-process-exit + process.exit(code); // eslint-disable-line unicorn/no-process-exit }, process.env.AVA_APPVEYOR ? 500 : 0); }; diff --git a/lib/process-adapter.js b/lib/process-adapter.js index 1e73b59..d620549 100644 --- a/lib/process-adapter.js +++ b/lib/process-adapter.js @@ -16,7 +16,7 @@ if (!isForked) { console.log(); console.error('Test files must be run with the AVA CLI:\n\n ' + chalk.grey.dim('$') + ' ' + chalk.cyan('ava ' + fp) + '\n'); - process.exit(1); // eslint-disable-line xo/no-process-exit + process.exit(1); // eslint-disable-line unicorn/no-process-exit } exports.send = function (name, data) { diff --git a/lib/reporters/mini.js b/lib/reporters/mini.js index c5d5341..8c7d9fb 100644 --- a/lib/reporters/mini.js +++ b/lib/reporters/mini.js @@ -159,12 +159,8 @@ MiniReporter.prototype.finish = function (runStatus) { status += '\n ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun'); } - var i = 0; - if (this.knownFailureCount > 0) { runStatus.knownFailures.forEach(function (test) { - i++; - var title = test.title; status += '\n\n ' + colors.title(title); @@ -179,8 +175,6 @@ MiniReporter.prototype.finish = function (runStatus) { return; } - i++; - var title = test.error ? test.title : 'Unhandled Error'; var description; var errorTitle = ' ' + test.error.message + '\n'; @@ -210,8 +204,6 @@ MiniReporter.prototype.finish = function (runStatus) { return; } - i++; - if (err.type === 'exception' && err.name === 'AvaError') { status += '\n\n ' + colors.error(cross + ' ' + err.message); } else { diff --git a/package.json b/package.json index f16a205..15078bb 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "node": ">=0.10.0" }, "scripts": { - "test": "xo && nyc --cache --reporter=lcov --reporter=text tap --no-cov --timeout=150 test/*.js test/reporters/*.js", + "test": "scripts/xo.js && nyc --cache --reporter=lcov --reporter=text tap --no-cov --timeout=150 test/*.js test/reporters/*.js", "test-win": "tap --no-cov --reporter=classic --timeout=150 test/*.js test/reporters/*.js", "visual": "node test/visual/run-visual-tests.js", "prepublish": "npm run make-ts", @@ -184,7 +184,7 @@ "source-map-fixtures": "^2.1.0", "tap": "^7.1.2", "touch": "^1.0.0", - "xo": "^0.16.0", + "xo": "^0.17.0", "zen-observable": "^0.3.0" }, "xo": { diff --git a/profile.js b/profile.js index 786f192..6f3c0a8 100644 --- a/profile.js +++ b/profile.js @@ -112,7 +112,7 @@ events.on('results', function (data) { if (process.exit) { // Delay is For Node 0.10 which emits uncaughtExceptions async. setTimeout(function () { - process.exit(data.stats.failCount + uncaughtExceptionCount); // eslint-disable-line xo/no-process-exit + process.exit(data.stats.failCount + uncaughtExceptionCount); // eslint-disable-line unicorn/no-process-exit }, 20); } }); @@ -139,5 +139,5 @@ if (console.profile) { } setImmediate(function () { - require('./lib/test-worker'); + require('./lib/test-worker'); // eslint-disable-line import/no-unassigned-import }); diff --git a/scripts/xo.js b/scripts/xo.js new file mode 100755 index 0000000..e060782 --- /dev/null +++ b/scripts/xo.js @@ -0,0 +1,10 @@ +#!/usr/bin/env node +/* eslint-disable import/no-unassigned-import */ +'use strict'; + +var major = Number(process.version.match(/^v(\d+)/)[1]); +if (major >= 4) { + require('xo/cli'); +} else { + console.warn('Linting requires Node.js >=4'); +} diff --git a/test/cli.js b/test/cli.js index 2c60b62..1430823 100644 --- a/test/cli.js +++ b/test/cli.js @@ -70,7 +70,7 @@ function execCli(args, opts, cb) { } test('disallow invalid babel config shortcuts', function (t) { - execCli('es2015.js', {dirname: 'fixture/invalid-babel-config'}, function (err, stdout) { + execCli('es2015.js', {dirname: 'fixture/invalid-babel-config'}, function (err, stdout, stderr) { t.ok(err); var expectedOutput = '\n '; @@ -78,7 +78,7 @@ test('disallow invalid babel config shortcuts', function (t) { expectedOutput += ' See ' + chalk.underline('https://github.com/avajs/ava#es2015-support') + ' for allowed values.'; expectedOutput += '\n'; - t.is(stdout, expectedOutput); + t.is(stderr, expectedOutput); t.end(); }); }); @@ -363,7 +363,7 @@ test('prefers local version of ava', function (t) { } proxyquire('../cli', { - 'debug': debugStub, + debug: debugStub, 'resolve-cwd': resolveCwdStub }); diff --git a/test/visual/lorem-ipsum.js b/test/visual/lorem-ipsum.js index 4512179..74f2cb3 100644 --- a/test/visual/lorem-ipsum.js +++ b/test/visual/lorem-ipsum.js @@ -1,7 +1,7 @@ 'use strict'; var delay = require('delay'); var test = require('../../'); -require('./print-lorem-ipsum'); +require('./print-lorem-ipsum'); // eslint-disable-line import/no-unassigned-import async function testFn(t) { await delay(40); diff --git a/test/visual/run-visual-tests.js b/test/visual/run-visual-tests.js index 7ab61de..9d0c144 100644 --- a/test/visual/run-visual-tests.js +++ b/test/visual/run-visual-tests.js @@ -1,5 +1,5 @@ 'use strict'; -require('loud-rejection'); +require('loud-rejection/register'); // eslint-disable-line import/no-unassigned-import var path = require('path'); var childProcess = require('child_process'); var chalk = require('chalk'); diff --git a/test/watcher.js b/test/watcher.js index b03e9ed..c18340f 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -53,8 +53,8 @@ group('chokidar', function (beforeEach, test, group) { function proxyWatcher(opts) { return proxyquire.noCallThru().load('../lib/watcher', opts || { - 'chokidar': chokidar, - 'debug': function (name) { + chokidar: chokidar, + debug: function (name) { return function () { var args = [name]; args.push.apply(args, arguments);