diff options
author | Florian Dold <florian.dold@gmail.com> | 2017-05-28 00:38:50 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2017-05-28 00:40:43 +0200 |
commit | 7fff4499fd915bcea3fa93b1aa8b35f4fe7a6027 (patch) | |
tree | 6de9a1aebd150a23b7f8c273ec657a5d0a18fe3e /node_modules/ava/api.js | |
parent | 963b7a41feb29cc4be090a2446bdfe0c1f1bcd81 (diff) |
add linting (and some initial fixes)
Diffstat (limited to 'node_modules/ava/api.js')
-rw-r--r-- | node_modules/ava/api.js | 350 |
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; |