-
-
Notifications
You must be signed in to change notification settings - Fork 305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Show full stack on failure #330
Conversation
5b0aeb1
to
4472fc4
Compare
@@ -157,8 +157,10 @@ function encodeResult (res, count) { | |||
if (res.at) { | |||
output += inner + 'at: ' + res.at + '\n'; | |||
} | |||
if (res.operator === 'error' && res.actual && res.actual.stack) { | |||
var lines = String(res.actual.stack).split('\n'); | |||
var error = (res.actual instanceof Error ? res.actual : res.error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instanceof
will fail on cross-realm Error instances (like from an iframe, or the vm
module.
if (res.operator === 'error' && res.actual && res.actual.stack) { | ||
var lines = String(res.actual.stack).split('\n'); | ||
var error = (res.actual instanceof Error ? res.actual : res.error) | ||
var stack = typeof error === 'object' ? error.stack : null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing semicolon
@@ -2,13 +2,15 @@ var tape = require('../'); | |||
var tap = require('tap'); | |||
var concat = require('concat-stream'); | |||
|
|||
var stripFullStack = require('./common').stripFullStack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
semicolon
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, sorry. This is what happens when you copy/paste. Will fix all cases.
} | ||
|
||
module.exports.stripFullStack = function (output) { | ||
return output.replace(/^\W+at.*:\d+:\d+.*$\W/gm, ''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than just stripping it, I'd prefer to try to normalize the output across engines - for example, use try/catch
to get an actual stack trace at runtime, and then using that to figure out how stack traces should work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, here's the first-order problems that checking full stacks with every test would create:
- The file names will change depending on the absolute directory of the git clone.
- Any changes to line position (adding or removing things from a test or even potentially to a file that contains code as part of a test traceback) will cause tests to fail.
These are the two biggest problems, and could be easily fixed by stripping the relevant volatile part of the stack. The deeper issue that can't be resolved is the structure of the stack itself. Any refactoring that adds or removes a method to any "failing stack" will now fail the test.
As a result, many trivial refactors would then result in lots of pointless new test failures. It was annoying enough to go through all these tests myself once to fix this issue. I don't want to put that burden on every future author.
Does that make sense? Maybe I'm just not understanding what you're expecting to happen when normalizing output across engines. Is there some deeper property of the stacks we want to test everywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re 1, we can determine the filename with __filename
and process.cwd()
and similar.
Changes to line position are a fair point, and i'd be fine normalizing all of those (ie, all line numbers would be normalized to 0 - so that relative line numbers mattered but not absolute numbers).
I guess I'm mainly concerned (especially because this regex is pretty inscrutable) that there will be bugs in the stack traces, and we won't know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about I modify this to something like verifyAndStripTraces
, which strips all stacks from the generated YAML and checks:
- That the expected test file shows up somewhere in the stack trace
- That every line of the stack trace "looks right". Honestly, the only thing I can think of here is matching against some basic regex (or regexes, which might make things more understandable).
For more clarification, here's an example error, with the stacks still fully in the test output:
+++ found
--- wanted
actual: |-
{}
stack: |-
Error: should be equivalent
+ at Test.assert [as _assert] (/Users/fmurphy/src/tape/lib/test.js:216:54)
+ at Test.bound [as _assert] (/Users/fmurphy/src/tape/lib/test.js:65:32)
+ at Test.deepEqual.Test.deepEquals.Test.isEquivalent.Test.same (/Users/fmurphy/src/tape/lib/test.js:384:10)
+ at Test.bound [as deepEqual] (/Users/fmurphy/src/tape/lib/test.js:65:32)
+ at Test.<anonymous> (/Users/fmurphy/src/tape/test/undef.js:36:11)
+ at Test.bound [as _cb] (/Users/fmurphy/src/tape/lib/test.js:65:32)
+ at Test.run (/Users/fmurphy/src/tape/lib/test.js:84:10)
+ at Test.bound [as run] (/Users/fmurphy/src/tape/lib/test.js:65:32)
+ at Immediate.next (/Users/fmurphy/src/tape/lib/results.js:71:15)
+ at runCallback (timers.js:574:20)
+ at tryOnImmediate (timers.js:554:5)
+ at processImmediate [as _immediateCallback] (timers.js:533:5)
...
If any refactoring adds or removes a function, pulls something into an anonymous function, or moves things into a new file ... then this stack trace will change. Even if we do obvious things, like strip line numbers and character positions, and normalize file paths.
Completely checking stack correctness would require sophisticated code analysis, which seems like overkill. The challenge here is thus figuring out the right tradeoff between "smart" parsing and effort involved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I think that it can be simplified by replacing process.cwd()
with the string "$PWD", for example - and line numbers replaced :[0-9]+:[0-9]+
with :#:#
- what other normalization would be needed there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is, stacks change. All the time. Even running the same test in node version 0.10.0 (which, guessing by the test matrix, you guys want to support) gives a different stack:
+++ found
--- wanted
actual: |-
{}
stack: |-
Error: should be equivalent
+ at Test.assert (/Users/fmurphy/src/tape/lib/test.js:216:54)
+ at Test.bound [as _assert] (/Users/fmurphy/src/tape/lib/test.js:65:32)
+ at Test.deepEqual.Test.deepEquals.Test.isEquivalent.Test.same (/Users/fmurphy/src/tape/lib/test.js:384:10)
+ at Test.bound [as deepEqual] (/Users/fmurphy/src/tape/lib/test.js:65:32)
+ at Test.<anonymous> (/Users/fmurphy/src/tape/test/undef.js:36:11)
+ at Test.bound [as _cb] (/Users/fmurphy/src/tape/lib/test.js:65:32)
+ at Test.run (/Users/fmurphy/src/tape/lib/test.js:84:10)
+ at Test.bound [as run] (/Users/fmurphy/src/tape/lib/test.js:65:32)
+ at Object.next [as _onImmediate] (/Users/fmurphy/src/tape/lib/results.js:71:15)
+ at processImmediate [as _immediateCallback] (timers.js:309:15)
...
Because, at some point, someone refactored the timers core library in node (and added the runCallback
and tryOnImmediate
frames). What normalization method would reduce both stacks to the same value? One has more frames than the other...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could strip out all frames that don't involve the test/
directory. So the result would look like this:
Error: should be equivalent
[... frames omitted ...]
at Test.<anonymous> ($BASE/test/undef.js:$LINE:$COLUMN)
[... frames omitted ...]
Would that be a good compromise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that would probably suffice, thanks for investigating!
@@ -3,6 +3,8 @@ var path = require('path'); | |||
var spawn = require('child_process').spawn; | |||
var concat = require('concat-stream'); | |||
|
|||
var stripFullStack = require('./common').stripFullStack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
;
@@ -3,12 +3,14 @@ var tape = require('../'); | |||
var tap = require('tap'); | |||
var concat = require('concat-stream'); | |||
|
|||
var stripFullStack = require('./common').stripFullStack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
;
247b6f5
to
bc8be57
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like tests are failing on node <= 4
@@ -157,8 +157,10 @@ function encodeResult (res, count) { | |||
if (res.at) { | |||
output += inner + 'at: ' + res.at + '\n'; | |||
} | |||
if (res.operator === 'error' && res.actual && res.actual.stack) { | |||
var lines = String(res.actual.stack).split('\n'); | |||
var error = (res.actual instanceof Error ? res.actual : res.error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer not to use instanceof
, since it's not reliable across realms. (same as #330 (comment))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, am still thinking about how to test / fix this. My current plan is to create a new test that throws an error from the node vm
module. Does that sound like a good plan? Any suggestions on actually fixing this issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a test that's great as long as the test is skipped in non-node environments.
For actually fixing it, it's probably easier to just continue ducktyping the existence of a stack
property.
if (res.operator === 'error' && res.actual && res.actual.stack) { | ||
var lines = String(res.actual.stack).split('\n'); | ||
var error = (res.actual instanceof Error ? res.actual : res.error); | ||
var stack = typeof error === 'object' ? error.stack : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this won't catch when error
is null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix.
I'm hoping to get to the rest of the comments tomorrow, just was trying to be sure that Travis was happy. Checking errors on node 5 now. |
@ljharb - I think it should be good to go now. I was never able to create a failing test with the For error-throw checks, I also tried doing: t.throws(function () {
vm.runInNewContext('throw new Error(\'CROSS\')');
}, /DOMAIN/); The problem there is that every Let me know if modifications are needed. |
|
||
var actualStack = res.actual && res.actual.stack; | ||
var errorStack = res.error && res.error.stack; | ||
var stack = defined(actualStack, errorStack); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why defined
and not actualStack || errorStack
? Are we worried about a stack trace that's falsy but not undefined
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, it just seemed like the right thing to me. I don't think there will be any practical difference between the two, so I'll switch it if you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nah, it's not adding a dep so i guess it's fine
@ljharb - anything needed from me to move this forward? I just checked back and I think I addressed all of your issues? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM - I'll test and merge this weekend.
8ddd19a
to
c7c5943
Compare
c7c5943
to
9302682
Compare
I'm only seeing one line of the stack trace on the latest version of tape. How do I get this working? |
@OliverJAsh a version containing this change has not yet been released. |
- [Fix] fix spurious "test exited without ending" (#223) - [New] show full error stack on failure (#330) - [Deps] update `resolve`, `object-inspect`, `glob` - [Dev Deps] update `tap`, `concat-stream`, `js-yaml` - [Tests] fix stack differences on node 0.8 - [Tests] npm v4.6+ breaks on node < v1, npm v5+ breaks on node < v4 - [Tests] on `node` `v8`; no need for sudo; `v0.8` passes now; allow v5/v7/iojs to fail.
Hmm, is it actually possible to make this change optional? We have downgraded our |
While I wouldn't say a short stack trace is a part of the contract for tape, if you want to open a new issue, I would support an option that limits the number of lines of a stack trace shown. |
if (res.operator === 'error' && res.actual && res.actual.stack) { | ||
var lines = String(res.actual.stack).split('\n'); | ||
|
||
var actualStack = res.actual && res.actual.stack; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rebased #265 and fixed all the tests. The easiest solution to fixing most tests was to strip out the stack trace parts that will change from machine to machine.
@devtristan @ljharb