Skip to content

Commit 7f54071

Browse files
BridgeARoyyd
authored andcommitted
assert: improve loose assertion message
So far the error message from a loose assertion is not always very informative and could be misleading. This is fixed by: * showing more from the actual error message * having a better error description * not using custom inspection * inspecting a higher depth * inspecting the full array instead of up to 100 entries PR-URL: nodejs#22155 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent f75e969 commit 7f54071

2 files changed

Lines changed: 88 additions & 110 deletions

File tree

lib/internal/assert.js

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ let white = '';
1212

1313
const kReadableOperator = {
1414
deepStrictEqual: 'Expected inputs to be strictly deep-equal:',
15-
notDeepStrictEqual: 'Expected "actual" not to be strictly deep-equal to:',
1615
strictEqual: 'Expected inputs to be strictly equal:',
16+
deepEqual: 'Expected inputs to be loosely deep-equal:',
17+
equal: 'Expected inputs to be loosely equal:',
18+
notDeepStrictEqual: 'Expected "actual" not to be strictly deep-equal to:',
1719
notStrictEqual: 'Expected "actual" to be strictly unequal to:',
20+
notDeepEqual: 'Expected "actual" not to be loosely deep-equal to:',
21+
notEqual: 'Expected "actual" to be loosely unequal to:',
1822
notIdentical: 'Inputs identical but not reference equal:',
1923
};
2024

@@ -50,7 +54,7 @@ function inspectValue(val) {
5054
// Assert does not detect proxies currently.
5155
showProxy: false
5256
}
53-
).split('\n');
57+
);
5458
}
5559

