From 30ce5460189afc0b35aa43cf8c9bab0460bf9521 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Thu, 16 Feb 2017 14:14:45 +0000 Subject: [PATCH 1/4] Resolve absolute source file path when serializing errors Fixes #1270. --- lib/cli.js | 4 ++-- lib/reporters/mini.js | 4 +--- lib/reporters/verbose.js | 4 +--- lib/serialize-error.js | 3 ++- test/reporters/mini.js | 23 +++++++++--------- test/reporters/verbose.js | 23 +++++++++--------- test/serialize-error.js | 50 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 78 insertions(+), 33 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index e95c5c22c..2848a5f9e 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -129,9 +129,9 @@ exports.run = () => { if (cli.flags.tap && !cli.flags.watch) { reporter = new TapReporter(); } else if (cli.flags.verbose || isCi) { - reporter = new VerboseReporter({color: cli.flags.color, basePath: projectDir}); + reporter = new VerboseReporter({color: cli.flags.color}); } else { - reporter = new MiniReporter({color: cli.flags.color, watching: cli.flags.watch, basePath: projectDir}); + reporter = new MiniReporter({color: cli.flags.color, watching: cli.flags.watch}); } reporter.api = api; diff --git a/lib/reporters/mini.js b/lib/reporters/mini.js index 40b9bd328..81ca5e86c 100644 --- a/lib/reporters/mini.js +++ b/lib/reporters/mini.js @@ -1,6 +1,5 @@ 'use strict'; const StringDecoder = require('string_decoder').StringDecoder; -const path = require('path'); const cliCursor = require('cli-cursor'); const lastLineTracker = require('last-line-stream/tracker'); const plur = require('plur'); @@ -187,8 +186,7 @@ class MiniReporter { if (test.error.source) { status += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; - const errorPath = path.join(this.options.basePath, test.error.source.file); - const excerpt = codeExcerpt(errorPath, test.error.source.line, {maxWidth: process.stdout.columns}); + const excerpt = codeExcerpt(test.error.source.file, test.error.source.line, {maxWidth: process.stdout.columns}); if (excerpt) { status += '\n' + indentString(excerpt, 2) + '\n'; } diff --git a/lib/reporters/verbose.js b/lib/reporters/verbose.js index a76d2c647..5d800de29 100644 --- a/lib/reporters/verbose.js +++ b/lib/reporters/verbose.js @@ -1,5 +1,4 @@ 'use strict'; -const path = require('path'); const indentString = require('indent-string'); const prettyMs = require('pretty-ms'); const figures = require('figures'); @@ -106,8 +105,7 @@ class VerboseReporter { if (test.error.source) { output += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; - const errorPath = path.join(this.options.basePath, test.error.source.file); - const excerpt = codeExcerpt(errorPath, test.error.source.line, {maxWidth: process.stdout.columns}); + const excerpt = codeExcerpt(test.error.source.file, test.error.source.line, {maxWidth: process.stdout.columns}); if (excerpt) { output += '\n' + indentString(excerpt, 2) + '\n'; } diff --git a/lib/serialize-error.js b/lib/serialize-error.js index e81e12256..0066597c1 100644 --- a/lib/serialize-error.js +++ b/lib/serialize-error.js @@ -1,4 +1,5 @@ 'use strict'; +const path = require('path'); const cleanYamlObject = require('clean-yaml-object'); const StackUtils = require('stack-utils'); const prettyFormat = require('@ava/pretty-format'); @@ -59,7 +60,7 @@ module.exports = error => { const source = stackUtils.parseLine(firstStackLine); if (source) { err.source = { - file: source.file.trim(), + file: path.resolve(source.file.trim()), line: source.line }; } diff --git a/test/reporters/mini.js b/test/reporters/mini.js index f53c60ad3..e1395b4e7 100644 --- a/test/reporters/mini.js +++ b/test/reporters/mini.js @@ -1,5 +1,4 @@ 'use strict'; -const path = require('path'); const indentString = require('indent-string'); const tempWrite = require('temp-write'); const flatten = require('arr-flatten'); @@ -354,7 +353,7 @@ test('results with errors', t => { const err1 = new Error('failure one'); err1.stack = beautifyStack(err1.stack); const err1Path = tempWrite.sync('a();'); - err1.source = {file: path.basename(err1Path), line: 1}; + err1.source = {file: err1Path, line: 1}; err1.showOutput = true; err1.actual = JSON.stringify('abc'); err1.actualType = 'string'; @@ -364,14 +363,14 @@ test('results with errors', t => { const err2 = new Error('failure two'); err2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); - err2.source = {file: path.basename(err2Path), line: 1}; + err2.source = {file: err2Path, line: 1}; err2.showOutput = true; err2.actual = JSON.stringify([1]); err2.actualType = 'array'; err2.expected = JSON.stringify([2]); err2.expectedType = 'array'; - const reporter = miniReporter({basePath: path.dirname(err1Path)}); + const reporter = miniReporter(); reporter.failCount = 1; const runStatus = { @@ -426,14 +425,14 @@ test('results with errors and disabled code excerpts', t => { const err2 = new Error('failure two'); err2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); - err2.source = {file: path.basename(err2Path), line: 1}; + err2.source = {file: err2Path, line: 1}; err2.showOutput = true; err2.actual = JSON.stringify([1]); err2.actualType = 'array'; err2.expected = JSON.stringify([2]); err2.expectedType = 'array'; - const reporter = miniReporter({color: true, basePath: path.dirname(err2Path)}); + const reporter = miniReporter({color: true}); reporter.failCount = 1; const runStatus = { @@ -477,7 +476,7 @@ test('results with errors and broken code excerpts', t => { const err1 = new Error('failure one'); err1.stack = beautifyStack(err1.stack); const err1Path = tempWrite.sync('a();'); - err1.source = {file: path.basename(err1Path), line: 10}; + err1.source = {file: err1Path, line: 10}; err1.showOutput = true; err1.actual = JSON.stringify('abc'); err1.actualType = 'string'; @@ -487,14 +486,14 @@ test('results with errors and broken code excerpts', t => { const err2 = new Error('failure two'); err2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); - err2.source = {file: path.basename(err2Path), line: 1}; + err2.source = {file: err2Path, line: 1}; err2.showOutput = true; err2.actual = JSON.stringify([1]); err2.actualType = 'array'; err2.expected = JSON.stringify([2]); err2.expectedType = 'array'; - const reporter = miniReporter({color: true, basePath: path.dirname(err2Path)}); + const reporter = miniReporter({color: true}); reporter.failCount = 1; const runStatus = { @@ -539,7 +538,7 @@ test('results with errors and disabled assert output', t => { const err1 = new Error('failure one'); err1.stack = beautifyStack(err1.stack); const err1Path = tempWrite.sync('a();'); - err1.source = {file: path.basename(err1Path), line: 1}; + err1.source = {file: err1Path, line: 1}; err1.showOutput = false; err1.actual = JSON.stringify('abc'); err1.actualType = 'string'; @@ -549,14 +548,14 @@ test('results with errors and disabled assert output', t => { const err2 = new Error('failure two'); err2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); - err2.source = {file: path.basename(err2Path), line: 1}; + err2.source = {file: err2Path, line: 1}; err2.showOutput = true; err2.actual = JSON.stringify([1]); err2.actualType = 'array'; err2.expected = JSON.stringify([2]); err2.expectedType = 'array'; - const reporter = miniReporter({color: true, basePath: path.dirname(err1Path)}); + const reporter = miniReporter({color: true}); reporter.failCount = 1; const runStatus = { diff --git a/test/reporters/verbose.js b/test/reporters/verbose.js index 2e00ee42a..4ead6ce8c 100644 --- a/test/reporters/verbose.js +++ b/test/reporters/verbose.js @@ -1,5 +1,4 @@ 'use strict'; -const path = require('path'); const indentString = require('indent-string'); const flatten = require('arr-flatten'); const tempWrite = require('temp-write'); @@ -364,7 +363,7 @@ test('results with errors', t => { const error1 = new Error('error one message'); error1.stack = beautifyStack(error1.stack); const err1Path = tempWrite.sync('a()'); - error1.source = {file: path.basename(err1Path), line: 1}; + error1.source = {file: err1Path, line: 1}; error1.showOutput = true; error1.actual = JSON.stringify('abc'); error1.actualType = 'string'; @@ -374,14 +373,14 @@ test('results with errors', t => { const error2 = new Error('error two message'); error2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b()'); - error2.source = {file: path.basename(err2Path), line: 1}; + error2.source = {file: err2Path, line: 1}; error2.showOutput = true; error2.actual = JSON.stringify([1]); error2.actualType = 'array'; error2.expected = JSON.stringify([2]); error2.expectedType = 'array'; - const reporter = createReporter({color: true, basePath: path.dirname(err1Path)}); + const reporter = createReporter({color: true}); const runStatus = createRunStatus(); runStatus.failCount = 1; runStatus.tests = [{ @@ -433,14 +432,14 @@ test('results with errors and disabled code excerpts', t => { const error2 = new Error('error two message'); error2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b()'); - error2.source = {file: path.basename(err2Path), line: 1}; + error2.source = {file: err2Path, line: 1}; error2.showOutput = true; error2.actual = JSON.stringify([1]); error2.actualType = 'array'; error2.expected = JSON.stringify([2]); error2.expectedType = 'array'; - const reporter = createReporter({color: true, basePath: path.dirname(err2Path)}); + const reporter = createReporter({color: true}); const runStatus = createRunStatus(); runStatus.failCount = 1; runStatus.tests = [{ @@ -481,7 +480,7 @@ test('results with errors and disabled code excerpts', t => { const error1 = new Error('error one message'); error1.stack = beautifyStack(error1.stack); const err1Path = tempWrite.sync('a();'); - error1.source = {file: path.basename(err1Path), line: 10}; + error1.source = {file: err1Path, line: 10}; error1.showOutput = true; error1.actual = JSON.stringify('abc'); error1.actualType = 'string'; @@ -491,14 +490,14 @@ test('results with errors and disabled code excerpts', t => { const error2 = new Error('error two message'); error2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b()'); - error2.source = {file: path.basename(err2Path), line: 1}; + error2.source = {file: err2Path, line: 1}; error2.showOutput = true; error2.actual = JSON.stringify([1]); error2.actualType = 'array'; error2.expected = JSON.stringify([2]); error2.expectedType = 'array'; - const reporter = createReporter({color: true, basePath: path.dirname(err2Path)}); + const reporter = createReporter({color: true}); const runStatus = createRunStatus(); runStatus.failCount = 1; runStatus.tests = [{ @@ -540,7 +539,7 @@ test('results with errors and disabled assert output', t => { const error1 = new Error('error one message'); error1.stack = beautifyStack(error1.stack); const err1Path = tempWrite.sync('a();'); - error1.source = {file: path.basename(err1Path), line: 1}; + error1.source = {file: err1Path, line: 1}; error1.showOutput = false; error1.actual = JSON.stringify('abc'); error1.actualType = 'string'; @@ -550,14 +549,14 @@ test('results with errors and disabled assert output', t => { const error2 = new Error('error two message'); error2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); - error2.source = {file: path.basename(err2Path), line: 1}; + error2.source = {file: err2Path, line: 1}; error2.showOutput = true; error2.actual = JSON.stringify([1]); error2.actualType = 'array'; error2.expected = JSON.stringify([2]); error2.expectedType = 'array'; - const reporter = createReporter({color: true, basePath: path.dirname(err1Path)}); + const reporter = createReporter({color: true}); const runStatus = createRunStatus(); runStatus.failCount = 1; runStatus.tests = [{ diff --git a/test/serialize-error.js b/test/serialize-error.js index b44b059e1..25262b16c 100644 --- a/test/serialize-error.js +++ b/test/serialize-error.js @@ -1,11 +1,19 @@ 'use strict'; +const fs = require('fs'); +const path = require('path'); const prettyFormat = require('@ava/pretty-format'); const reactTestPlugin = require('@ava/pretty-format/plugins/ReactTestComponent'); +const sourceMapFixtures = require('source-map-fixtures'); +const sourceMapSupport = require('source-map-support'); +const uniqueTempDir = require('unique-temp-dir'); const test = require('tap').test; const beautifyStack = require('../lib/beautify-stack'); const serialize = require('../lib/serialize-error'); +// Needed to test stack traces from source map fixtures. +sourceMapSupport.install({environment: 'node'}); + function serializeValue(value) { return prettyFormat(value, { plugins: [reactTestPlugin], @@ -26,6 +34,48 @@ test('serialize standard props', t => { t.end(); }); +test('source file is an absolute path', t => { + const err = new Error('Hello'); + const serializedErr = serialize(err); + + t.is(serializedErr.source.file, __filename); + t.end(); +}); + +test('source file is an absolute path, after source map correction', t => { + const fixture = sourceMapFixtures.mapFile('throws'); + try { + fixture.require().run(); + t.fail('Fixture should have thrown'); + } catch (err) { + const serializedErr = serialize(err); + t.is(serializedErr.source.file, fixture.sourceFile); + t.end(); + } +}); + +test('source file is an absolute path, after source map correction, even if already absolute', t => { + const fixture = sourceMapFixtures.mapFile('throws'); + const map = JSON.parse(fs.readFileSync(fixture.file + '.map')); + + const tmp = uniqueTempDir({create: true}); + const sourceRoot = path.join(tmp, 'src'); + const expectedSourceFile = path.join(sourceRoot, map.file); + + const tmpFile = path.join(tmp, path.basename(fixture.file)); + fs.writeFileSync(tmpFile, fs.readFileSync(fixture.file)); + fs.writeFileSync(tmpFile + '.map', JSON.stringify(Object.assign(map, {sourceRoot}), null, 2)); + + try { + require(tmpFile).run(); // eslint-disable-line import/no-dynamic-require + t.fail('Fixture should have thrown'); + } catch (err) { + const serializedErr = serialize(err); + t.is(serializedErr.source.file, expectedSourceFile); + t.end(); + } +}); + test('serialize statements', t => { const err = new Error(); err.showOutput = true; From 6e3f8711b257e8cedc38b2f88f20860e0a2da178 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Thu, 16 Feb 2017 16:35:35 +0000 Subject: [PATCH 2/4] Bail excerpting code if file cannot be read --- lib/code-excerpt.js | 8 +++++++- test/code-excerpt.js | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/code-excerpt.js b/lib/code-excerpt.js index 03f2e7d82..add7a7856 100644 --- a/lib/code-excerpt.js +++ b/lib/code-excerpt.js @@ -12,7 +12,13 @@ module.exports = (file, line, options) => { options = options || {}; const maxWidth = options.maxWidth || 80; - const source = fs.readFileSync(file, 'utf8'); + let source; + try { + source = fs.readFileSync(file, 'utf8'); + } catch (err) { + return null; + } + const excerpt = codeExcerpt(source, line, {around: 1}); if (!excerpt) { return null; diff --git a/test/code-excerpt.js b/test/code-excerpt.js index 4608a9a39..ee5d55fea 100644 --- a/test/code-excerpt.js +++ b/test/code-excerpt.js @@ -1,4 +1,5 @@ 'use strict'; +const fs = require('fs'); const tempWrite = require('temp-write'); const chalk = require('chalk'); const test = require('tap').test; @@ -60,3 +61,12 @@ test('format line numbers', t => { t.is(excerpt, expected); t.end(); }); + +test('noop if file cannot be read', t => { + const file = tempWrite.sync(''); + fs.unlinkSync(file); + + const excerpt = codeExcerpt(file, 10); + t.is(excerpt, null); + t.end(); +}); From d9be329d32c22ef719498dee5f05ddfb3dce2659 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Fri, 17 Feb 2017 10:44:52 +0000 Subject: [PATCH 3/4] temp-write@^3.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 65847bd1c..f2ed5bcde 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,7 @@ "sinon": "^1.17.2", "source-map-fixtures": "^2.1.0", "tap": "^10.0.0", - "temp-write": "^3.0.0", + "temp-write": "^3.1.0", "touch": "^1.0.0", "xo": "^0.17.0", "zen-observable": "^0.4.0" From 6497ca9bb729867fe871d3d428b6ff1527e9754e Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Thu, 16 Feb 2017 17:42:25 +0000 Subject: [PATCH 4/4] Restrict code excerpts to tests/helpers/sources Don't show code excerpts for dependencies or code outside the project directory. Determine where the source lives when serializing the error. Then, when excerpting the code, bail if it lives outside the project or is a dependency. --- lib/code-excerpt.js | 17 ++++++++++++----- lib/reporters/mini.js | 2 +- lib/reporters/verbose.js | 2 +- lib/serialize-error.js | 15 ++++++++++++++- test/code-excerpt.js | 26 ++++++++++++++++++------- test/reporters/mini.js | 35 +++++++++++++++++++++------------- test/reporters/verbose.js | 35 +++++++++++++++++++++------------- test/serialize-error.js | 40 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 131 insertions(+), 41 deletions(-) diff --git a/lib/code-excerpt.js b/lib/code-excerpt.js index add7a7856..aa619a0b2 100644 --- a/lib/code-excerpt.js +++ b/lib/code-excerpt.js @@ -8,18 +8,25 @@ const chalk = require('chalk'); const formatLineNumber = (lineNumber, maxLineNumber) => ' '.repeat(Math.max(0, String(maxLineNumber).length - String(lineNumber).length)) + lineNumber; -module.exports = (file, line, options) => { - options = options || {}; +module.exports = (source, options) => { + if (!source.isWithinProject || source.isDependency) { + return null; + } + const file = source.file; + const line = source.line; + + options = options || {}; const maxWidth = options.maxWidth || 80; - let source; + + let contents; try { - source = fs.readFileSync(file, 'utf8'); + contents = fs.readFileSync(file, 'utf8'); } catch (err) { return null; } - const excerpt = codeExcerpt(source, line, {around: 1}); + const excerpt = codeExcerpt(contents, line, {around: 1}); if (!excerpt) { return null; } diff --git a/lib/reporters/mini.js b/lib/reporters/mini.js index 81ca5e86c..f5b30ffd6 100644 --- a/lib/reporters/mini.js +++ b/lib/reporters/mini.js @@ -186,7 +186,7 @@ class MiniReporter { if (test.error.source) { status += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; - const excerpt = codeExcerpt(test.error.source.file, test.error.source.line, {maxWidth: process.stdout.columns}); + const excerpt = codeExcerpt(test.error.source, {maxWidth: process.stdout.columns}); if (excerpt) { status += '\n' + indentString(excerpt, 2) + '\n'; } diff --git a/lib/reporters/verbose.js b/lib/reporters/verbose.js index 5d800de29..4d6e127cb 100644 --- a/lib/reporters/verbose.js +++ b/lib/reporters/verbose.js @@ -105,7 +105,7 @@ class VerboseReporter { if (test.error.source) { output += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; - const excerpt = codeExcerpt(test.error.source.file, test.error.source.line, {maxWidth: process.stdout.columns}); + const excerpt = codeExcerpt(test.error.source, {maxWidth: process.stdout.columns}); if (excerpt) { output += '\n' + indentString(excerpt, 2) + '\n'; } diff --git a/lib/serialize-error.js b/lib/serialize-error.js index 0066597c1..c3b3350a7 100644 --- a/lib/serialize-error.js +++ b/lib/serialize-error.js @@ -59,8 +59,21 @@ module.exports = error => { const firstStackLine = extractStack(err.stack).split('\n')[0]; const source = stackUtils.parseLine(firstStackLine); if (source) { + // Assume the CWD is the project directory. This holds since this function + // is only called in test workers, which are created with their working + // directory set to the project directory. + const projectDir = process.cwd(); + + const file = path.resolve(projectDir, source.file.trim()); + const rel = path.relative(projectDir, file); + + const isWithinProject = rel.split(path.sep)[0] !== '..'; + const isDependency = isWithinProject && path.dirname(rel).split(path.sep).indexOf('node_modules') > -1; + err.source = { - file: path.resolve(source.file.trim()), + isDependency, + isWithinProject, + file, line: source.line }; } diff --git a/test/code-excerpt.js b/test/code-excerpt.js index ee5d55fea..534196c01 100644 --- a/test/code-excerpt.js +++ b/test/code-excerpt.js @@ -8,13 +8,13 @@ const codeExcerpt = require('../lib/code-excerpt'); chalk.enabled = true; test('read code excerpt', t => { - const path = tempWrite.sync([ + const file = tempWrite.sync([ 'function a() {', '\talert();', '}' ].join('\n')); - const excerpt = codeExcerpt(path, 2); + const excerpt = codeExcerpt({file, line: 2, isWithinProject: true, isDependency: false}); const expected = [ ` ${chalk.grey('1:')} function a() {`, chalk.bgRed(` 2: alert(); `), @@ -26,13 +26,13 @@ test('read code excerpt', t => { }); test('truncate lines', t => { - const path = tempWrite.sync([ + const file = tempWrite.sync([ 'function a() {', '\talert();', '}' ].join('\n')); - const excerpt = codeExcerpt(path, 2, {maxWidth: 14}); + const excerpt = codeExcerpt({file, line: 2, isWithinProject: true, isDependency: false}, {maxWidth: 14}); const expected = [ ` ${chalk.grey('1:')} functio…`, chalk.bgRed(` 2: alert…`), @@ -44,14 +44,14 @@ test('truncate lines', t => { }); test('format line numbers', t => { - const path = tempWrite.sync([ + const file = tempWrite.sync([ '', '', '', '', '', '', '', '', 'function a() {', '\talert();', '}' ].join('\n')); - const excerpt = codeExcerpt(path, 10); + const excerpt = codeExcerpt({file, line: 10, isWithinProject: true, isDependency: false}); const expected = [ ` ${chalk.grey(' 9:')} function a() {`, chalk.bgRed(` 10: alert(); `), @@ -66,7 +66,19 @@ test('noop if file cannot be read', t => { const file = tempWrite.sync(''); fs.unlinkSync(file); - const excerpt = codeExcerpt(file, 10); + const excerpt = codeExcerpt({file, line: 10, isWithinProject: true, isDependency: false}); + t.is(excerpt, null); + t.end(); +}); + +test('noop if file is not within project', t => { + const excerpt = codeExcerpt({isWithinProject: false, file: __filename, line: 1}); + t.is(excerpt, null); + t.end(); +}); + +test('noop if file is a dependency', t => { + const excerpt = codeExcerpt({isWithinProject: true, isDependency: true, file: __filename, line: 1}); t.is(excerpt, null); t.end(); }); diff --git a/test/reporters/mini.js b/test/reporters/mini.js index e1395b4e7..6d74c627f 100644 --- a/test/reporters/mini.js +++ b/test/reporters/mini.js @@ -34,6 +34,15 @@ function miniReporter(options) { return reporter; } +function source(file, line) { + return { + file, + line: line || 1, + isWithinProject: true, + isDependency: false + }; +} + process.stderr.setMaxListeners(50); test('start', t => { @@ -353,7 +362,7 @@ test('results with errors', t => { const err1 = new Error('failure one'); err1.stack = beautifyStack(err1.stack); const err1Path = tempWrite.sync('a();'); - err1.source = {file: err1Path, line: 1}; + err1.source = source(err1Path); err1.showOutput = true; err1.actual = JSON.stringify('abc'); err1.actualType = 'string'; @@ -363,7 +372,7 @@ test('results with errors', t => { const err2 = new Error('failure two'); err2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); - err2.source = {file: err2Path, line: 1}; + err2.source = source(err2Path); err2.showOutput = true; err2.actual = JSON.stringify([1]); err2.actualType = 'array'; @@ -392,7 +401,7 @@ test('results with errors', t => { ' ' + chalk.bold.white('failed one'), ' ' + chalk.grey(`${err1.source.file}:${err1.source.line}`), '', - indentString(codeExcerpt(err1Path, err1.source.line), 2).split('\n'), + indentString(codeExcerpt(err1.source), 2).split('\n'), '', indentString(formatAssertError(err1), 2).split('\n'), /failure one/, @@ -405,7 +414,7 @@ test('results with errors', t => { ' ' + chalk.bold.white('failed two'), ' ' + chalk.grey(`${err2.source.file}:${err2.source.line}`), '', - indentString(codeExcerpt(err2Path, err2.source.line), 2).split('\n'), + indentString(codeExcerpt(err2.source), 2).split('\n'), '', indentString(formatAssertError(err2), 2).split('\n'), /failure two/ @@ -425,7 +434,7 @@ test('results with errors and disabled code excerpts', t => { const err2 = new Error('failure two'); err2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); - err2.source = {file: err2Path, line: 1}; + err2.source = source(err2Path); err2.showOutput = true; err2.actual = JSON.stringify([1]); err2.actualType = 'array'; @@ -464,7 +473,7 @@ test('results with errors and disabled code excerpts', t => { ' ' + chalk.bold.white('failed two'), ' ' + chalk.grey(`${err2.source.file}:${err2.source.line}`), '', - indentString(codeExcerpt(err2Path, err2.source.line), 2).split('\n'), + indentString(codeExcerpt(err2.source), 2).split('\n'), '', indentString(formatAssertError(err2), 2).split('\n'), /failure two/ @@ -476,7 +485,7 @@ test('results with errors and broken code excerpts', t => { const err1 = new Error('failure one'); err1.stack = beautifyStack(err1.stack); const err1Path = tempWrite.sync('a();'); - err1.source = {file: err1Path, line: 10}; + err1.source = source(err1Path, 10); err1.showOutput = true; err1.actual = JSON.stringify('abc'); err1.actualType = 'string'; @@ -486,7 +495,7 @@ test('results with errors and broken code excerpts', t => { const err2 = new Error('failure two'); err2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); - err2.source = {file: err2Path, line: 1}; + err2.source = source(err2Path); err2.showOutput = true; err2.actual = JSON.stringify([1]); err2.actualType = 'array'; @@ -526,7 +535,7 @@ test('results with errors and broken code excerpts', t => { ' ' + chalk.bold.white('failed two'), ' ' + chalk.grey(`${err2.source.file}:${err2.source.line}`), '', - indentString(codeExcerpt(err2Path, err2.source.line), 2).split('\n'), + indentString(codeExcerpt(err2.source), 2).split('\n'), '', indentString(formatAssertError(err2), 2).split('\n'), /failure two/ @@ -538,7 +547,7 @@ test('results with errors and disabled assert output', t => { const err1 = new Error('failure one'); err1.stack = beautifyStack(err1.stack); const err1Path = tempWrite.sync('a();'); - err1.source = {file: err1Path, line: 1}; + err1.source = source(err1Path); err1.showOutput = false; err1.actual = JSON.stringify('abc'); err1.actualType = 'string'; @@ -548,7 +557,7 @@ test('results with errors and disabled assert output', t => { const err2 = new Error('failure two'); err2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); - err2.source = {file: err2Path, line: 1}; + err2.source = source(err2Path); err2.showOutput = true; err2.actual = JSON.stringify([1]); err2.actualType = 'array'; @@ -577,7 +586,7 @@ test('results with errors and disabled assert output', t => { ' ' + chalk.bold.white('failed one'), ' ' + chalk.grey(`${err1.source.file}:${err1.source.line}`), '', - indentString(codeExcerpt(err1Path, err1.source.line), 2).split('\n'), + indentString(codeExcerpt(err1.source), 2).split('\n'), '', /failure one/, '', @@ -589,7 +598,7 @@ test('results with errors and disabled assert output', t => { ' ' + chalk.bold.white('failed two'), ' ' + chalk.grey(`${err2.source.file}:${err2.source.line}`), '', - indentString(codeExcerpt(err2Path, err2.source.line), 2).split('\n'), + indentString(codeExcerpt(err2.source), 2).split('\n'), '', indentString(formatAssertError(err2), 2).split('\n'), /failure two/ diff --git a/test/reporters/verbose.js b/test/reporters/verbose.js index 4ead6ce8c..67b978ebf 100644 --- a/test/reporters/verbose.js +++ b/test/reporters/verbose.js @@ -44,6 +44,15 @@ function barFunc() { throw new Error(); } +function source(file, line) { + return { + file, + line: line || 1, + isWithinProject: true, + isDependency: false + }; +} + test('beautify stack - removes uninteresting lines', t => { try { fooFunc(); @@ -363,7 +372,7 @@ test('results with errors', t => { const error1 = new Error('error one message'); error1.stack = beautifyStack(error1.stack); const err1Path = tempWrite.sync('a()'); - error1.source = {file: err1Path, line: 1}; + error1.source = source(err1Path); error1.showOutput = true; error1.actual = JSON.stringify('abc'); error1.actualType = 'string'; @@ -373,7 +382,7 @@ test('results with errors', t => { const error2 = new Error('error two message'); error2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b()'); - error2.source = {file: err2Path, line: 1}; + error2.source = source(err2Path); error2.showOutput = true; error2.actual = JSON.stringify([1]); error2.actualType = 'array'; @@ -399,7 +408,7 @@ test('results with errors', t => { ' ' + chalk.bold.white('fail one'), ' ' + chalk.grey(`${error1.source.file}:${error1.source.line}`), '', - indentString(codeExcerpt(err1Path, error1.source.line), 2).split('\n'), + indentString(codeExcerpt(error1.source), 2).split('\n'), '', indentString(formatAssertError(error1), 2).split('\n'), /error one message/, @@ -412,7 +421,7 @@ test('results with errors', t => { ' ' + chalk.bold.white('fail two'), ' ' + chalk.grey(`${error2.source.file}:${error2.source.line}`), '', - indentString(codeExcerpt(err2Path, error2.source.line), 2).split('\n'), + indentString(codeExcerpt(error2.source), 2).split('\n'), '', indentString(formatAssertError(error2), 2).split('\n'), /error two message/ @@ -432,7 +441,7 @@ test('results with errors and disabled code excerpts', t => { const error2 = new Error('error two message'); error2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b()'); - error2.source = {file: err2Path, line: 1}; + error2.source = source(err2Path); error2.showOutput = true; error2.actual = JSON.stringify([1]); error2.actualType = 'array'; @@ -468,7 +477,7 @@ test('results with errors and disabled code excerpts', t => { ' ' + chalk.bold.white('fail two'), ' ' + chalk.grey(`${error2.source.file}:${error2.source.line}`), '', - indentString(codeExcerpt(err2Path, error2.source.line), 2).split('\n'), + indentString(codeExcerpt(error2.source), 2).split('\n'), '', indentString(formatAssertError(error2), 2).split('\n'), /error two message/ @@ -480,7 +489,7 @@ test('results with errors and disabled code excerpts', t => { const error1 = new Error('error one message'); error1.stack = beautifyStack(error1.stack); const err1Path = tempWrite.sync('a();'); - error1.source = {file: err1Path, line: 10}; + error1.source = source(err1Path, 10); error1.showOutput = true; error1.actual = JSON.stringify('abc'); error1.actualType = 'string'; @@ -490,7 +499,7 @@ test('results with errors and disabled code excerpts', t => { const error2 = new Error('error two message'); error2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b()'); - error2.source = {file: err2Path, line: 1}; + error2.source = source(err2Path); error2.showOutput = true; error2.actual = JSON.stringify([1]); error2.actualType = 'array'; @@ -527,7 +536,7 @@ test('results with errors and disabled code excerpts', t => { ' ' + chalk.bold.white('fail two'), ' ' + chalk.grey(`${error2.source.file}:${error2.source.line}`), '', - indentString(codeExcerpt(err2Path, error2.source.line), 2).split('\n'), + indentString(codeExcerpt(error2.source), 2).split('\n'), '', indentString(formatAssertError(error2), 2).split('\n'), /error two message/ @@ -539,7 +548,7 @@ test('results with errors and disabled assert output', t => { const error1 = new Error('error one message'); error1.stack = beautifyStack(error1.stack); const err1Path = tempWrite.sync('a();'); - error1.source = {file: err1Path, line: 1}; + error1.source = source(err1Path); error1.showOutput = false; error1.actual = JSON.stringify('abc'); error1.actualType = 'string'; @@ -549,7 +558,7 @@ test('results with errors and disabled assert output', t => { const error2 = new Error('error two message'); error2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); - error2.source = {file: err2Path, line: 1}; + error2.source = source(err2Path); error2.showOutput = true; error2.actual = JSON.stringify([1]); error2.actualType = 'array'; @@ -575,7 +584,7 @@ test('results with errors and disabled assert output', t => { ' ' + chalk.bold.white('fail one'), ' ' + chalk.grey(`${error1.source.file}:${error1.source.line}`), '', - indentString(codeExcerpt(err1Path, error1.source.line), 2).split('\n'), + indentString(codeExcerpt(error1.source), 2).split('\n'), '', /error one message/, '', @@ -587,7 +596,7 @@ test('results with errors and disabled assert output', t => { ' ' + chalk.bold.white('fail two'), ' ' + chalk.grey(`${error2.source.file}:${error2.source.line}`), '', - indentString(codeExcerpt(err2Path, error2.source.line), 2).split('\n'), + indentString(codeExcerpt(error2.source), 2).split('\n'), '', indentString(formatAssertError(error2), 2).split('\n'), /error two message/ diff --git a/test/serialize-error.js b/test/serialize-error.js index 25262b16c..bab0cba22 100644 --- a/test/serialize-error.js +++ b/test/serialize-error.js @@ -6,6 +6,7 @@ const prettyFormat = require('@ava/pretty-format'); const reactTestPlugin = require('@ava/pretty-format/plugins/ReactTestComponent'); const sourceMapFixtures = require('source-map-fixtures'); const sourceMapSupport = require('source-map-support'); +const tempWrite = require('temp-write'); const uniqueTempDir = require('unique-temp-dir'); const test = require('tap').test; const beautifyStack = require('../lib/beautify-stack'); @@ -29,6 +30,8 @@ test('serialize standard props', t => { t.is(serializedErr.name, 'Error'); t.is(serializedErr.stack, beautifyStack(err.stack)); t.is(serializedErr.message, 'Hello'); + t.is(typeof serializedErr.source.isDependency, 'boolean'); + t.is(typeof serializedErr.source.isWithinProject, 'boolean'); t.is(typeof serializedErr.source.file, 'string'); t.is(typeof serializedErr.source.line, 'number'); t.end(); @@ -76,6 +79,43 @@ test('source file is an absolute path, after source map correction, even if alre } }); +test('determines whether source file is within the project', t => { + const file = tempWrite.sync('module.exports = () => { throw new Error("hello") }'); + try { + require(file)(); // eslint-disable-line import/no-dynamic-require + t.fail('Should have thrown'); + } catch (err) { + const serializedErr = serialize(err); + t.is(serializedErr.source.file, file); + t.is(serializedErr.source.isWithinProject, false); + } + + const err = new Error('Hello'); + const serializedErr = serialize(err); + t.is(serializedErr.source.file, __filename); + t.is(serializedErr.source.isWithinProject, true); + t.end(); +}); + +test('determines whether source file, if within the project, is a dependency', t => { + const fixture = sourceMapFixtures.mapFile('throws'); + try { + fixture.require().run(); + t.fail('Fixture should have thrown'); + } catch (err) { + const serializedErr = serialize(err); + t.is(serializedErr.source.file, fixture.sourceFile); + t.is(serializedErr.source.isWithinProject, true); + t.is(serializedErr.source.isDependency, true); + } + + const err = new Error('Hello'); + const serializedErr = serialize(err); + t.is(serializedErr.source.file, __filename); + t.is(serializedErr.source.isDependency, false); + t.end(); +}); + test('serialize statements', t => { const err = new Error(); err.showOutput = true;