Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 62 additions & 26 deletions lib/reporters/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,32 @@
});

runner.on(EVENT_TEST_FAIL, function (test, err) {
if (showDiff(err)) {
stringifyDiffObjs(err);
try {
if (showDiff(err)) {
stringifyDiffObjs(err);
}
// more than one error per test
if (test.err && err instanceof Error) {
test.err.multiple = (test.err.multiple || []).concat(err);
} else {
test.err = err;
}
failures.push(test);
} catch (listenerErr) {
try {
process.stderr.write(
"\n[mocha] reporter error while handling test failure: " +
(listenerErr && listenerErr.stack
? listenerErr.stack
: String(listenerErr)) +
"\n",
);
} catch {}

Check failure on line 106 in lib/reporters/base.js

View workflow job for this annotation

GitHub Actions / lint / lint

Empty block statement

Check failure on line 106 in lib/reporters/base.js

View workflow job for this annotation

GitHub Actions / lint / lint

Empty block statement
try {
if (!test.err) test.err = err;
failures.push(test);
} catch {}

Check failure on line 110 in lib/reporters/base.js

View workflow job for this annotation

GitHub Actions / lint / lint

Empty block statement

Check failure on line 110 in lib/reporters/base.js

View workflow job for this annotation

GitHub Actions / lint / lint

Empty block statement
}
// more than one error per test
if (test.err && err instanceof Error) {
test.err.multiple = (test.err.multiple || []).concat(err);
} else {
test.err = err;
}
failures.push(test);
});
}

Expand Down Expand Up @@ -331,23 +347,28 @@
if (!utils.isString(err.actual) || !utils.isString(err.expected)) {
// Estimate size before stringifying to avoid hangs
const maxSafeSize = exports.maxDiffSize || 8192;
const actualSize = estimateSize(err.actual, 10);
const expectedSize = estimateSize(err.expected, 10);

if (
actualSize === -1 ||
expectedSize === -1 ||
actualSize > maxSafeSize ||
expectedSize > maxSafeSize
) {
// Values too large/complex - provide safe fallback
err.actual = "[object too large to diff]";
err.expected = "[object too large to diff]";
return;
}
try {
const actualSize = estimateSize(err.actual, 10);
const expectedSize = estimateSize(err.expected, 10);

if (
actualSize === -1 ||
expectedSize === -1 ||
actualSize > maxSafeSize ||
expectedSize > maxSafeSize
) {
// Values too large/complex - provide safe fallback
err.actual = "[object too large to diff]";
err.expected = "[object too large to diff]";
return;
}

err.actual = utils.stringify(err.actual);
err.expected = utils.stringify(err.expected);
err.actual = utils.stringify(err.actual);
err.expected = utils.stringify(err.expected);
} catch {
err.actual = "[object could not be stringified]";
err.expected = "[object could not be stringified]";
}
}
}

Expand Down Expand Up @@ -456,6 +477,21 @@
var multipleErr, multipleTest;
Base.consoleLog();
failures.forEach(function (test, i) {
try {
renderOneFailure(test, i);
} catch (renderErr) {
try {
Base.consoleLog(
" %s) %s:\n [mocha] failed to render error: %s",
i + 1,
test && test.titlePath ? test.titlePath().join(" ") : "<unknown>",
renderErr && renderErr.message ? renderErr.message : renderErr,
);
} catch {}

Check failure on line 490 in lib/reporters/base.js

View workflow job for this annotation

GitHub Actions / lint / lint

Empty block statement

Check failure on line 490 in lib/reporters/base.js

View workflow job for this annotation

GitHub Actions / lint / lint

Empty block statement
}
});

function renderOneFailure(test, i) {
// format
var fmt =
color("error title", " %s) %s:\n") +
Expand Down Expand Up @@ -507,7 +543,7 @@
});

Base.consoleLog(fmt, i + 1, testTitle, msg, stack);
});
}
};

/**
Expand Down
25 changes: 25 additions & 0 deletions test/integration/fixtures/reporters/listener-throws.fixture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use strict";

const assert = require("node:assert");

it("listener-throws", () => {
const poison = new Proxy(
{},
{
ownKeys() {
throw new TypeError("poisoned ownKeys");
},
getOwnPropertyDescriptor() {
throw new TypeError("poisoned descriptor");
},
get() {
throw new TypeError("poisoned get");
},
},
);
throw new assert.AssertionError({
actual: poison,
expected: { a: 1 },
message: "boom",
});
});
34 changes: 34 additions & 0 deletions test/integration/reporter-crash.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"use strict";

const { invokeMochaAsync, resolveFixturePath } = require("./helpers");

describe("reporter resilience", function () {
this.timeout(10000);

async function runFixture() {
const [, promise] = invokeMochaAsync(
[resolveFixturePath("reporters/listener-throws")],
{ stdio: "pipe" },
);
return promise;
}

it("should exit non-zero when a Base listener throws during EVENT_TEST_FAIL", async function () {
const res = await runFixture();
expect(res, "to have failed");
});

it("should log a reporter-error message to stderr", async function () {
const res = await runFixture();
expect(
res.output,
"to match",
/\[mocha\] (reporter error|failed to render)/,
);
});

it("should still report the failing test in the epilougue", async function () {
const res = await runFixture();
expect(res.output, "to match", /1 failing/);
});
});
51 changes: 51 additions & 0 deletions test/reporters/base.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,57 @@ describe("Base reporter", function () {
});
});

describe("reporter resilience", function () {
// "resilience" sounds dramatic or....dramatic.
it("should not throw out of Base.list when err.actual triggers a throw in showDiff", function () {
var poison = new Proxy(
{},
{
ownKeys: function () {
throw new TypeError("poisoned ownKeys");
},
getOwnPropertyDescriptor: function () {
throw new TypeError("poisoned descriptor");
},
get: function () {
throw new TypeError("poisoned get");
},
},
);
var err = new AssertionError({
actual: poison,
expected: { a: 1 },
message: "boom",
});
var test = makeTest(err);

list([test]);

var errOut = stdout.join("\n");
expect(errOut, "to contain", "test title");
});

it("should swallow errors thrown by stringifyDiffObjs and substitute a placeholder", function () {
var utils = require("../../lib/utils");
sinon
.stub(utils, "stringify")
.throws(new Error("simulated stringify failure"));
try {
var err = new AssertionError({
actual: { a: 1 },
expected: { a: 2 },
message: "boom",
});
var test = makeTest(err);
list([test]);
var errOut = stdout.join("\n");
expect(errOut, "to contain", "test title");
} finally {
sinon.restore();
}
});
});

it("should list multiple Errors per test", function () {
var err = new Error("First Error");
err.multiple = [new Error("Second Error - same test")];
Expand Down
Loading