Skip to content

Commit eb427ca

Browse files
BridgeARjasnell
authored andcommitted
assert: improve default error messages
This improves the error messages for: - assert.notDeepStrictEqual - assert.deepStrictEqual - assert.notStrictEqual - assert.strictEqual Those will now always use the same error message as used in the strict mode. PR-URL: #19467 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent f848c60 commit eb427ca

File tree

8 files changed

+320
-120
lines changed

8 files changed

+320
-120
lines changed

lib/assert.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ const meta = [
5454

5555
const escapeFn = (str) => meta[str.charCodeAt(0)];
5656

57-
const ERR_DIFF_DEACTIVATED = 0;
5857
const ERR_DIFF_NOT_EQUAL = 1;
5958
const ERR_DIFF_EQUAL = 2;
6059

@@ -323,7 +322,7 @@ assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
323322
message,
324323
operator: 'deepStrictEqual',
325324
stackStartFn: deepStrictEqual,
326-
errorDiff: this === strict ? ERR_DIFF_EQUAL : ERR_DIFF_DEACTIVATED
325+
errorDiff: ERR_DIFF_EQUAL
327326
});
328327
}
329328
};
@@ -337,7 +336,7 @@ function notDeepStrictEqual(actual, expected, message) {
337336
message,
338337
operator: 'notDeepStrictEqual',
339338
stackStartFn: notDeepStrictEqual,
340-
errorDiff: this === strict ? ERR_DIFF_NOT_EQUAL : ERR_DIFF_DEACTIVATED
339+
errorDiff: ERR_DIFF_NOT_EQUAL
341340
});
342341
}
343342
}
@@ -350,7 +349,7 @@ assert.strictEqual = function strictEqual(actual, expected, message) {
350349
message,
351350
operator: 'strictEqual',
352351
stackStartFn: strictEqual,
353-
errorDiff: this === strict ? ERR_DIFF_EQUAL : ERR_DIFF_DEACTIVATED
352+
errorDiff: ERR_DIFF_EQUAL
354353
});
355354
}
356355
};
@@ -363,7 +362,7 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
363362
message,
364363
operator: 'notStrictEqual',
365364
stackStartFn: notStrictEqual,
366-
errorDiff: this === strict ? ERR_DIFF_NOT_EQUAL : ERR_DIFF_DEACTIVATED
365+
errorDiff: ERR_DIFF_NOT_EQUAL
367366
});
368367
}
369368
};

lib/internal/errors.js

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ let green = '';
1919
let red = '';
2020
let white = '';
2121

