diff options
author | Florian Dold <florian.dold@gmail.com> | 2017-12-27 19:33:54 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2017-12-27 19:34:16 +0100 |
commit | 0e6de2c31dbf8c21277481f112e99c52b913940f (patch) | |
tree | 91789032de3b8eec9d789acd1323f25fc5d08422 /node_modules/ava/lib | |
parent | ceda0da31ad542c598c68146ae0712ca03df3d71 (diff) |
node_modules
Diffstat (limited to 'node_modules/ava/lib')
24 files changed, 353 insertions, 123 deletions
diff --git a/node_modules/ava/lib/assert.js b/node_modules/ava/lib/assert.js index a0e9fe82c..18009b97d 100644 --- a/node_modules/ava/lib/assert.js +++ b/node_modules/ava/lib/assert.js @@ -64,6 +64,7 @@ function wrapAssertions(callbacks) { const pass = callbacks.pass; const pending = callbacks.pending; const fail = callbacks.fail; + const log = callbacks.log; const noop = () => {}; const makeRethrow = reason => () => { @@ -86,14 +87,25 @@ function wrapAssertions(callbacks) { if (Object.is(actual, expected)) { pass(this); } else { - const actualDescriptor = concordance.describe(actual, concordanceOptions); - const expectedDescriptor = concordance.describe(expected, concordanceOptions); - fail(this, new AssertionError({ - assertion: 'is', - message, - raw: {actual, expected}, - values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)] - })); + const result = concordance.compare(actual, expected, concordanceOptions); + const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions); + const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions); + + if (result.pass) { + fail(this, new AssertionError({ + assertion: 'is', + message, + raw: {actual, expected}, + values: [formatDescriptorWithLabel('Values are deeply equal to each other, but they are not the same:', actualDescriptor)] + })); + } else { + fail(this, new AssertionError({ + assertion: 'is', + message, + raw: {actual, expected}, + values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)] + })); + } } }, @@ -110,6 +122,10 @@ function wrapAssertions(callbacks) { } }, + log(text) { + log(this, text); + }, + deepEqual(actual, expected, message) { const result = concordance.compare(actual, expected, concordanceOptions); if (result.pass) { diff --git a/node_modules/ava/lib/ava-files.js b/node_modules/ava/lib/ava-files.js index cfdc9f202..b6520da37 100644 --- a/node_modules/ava/lib/ava-files.js +++ b/node_modules/ava/lib/ava-files.js @@ -125,6 +125,7 @@ class AvaFiles { autoBind(this); } + findTestFiles() { return handlePaths(this.files, this.excludePatterns, { cwd: this.cwd, @@ -134,6 +135,7 @@ class AvaFiles { symlinks: Object.create(null) }); } + findTestHelpers() { return handlePaths(defaultHelperPatterns(), ['!**/node_modules/**'], { cwd: this.cwd, @@ -144,6 +146,7 @@ class AvaFiles { symlinks: Object.create(null) }); } + isSource(filePath) { let mixedPatterns = []; const defaultIgnorePatterns = getDefaultIgnorePatterns(); @@ -195,6 +198,7 @@ class AvaFiles { return false; } + isTest(filePath) { const excludePatterns = this.excludePatterns; const initialPatterns = this.files.concat(excludePatterns); @@ -241,6 +245,7 @@ class AvaFiles { // excludePatterns into account. This mimicks the behavior in api.js return multimatch(matchable(filePath), recursivePatterns.concat(excludePatterns)).length === 1; } + getChokidarPatterns() { let paths = []; let ignored = []; diff --git a/node_modules/ava/lib/babel-config.js b/node_modules/ava/lib/babel-config.js index 62e841f05..d58b700b8 100644 --- a/node_modules/ava/lib/babel-config.js +++ b/node_modules/ava/lib/babel-config.js @@ -6,6 +6,7 @@ const figures = require('figures'); const configManager = require('hullabaloo-config-manager'); const md5Hex = require('md5-hex'); const makeDir = require('make-dir'); +const semver = require('semver'); const colors = require('./colors'); function validate(conf) { @@ -99,6 +100,7 @@ function build(projectDir, cacheDir, userOptions, powerAssert) { const baseOptions = { babelrc: false, + plugins: [], presets: [ ['@ava/transform-test-files', {powerAssert}] ] @@ -107,6 +109,12 @@ function build(projectDir, cacheDir, userOptions, powerAssert) { baseOptions.presets.unshift('@ava/stage-4'); } + // Include object rest spread support for node versions that support it + // natively. + if (userOptions === 'default' && semver.satisfies(process.versions.node, '>= 8.3.0')) { + baseOptions.plugins.push('babel-plugin-syntax-object-rest-spread'); + } + const baseConfig = configManager.createConfig({ dir: AVA_DIR, // Presets are resolved relative to this directory hash: md5Hex(JSON.stringify(baseOptions)), diff --git a/node_modules/ava/lib/beautify-stack.js b/node_modules/ava/lib/beautify-stack.js index 189ed0714..4ae8c04be 100644 --- a/node_modules/ava/lib/beautify-stack.js +++ b/node_modules/ava/lib/beautify-stack.js @@ -8,6 +8,7 @@ let ignoreStackLines = []; const avaInternals = /\/ava\/(?:lib\/)?[\w-]+\.js:\d+:\d+\)?$/; const avaDependencies = /\/node_modules\/(?:bluebird|empower-core|(?:ava\/node_modules\/)?(?:babel-runtime|core-js))\//; +const stackFrameLine = /^.+( \(.+:\d+:\d+\)|:\d+:\d+)$/; if (!debug.enabled) { ignoreStackLines = StackUtils.nodeInternals(); @@ -17,21 +18,55 @@ if (!debug.enabled) { const stackUtils = new StackUtils({internals: ignoreStackLines}); +function extractFrames(stack) { + return stack + .split('\n') + .map(line => line.trim()) + .filter(line => stackFrameLine.test(line)) + .join('\n'); +} + +/** + * Given a string value of the format generated for the `stack` property of a + * V8 error object, return a string that contains only stack frame information + * for frames that have relevance to the consumer. + * + * For example, given the following string value: + * + * ``` + * Error + * at inner (/home/ava/ex.js:7:12) + * at /home/ava/ex.js:12:5 + * at outer (/home/ava/ex.js:13:4) + * at Object.<anonymous> (/home/ava/ex.js:14:3) + * at Module._compile (module.js:570:32) + * at Object.Module._extensions..js (module.js:579:10) + * at Module.load (module.js:487:32) + * at tryModuleLoad (module.js:446:12) + * at Function.Module._load (module.js:438:3) + * at Module.runMain (module.js:604:10) + * ``` + * + * ...this function returns the following string value: + * + * ``` + * inner (/home/ava/ex.js:7:12) + * /home/ava/ex.js:12:5 + * outer (/home/ava/ex.js:13:4) + * Object.<anonymous> (/home/ava/ex.js:14:3) + * ``` + */ module.exports = stack => { if (!stack) { return ''; } + stack = extractFrames(stack); // Workaround for https://github.com/tapjs/stack-utils/issues/14 // TODO: fix it in `stack-utils` stack = cleanStack(stack); - const title = stack.split('\n')[0]; - const lines = stackUtils - .clean(stack) - .split('\n') - .map(x => ` ${x}`) - .join('\n'); - - return `${title}\n${lines}`; + return stackUtils.clean(stack) + // Remove the trailing newline inserted by the `stack-utils` module + .trim(); }; diff --git a/node_modules/ava/lib/caching-precompiler.js b/node_modules/ava/lib/caching-precompiler.js index 937309bf0..f6e5e47c3 100644 --- a/node_modules/ava/lib/caching-precompiler.js +++ b/node_modules/ava/lib/caching-precompiler.js @@ -33,6 +33,7 @@ class CachingPrecompiler { this.fileHashes = {}; this.transform = this._createTransform(); } + precompileFile(filePath) { if (!this.fileHashes[filePath]) { const source = stripBomBuf(fs.readFileSync(filePath)); @@ -41,11 +42,13 @@ class CachingPrecompiler { return this.fileHashes[filePath]; } + // Conditionally called by caching-transform when precompiling is required _init() { this.babel = require('babel-core'); return this._transform; } + _transform(code, filePath, hash) { code = code.toString(); @@ -79,6 +82,7 @@ class CachingPrecompiler { return `${result.code}\n${comment}`; } + _createTransform() { const salt = packageHash.sync([ require.resolve('../package.json'), @@ -93,6 +97,7 @@ class CachingPrecompiler { ext: '.js' }); } + _generateHash(code, filePath, salt) { const hash = md5Hex([code, filePath, salt]); this.fileHashes[filePath] = hash; diff --git a/node_modules/ava/lib/cli.js b/node_modules/ava/lib/cli.js index 5649a8190..0c2c2f82f 100644 --- a/node_modules/ava/lib/cli.js +++ b/node_modules/ava/lib/cli.js @@ -43,7 +43,7 @@ exports.run = () => { --match, -m Only run tests with matching title (Can be repeated) --watch, -w Re-run tests when tests and source files change --timeout, -T Set global timeout - --concurrency, -c Maximum number of test files running at the same time (EXPERIMENTAL) + --concurrency, -c Max number of test files running at the same time (Default: CPU cores) --update-snapshots, -u Update snapshots Examples @@ -75,7 +75,7 @@ exports.run = () => { ], default: { cache: conf.cache, - color: 'color' in conf ? conf.color : require('supports-color') !== false, + color: 'color' in conf ? conf.color : require('supports-color').stdout !== false, concurrency: conf.concurrency, failFast: conf.failFast, init: conf.init, @@ -119,7 +119,12 @@ exports.run = () => { } if (cli.flags.concurrency === '') { - throw new Error(colors.error(figures.cross) + ' The --concurrency and -c flags must be provided the maximum number of test files to run at once.'); + throw new Error(colors.error(figures.cross) + ' The --concurrency and -c flags must be provided.'); + } + + if (cli.flags.concurrency && + (!Number.isInteger(Number.parseFloat(cli.flags.concurrency)) || parseInt(cli.flags.concurrency, 10) < 0)) { + throw new Error(colors.error(figures.cross) + ' The --concurrency and -c flags must be a nonnegative integer.'); } if (hasFlag('--require') || hasFlag('-r')) { @@ -144,6 +149,7 @@ exports.run = () => { timeout: conf.timeout, concurrency: conf.concurrency ? parseInt(conf.concurrency, 10) : 0, updateSnapshots: conf.updateSnapshots, + snapshotDir: conf.snapshotDir ? path.resolve(projectDir, conf.snapshotDir) : null, color: conf.color }); @@ -152,7 +158,7 @@ exports.run = () => { if (conf.tap && !conf.watch) { reporter = new TapReporter(); } else if (conf.verbose || isCi) { - reporter = new VerboseReporter({color: conf.color}); + reporter = new VerboseReporter({color: conf.color, watching: conf.watch}); } else { reporter = new MiniReporter({color: conf.color, watching: conf.watch}); } diff --git a/node_modules/ava/lib/colors.js b/node_modules/ava/lib/colors.js index 74be14bb1..75fb4d8aa 100644 --- a/node_modules/ava/lib/colors.js +++ b/node_modules/ava/lib/colors.js @@ -2,6 +2,7 @@ const chalk = require('chalk'); module.exports = { + log: chalk.gray, title: chalk.bold.white, error: chalk.red, skip: chalk.yellow, diff --git a/node_modules/ava/lib/enhance-assert.js b/node_modules/ava/lib/enhance-assert.js index 6e127b3d6..6991caf40 100644 --- a/node_modules/ava/lib/enhance-assert.js +++ b/node_modules/ava/lib/enhance-assert.js @@ -1,6 +1,7 @@ 'use strict'; const concordance = require('concordance'); const dotProp = require('dot-prop'); +const generate = require('babel-generator').default; const concordanceOptions = require('./concordance-options').default; // When adding patterns, don't forget to add to @@ -15,31 +16,16 @@ const PATTERNS = [ 't.notRegex(contents, regex, [message])' ]; -const isRangeMatch = (a, b) => { - return (a[0] === b[0] && a[1] === b[1]) || - (a[0] > b[0] && a[0] < b[1]) || - (a[1] > b[0] && a[1] < b[1]); -}; - -const computeStatement = (tokens, range) => { - return tokens - .filter(token => isRangeMatch(token.range, range)) - .map(token => token.value === undefined ? token.type.label : token.value) - .join(''); -}; - +const computeStatement = node => generate(node, {quotes: 'single'}).code; const getNode = (ast, path) => dotProp.get(ast, path.replace(/\//g, '.')); const formatter = context => { const ast = JSON.parse(context.source.ast); - const tokens = JSON.parse(context.source.tokens); const args = context.args[0].events; - return args .map(arg => { - const range = getNode(ast, arg.espath).range; - const statement = computeStatement(tokens, range); - + const node = getNode(ast, arg.espath); + const statement = computeStatement(node); const formatted = concordance.format(arg.value, concordanceOptions); return [statement, formatted]; }) diff --git a/node_modules/ava/lib/extract-stack.js b/node_modules/ava/lib/extract-stack.js deleted file mode 100644 index 64f63db1c..000000000 --- a/node_modules/ava/lib/extract-stack.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; -const stackLineRegex = /^.+ \(.+:[0-9]+:[0-9]+\)$/; - -module.exports = stack => { - return stack - .split('\n') - .filter(line => stackLineRegex.test(line)) - .map(line => line.trim()) - .join('\n'); -}; diff --git a/node_modules/ava/lib/fork.js b/node_modules/ava/lib/fork.js index bf918d391..0ca0f45a4 100644 --- a/node_modules/ava/lib/fork.js +++ b/node_modules/ava/lib/fork.js @@ -10,12 +10,10 @@ if (fs.realpathSync(__filename) !== __filename) { console.warn('WARNING: `npm link ava` and the `--preserve-symlink` flag are incompatible. We have detected that AVA is linked via `npm link`, and that you are using either an early version of Node 6, or the `--preserve-symlink` flag. This breaks AVA. You should upgrade to Node 6.2.0+, avoid the `--preserve-symlink` flag, or avoid using `npm link ava`.'); } -let env = process.env; +const env = Object.assign({NODE_ENV: 'test'}, process.env); // Ensure NODE_PATH paths are absolute if (env.NODE_PATH) { - env = Object.assign({}, env); - env.NODE_PATH = env.NODE_PATH .split(path.delimiter) .map(x => path.resolve(x)) diff --git a/node_modules/ava/lib/logger.js b/node_modules/ava/lib/logger.js index 54bd23c94..e8edb120f 100644 --- a/node_modules/ava/lib/logger.js +++ b/node_modules/ava/lib/logger.js @@ -6,6 +6,7 @@ class Logger { this.reporter = reporter; autoBind(this); } + start(runStatus) { if (!this.reporter.start) { return; @@ -13,6 +14,7 @@ class Logger { this.write(this.reporter.start(runStatus), runStatus); } + reset(runStatus) { if (!this.reporter.reset) { return; @@ -20,9 +22,11 @@ class Logger { this.write(this.reporter.reset(runStatus), runStatus); } + test(test, runStatus) { this.write(this.reporter.test(test, runStatus), runStatus); } + unhandledError(err, runStatus) { if (!this.reporter.unhandledError) { return; @@ -30,6 +34,7 @@ class Logger { this.write(this.reporter.unhandledError(err, runStatus), runStatus); } + finish(runStatus) { if (!this.reporter.finish) { return; @@ -37,6 +42,7 @@ class Logger { this.write(this.reporter.finish(runStatus), runStatus); } + section() { if (!this.reporter.section) { return; @@ -44,6 +50,7 @@ class Logger { this.write(this.reporter.section()); } + clear() { if (!this.reporter.clear) { return false; @@ -52,6 +59,7 @@ class Logger { this.write(this.reporter.clear()); return true; } + write(str, runStatus) { if (typeof str === 'undefined') { return; @@ -59,6 +67,7 @@ class Logger { this.reporter.write(str, runStatus); } + stdout(data, runStatus) { if (!this.reporter.stdout) { return; @@ -66,6 +75,7 @@ class Logger { this.reporter.stdout(data, runStatus); } + stderr(data, runStatus) { if (!this.reporter.stderr) { return; @@ -73,6 +83,7 @@ class Logger { this.reporter.stderr(data, runStatus); } + exit(code) { process.exit(code); // eslint-disable-line unicorn/no-process-exit } diff --git a/node_modules/ava/lib/main.js b/node_modules/ava/lib/main.js index 1b03cc854..4c5fdc373 100644 --- a/node_modules/ava/lib/main.js +++ b/node_modules/ava/lib/main.js @@ -13,7 +13,8 @@ const runner = new Runner({ match: opts.match, projectDir: opts.projectDir, serial: opts.serial, - updateSnapshots: opts.updateSnapshots + updateSnapshots: opts.updateSnapshots, + snapshotDir: opts.snapshotDir }); worker.setRunner(runner); diff --git a/node_modules/ava/lib/reporters/improper-usage-messages.js b/node_modules/ava/lib/reporters/improper-usage-messages.js index 298ef79a5..014a4bf0d 100644 --- a/node_modules/ava/lib/reporters/improper-usage-messages.js +++ b/node_modules/ava/lib/reporters/improper-usage-messages.js @@ -15,7 +15,9 @@ exports.forError = error => { Visit the following URL for more details: ${chalk.blue.underline('https://github.com/avajs/ava#throwsfunctionpromise-error-message')}`; - } else if (assertion === 'snapshot') { + } + + if (assertion === 'snapshot') { const name = error.improperUsage.name; const snapPath = error.improperUsage.snapPath; diff --git a/node_modules/ava/lib/reporters/mini.js b/node_modules/ava/lib/reporters/mini.js index 8acfab8e7..a21d02a6b 100644 --- a/node_modules/ava/lib/reporters/mini.js +++ b/node_modules/ava/lib/reporters/mini.js @@ -5,12 +5,12 @@ const lastLineTracker = require('last-line-stream/tracker'); const plur = require('plur'); const spinners = require('cli-spinners'); const chalk = require('chalk'); +const figures = require('figures'); const cliTruncate = require('cli-truncate'); const cross = require('figures').cross; const indentString = require('indent-string'); const ansiEscapes = require('ansi-escapes'); const trimOffNewlines = require('trim-off-newlines'); -const extractStack = require('../extract-stack'); const codeExcerpt = require('../code-excerpt'); const colors = require('../colors'); const formatSerializedError = require('./format-serialized-error'); @@ -33,6 +33,7 @@ class MiniReporter { this.stream = process.stderr; this.stringDecoder = new StringDecoder(); } + start() { this.interval = setInterval(() => { this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length; @@ -41,6 +42,7 @@ class MiniReporter { return this.prefix(''); } + reset() { this.clearInterval(); this.passCount = 0; @@ -56,13 +58,16 @@ class MiniReporter { this.spinnerIndex = 0; this.lastLineTracker = lastLineTracker(); } + spinnerChar() { return this.spinnerFrames[this.spinnerIndex]; } + clearInterval() { clearInterval(this.interval); this.interval = null; } + test(test) { if (test.todo) { this.todoCount++; @@ -83,6 +88,7 @@ class MiniReporter { return this.prefix(this._test(test)); } + prefix(str) { str = str || this.currentTest; this.currentTest = str; @@ -91,6 +97,7 @@ class MiniReporter { // TODO(jamestalmage): Figure out why it's needed and document it here return ` \n ${this.spinnerChar()} ${str}`; } + _test(test) { const SPINNER_WIDTH = 3; const PADDING = 1; @@ -102,6 +109,7 @@ class MiniReporter { return title + '\n' + this.reportCounts(); } + unhandledError(err) { if (err.type === 'exception') { this.exceptionCount++; @@ -109,6 +117,7 @@ class MiniReporter { this.rejectionCount++; } } + reportCounts(time) { const lines = [ this.passCount > 0 ? '\n ' + colors.pass(this.passCount, 'passed') : '', @@ -124,6 +133,7 @@ class MiniReporter { return lines.join(''); } + finish(runStatus) { this.clearInterval(); let time; @@ -163,6 +173,21 @@ class MiniReporter { } status += ' ' + colors.title(test.title) + '\n'; + + if (test.logs) { + test.logs.forEach(log => { + const logLines = indentString(colors.log(log), 6); + const logLinesWithLeadingFigure = logLines.replace( + /^ {6}/, + ` ${colors.information(figures.info)} ` + ); + + status += logLinesWithLeadingFigure + '\n'; + }); + + status += '\n'; + } + if (test.error.source) { status += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; @@ -191,9 +216,9 @@ class MiniReporter { } if (test.error.stack) { - const extracted = extractStack(test.error.stack); - if (extracted.includes('\n')) { - status += '\n' + indentString(colors.errorStack(extracted), 2) + '\n'; + const stack = test.error.stack; + if (stack.includes('\n')) { + status += '\n' + indentString(colors.errorStack(stack), 2) + '\n'; } } @@ -212,14 +237,14 @@ class MiniReporter { status += ' ' + colors.error(cross + ' ' + err.message) + '\n\n'; } else { const title = err.type === 'rejection' ? 'Unhandled Rejection' : 'Uncaught Exception'; - let description = err.stack ? err.stack.trimRight() : JSON.stringify(err); - description = description.split('\n'); - const errorTitle = err.name ? description[0] : 'Threw non-error: ' + description[0]; - const errorStack = description.slice(1).join('\n'); - status += ' ' + colors.title(title) + '\n'; - status += ' ' + colors.stack(errorTitle) + '\n'; - status += colors.errorStack(errorStack) + '\n\n'; + + if (err.name) { + status += ' ' + colors.stack(err.summary) + '\n'; + status += colors.errorStack(err.stack) + '\n\n'; + } else { + status += ' Threw non-error: ' + err.summary + '\n'; + } } }); } @@ -235,24 +260,30 @@ class MiniReporter { return '\n' + trimOffNewlines(status) + '\n'; } + section() { return '\n' + chalk.gray.dim('\u2500'.repeat(process.stdout.columns || 80)); } + clear() { return ''; } + write(str) { cliCursor.hide(); this.currentStatus = str; this._update(); this.statusLineCount = this.currentStatus.split('\n').length; } + stdout(data) { this._update(data); } + stderr(data) { this._update(data); } + _update(data) { let str = ''; let ct = this.statusLineCount; diff --git a/node_modules/ava/lib/reporters/tap.js b/node_modules/ava/lib/reporters/tap.js index 37c2cfd95..5ef8a23e3 100644 --- a/node_modules/ava/lib/reporters/tap.js +++ b/node_modules/ava/lib/reporters/tap.js @@ -3,12 +3,6 @@ const format = require('util').format; const indentString = require('indent-string'); const stripAnsi = require('strip-ansi'); const yaml = require('js-yaml'); -const extractStack = require('../extract-stack'); - -// Parses stack trace and extracts original function name, file name and line -function getSourceFromStack(stack) { - return extractStack(stack).split('\n')[0]; -} function dumpError(error, includeMessage) { const obj = Object.assign({}, error.object); @@ -35,7 +29,7 @@ function dumpError(error, includeMessage) { } if (error.stack) { - obj.at = getSourceFromStack(error.stack); + obj.at = error.stack.split('\n')[0]; } return ` ---\n${indentString(yaml.safeDump(obj).trim(), 4)}\n ...`; @@ -45,11 +39,13 @@ class TapReporter { constructor() { this.i = 0; } + start() { return 'TAP version 13'; } + test(test) { - let output; + const output = []; let directive = ''; const passed = test.todo ? 'not ok' : 'ok'; @@ -62,21 +58,34 @@ class TapReporter { const title = stripAnsi(test.title); + const appendLogs = () => { + if (test.logs) { + test.logs.forEach(log => { + const logLines = indentString(log, 4); + const logLinesWithLeadingFigure = logLines.replace( + /^ {4}/, + ' * ' + ); + + output.push(logLinesWithLeadingFigure); + }); + } + }; + + output.push(`# ${title}`); + if (test.error) { - output = [ - '# ' + title, - format('not ok %d - %s', ++this.i, title), - dumpError(test.error, true) - ]; + output.push(format('not ok %d - %s', ++this.i, title)); + appendLogs(); + output.push(dumpError(test.error, true)); } else { - output = [ - `# ${title}`, - format('%s %d - %s %s', passed, ++this.i, title, directive).trim() - ]; + output.push(format('%s %d - %s %s', passed, ++this.i, title, directive).trim()); + appendLogs(); } return output.join('\n'); } + unhandledError(err) { const output = [ `# ${err.message}`, @@ -89,6 +98,7 @@ class TapReporter { return output.join('\n'); } + finish(runStatus) { const output = [ '', @@ -105,12 +115,15 @@ class TapReporter { return output.join('\n'); } + write(str) { console.log(str); } + stdout(data) { process.stderr.write(data); } + stderr(data) { this.stdout(data); } diff --git a/node_modules/ava/lib/reporters/verbose.js b/node_modules/ava/lib/reporters/verbose.js index cd47683e8..c58d8db3b 100644 --- a/node_modules/ava/lib/reporters/verbose.js +++ b/node_modules/ava/lib/reporters/verbose.js @@ -5,7 +5,6 @@ const figures = require('figures'); const chalk = require('chalk'); const plur = require('plur'); const trimOffNewlines = require('trim-off-newlines'); -const extractStack = require('../extract-stack'); const codeExcerpt = require('../code-excerpt'); const colors = require('../colors'); const formatSerializedError = require('./format-serialized-error'); @@ -20,34 +19,46 @@ class VerboseReporter { colors[key].enabled = this.options.color; } } + start() { return ''; } + test(test, runStatus) { + const lines = []; if (test.error) { - return ' ' + colors.error(figures.cross) + ' ' + test.title + ' ' + colors.error(test.error.message); - } - - if (test.todo) { - return ' ' + colors.todo('- ' + test.title); + lines.push(' ' + colors.error(figures.cross) + ' ' + test.title + ' ' + colors.error(test.error.message)); + } else if (test.todo) { + lines.push(' ' + colors.todo('- ' + test.title)); } else if (test.skip) { - return ' ' + colors.skip('- ' + test.title); - } + lines.push(' ' + colors.skip('- ' + test.title)); + } else if (test.failing) { + lines.push(' ' + colors.error(figures.tick) + ' ' + colors.error(test.title)); + } else if (runStatus.fileCount === 1 && runStatus.testCount === 1 && test.title === '[anonymous]') { + // No output + } else { + // Display duration only over a threshold + const threshold = 100; + const duration = test.duration > threshold ? colors.duration(' (' + prettyMs(test.duration) + ')') : ''; - if (test.failing) { - return ' ' + colors.error(figures.tick) + ' ' + colors.error(test.title); + lines.push(' ' + colors.pass(figures.tick) + ' ' + test.title + duration); } - if (runStatus.fileCount === 1 && runStatus.testCount === 1 && test.title === '[anonymous]') { - return undefined; - } + if (test.logs) { + test.logs.forEach(log => { + const logLines = indentString(colors.log(log), 6); + const logLinesWithLeadingFigure = logLines.replace( + /^ {6}/, + ` ${colors.information(figures.info)} ` + ); - // Display duration only over a threshold - const threshold = 100; - const duration = test.duration > threshold ? colors.duration(' (' + prettyMs(test.duration) + ')') : ''; + lines.push(logLinesWithLeadingFigure); + }); + } - return ' ' + colors.pass(figures.tick) + ' ' + test.title + duration; + return lines.length > 0 ? lines.join('\n') : undefined; } + unhandledError(err) { if (err.type === 'exception' && err.name === 'AvaError') { return colors.error(' ' + figures.cross + ' ' + err.message); @@ -61,6 +72,7 @@ class VerboseReporter { let output = colors.error(types[err.type] + ':', err.file) + '\n'; if (err.stack) { + output += ' ' + colors.stack(err.title || err.summary) + '\n'; output += ' ' + colors.stack(err.stack) + '\n'; } else { output += ' ' + colors.stack(JSON.stringify(err)) + '\n'; @@ -70,6 +82,7 @@ class VerboseReporter { return output; } + finish(runStatus) { let output = ''; @@ -86,7 +99,9 @@ class VerboseReporter { ].filter(Boolean); if (lines.length > 0) { - lines[0] += ' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']'); + if (this.options.watching) { + lines[0] += ' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']'); + } output += lines.join('\n') + '\n'; } @@ -104,6 +119,21 @@ class VerboseReporter { } output += ' ' + colors.title(test.title) + '\n'; + + if (test.logs) { + test.logs.forEach(log => { + const logLines = indentString(colors.log(log), 6); + const logLinesWithLeadingFigure = logLines.replace( + /^ {6}/, + ` ${colors.information(figures.info)} ` + ); + + output += logLinesWithLeadingFigure + '\n'; + }); + + output += '\n'; + } + if (test.error.source) { output += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; @@ -132,9 +162,9 @@ class VerboseReporter { } if (test.error.stack) { - const extracted = extractStack(test.error.stack); - if (extracted.includes('\n')) { - output += '\n' + indentString(colors.errorStack(extracted), 2) + '\n'; + const stack = test.error.stack; + if (stack.includes('\n')) { + output += '\n' + indentString(colors.errorStack(stack), 2) + '\n'; } } @@ -153,15 +183,19 @@ class VerboseReporter { return '\n' + trimOffNewlines(output) + '\n'; } + section() { return chalk.gray.dim('\u2500'.repeat(process.stdout.columns || 80)); } + write(str) { console.error(str); } + stdout(data) { process.stderr.write(data); } + stderr(data) { process.stderr.write(data); } diff --git a/node_modules/ava/lib/run-status.js b/node_modules/ava/lib/run-status.js index 8e095655a..461ab8f90 100644 --- a/node_modules/ava/lib/run-status.js +++ b/node_modules/ava/lib/run-status.js @@ -44,6 +44,7 @@ class RunStatus extends EventEmitter { autoBind(this); } + observeFork(emitter) { emitter .on('teardown', this.handleTeardown) @@ -54,6 +55,7 @@ class RunStatus extends EventEmitter { .on('stdout', this.handleOutput.bind(this, 'stdout')) .on('stderr', this.handleOutput.bind(this, 'stderr')); } + handleRejections(data) { this.rejectionCount += data.rejections.length; @@ -64,6 +66,7 @@ class RunStatus extends EventEmitter { this.errors.push(err); }); } + handleExceptions(data) { this.exceptionCount++; const err = data.exception; @@ -72,10 +75,12 @@ class RunStatus extends EventEmitter { this.emit('error', err, this); this.errors.push(err); } + handleTeardown(data) { this.emit('dependencies', data.file, data.dependencies, this); this.emit('touchedFiles', data.touchedFiles); } + handleStats(stats) { this.emit('stats', stats, this); @@ -85,6 +90,7 @@ class RunStatus extends EventEmitter { this.testCount += stats.testCount; } + handleTest(test) { test.title = this.prefixTitle(test.file) + test.title; @@ -98,6 +104,7 @@ class RunStatus extends EventEmitter { this.emit('test', test, this); } + prefixTitle(file) { if (!this.prefixTitles) { return ''; @@ -107,9 +114,11 @@ class RunStatus extends EventEmitter { return prefixTitle(file, this.base, separator); } + handleOutput(channel, data) { this.emit(channel, data, this); } + processResults(results) { // Assemble stats from all tests this.stats = results.map(result => result.stats); diff --git a/node_modules/ava/lib/runner.js b/node_modules/ava/lib/runner.js index bda2132fd..eb02dde45 100644 --- a/node_modules/ava/lib/runner.js +++ b/node_modules/ava/lib/runner.js @@ -52,6 +52,7 @@ class Runner extends EventEmitter { this.projectDir = options.projectDir; this.serial = options.serial; this.updateSnapshots = options.updateSnapshots; + this.snapshotDir = options.snapshotDir; this.hasStarted = false; this.results = []; @@ -112,7 +113,7 @@ class Runner extends EventEmitter { } if (metadata.type === 'test' && this.match.length > 0) { - metadata.exclusive = title !== null && matcher([title], this.match).length === 1; + metadata.exclusive = matcher([title || ''], this.match).length === 1; } const validationError = validateTest(title, fn, metadata); @@ -130,6 +131,7 @@ class Runner extends EventEmitter { addTestResult(result) { const test = result.result; const props = { + logs: test.logs, duration: test.duration, title: test.title, error: result.reason, @@ -183,6 +185,8 @@ class Runner extends EventEmitter { compareTestSnapshot(options) { if (!this.snapshots) { this.snapshots = snapshotManager.load({ + file: this.file, + fixedLocation: this.snapshotDir, name: path.basename(this.file), projectDir: this.projectDir, relFile: path.relative(this.projectDir, this.file), @@ -219,6 +223,7 @@ class Runner extends EventEmitter { }); return Bluebird.try(() => this.tests.build().run()); } + attributeLeakedError(err) { return this.tests.attributeLeakedError(err); } diff --git a/node_modules/ava/lib/serialize-error.js b/node_modules/ava/lib/serialize-error.js index 55717e161..13146ff42 100644 --- a/node_modules/ava/lib/serialize-error.js +++ b/node_modules/ava/lib/serialize-error.js @@ -4,7 +4,6 @@ const cleanYamlObject = require('clean-yaml-object'); const StackUtils = require('stack-utils'); const assert = require('./assert'); const beautifyStack = require('./beautify-stack'); -const extractStack = require('./extract-stack'); function isAvaAssertionError(source) { return source instanceof assert.AssertionError; @@ -20,7 +19,7 @@ function extractSource(stack) { return null; } - const firstStackLine = extractStack(stack).split('\n')[0]; + const firstStackLine = stack.split('\n')[0]; return stackUtils.parseLine(firstStackLine); } function buildSource(source) { @@ -90,5 +89,11 @@ module.exports = error => { } } + if (typeof error.stack === 'string') { + retval.summary = error.stack.split('\n')[0]; + } else { + retval.summary = JSON.stringify(error); + } + return retval; }; diff --git a/node_modules/ava/lib/snapshot-manager.js b/node_modules/ava/lib/snapshot-manager.js index ea1246585..fcc24922e 100644 --- a/node_modules/ava/lib/snapshot-manager.js +++ b/node_modules/ava/lib/snapshot-manager.js @@ -11,6 +11,7 @@ const indentString = require('indent-string'); const makeDir = require('make-dir'); const md5Hex = require('md5-hex'); const Buffer = require('safe-buffer').Buffer; +const convertSourceMap = require('convert-source-map'); const concordanceOptions = require('./concordance-options').snapshotManager; @@ -355,18 +356,39 @@ class Manager { } } -function determineSnapshotDir(projectDir, testDir) { - const parts = new Set(path.relative(projectDir, testDir).split(path.sep)); +function determineSnapshotDir(options) { + const testDir = determineSourceMappedDir(options); + if (options.fixedLocation) { + const relativeTestLocation = path.relative(options.projectDir, testDir); + return path.join(options.fixedLocation, relativeTestLocation); + } + + const parts = new Set(path.relative(options.projectDir, testDir).split(path.sep)); if (parts.has('__tests__')) { return path.join(testDir, '__snapshots__'); - } else if (parts.has('test') || parts.has('tests')) { // Accept tests, even though it's not in the default test patterns + } + if (parts.has('test') || parts.has('tests')) { // Accept tests, even though it's not in the default test patterns return path.join(testDir, 'snapshots'); } + return testDir; } +function determineSourceMappedDir(options) { + const source = tryRead(options.file).toString(); + const converter = convertSourceMap.fromSource(source) || convertSourceMap.fromMapFileSource(source, options.testDir); + if (converter) { + const map = converter.toObject(); + const firstSource = `${map.sourceRoot || ''}${map.sources[0]}`; + const sourceFile = path.resolve(options.testDir, firstSource); + return path.dirname(sourceFile); + } + + return options.testDir; +} + function load(options) { - const dir = determineSnapshotDir(options.projectDir, options.testDir); + const dir = determineSnapshotDir(options); const reportFile = `${options.name}.md`; const snapFile = `${options.name}.snap`; const snapPath = path.join(dir, snapFile); diff --git a/node_modules/ava/lib/test-collection.js b/node_modules/ava/lib/test-collection.js index 91c604e06..fd34bc489 100644 --- a/node_modules/ava/lib/test-collection.js +++ b/node_modules/ava/lib/test-collection.js @@ -33,6 +33,7 @@ class TestCollection extends EventEmitter { this._emitTestResult = this._emitTestResult.bind(this); } + add(test) { const metadata = test.metadata; const type = metadata.type; @@ -91,6 +92,7 @@ class TestCollection extends EventEmitter { this.tests.concurrent.push(test); } } + _skippedTest(test) { return { run: () => { @@ -103,10 +105,12 @@ class TestCollection extends EventEmitter { } }; } + _emitTestResult(result) { this.pendingTestInstances.delete(result.result); this.emit('test', result); } + _buildHooks(hooks, testTitle, context) { return hooks.map(hook => { const test = this._buildHook(hook, testTitle, context); @@ -118,6 +122,7 @@ class TestCollection extends EventEmitter { return test; }); } + _buildHook(hook, testTitle, contextRef) { let title = hook.title; @@ -141,6 +146,7 @@ class TestCollection extends EventEmitter { this.pendingTestInstances.add(test); return test; } + _buildTest(test, contextRef) { if (!contextRef) { contextRef = null; @@ -158,6 +164,7 @@ class TestCollection extends EventEmitter { this.pendingTestInstances.add(test); return test; } + _buildTestWithHooks(test) { if (test.metadata.skipped || test.metadata.todo) { return new Sequence([this._skippedTest(this._buildTest(test))], true); @@ -175,24 +182,41 @@ class TestCollection extends EventEmitter { } return sequence; } + _buildTests(tests) { return tests.map(test => this._buildTestWithHooks(test)); } - build() { - const beforeHooks = new Sequence(this._buildHooks(this.hooks.before)); - const afterHooks = new Sequence(this._buildHooks(this.hooks.after)); + _hasUnskippedTests() { + return this.tests.serial.concat(this.tests.concurrent) + .some(test => { + return !(test.metadata && test.metadata.skipped === true); + }); + } + + build() { const serialTests = new Sequence(this._buildTests(this.tests.serial), this.bail); const concurrentTests = new Concurrent(this._buildTests(this.tests.concurrent), this.bail); const allTests = new Sequence([serialTests, concurrentTests]); - let finalTests = new Sequence([beforeHooks, allTests, afterHooks], true); + let finalTests; + // Only run before and after hooks when there are unskipped tests + if (this._hasUnskippedTests()) { + const beforeHooks = new Sequence(this._buildHooks(this.hooks.before)); + const afterHooks = new Sequence(this._buildHooks(this.hooks.after)); + finalTests = new Sequence([beforeHooks, allTests, afterHooks], true); + } else { + finalTests = new Sequence([allTests], true); + } + if (this.hooks.afterAlways.length > 0) { const afterAlwaysHooks = new Sequence(this._buildHooks(this.hooks.afterAlways)); finalTests = new Sequence([finalTests, afterAlwaysHooks], false); } + return finalTests; } + attributeLeakedError(err) { for (const test of this.pendingTestInstances) { if (test.attributeLeakedError(err)) { diff --git a/node_modules/ava/lib/test-worker.js b/node_modules/ava/lib/test-worker.js index 0061775f0..84fba364e 100644 --- a/node_modules/ava/lib/test-worker.js +++ b/node_modules/ava/lib/test-worker.js @@ -2,7 +2,6 @@ // Check if the test is being run without AVA cli { - /* eslint-disable import/order */ const path = require('path'); const chalk = require('chalk'); @@ -17,22 +16,18 @@ } } +const currentlyUnhandled = require('currently-unhandled')(); +const isObj = require('is-obj'); + const adapter = require('./process-adapter'); const globals = require('./globals'); const opts = adapter.opts; globals.options = opts; -/* eslint-enable import/order */ -const Bluebird = require('bluebird'); -const currentlyUnhandled = require('currently-unhandled')(); -const isObj = require('is-obj'); const serializeError = require('./serialize-error'); -// Bluebird specific -Bluebird.longStackTraces(); - -(opts.require || []).forEach(require); +(opts.require || []).forEach(x => require(x)); adapter.installSourceMapSupport(); adapter.installPrecompilerHook(); diff --git a/node_modules/ava/lib/test.js b/node_modules/ava/lib/test.js index 58be54d32..839101b40 100644 --- a/node_modules/ava/lib/test.js +++ b/node_modules/ava/lib/test.js @@ -71,6 +71,7 @@ class ExecutionContext { _throwsArgStart(assertion, file, line) { this._test.trackThrows({assertion, file, line}); } + _throwsArgEnd() { this._test.trackThrows(null); } @@ -78,6 +79,10 @@ class ExecutionContext { { const assertions = assert.wrapAssertions({ + log(executionContext, text) { + executionContext._test.addLog(text); + }, + pass(executionContext) { executionContext._test.countPassedAssertion(); }, @@ -108,6 +113,7 @@ class Test { this.metadata = options.metadata; this.onResult = options.onResult; this.title = options.title; + this.logs = []; this.snapshotInvocationCount = 0; this.compareWithSnapshot = assertionOptions => { @@ -175,6 +181,10 @@ class Test { this.assertCount++; } + addLog(text) { + this.logs.push(text); + } + addPendingAssertion(promise) { if (this.finishing) { this.saveFirstError(new Error('Assertion passed, but test has already finished')); diff --git a/node_modules/ava/lib/watcher.js b/node_modules/ava/lib/watcher.js index c90c810f0..3f5ed3ee7 100644 --- a/node_modules/ava/lib/watcher.js +++ b/node_modules/ava/lib/watcher.js @@ -25,6 +25,7 @@ class Debouncer { this.timer = null; this.repeat = false; } + debounce(delay) { if (this.timer) { this.again = true; @@ -55,6 +56,7 @@ class Debouncer { this.timer = timer; } + cancel() { if (this.timer) { clearTimeout(this.timer); @@ -69,6 +71,7 @@ class TestDependency { this.file = file; this.sources = sources; } + contains(source) { return this.sources.indexOf(source) !== -1; } @@ -146,6 +149,7 @@ class Watcher { this.watchFiles(); this.rerunAll(); } + watchFiles() { const patterns = this.avaFiles.getChokidarPatterns(); @@ -160,16 +164,18 @@ class Watcher { } }); } + trackTestDependencies(api) { const relative = absPath => nodePath.relative(process.cwd(), absPath); api.on('test-run', runStatus => { runStatus.on('dependencies', (file, dependencies) => { - const sourceDeps = dependencies.map(relative).filter(this.avaFiles.isSource); + const sourceDeps = dependencies.map(x => relative(x)).filter(this.avaFiles.isSource); this.updateTestDependencies(file, sourceDeps); }); }); } + updateTestDependencies(file, sources) { if (sources.length === 0) { this.testDependencies = this.testDependencies.filter(dep => dep.file !== file); @@ -190,6 +196,7 @@ class Watcher { this.testDependencies.push(new TestDependency(file, sources)); } } + trackTouchedFiles(api) { api.on('test-run', runStatus => { runStatus.on('touchedFiles', files => { @@ -199,11 +206,13 @@ class Watcher { }); }); } + trackExclusivity(api) { api.on('stats', stats => { this.updateExclusivity(stats.file, stats.hasExclusive); }); } + updateExclusivity(file, hasExclusiveTests) { const index = this.filesWithExclusiveTests.indexOf(file); @@ -213,6 +222,7 @@ class Watcher { this.filesWithExclusiveTests.splice(index, 1); } } + trackFailures(api) { api.on('test-run', (runStatus, files) => { files.forEach(file => { @@ -230,9 +240,11 @@ class Watcher { }); }); } + pruneFailures(file) { this.filesWithFailures = this.filesWithFailures.filter(state => state.file !== file); } + countFailure(file, vector) { const isUpdate = this.filesWithFailures.some(state => { if (state.file !== file) { @@ -251,6 +263,7 @@ class Watcher { }); } } + sumPreviousFailures(beforeVector) { let total = 0; @@ -262,6 +275,7 @@ class Watcher { return total; } + cleanUnlinkedTests(unlinkedTests) { unlinkedTests.forEach(testFile => { this.updateTestDependencies(testFile, []); @@ -269,6 +283,7 @@ class Watcher { this.pruneFailures(testFile); }); } + observeStdin(stdin) { stdin.resume(); stdin.setEncoding('utf8'); @@ -295,14 +310,17 @@ class Watcher { }); }); } + rerunAll() { this.dirtyStates = {}; this.run(); } + updatePreviousSnapshots() { this.dirtyStates = {}; this.run(this.previousFiles, true); } + runAfterChanges() { const dirtyStates = this.dirtyStates; this.dirtyStates = {}; |