aboutsummaryrefslogtreecommitdiff
path: root/node_modules/ava/api.js
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-05-28 00:38:50 +0200
committerFlorian Dold <florian.dold@gmail.com>2017-05-28 00:40:43 +0200
commit7fff4499fd915bcea3fa93b1aa8b35f4fe7a6027 (patch)
tree6de9a1aebd150a23b7f8c273ec657a5d0a18fe3e /node_modules/ava/api.js
parent963b7a41feb29cc4be090a2446bdfe0c1f1bcd81 (diff)
add linting (and some initial fixes)
Diffstat (limited to 'node_modules/ava/api.js')
-rw-r--r--node_modules/ava/api.js350
1 files changed, 350 insertions, 0 deletions
diff --git a/node_modules/ava/api.js b/node_modules/ava/api.js
new file mode 100644
index 000000000..e5c5a7b92
--- /dev/null
+++ b/node_modules/ava/api.js
@@ -0,0 +1,350 @@
+'use strict';
+const EventEmitter = require('events');
+const path = require('path');
+const fs = require('fs');
+const commonPathPrefix = require('common-path-prefix');
+const uniqueTempDir = require('unique-temp-dir');
+const findCacheDir = require('find-cache-dir');
+const resolveCwd = require('resolve-cwd');
+const debounce = require('lodash.debounce');
+const autoBind = require('auto-bind');
+const Promise = require('bluebird');
+const getPort = require('get-port');
+const arrify = require('arrify');
+const ms = require('ms');
+const babelConfigHelper = require('./lib/babel-config');
+const CachingPrecompiler = require('./lib/caching-precompiler');
+const RunStatus = require('./lib/run-status');
+const AvaError = require('./lib/ava-error');
+const AvaFiles = require('./lib/ava-files');
+const fork = require('./lib/fork');
+
+function resolveModules(modules) {
+ return arrify(modules).map(name => {
+ const modulePath = resolveCwd(name);
+
+ if (modulePath === null) {
+ throw new Error(`Could not resolve required module '${name}'`);
+ }
+
+ return modulePath;
+ });
+}
+
+function getBlankResults() {
+ return {
+ stats: {
+ knownFailureCount: 0,
+ testCount: 0,
+ passCount: 0,
+ skipCount: 0,
+ todoCount: 0,
+ failCount: 0
+ },
+ tests: []
+ };
+}
+
+class Api extends EventEmitter {
+ constructor(options) {
+ super();
+ autoBind(this);
+
+ this.options = Object.assign({match: []}, options);
+ this.options.require = resolveModules(this.options.require);
+ }
+ _runFile(file, runStatus, execArgv) {
+ const hash = this.precompiler.precompileFile(file);
+ const precompiled = Object.assign({}, this._precompiledHelpers);
+ const resolvedfpath = fs.realpathSync(file);
+ precompiled[resolvedfpath] = hash;
+
+ const options = Object.assign({}, this.options, {precompiled});
+ const emitter = fork(file, options, execArgv);
+ runStatus.observeFork(emitter);
+
+ return emitter;
+ }
+ run(files, options) {
+ return new AvaFiles({cwd: this.options.resolveTestsFrom, files})
+ .findTestFiles()
+ .then(files => this._run(files, options));
+ }
+ _onTimeout(runStatus) {
+ const timeout = ms(this.options.timeout);
+ const err = new AvaError(`Exited because no new tests completed within the last ${timeout}ms of inactivity`);
+ this._handleError(runStatus, err);
+ runStatus.emit('timeout');
+ }
+ _setupTimeout(runStatus) {
+ const timeout = ms(this.options.timeout);
+
+ runStatus._restartTimer = debounce(() => {
+ this._onTimeout(runStatus);
+ }, timeout);
+
+ runStatus._restartTimer();
+ runStatus.on('test', runStatus._restartTimer);
+ }
+ _cancelTimeout(runStatus) {
+ runStatus._restartTimer.cancel();
+ }
+ _setupPrecompiler(files) {
+ const isCacheEnabled = this.options.cacheEnabled !== false;
+ let cacheDir = uniqueTempDir();
+
+ if (isCacheEnabled) {
+ const foundDir = findCacheDir({
+ name: 'ava',
+ files
+ });
+ if (foundDir !== null) {
+ cacheDir = foundDir;
+ }
+ }
+
+ this.options.cacheDir = cacheDir;
+
+ const isPowerAssertEnabled = this.options.powerAssert !== false;
+ return babelConfigHelper.build(this.options.projectDir, cacheDir, this.options.babelConfig, isPowerAssertEnabled)
+ .then(result => {
+ this.precompiler = new CachingPrecompiler({
+ path: cacheDir,
+ getBabelOptions: result.getOptions,
+ babelCacheKeys: result.cacheKeys
+ });
+ });
+ }
+ _precompileHelpers() {
+ this._precompiledHelpers = {};
+
+ // Assumes the tests only load helpers from within the `resolveTestsFrom`
+ // directory. Without arguments this is the `projectDir`, else it's
+ // `process.cwd()` which may be nested too deeply. This will be solved
+ // as we implement RFC 001 and move helper compilation into the worker
+ // processes, avoiding the need for precompilation.
+ return new AvaFiles({cwd: this.options.resolveTestsFrom})
+ .findTestHelpers()
+ .map(file => { // eslint-disable-line array-callback-return
+ const hash = this.precompiler.precompileFile(file);
+ this._precompiledHelpers[file] = hash;
+ });
+ }
+ _run(files, options) {
+ options = options || {};
+
+ const runStatus = new RunStatus({
+ runOnlyExclusive: options.runOnlyExclusive,
+ prefixTitles: this.options.explicitTitles || files.length > 1,
+ base: path.relative(process.cwd(), commonPathPrefix(files)) + path.sep,
+ failFast: this.options.failFast
+ });
+
+ this.emit('test-run', runStatus, files);
+
+ if (files.length === 0) {
+ const err = new AvaError('Couldn\'t find any files to test');
+ this._handleError(runStatus, err);
+ return Promise.resolve(runStatus);
+ }
+
+ return this._setupPrecompiler(files)
+ .then(() => this._precompileHelpers())
+ .then(() => {
+ if (this.options.timeout) {
+ this._setupTimeout(runStatus);
+ }
+
+ let overwatch;
+ if (this.options.concurrency > 0) {
+ const concurrency = this.options.serial ? 1 : this.options.concurrency;
+ overwatch = this._runWithPool(files, runStatus, concurrency);
+ } else {
+ // _runWithoutPool exists to preserve legacy behavior, specifically around `.only`
+ overwatch = this._runWithoutPool(files, runStatus);
+ }
+
+ return overwatch;
+ });
+ }
+ _computeForkExecArgs(files) {
+ const execArgv = this.options.testOnlyExecArgv || process.execArgv;
+ let debugArgIndex = -1;
+
+ // --debug-brk is used in addition to --inspect to break on first line and wait
+ execArgv.some((arg, index) => {
+ const isDebugArg = arg === '--inspect' || arg.indexOf('--inspect=') === 0;
+ if (isDebugArg) {
+ debugArgIndex = index;
+ }
+
+ return isDebugArg;
+ });
+
+ const isInspect = debugArgIndex >= 0;
+ if (!isInspect) {
+ execArgv.some((arg, index) => {
+ const isDebugArg = arg === '--debug' || arg === '--debug-brk' || arg.indexOf('--debug-brk=') === 0 || arg.indexOf('--debug=') === 0;
+ if (isDebugArg) {
+ debugArgIndex = index;
+ }
+
+ return isDebugArg;
+ });
+ }
+
+ if (debugArgIndex === -1) {
+ return Promise.resolve([]);
+ }
+
+ return Promise
+ .map(files, () => getPort())
+ .map(port => {
+ const forkExecArgv = execArgv.slice();
+ let flagName = isInspect ? '--inspect' : '--debug';
+ const oldValue = forkExecArgv[debugArgIndex];
+ if (oldValue.indexOf('brk') > 0) {
+ flagName += '-brk';
+ }
+
+ forkExecArgv[debugArgIndex] = `${flagName}=${port}`;
+
+ return forkExecArgv;
+ });
+ }
+ _handleError(runStatus, err) {
+ runStatus.handleExceptions({
+ exception: err,
+ file: err.file ? path.relative(process.cwd(), err.file) : undefined
+ });
+ }
+ _runWithoutPool(files, runStatus) {
+ const tests = [];
+ let execArgvList;
+
+ // TODO: This should be cleared at the end of the run
+ runStatus.on('timeout', () => {
+ tests.forEach(fork => {
+ fork.exit();
+ });
+ });
+
+ return this._computeForkExecArgs(files)
+ .then(argvList => {
+ execArgvList = argvList;
+ })
+ .return(files)
+ .each((file, index) => {
+ return new Promise(resolve => {
+ const forkArgs = execArgvList[index];
+ const test = this._runFile(file, runStatus, forkArgs);
+ tests.push(test);
+ test.on('stats', resolve);
+ test.catch(resolve);
+ }).catch(err => {
+ err.results = [];
+ err.file = file;
+ return Promise.reject(err);
+ });
+ })
+ .then(() => {
+ if (this.options.match.length > 0 && !runStatus.hasExclusive) {
+ const err = new AvaError('Couldn\'t find any matching tests');
+ err.file = undefined;
+ err.results = [];
+ return Promise.reject(err);
+ }
+
+ const method = this.options.serial ? 'mapSeries' : 'map';
+ const options = {
+ runOnlyExclusive: runStatus.hasExclusive
+ };
+
+ return Promise[method](files, (file, index) => {
+ return tests[index].run(options).catch(err => {
+ err.file = file;
+ this._handleError(runStatus, err);
+ return getBlankResults();
+ });
+ });
+ })
+ .catch(err => {
+ this._handleError(runStatus, err);
+ return err.results;
+ })
+ .tap(results => {
+ // If no tests ran, make sure to tear down the child processes
+ if (results.length === 0) {
+ tests.forEach(test => {
+ test.send('teardown');
+ });
+ }
+ })
+ .then(results => {
+ // Cancel debounced _onTimeout() from firing
+ if (this.options.timeout) {
+ this._cancelTimeout(runStatus);
+ }
+
+ runStatus.processResults(results);
+
+ return runStatus;
+ });
+ }
+ _runWithPool(files, runStatus, concurrency) {
+ const tests = [];
+ let execArgvList;
+
+ runStatus.on('timeout', () => {
+ tests.forEach(fork => {
+ fork.exit();
+ });
+ });
+
+ return this._computeForkExecArgs(files)
+ .then(argvList => {
+ execArgvList = argvList;
+ })
+ .return(files)
+ .map((file, index) => {
+ return new Promise(resolve => {
+ const forkArgs = execArgvList[index];
+ const test = this._runFile(file, runStatus, forkArgs);
+ tests.push(test);
+
+ // If we're looking for matches, run every single test process in exclusive-only mode
+ const options = {
+ runOnlyExclusive: this.options.match.length > 0
+ };
+
+ resolve(test.run(options));
+ }).catch(err => {
+ err.file = file;
+ this._handleError(runStatus, err);
+ return getBlankResults();
+ });
+ }, {concurrency})
+ .then(results => {
+ // Filter out undefined results (usually result of caught exceptions)
+ results = results.filter(Boolean);
+
+ // Cancel debounced _onTimeout() from firing
+ if (this.options.timeout) {
+ this._cancelTimeout(runStatus);
+ }
+
+ if (this.options.match.length > 0 && !runStatus.hasExclusive) {
+ results = [];
+
+ const err = new AvaError('Couldn\'t find any matching tests');
+ this._handleError(runStatus, err);
+ }
+
+ runStatus.processResults(results);
+
+ return runStatus;
+ });
+ }
+}
+
+module.exports = Api;