22+
const READABLE_OPERATOR = {
23+
deepStrictEqual: 'Input A expected to strictly deep-equal input B',
24+
notDeepStrictEqual: 'Input A expected to strictly not deep-equal input B',
25+
strictEqual: 'Input A expected to strictly equal input B',
26+
notStrictEqual: 'Input A expected to strictly not equal input B'
27+
};
28+
2229
const {
2330
errmap,
2431
UV_EAI_MEMORY,
@@ -40,10 +47,34 @@ function lazyInternalUtil() {
4047
return internalUtil;
4148
}
4249

50+
function copyError(source) {
51+
const keys = Object.keys(source);
52+
const target = Object.create(Object.getPrototypeOf(source));
53+
for (const key of keys) {
54+
target[key] = source[key];
55+
}
56+
Object.defineProperty(target, 'message', { value: source.message });
57+
return target;
58+
}
59+
4360
function inspectValue(val) {
61+
// The util.inspect default values could be changed. This makes sure the
62+
// error messages contain the necessary information nevertheless.
4463
return util.inspect(
4564
val,
46-
{ compact: false, customInspect: false }
65+
{
66+
compact: false,
67+
customInspect: false,
68+
depth: 1000,
69+
maxArrayLength: Infinity,
70+
// Assert compares only enumerable properties (with a few exceptions).
71+
showHidden: false,
72+
// Having a long line as error is better than wrapping the line for
73+
// comparison.
74+
breakLength: Infinity,
75+
// Assert does not detect proxies currently.
76+
showProxy: false
77+
}
4778
).split('\n');
4879
}
4980

@@ -226,8 +257,8 @@ function createErrDiff(actual, expected, operator) {
226257
if (util === undefined) util = require('util');
227258
const actualLines = inspectValue(actual);
228259
const expectedLines = inspectValue(expected);
229-
const msg = `Input A expected to ${operator} input B:\n` +
230-
`${green}+ expected${white} ${red}- actual${white}`;
260+
const msg = READABLE_OPERATOR[operator] +
261+
`:\n${green}+ expected${white} ${red}- actual${white}`;
231262
const skippedMsg = ' ... Lines skipped';
232263

233264
// Remove all ending lines that match (this optimizes the output for
@@ -259,6 +290,7 @@ function createErrDiff(actual, expected, operator) {
259290

260291
const maxLines = Math.max(actualLines.length, expectedLines.length);
261292
var printedLines = 0;
293+
var identical = 0;
262294
for (i = 0; i < maxLines; i++) {
263295
// Only extra expected lines exist
264296
const cur = i - lastPos;
@@ -318,12 +350,38 @@ function createErrDiff(actual, expected, operator) {
318350
res += `\n ${actualLines[i]}`;
319351
printedLines++;
320352
}
353+
identical++;
321354
}
322355
// Inspected object to big (Show ~20 rows max)
323356
if (printedLines > 20 && i < maxLines - 2) {
324357
return `${msg}${skippedMsg}\n${res}\n...${other}\n...`;
325358
}
326359
}
360+
361+
// Strict equal with identical objects that are not identical by reference.
362+
if (identical === maxLines) {
363+
let base = 'Input object identical but not reference equal:';
364+
365+
if (operator !== 'strictEqual') {
366+
// This code path should not be possible to reach.
367+
// The output is identical but it is not clear why.
368+
base = 'Input objects not identical:';
369+
}
370+
371+
// We have to get the result again. The lines were all removed before.
372+
const actualLines = inspectValue(actual);
373+
374+
// Only remove lines in case it makes sense to collapse those.
375+
// TODO: Accept env to always show the full error.
376+
if (actualLines.length > 30) {
377+
actualLines[26] = '...';
378+
while (actualLines.length > 27) {
379+
actualLines.pop();
380+
}
381+
}
382+
383+
return `${base}\n\n ${actualLines.join('\n ')}\n`;
384+
}
327385
return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}`;
328386
}
329387

@@ -358,13 +416,15 @@ class AssertionError extends Error {
358416
}
359417
}
360418
if (util === undefined) util = require('util');
419+
// Prevent the error stack from being visible by duplicating the error
420+
// in a very close way to the original in case both sides are actually
421+
// instances of Error.
361422
if (typeof actual === 'object' && actual !== null &&
362-
'stack' in actual && actual instanceof Error) {
363-
actual = `${actual.name}: ${actual.message}`;
364-
}
365-
if (typeof expected === 'object' && expected !== null &&
366-
'stack' in expected && expected instanceof Error) {
367-
expected = `${expected.name}: ${expected.message}`;
423+
typeof expected === 'object' && expected !== null &&
424+
'stack' in actual && actual instanceof Error &&
425+
'stack' in expected && expected instanceof Error) {
426+
actual = copyError(actual);
427+
expected = copyError(expected);
368428
}
369429

370430
if (errorDiff === 0) {
@@ -379,15 +439,23 @@ class AssertionError extends Error {
379439
// In case the objects are equal but the operator requires unequal, show
380440
// the first object and say A equals B
381441
const res = inspectValue(actual);
442+
const base = `Identical input passed to ${operator}:`;
382443

383-
if (res.length > 20) {
384-
res[19] = '...';
385-
while (res.length > 20) {
444+
// Only remove lines in case it makes sense to collapse those.
445+
// TODO: Accept env to always show the full error.
446+
if (res.length > 30) {
447+
res[26] = '...';
448+
while (res.length > 27) {
386449
res.pop();
387450
}
388451
}
389-
// Only print a single object.
390-
super(`Identical input passed to ${operator}:\n${res.join('\n')}`);
452+
453+
// Only print a single input.
454+
if (res.length === 1) {
455+
super(`${base} ${res[0]}`);
456+
} else {
457+
super(`${base}\n\n ${res.join('\n ')}\n`);
458+
}
391459
} else {
392460
super(createErrDiff(actual, expected, operator));
393461
}

test/message/assert_throws_stack.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ assert.js:*
22
throw new AssertionError(obj);
33
^
44

5-
AssertionError [ERR_ASSERTION]: Input A expected to deepStrictEqual input B:
5+
AssertionError [ERR_ASSERTION]: Input A expected to strictly deep-equal input B:
66
+ expected - actual
77

88
- Comparison {}

test/message/error_exit.out

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ assert.js:*
33
throw new AssertionError(obj);
44
^
55

6-
AssertionError [ERR_ASSERTION]: 1 strictEqual 2
6+
AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:
7+
+ expected - actual
8+
9+
- 1
10+
+ 2
711
at Object.<anonymous> (*test*message*error_exit.js:*:*)
812
at Module._compile (internal/modules/cjs/loader.js:*:*)
913
at Object.Module._extensions..js (internal/modules/cjs/loader.js:*:*)

test/parallel/test-assert-checktag.js

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,6 @@
11
'use strict';
2-
const common = require('../common');
2+
require('../common');
33
const assert = require('assert');
4-
const util = require('util');
5-
6-
// Template tag function turning an error message into a RegExp
7-
// for assert.throws()
8-
function re(literals, ...values) {
9-
let result = literals[0];
10-
const escapeRE = /[\\^$.*+?()[\]{}|=!<>:-]/g;
11-
for (const [i, value] of values.entries()) {
12-
const str = util.inspect(value);
13-
// Need to escape special characters.
14-
result += str.replace(escapeRE, '\\$&');
15-
result += literals[i + 1];
16-
}
17-
return common.expectsError({
18-
code: 'ERR_ASSERTION',
19-
message: new RegExp(`^${result}$`)
20-
});
21-
}
224

235
// Turn off no-restricted-properties because we are testing deepEqual!
246
/* eslint-disable no-restricted-properties */
@@ -35,10 +17,20 @@ function re(literals, ...values) {
3517

3618
// For deepStrictEqual we check the runtime type,
3719
// then reveal the fakeness of the fake date
38-
assert.throws(() => assert.deepStrictEqual(date, fake),
39-
re`${date} deepStrictEqual Date {}`);
40-
assert.throws(() => assert.deepStrictEqual(fake, date),
41-
re`Date {} deepStrictEqual ${date}`);
20+
assert.throws(
21+
() => assert.deepStrictEqual(date, fake),
22+
{
23+
message: 'Input A expected to strictly deep-equal input B:\n' +
24+
'+ expected - actual\n\n- 2016-01-01T00:00:00.000Z\n+ Date {}'
25+
}
26+
);
27+
assert.throws(
28+
() => assert.deepStrictEqual(fake, date),
29+
{
30+
message: 'Input A expected to strictly deep-equal input B:\n' +
31+
'+ expected - actual\n\n- Date {}\n+ 2016-01-01T00:00:00.000Z'
32+
}
33+
);
4234
}
4335

4436
{ // At the moment global has its own type tag

0 commit comments

Comments
 (0)