Skip to content

Conversation

@pulkit-30
Copy link
Contributor

fix #44656

code:

import test from 'node:test'; test('should print error cause', () => { throw new Error('foo', {cause: new Error('bar')}); }); 

before: test runner doesn't print Error cause

TAP version 13 # Subtest: should print error cause not ok 1 - should print error cause --- duration_ms: 0.818167 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) Test.run (node:internal/test_runner/test:549:25) Test.start (node:internal/test_runner/test:465:17) test (node:internal/test_runner/harness:171:18) file:///Users/pulkitgupta/Desktop/node/_test.mjs:3:1 ModuleJob.run (node:internal/modules/esm/module_job:193:25) ... 1..1 # tests 1 # pass 0 # fail 1 # cancelled 0 # skipped 0 # todo 0 # duration_ms 5.839667 

after changes:

TAP version 13 # Subtest: should print error cause not ok 1 - should print error cause --- duration_ms: 0.648959 failureType: 'testCodeFailure' error: 'foo' code: 'ERR_TEST_FAILURE' stack: |- Error: foo TestContext.<anonymous> (file:///Users/pulkitgupta/Desktop/node/_test.mjs:4:9) Test.runInAsyncScope (node:async_hooks:206:9) ... 4 lines matching cause stack trace ... 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) } ... 1..1 # tests 1 # pass 0 # fail 1 # cancelled 0 # skipped 0 # todo 0 # duration_ms 5.896167
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/test_runner
@nodejs-github-bot nodejs-github-bot added dont-land-on-v14.x needs-ci PRs that need a full CI run. test_runner Issues and PRs related to the test runner subsystem. labels Feb 8, 2023
}

function hasInnerCause(value) {
return value?.cause?.cause?.stack ? true : false;
Copy link
Member

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?

Copy link
Contributor

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') }) }) }) })
Copy link
Member

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 Error with .cause with a getter that throws?
  • What if .stack throws?
Copy link
Member

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.

@MoLow
Copy link
Member

MoLow commented Feb 9, 2023

please also make sure (with a test?) that the cause is shown both when running with --test and without (the TAP & yaml are serialized and deserialized when running with --test)

*
*
*
*
Copy link
Member

@MoLow MoLow Feb 9, 2023

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

Copy link
Contributor

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.

@pulkit-30
Copy link
Contributor Author

Thanks for reviews @cjihrig @ljharb @benjamingr @MoLow,
I will update PR with required changes ASAP 🚀
also looking at failing tests

@pulkit-30
Copy link
Contributor Author

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

@ljharb @cjihrig

StringPrototypeReplaceAll,
StringPrototypeSplit,
StringPrototypeRepeat,
StringPrototypeStartsWith
Copy link
Contributor

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');
Copy link
Contributor

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)
Copy link
Contributor

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)
Copy link
Contributor

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('should print error cause', () => {
throw new Error('foo', {cause: new Error('bar')});
Copy link
Contributor

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.

*
*
*
*
Copy link
Contributor

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('should print error cause', () => {
throw new Error('foo', {cause: new Error('bar')});
Copy link
Member

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.

@pulkit-30 pulkit-30 requested review from MoLow and cjihrig and removed request for cjihrig February 14, 2023 16:59
Comment on lines 405 to 409
*
*
*
*
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*
*
*
*
*
*
failureType: 'multipleCallbackInvocations',
cause: 'callback invoked multiple times',
code: 'ERR_TEST_FAILURE'
*
Comment on lines 450 to 454
*
*
*
*
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*
*
*
*
*
*
failureType: 'multipleCallbackInvocations',
cause: 'callback invoked multiple times',
code: 'ERR_TEST_FAILURE'
*
Comment on lines 636 to 535
*
*
*
*
*
*
*
*
*
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*
*
*
*
*
*
*
*
*
*
*
*
*
*
[cause]: Error: bar
*
*
*
*
*
Comment on lines 655 to 672
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
[cause]: Error: b
*
*
*
*
[cause]: Error: c
*
*
*
*
[cause]: [Error]
*
*
*
Comment on lines 682 to 685
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
[cause]: Error: c
*
*
*
*
[cause]: Error: d
*
*
*
*
[cause]: [Circular *1]
*
*
*
});
});

test('should print error cause', () => {
Copy link
Member

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/stack throws
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cause/stack throws

How can I test this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cause/stack throws

How can I test this?

const error = new Error; Reflect.defineProperty(error, 'cause', { get() { throw null } }); throw error;
code: 'ERR_TEST_FAILURE'
stack: |-
*
*
Copy link
Member

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)
Copy link
Member

@MoLow MoLow Feb 14, 2023

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.)

@cjihrig
Copy link
Contributor

cjihrig commented May 10, 2023

This has conflicts now and should be superseded by #47867. Closing. Thanks for the PR though.

@cjihrig cjihrig closed this May 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci PRs that need a full CI run. test_runner Issues and PRs related to the test runner subsystem.

9 participants