Skip to content

Commit 72bb444

Browse files
committed
assert: wrap original error in ifError
It is hard to know where ifError is actually triggered due to the original error being thrown. This changes it by wrapping the original error in a AssertionError. This has the positive effect of also making clear that it is indeed a assertion function that triggered that error. The original stack can still be accessed by checking the `actual` property. PR-URL: #18247 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 7a23fc0 commit 72bb444

File tree

9 files changed

+187
-32
lines changed

9 files changed

+187
-32
lines changed

doc/api/assert.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,11 @@ suppressFrame();
469469
## assert.ifError(value)
470470
<!-- YAML
471471
added: v0.1.97
472+
changes:
473+
- version: REPLACEME
474+
pr-url: https://github.com/nodejs/node/pull/18247
475+
description: Instead of throwing the original error it is now wrapped into
476+
a AssertionError that contains the full stack trace.
472477
-->
473478
* `value` {any}
474479

@@ -483,11 +488,11 @@ assert.ifError(null);
483488
assert.ifError(0);
484489
// OK
485490
assert.ifError(1);
486-
// Throws 1
491+
// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 1
487492
assert.ifError('error');
488-
// Throws 'error'
493+
// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 'error'
489494
assert.ifError(new Error());
490-
// Throws Error
495+
// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: Error
491496
```
492497

493498
## assert.notDeepEqual(actual, expected[, message])

lib/assert.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,53 @@ assert.doesNotThrow = function doesNotThrow(block, error, message) {
452452
throw actual;
453453
};
454454

455-
assert.ifError = function ifError(err) { if (err) throw err; };
455+
assert.ifError = function ifError(err) {
456+
if (err) {
457+
let message = 'ifError got unwanted exception: ';
458+
if (typeof err === 'object' && typeof err.message === 'string') {
459+
if (err.message.length === 0 && err.constructor) {
460+
message += err.constructor.name;
461+
} else {
462+
message += err.message;
463+
}
464+
} else {
465+
message += inspect(err);
466+
}
467+
468+
const newErr = new assert.AssertionError({
469+
actual: err,
470+
expected: null,
471+
operator: 'ifError',
472+
message,
473+
stackStartFn: ifError
474+
});
475+
476+
// Make sure we actually have a stack trace!
477+
const origStack = err.stack;
478+
479+
if (typeof origStack === 'string') {
480+
// This will remove any duplicated frames from the error frames taken
481+
// from within `ifError` and add the original error frames to the newly
482+
// created ones.
483+
const tmp2 = origStack.split('\n');
484+
tmp2.shift();
485+
// Filter all frames existing in err.stack.
486+
let tmp1 = newErr.stack.split('\n');
487+
for (var i = 0; i < tmp2.length; i++) {
488+
// Find the first occurrence of the frame.
489+
const pos = tmp1.indexOf(tmp2[i]);
490+
if (pos !== -1) {
491+
// Only keep new frames.
492+
tmp1 = tmp1.slice(0, pos);
493+
break;
494+
}
495+
}
496+
newErr.stack = `${tmp1.join('\n')}\n${tmp2.join('\n')}`;
497+
}
498+
499+
throw newErr;
500+
}
501+
};
456502

457503
// Expose a strict only variant of assert
458504
function strict(...args) {

test/fixtures/uncaught-exceptions/callbackify2.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ const { callbackify } = require('util');
88
{
99
const sentinel = new Error(__filename);
1010
process.once('uncaughtException', (err) => {
11-
assert.strictEqual(err, sentinel);
11+
assert.notStrictEqual(err, sentinel);
1212
// Calling test will use `stdout` to assert value of `err.message`
1313
console.log(err.message);
1414
});
1515

16-
async function fn() {
17-
return await Promise.reject(sentinel);
16+
function fn() {
17+
return Promise.reject(sentinel);
1818
}
1919

2020
const cbFn = callbackify(fn);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
6+
let err;
7+
// Create some random error frames.
8+
(function a() {
9+
(function b() {
10+
(function c() {
11+
err = new Error('test error');
12+
})();
13+
})();
14+
})();
15+
16+
(function x() {
17+
(function y() {
18+
(function z() {
19+
assert.ifError(err);
20+
})();
21+
})();
22+
})();
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
assert.js:*
2+
throw newErr;
3+
^
4+
5+
AssertionError [ERR_ASSERTION]: ifError got unwanted exception: test error
6+
at z (*/if-error-has-good-stack.js:*:*
7+
at y (*/if-error-has-good-stack.js:*:*)
8+
at x (*/if-error-has-good-stack.js:*:*)
9+
at Object.<anonymous> (*/if-error-has-good-stack.js:*:*)
10+
at c (*/if-error-has-good-stack.js:*:*)
11+
at b (*/if-error-has-good-stack.js:*:*)
12+
at a (*/if-error-has-good-stack.js:*:*)
13+
at Object.<anonymous> (*/if-error-has-good-stack.js:*:*)
14+
at Module._compile (module.js:*:*)
15+
at Object.Module._extensions..js (module.js:*:*)
16+
at Module.load (module.js:*:*)
17+
at tryModuleLoad (module.js:*:*)
18+
at Function.Module._load (module.js:*:*)
19+
at Function.Module.runMain (module.js:*:*)

test/parallel/test-assert-if-error.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert').strict;
5+
/* eslint-disable no-restricted-properties */
6+
7+
// Test that assert.ifError has the correct stack trace of both stacks.
8+
9+
let err;
10+
// Create some random error frames.
11+
(function a() {
12+
(function b() {
13+
(function c() {
14+
err = new Error('test error');
15+
})();
16+
})();
17+
})();
18+
19+
const msg = err.message;
20+
const stack = err.stack;
21+
22+
(function x() {
23+
(function y() {
24+
(function z() {
25+
let threw = false;
26+
try {
27+
assert.ifError(err);
28+
} catch (e) {
29+
assert.equal(e.message, 'ifError got unwanted exception: test error');
30+
assert.equal(err.message, msg);
31+
assert.equal(e.actual, err);
32+
assert.equal(e.actual.stack, stack);
33+
assert.equal(e.expected, null);
34+
assert.equal(e.operator, 'ifError');
35+
threw = true;
36+
}
37+
assert(threw);
38+
})();
39+
})();
40+
})();
41+
42+
assert.throws(
43+
() => assert.ifError(new TypeError()),
44+
{
45+
message: 'ifError got unwanted exception: TypeError'
46+
}
47+
);
48+
49+
assert.throws(
50+
() => assert.ifError({ stack: false }),
51+
{
52+
message: 'ifError got unwanted exception: { stack: false }'
53+
}
54+
);
55+
56+
assert.throws(
57+
() => assert.ifError({ constructor: null, message: '' }),
58+
{
59+
message: 'ifError got unwanted exception: '
60+
}
61+
);
62+
63+
assert.doesNotThrow(() => { assert.ifError(null); });
64+
assert.doesNotThrow(() => { assert.ifError(); });
65+
assert.doesNotThrow(() => { assert.ifError(undefined); });
66+
assert.doesNotThrow(() => { assert.ifError(false); });
67+
68+
// https://github.com/nodejs/node-v0.x-archive/issues/2893
69+
{
70+
let threw = false;
71+
try {
72+
// eslint-disable-next-line no-restricted-syntax
73+
assert.throws(() => {
74+
assert.ifError(null);
75+
});
76+
} catch (e) {
77+
threw = true;
78+
assert.strictEqual(e.message, 'Missing expected exception.');
79+
assert(!e.stack.includes('throws'), e.stack);
80+
}
81+
assert(threw);
82+
}

test/parallel/test-assert.js

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -452,11 +452,6 @@ assert.throws(makeBlock(thrower, TypeError));
452452
'a.doesNotThrow is not catching type matching errors');
453453
}
454454

455-
assert.throws(() => { assert.ifError(new Error('test error')); },
456-
/^Error: test error$/);
457-
assert.doesNotThrow(() => { assert.ifError(null); });
458-
assert.doesNotThrow(() => { assert.ifError(); });
459-
460455
common.expectsError(
461456
() => assert.doesNotThrow(makeBlock(thrower, Error), 'user message'),
462457
{
@@ -602,22 +597,6 @@ testAssertionMessage({ a: undefined, b: null }, '{ a: undefined, b: null }');
602597
testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity },
603598
'{ a: NaN, b: Infinity, c: -Infinity }');
604599

605-
// https://github.com/nodejs/node-v0.x-archive/issues/2893
606-
{
607-
let threw = false;
608-
try {
609-
// eslint-disable-next-line no-restricted-syntax
610-
assert.throws(() => {
611-
assert.ifError(null);
612-
});
613-
} catch (e) {
614-
threw = true;
615-
assert.strictEqual(e.message, 'Missing expected exception.');
616-
assert.ok(!e.stack.includes('throws'), e.stack);
617-
}
618-
assert.ok(threw);
619-
}
620-
621600
// https://github.com/nodejs/node-v0.x-archive/issues/5292
622601
try {
623602
assert.strictEqual(1, 2);

test/parallel/test-domain-implicit-fs.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ d.on('error', common.mustCall(function(er) {
3434
assert.strictEqual(er.domain, d);
3535
assert.strictEqual(er.domainThrown, true);
3636
assert.ok(!er.domainEmitter);
37-
assert.strictEqual(er.code, 'ENOENT');
38-
assert.ok(/\bthis file does not exist\b/i.test(er.path));
39-
assert.strictEqual(typeof er.errno, 'number');
37+
assert.strictEqual(er.actual.code, 'ENOENT');
38+
assert.ok(/\bthis file does not exist\b/i.test(er.actual.path));
39+
assert.strictEqual(typeof er.actual.errno, 'number');
4040
}));
4141

4242

test/parallel/test-util-callbackify.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,9 @@ const values = [
220220
[fixture],
221221
common.mustCall((err, stdout, stderr) => {
222222
assert.ifError(err);
223-
assert.strictEqual(stdout.trim(), fixture);
223+
assert.strictEqual(
224+
stdout.trim(),
225+
`ifError got unwanted exception: ${fixture}`);
224226
assert.strictEqual(stderr, '');
225227
})
226228
);

0 commit comments

Comments
 (0)