5660
function createErrDiff(actual, expected, operator) {
@@ -59,8 +63,9 @@ function createErrDiff(actual, expected, operator) {
5963
let lastPos = 0;
6064
let end = '';
6165
let skipped = false;
62-
const actualLines = inspectValue(actual);
63-
const expectedLines = inspectValue(expected);
66+
const actualInspected = inspectValue(actual);
67+
const actualLines = actualInspected.split('\n');
68+
const expectedLines = inspectValue(expected).split('\n');
6469
const msg = kReadableOperator[operator] +
6570
`\n${green}+ actual${white} ${red}- expected${white}`;
6671
const skippedMsg = ` ${blue}...${white} Lines skipped`;
@@ -126,7 +131,7 @@ function createErrDiff(actual, expected, operator) {
126131
// E.g., assert.deepStrictEqual({ a: Symbol() }, { a: Symbol() })
127132
if (maxLines === 0) {
128133
// We have to get the result again. The lines were all removed before.
129-
const actualLines = inspectValue(actual);
134+
const actualLines = actualInspected.split('\n');
130135

131136
// Only remove lines in case it makes sense to collapse those.
132137
// TODO: Accept env to always show the full error.
@@ -268,7 +273,7 @@ class AssertionError extends Error {
268273
operator === 'notStrictEqual') {
269274
// In case the objects are equal but the operator requires unequal, show
270275
// the first object and say A equals B
271-
const res = inspectValue(actual);
276+
const res = inspectValue(actual).split('\n');
272277
const base = kReadableOperator[operator];
273278

274279
// Only remove lines in case it makes sense to collapse those.
@@ -287,13 +292,29 @@ class AssertionError extends Error {
287292
super(`${base}\n\n${res.join('\n')}\n`);
288293
}
289294
} else {
290-
let res = inspect(actual);
291-
let other = inspect(expected);
292-
if (res.length > 128)
293-
res = `${res.slice(0, 125)}...`;
294-
if (other.length > 128)
295-
other = `${other.slice(0, 125)}...`;
296-
super(`${res} ${operator} ${other}`);
295+
let res = inspectValue(actual);
296+
let other = '';
297+
const knownOperators = kReadableOperator[operator];
298+
if (operator === 'notDeepEqual' || operator === 'notEqual') {
299+
res = `${kReadableOperator[operator]}\n\n${res}`;
300+
if (res.length > 1024) {
301+
res = `${res.slice(0, 1021)}...`;
302+
}
303+
} else {
304+
other = `${inspectValue(expected)}`;
305+
if (res.length > 512) {
306+
res = `${res.slice(0, 509)}...`;
307+
}
308+
if (other.length > 512) {
309+
other = `${other.slice(0, 509)}...`;
310+
}
311+
if (operator === 'deepEqual' || operator === 'equal') {
312+
res = `${knownOperators}\n\n${res}\n\nshould equal\n\n`;
313+
} else {
314+
other = ` ${operator} ${other}`;
315+
}
316+
}
317+
super(`${res}${other}`);
297318
}
298319
}
299320

test/parallel/test-assert-deep.js

Lines changed: 54 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const common = require('../common');
3+
require('../common');
44
const assert = require('assert');
55
const util = require('util');
66
const { AssertionError } = assert;
@@ -16,18 +16,23 @@ if (process.stdout.isTTY)
1616
// Template tag function turning an error message into a RegExp
1717
// for assert.throws()
1818
function re(literals, ...values) {
19-
let result = literals[0];
20-
const escapeRE = /[\\^$.*+?()[\]{}|=!<>:-]/g;
19+
let result = 'Expected inputs to be loosely deep-equal:\n\n';
2120
for (const [i, value] of values.entries()) {
22-
const str = util.inspect(value);
21+
const str = util.inspect(value, {
22+
compact: false,
23+
depth: 1000,
24+
customInspect: false,
25+
maxArrayLength: Infinity,
26+
breakLength: Infinity
27+
});
2328
// Need to escape special characters.
24-
result += str.replace(escapeRE, '\\$&');
29+
result += str;
2530
result += literals[i + 1];
2631
}
27-
return common.expectsError({
32+
return {
2833
code: 'ERR_ASSERTION',
29-
message: new RegExp(`^${result}$`)
30-
});
34+
message: result
35+
};
3136
}
3237

3338
// The following deepEqual tests might seem very weird.
@@ -173,13 +178,6 @@ assert.throws(
173178
}
174179
}
175180

176-
common.expectsError(() => {
177-
assert.deepEqual(new Set([{ a: 0 }]), new Set([{ a: 1 }]));
178-
}, {
179-
code: 'ERR_ASSERTION',
180-
message: /^Set { { a: 0 } } deepEqual Set { { a: 1 } }$/
181-
});
182-
183181
function assertDeepAndStrictEqual(a, b) {
184182
assert.deepEqual(a, b);
185183
assert.deepStrictEqual(a, b);
@@ -189,13 +187,19 @@ function assertDeepAndStrictEqual(a, b) {
189187
}
190188

191189
function assertNotDeepOrStrict(a, b, err) {
192-
assert.throws(() => assert.deepEqual(a, b), err || re`${a} deepEqual ${b}`);
190+
assert.throws(
191+
() => assert.deepEqual(a, b),
192+
err || re`${a}\n\nshould equal\n\n${b}`
193+
);
193194
assert.throws(
194195
() => assert.deepStrictEqual(a, b),
195196
err || { code: 'ERR_ASSERTION' }
196197
);
197198

198-
assert.throws(() => assert.deepEqual(b, a), err || re`${b} deepEqual ${a}`);
199+
assert.throws(
200+
() => assert.deepEqual(b, a),
201+
err || re`${b}\n\nshould equal\n\n${a}`
202+
);
199203
assert.throws(
200204
() => assert.deepStrictEqual(b, a),
201205
err || { code: 'ERR_ASSERTION' }
@@ -225,6 +229,7 @@ assertNotDeepOrStrict(new Set([1, 2, 3]), new Set([1, 2, 3, 4]));
225229
assertNotDeepOrStrict(new Set([1, 2, 3, 4]), new Set([1, 2, 3]));
226230
assertDeepAndStrictEqual(new Set(['1', '2', '3']), new Set(['1', '2', '3']));
227231
assertDeepAndStrictEqual(new Set([[1, 2], [3, 4]]), new Set([[3, 4], [1, 2]]));
232+
assertNotDeepOrStrict(new Set([{ a: 0 }]), new Set([{ a: 1 }]));
228233

229234
{
230235
const a = [ 1, 2 ];
@@ -634,41 +639,16 @@ assert.throws(
634639

635640
assert.notDeepEqual(new Date(), new Date(2000, 3, 14));
636641

637-
assert.deepEqual(/a/, /a/);
638-
assert.deepEqual(/a/g, /a/g);
639-
assert.deepEqual(/a/i, /a/i);
640-
assert.deepEqual(/a/m, /a/m);
641-
assert.deepEqual(/a/igm, /a/igm);
642-
assert.throws(() => assert.deepEqual(/ab/, /a/),
643-
{
644-
code: 'ERR_ASSERTION',
645-
name: 'AssertionError [ERR_ASSERTION]',
646-
message: '/ab/ deepEqual /a/'
647-
});
648-
assert.throws(() => assert.deepEqual(/a/g, /a/),
649-
{
650-
code: 'ERR_ASSERTION',
651-
name: 'AssertionError [ERR_ASSERTION]',
652-
message: '/a/g deepEqual /a/'
653-
});
654-
assert.throws(() => assert.deepEqual(/a/i, /a/),
655-
{
656-
code: 'ERR_ASSERTION',
657-
name: 'AssertionError [ERR_ASSERTION]',
658-
message: '/a/i deepEqual /a/'
659-
});
660-
assert.throws(() => assert.deepEqual(/a/m, /a/),
661-
{
662-
code: 'ERR_ASSERTION',
663-
name: 'AssertionError [ERR_ASSERTION]',
664-
message: '/a/m deepEqual /a/'
665-
});
666-
assert.throws(() => assert.deepEqual(/a/igm, /a/im),
667-
{
668-
code: 'ERR_ASSERTION',
669-
name: 'AssertionError [ERR_ASSERTION]',
670-
message: '/a/gim deepEqual /a/im'
671-
});
642+
assertDeepAndStrictEqual(/a/, /a/);
643+
assertDeepAndStrictEqual(/a/g, /a/g);
644+
assertDeepAndStrictEqual(/a/i, /a/i);
645+
assertDeepAndStrictEqual(/a/m, /a/m);
646+
assertDeepAndStrictEqual(/a/igm, /a/igm);
647+
assertNotDeepOrStrict(/ab/, /a/);
648+
assertNotDeepOrStrict(/a/g, /a/);
649+
assertNotDeepOrStrict(/a/i, /a/);
650+
assertNotDeepOrStrict(/a/m, /a/);
651+
assertNotDeepOrStrict(/a/igm, /a/im);
672652

673653
{
674654
const re1 = /a/g;
@@ -728,23 +708,32 @@ nameBuilder2.prototype = Object;
728708
nb2 = new nameBuilder2('Ryan', 'Dahl');
729709
assert.deepEqual(nb1, nb2);
730710

731-
// Primitives and object.
732-
assert.throws(() => assert.deepEqual(null, {}), AssertionError);
733-
assert.throws(() => assert.deepEqual(undefined, {}), AssertionError);
734-
assert.throws(() => assert.deepEqual('a', ['a']), AssertionError);
735-
assert.throws(() => assert.deepEqual('a', { 0: 'a' }), AssertionError);
736-
assert.throws(() => assert.deepEqual(1, {}), AssertionError);
737-
assert.throws(() => assert.deepEqual(true, {}), AssertionError);
738-
assert.throws(() => assert.deepEqual(Symbol(), {}), AssertionError);
711+
// Primitives
712+
assertNotDeepOrStrict(null, {});
713+
assertNotDeepOrStrict(undefined, {});
714+
assertNotDeepOrStrict('a', ['a']);
715+
assertNotDeepOrStrict('a', { 0: 'a' });
716+
assertNotDeepOrStrict(1, {});
717+
assertNotDeepOrStrict(true, {});
718+
assertNotDeepOrStrict(Symbol(), {});
719+
assertNotDeepOrStrict(Symbol(), Symbol());
720+
721+
assertOnlyDeepEqual(4, '4');
722+
assertOnlyDeepEqual(true, 1);
723+
724+
{
725+
const s = Symbol();
726+
assertDeepAndStrictEqual(s, s);
727+
}
739728

740729
// Primitive wrappers and object.
741-
assert.deepEqual(new String('a'), ['a']);
742-
assert.deepEqual(new String('a'), { 0: 'a' });
743-
assert.deepEqual(new Number(1), {});
744-
assert.deepEqual(new Boolean(true), {});
730+
assertOnlyDeepEqual(new String('a'), ['a']);
731+
assertOnlyDeepEqual(new String('a'), { 0: 'a' });
732+
assertOnlyDeepEqual(new Number(1), {});
733+
assertOnlyDeepEqual(new Boolean(true), {});
745734

746735
// Same number of keys but different key names.
747-
assert.throws(() => assert.deepEqual({ a: 1 }, { b: 1 }), AssertionError);
736+
assertNotDeepOrStrict({ a: 1 }, { b: 1 });
748737

749738
assert.deepStrictEqual(new Date(2000, 3, 14), new Date(2000, 3, 14));
750739

@@ -765,11 +754,6 @@ assert.throws(
765754

766755
assert.notDeepStrictEqual(new Date(), new Date(2000, 3, 14));
767756

768-
assert.deepStrictEqual(/a/, /a/);
769-
assert.deepStrictEqual(/a/g, /a/g);
770-
assert.deepStrictEqual(/a/i, /a/i);
771-
assert.deepStrictEqual(/a/m, /a/m);
772-
assert.deepStrictEqual(/a/igm, /a/igm);
773757
assert.throws(
774758
() => assert.deepStrictEqual(/ab/, /a/),
775759
{
@@ -879,33 +863,6 @@ obj2 = new Constructor2('Ryan', 'Dahl');
879863

880864
assert.deepStrictEqual(obj1, obj2);
881865

882-
// primitives
883-
assert.throws(() => assert.deepStrictEqual(4, '4'), AssertionError);
884-
assert.throws(() => assert.deepStrictEqual(true, 1), AssertionError);
885-
assert.throws(() => assert.deepStrictEqual(Symbol(), Symbol()),
886-
AssertionError);
887-
888-
const s = Symbol();
889-
assert.deepStrictEqual(s, s);
890-
891-
// Primitives and object.
892-
assert.throws(() => assert.deepStrictEqual(null, {}), AssertionError);
893-
assert.throws(() => assert.deepStrictEqual(undefined, {}), AssertionError);
894-
assert.throws(() => assert.deepStrictEqual('a', ['a']), AssertionError);
895-
assert.throws(() => assert.deepStrictEqual('a', { 0: 'a' }), AssertionError);
896-
assert.throws(() => assert.deepStrictEqual(1, {}), AssertionError);
897-
assert.throws(() => assert.deepStrictEqual(true, {}), AssertionError);
898-
assert.throws(() => assert.deepStrictEqual(Symbol(), {}), AssertionError);
899-
900-
// Primitive wrappers and object.
901-
assert.throws(() => assert.deepStrictEqual(new String('a'), ['a']),
902-
AssertionError);
903-
assert.throws(() => assert.deepStrictEqual(new String('a'), { 0: 'a' }),
904-
AssertionError);
905-
assert.throws(() => assert.deepStrictEqual(new Number(1), {}), AssertionError);
906-
assert.throws(() => assert.deepStrictEqual(new Boolean(true), {}),
907-
AssertionError);
908-
909866
// Check extra properties on errors.
910867
{
911868
const a = new TypeError('foo');

0 commit comments

Comments
 (0)