Skip to content

Commit

Permalink
feat: add support for aggregateerror in stack trace parser
Browse files Browse the repository at this point in the history
  • Loading branch information
galargh committed Sep 3, 2024
1 parent b014f91 commit 22b2f5c
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 131 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@

1) error with circular cause in top level test

2) aggregate error in top level test

0 passing (547ms)
1 failing

0 passing (534ms)
2 failing

1) error with circular cause in top level test:

Expand All @@ -14,5 +16,16 @@
[cause]: Error: circular error
 at TestContext.<anonymous> (integration-tests/fixture-tests/errors-test/test.ts:4:17)
[cause]: The error chain has been truncated because it's too long (limit: 3)
 

2) aggregate error in top level test:

AggregateError: All promises were rejected
[inner]: Error: Promise 1 failed
 at TestContext.<anonymous> (integration-tests/fixture-tests/errors-test/test.ts:11:5)
[cause]: Error: Promise 1 cause
 at TestContext.<anonymous> (integration-tests/fixture-tests/errors-test/test.ts:12:14)
[cause]: AggregateError
 at TestContext.<anonymous> (integration-tests/fixture-tests/errors-test/test.ts:13:16)
[inner]: Error: Promise 2 failed
 at TestContext.<anonymous> (integration-tests/fixture-tests/errors-test/test.ts:17:35)

Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,16 @@ test("error with circular cause in top level test", async () => {
error.cause = error;
throw error;
});

test("aggregate error in top level test", async () => {
const promise1 = Promise.reject(
new Error("Promise 1 failed", {
cause: new Error("Promise 1 cause", {
cause: new AggregateError([new Error("A"), new Error("B")]),
}),
}),
);
const promise2 = Promise.reject(new Error("Promise 2 failed"));

return Promise.any([promise1, promise2]);
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
7) level 0


3 passing (134ms)
3 passing (135ms)
7 failing

1) level 0
Expand Down Expand Up @@ -102,5 +102,4 @@
level 0:

Error [ERR_TEST_FAILURE]: 1 subtest failed
 

2 changes: 2 additions & 0 deletions v-next/hardhat-node-test-reporter/integration-tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const entries = readdirSync("integration-tests/fixture-tests").filter(
},
);

process.setMaxListeners(entries.length)

for (const entry of entries) {
const entryPath = `integration-tests/fixture-tests/${entry}`;

Expand Down
53 changes: 40 additions & 13 deletions v-next/hardhat-node-test-reporter/src/error-formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { diff as getDiff } from "jest-diff";
import { indent } from "./formatting.js";
import {
cleanupTestFailError,
getErrorChain,
isCancelledByParentError,
isTestFileExecutionFailureError,
} from "./node-test-error-utils.js";
Expand Down Expand Up @@ -43,16 +42,14 @@ export function formatError(error: Error): string {

error = cleanupTestFailError(error);

const errorChain = getErrorChain(error);

const messages = errorChain
.map((message, index) => formatSingleError(message, index !== 0))
.map((message, index) => indent(message, index * 2));

return messages.join("\n");
return formatSingleError(error);
}

function formatSingleError(error: Error, isCause: boolean = false): string {
function formatSingleError(
error: Error,
prefix: string = "",
depth: number = 0,
): string {
const stackLines = (error.stack ?? "").split("\n");

let message = error.message.split("\n")[0];
Expand All @@ -61,8 +58,8 @@ function formatSingleError(error: Error, isCause: boolean = false): string {
}
message = message.replace(" [ERR_ASSERTION]", "").replace(/:$/, "");

if (isCause) {
message = `[cause]: ${message}`;
if (prefix !== "") {
message = `[${prefix}]: ${message}`;
}

const diff = getErrorDiff(error);
Expand All @@ -89,15 +86,45 @@ function formatSingleError(error: Error, isCause: boolean = false): string {

const stack = stackReferences.map(formatStackReference).join("\n");

let formattedError = isCause ? chalk.grey(message) : chalk.red(message);
let formattedError = depth === 0 ? chalk.red(message) : chalk.grey(message);
if (diff !== undefined) {
formattedError += `\n${diff}\n`;
}
formattedError += `\n${chalk.gray(indent(stack, 4))}`;
if (stack !== "") {
formattedError += `\n${chalk.gray(indent(stack, 4))}`;
}

if (isAggregateError(error)) {
// Only the first aggregate error in a chain survives serialization
// This is why we can safely not increase the depth here
const formattedErrors = error.errors
.map((e) => indent(formatSingleError(e, "inner", depth), 2))
.join("\n");
return `${formattedError}\n${formattedErrors}`;
}

if (error.cause instanceof Error) {
let cause = error.cause;
if (depth + 1 >= 3) {
cause = new Error(
"The error chain has been truncated because it's too long (limit: 3)",
);
cause.stack = undefined;
}
const formattedCause = indent(
formatSingleError(cause, "cause", depth + 1),
2,
);
return `${formattedError}\n${formattedCause}`;
}

return formattedError;
}

function isAggregateError(error: Error): error is Error & { errors: Error[] } {
return "errors" in error && Array.isArray(error.errors);
}

function isDiffableError(
error: Error,
): error is Error & { actual: any; expected: any } {
Expand Down
27 changes: 0 additions & 27 deletions v-next/hardhat-node-test-reporter/src/node-test-error-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,30 +57,3 @@ export function cleanupTestFailError(error: Error): Error {

return error;
}

/**
* Turns an error into an array of errors, starting from the error itself and
* following with its causes.
*/
export function getErrorChain(error: Error, limit: number = 3): Error[] {
const errorChain = [error];
let previousError = error;

while (previousError.cause instanceof Error) {
// If we reach the limit, we add a placeholder error to indicate that the
// chain has been truncated.
if (errorChain.length >= limit) {
const err = new Error(
`The error chain has been truncated because it's too long (limit: ${limit})`,
);
// We remove the stack not to expose reporter internals
err.stack = undefined;
errorChain.push(err);
break;
}
errorChain.push(previousError.cause);
previousError = previousError.cause;
}

return errorChain;
}
38 changes: 0 additions & 38 deletions v-next/hardhat-node-test-reporter/test/node-test-error-utils.ts

This file was deleted.

0 comments on commit 22b2f5c

Please sign in to comment.