tape-testing / tape

tap-producing test harness for node and browsers

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Exceptions throw in `test('.', async (t) => { ... })` do not have meaningful stacktrace

Raynos opened this issue · comments

When using tape@5 instead of tape@4 a programmer error in my test program is captured by tape and showing a not ok ${err.message}

# sync
not ok 1 TypeError: Cannot read property 'length' of undefined
  ---
    operator: fail
    stack: |-
      Error: TypeError: Cannot read property 'length' of undefined
          at Test.assert [as _assert] (/tape/lib/test.js:260:54)
          at Test.bound [as _assert] (tape/lib/test.js:84:32)
          at Test.fail (tape/lib/test.js:354:10)
          at Test.bound [as fail] (tape/lib/test.js:84:32)
          at onError (tape/lib/test.js:114:18)
  ...

The stack trace is the stack trace of tape itself handling promise.catch() ( https://github.com/substack/tape/blob/master/lib/test.js#L113 ) instead of the stack trace for my code.

I cannot tell which line of code in my test function has .length of undefined because tape is printing the wrong stack trace.

Ideally tape would rethrow the err to the global process uncaught exception handler so that I can see the real stack trace and so that the tests can fail hard and fast immediately.

A meaningful stack trace is important.

However, node 15's default behavior of crashing on unhandled rejections violates language semantics - an unhandled rejection is not the same as an uncaught exception, and it's not supposed to fail the program overall, fast or otherwise.

It seems like the real issue is that https://github.com/substack/tape/blob/master/lib/test.js#L260 is creating a new Error, and thus a new stack trace, rather than preserving the original error instance. I think it should be doable to fix it so that either 1) when fail is called with an error instance, the error instance is preserved, or 2) specifically in the case of a rejected promise returned to tape, we directly use _assert or .emit rather than passing it through .fail.

Yeah I think calling self.ifError(err) instead of self.fail(err) should preserve the stack trace.

However we should probably double check the rejection is an Error instance and call self.fail(reason) with any non-Error reason.