-
- Notifications
You must be signed in to change notification settings - Fork 33.8k
test: fix test runner does not print error cause #46576
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
Conversation
| Review requested:
|
| } | ||
| | ||
| function hasInnerCause(value) { | ||
| return value?.cause?.cause?.stack ? true : false; |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps some of these lines (that are not a stack trace) should not be an asterisk
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 agree with @MoLow here.
| 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 |
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.
Please keep these sorted.
| 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please run make lint locally.
| * | ||
| * | ||
| * | ||
| * |
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 agree with @MoLow here.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| failureType: 'multipleCallbackInvocations', | |
| cause: 'callback invoked multiple times', | |
| code: 'ERR_TEST_FAILURE' | |
| * |
| * | ||
| * | ||
| * | ||
| * | ||
| * |
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.
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| failureType: 'multipleCallbackInvocations', | |
| cause: 'callback invoked multiple times', | |
| code: 'ERR_TEST_FAILURE' | |
| * |
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * |
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.
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| [cause]: Error: bar | |
| * | |
| * | |
| * | |
| * | |
| * |
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * | ||
| * |
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.
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| [cause]: Error: b | |
| * | |
| * | |
| * | |
| * | |
| [cause]: Error: c | |
| * | |
| * | |
| * | |
| * | |
| [cause]: [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.
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| * | |
| [cause]: Error: c | |
| * | |
| * | |
| * | |
| * | |
| [cause]: Error: d | |
| * | |
| * | |
| * | |
| * | |
| [cause]: [Circular *1] | |
| * | |
| * | |
| * |
| }); | ||
| }); | ||
| | ||
| test('should print error cause', () => { |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cause/stackthrows
How can I test this?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same suggestions I maid apply to this file as well
| * | ||
| * | ||
| | ||
| should print error cause (*ms) |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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: