test: fix test runner does not print error cause#46576
test: fix test runner does not print error cause#46576pulkit-30 wants to merge 1 commit intonodejs:mainfrom
Conversation
|
Review requested:
|
| } | ||
|
|
||
| function hasInnerCause(value) { | ||
| return value?.cause?.cause?.stack ? true : false; |
There was a problem hiding this comment.
a cause can be any JS value, including a primitive, and undefined. Causes should be printed whether they have a stack or not. Can you add some tests for non-error cause values?
There was a problem hiding this comment.
Can you also add a test for something like this:
new Error('a', { cause: new Error('b', { cause: new Error('c', { cause: new Error('d', { cause: new Error('e') }) }) }) })There was a problem hiding this comment.
I'm usually in the "don't worry about primordials" crowd but since this is error handling and debugging code it needs to be very resilient.
I would expect it to deal the following (all real cases from the past):
- What if someone subclassed
Errorwith .causewith a getter that throws? - What if
.stackthrows?
There was a problem hiding this comment.
In addition to @chjirig's test case, please add one like this:
const outer = new Error('b', { cause: null });
outer.cause = new Error('c', { cause: new Error('d', { cause: outer }) }) });`to ensure cycles are handled.
|
please also make sure (with a test?) that the cause is shown both when running with |
| * | ||
| * | ||
| * | ||
| * |
There was a problem hiding this comment.
perhaps some of these lines (that are not a stack trace) should not be an asterisk
|
Thanks for reviews @cjihrig @ljharb @benjamingr @MoLow, |
|
test.mjs import test from 'node:test';
test('should print error cause', () => {
throw new Error('foo', {cause: new Error('bar')});
});
test('should print error cause for nested errors', () => {
throw new Error('a', { cause: new Error('b', { cause: new Error('c', { cause: new Error('d', { cause: new Error('e') }) }) }) })
});
test("should handle cycles in error", () => {
const outer = new Error('b', { cause: null });
outer.cause = new Error('c', { cause: new Error('d', { cause: outer }) });
throw outer;
});output: TAP version 13
# Subtest: should print error cause
not ok 1 - should print error cause
---
duration_ms: 0.773584
failureType: 'testCodeFailure'
error: 'foo'
code: 'ERR_TEST_FAILURE'
stack: |-
TestContext.<anonymous> (file:///Users/pulkitgupta/Desktop/node/_test.mjs:4:9)
Test.runInAsyncScope (node:async_hooks:206:9)
ModuleJob.run (node:internal/modules/esm/module_job:193:25) {
[cause]: Error: bar
at TestContext.<anonymous> (file:///Users/pulkitgupta/Desktop/node/_test.mjs:4:34)
at Test.runInAsyncScope (node:async_hooks:206:9)
at Test.run (node:internal/test_runner/test:549:25)
at Test.start (node:internal/test_runner/test:465:17)
at test (node:internal/test_runner/harness:171:18)
at file:///Users/pulkitgupta/Desktop/node/_test.mjs:3:1
at ModuleJob.run (node:internal/modules/esm/module_job:193:25)
}
...
# Subtest: should print error cause for nested errors
not ok 2 - should print error cause for nested errors
---
duration_ms: 1.281542
failureType: 'testCodeFailure'
error: 'a'
code: 'ERR_TEST_FAILURE'
stack: |-
TestContext.<anonymous> (file:///Users/pulkitgupta/Desktop/node/_test.mjs:8:9)
Test.runInAsyncScope (node:async_hooks:206:9)
Test.run (node:internal/test_runner/test:577:10) {
[cause]: Error: b
at TestContext.<anonymous> (file:///Users/pulkitgupta/Desktop/node/_test.mjs:8:33)
at Test.runInAsyncScope (node:async_hooks:206:9)
at Test.run (node:internal/test_runner/test:577:10) {
[cause]: Error: c
at TestContext.<anonymous> (file:///Users/pulkitgupta/Desktop/node/_test.mjs:8:57)
at Test.runInAsyncScope (node:async_hooks:206:9)
at Test.run (node:internal/test_runner/test:577:10) {
[cause]: [Error]
}
}
}
...
# Subtest: should handle cycles in error
not ok 3 - should handle cycles in error
---
duration_ms: 0.057458
failureType: 'testCodeFailure'
error: 'b'
code: 'ERR_TEST_FAILURE'
stack: |-
TestContext.<anonymous> (file:///Users/pulkitgupta/Desktop/node/_test.mjs:12:17)
Test.runInAsyncScope (node:async_hooks:206:9)
async Test.processPendingSubtests (node:internal/test_runner/test:304:7) {
[cause]: Error: c
at TestContext.<anonymous> (file:///Users/pulkitgupta/Desktop/node/_test.mjs:13:17)
at Test.runInAsyncScope (node:async_hooks:206:9)
at async Test.processPendingSubtests (node:internal/test_runner/test:304:7) {
[cause]: Error: d
at TestContext.<anonymous> (file:///Users/pulkitgupta/Desktop/node/_test.mjs:13:41)
at Test.runInAsyncScope (node:async_hooks:206:9)
at async Test.processPendingSubtests (node:internal/test_runner/test:304:7) {
[cause]: [Circular *1]
}
}
}
...
1..3
# tests 3
# pass 0
# fail 3
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6.749875 |
| StringPrototypeReplaceAll, | ||
| StringPrototypeSplit, | ||
| StringPrototypeRepeat, | ||
| StringPrototypeStartsWith |
| const { inspectWithNoCustomRetry } = require('internal/errors'); | ||
| const { isError, kEmptyObject } = require('internal/util'); | ||
| const { relative } = require('path'); | ||
| const util = require('util'); |
There was a problem hiding this comment.
We should use internal/util/inspect.js I think. Also use destructuring to reduce the chance of things being tampered with.
| const frames = []; | ||
|
|
||
| if (cause?.cause) { | ||
| const errCause = util.inspect(cause).split('\n').slice(1) |
There was a problem hiding this comment.
split(), slice(), filter(), and join() all need to use primordials.
|
|
||
| if (cause?.cause) { | ||
| const errCause = util.inspect(cause).split('\n').slice(1) | ||
| .filter((line) => !StringPrototypeStartsWith(line.trim(), '...') && line) |
There was a problem hiding this comment.
It seems like the && line should be line &&. Also, I would check for typeof line === 'string'
test/message/test_runner_output.js
Outdated
| }); | ||
|
|
||
| test('should print error cause', () => { | ||
| throw new Error('foo', {cause: new Error('bar')}); |
There was a problem hiding this comment.
Please run make lint locally.
| * | ||
| * | ||
| * | ||
| * |
test/message/test_runner_output.js
Outdated
| }); | ||
|
|
||
| test('should print error cause', () => { | ||
| throw new Error('foo', {cause: new Error('bar')}); |
There was a problem hiding this comment.
Please add Error.stackTraceLimit = 0; to eliminate the stack traces from the output (and therefore the streams of asterisks) unless you’re intending to evaluate something in the stack trace. It’s annoying for tests to start failing because a refactoring changes the number of lines of a stack trace.
| * | ||
| * | ||
| * | ||
| * | ||
| * |
There was a problem hiding this comment.
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| failureType: 'multipleCallbackInvocations', | |
| cause: 'callback invoked multiple times', | |
| code: 'ERR_TEST_FAILURE' | |
| * |
| * | ||
| * | ||
| * | ||
| * | ||
| * |
There was a problem hiding this comment.
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| failureType: 'multipleCallbackInvocations', | |
| cause: 'callback invoked multiple times', | |
| code: 'ERR_TEST_FAILURE' | |
| * |
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * |
There was a problem hiding this comment.
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| [cause]: Error: bar | |
| * | |
| * | |
| * | |
| * | |
| * |
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * |
There was a problem hiding this comment.
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| [cause]: Error: b | |
| * | |
| * | |
| * | |
| * | |
| [cause]: Error: c | |
| * | |
| * | |
| * | |
| * | |
| [cause]: [Error] | |
| * | |
| * | |
| * |
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * |
There was a problem hiding this comment.
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| [cause]: Error: c | |
| * | |
| * | |
| * | |
| * | |
| [cause]: Error: d | |
| * | |
| * | |
| * | |
| * | |
| [cause]: [Circular *1] | |
| * | |
| * | |
| * |
| }); | ||
| }); | ||
|
|
||
| test('should print error cause', () => { |
There was a problem hiding this comment.
there are still some cases discussed here with no tests:
#46576 (comment)
- non error cause (primitives, undefined etc)
cause/stackthrows
There was a problem hiding this comment.
cause/stackthrows
How can I test this?
There was a problem hiding this comment.
cause/stackthrowsHow can I test this?
const error = new Error;
Reflect.defineProperty(error, 'cause', { get() { throw null } });
throw error;
test/message/test_runner_output.out
Outdated
| code: 'ERR_TEST_FAILURE' | ||
| stack: |- | ||
| * | ||
| * |
There was a problem hiding this comment.
same suggestions I maid apply to this file as well
| * | ||
| * | ||
|
|
||
| should print error cause (*ms) |
There was a problem hiding this comment.
same suggestions I made apply to this file as well
| // then try to unwrap the original error message and stack. | ||
| if (code === 'ERR_TEST_FAILURE' && kUnwrapErrors.has(failureType)) { | ||
| errStack = cause?.stack ?? errStack; | ||
| if (cause?.cause) { |
There was a problem hiding this comment.
| if (cause?.cause) { | |
| if (cause != null && ObjectPrototypeHasOwnProperty(cause, 'cause')) { |
Otherwise it wouldn't detect a falsy cause (e.g. null, undefined, false, empty string, etc.)
|
This has conflicts now and should be superseded by #47867. Closing. Thanks for the PR though. |
fix #44656
code:
before: test runner doesn't print
Errorcauseafter changes: