-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
assert: improve error messages #19467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
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.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,13 @@ let green = ''; | |
| let red = ''; | ||
| let white = ''; | ||
|
|
||
| const READABLE_OPERATOR = { | ||
| deepStrictEqual: 'Input A expected to strictly deep-equal input B', | ||
| notDeepStrictEqual: 'Input A expected to strictly not deep-equal input B', | ||
| strictEqual: 'Input A expected to strictly equal input B', | ||
| notStrictEqual: 'Input A expected to strictly not equal input B' | ||
| }; | ||
|
|
||
| const { | ||
| errmap, | ||
| UV_EAI_MEMORY, | ||
|
|
@@ -40,10 +47,34 @@ function lazyInternalUtil() { | |
| return internalUtil; | ||
| } | ||
|
|
||
| function copyError(source) { | ||
| const keys = Object.keys(source); | ||
| const target = Object.create(Object.getPrototypeOf(source)); | ||
| for (const key of keys) { | ||
| target[key] = source[key]; | ||
| } | ||
| Object.defineProperty(target, 'message', { value: source.message }); | ||
| return target; | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was the easiest and best way to pretend to be an error without a stack trace. |
||
|
|
||
| function inspectValue(val) { | ||
| // The util.inspect default values could be changed. This makes sure the | ||
| // error messages contain the necessary information nevertheless. | ||
| return util.inspect( | ||
| val, | ||
| { compact: false, customInspect: false } | ||
| { | ||
| compact: false, | ||
| customInspect: false, | ||
| depth: 1000, | ||
| maxArrayLength: Infinity, | ||
| // Assert compares only enumerable properties (with a few exceptions). | ||
| showHidden: false, | ||
| // Having a long line as error is better than wrapping the line for | ||
| // comparison. | ||
| breakLength: Infinity, | ||
| // Assert does not detect proxies currently. | ||
| showProxy: false | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: this is actually relatively important to make sure the errors are actually printed properly, no matter how the default options where changed. Only the color setting is not important. |
||
| ).split('\n'); | ||
| } | ||
|
|
||
|
|
@@ -226,8 +257,8 @@ function createErrDiff(actual, expected, operator) { | |
| if (util === undefined) util = require('util'); | ||
| const actualLines = inspectValue(actual); | ||
| const expectedLines = inspectValue(expected); | ||
| const msg = `Input A expected to ${operator} input B:\n` + | ||
| `${green}+ expected${white} ${red}- actual${white}`; | ||
| const msg = READABLE_OPERATOR[operator] + | ||
| `:\n${green}+ expected${white} ${red}- actual${white}`; | ||
| const skippedMsg = ' ... Lines skipped'; | ||
|
|
||
| // Remove all ending lines that match (this optimizes the output for | ||
|
|
@@ -259,6 +290,7 @@ function createErrDiff(actual, expected, operator) { | |
|
|
||
| const maxLines = Math.max(actualLines.length, expectedLines.length); | ||
| var printedLines = 0; | ||
| var identical = 0; | ||
| for (i = 0; i < maxLines; i++) { | ||
| // Only extra expected lines exist | ||
| const cur = i - lastPos; | ||
|
|
@@ -318,12 +350,38 @@ function createErrDiff(actual, expected, operator) { | |
| res += `\n ${actualLines[i]}`; | ||
| printedLines++; | ||
| } | ||
| identical++; | ||
| } | ||
| // Inspected object to big (Show ~20 rows max) | ||
| if (printedLines > 20 && i < maxLines - 2) { | ||
| return `${msg}${skippedMsg}\n${res}\n...${other}\n...`; | ||
| } | ||
| } | ||
|
|
||
| // Strict equal with identical objects that are not identical by reference. | ||
| if (identical === maxLines) { | ||
| let base = 'Input object identical but not reference equal:'; | ||
|
|
||
| if (operator !== 'strictEqual') { | ||
| // This code path should not be possible to reach. | ||
| // The output is identical but it is not clear why. | ||
| base = 'Input objects not identical:'; | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has happened before and there might still be some rare cases that I am not aware about that could produce this weird output. I am not sure how to handle that better though. |
||
|
|
||
| // We have to get the result again. The lines were all removed before. | ||
| const actualLines = inspectValue(actual); | ||
|
|
||
| // Only remove lines in case it makes sense to collapse those. | ||
| // TODO: Accept env to always show the full error. | ||
| if (actualLines.length > 30) { | ||
| actualLines[26] = '...'; | ||
| while (actualLines.length > 27) { | ||
| actualLines.pop(); | ||
| } | ||
| } | ||
|
|
||
| return `${base}\n\n ${actualLines.join('\n ')}\n`; | ||
| } | ||
| return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}`; | ||
| } | ||
|
|
||
|
|
@@ -358,13 +416,15 @@ class AssertionError extends Error { | |
| } | ||
| } | ||
| if (util === undefined) util = require('util'); | ||
| // Prevent the error stack from being visible by duplicating the error | ||
| // in a very close way to the original in case both sides are actually | ||
| // instances of Error. | ||
| if (typeof actual === 'object' && actual !== null && | ||
| 'stack' in actual && actual instanceof Error) { | ||
| actual = `${actual.name}: ${actual.message}`; | ||
| } | ||
| if (typeof expected === 'object' && expected !== null && | ||
| 'stack' in expected && expected instanceof Error) { | ||
| expected = `${expected.name}: ${expected.message}`; | ||
| typeof expected === 'object' && expected !== null && | ||
| 'stack' in actual && actual instanceof Error && | ||
| 'stack' in expected && expected instanceof Error) { | ||
| actual = copyError(actual); | ||
| expected = copyError(expected); | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is best to actually show the stack trace in the comparison in case there is only a single error. That way it is clearer in what way the input is different from each other. |
||
|
|
||
| if (errorDiff === 0) { | ||
|
|
@@ -379,15 +439,23 @@ class AssertionError extends Error { | |
| // In case the objects are equal but the operator requires unequal, show | ||
| // the first object and say A equals B | ||
| const res = inspectValue(actual); | ||
| const base = `Identical input passed to ${operator}:`; | ||
|
|
||
| if (res.length > 20) { | ||
| res[19] = '...'; | ||
| while (res.length > 20) { | ||
| // Only remove lines in case it makes sense to collapse those. | ||
| // TODO: Accept env to always show the full error. | ||
| if (res.length > 30) { | ||
| res[26] = '...'; | ||
| while (res.length > 27) { | ||
| res.pop(); | ||
| } | ||
| } | ||
| // Only print a single object. | ||
| super(`Identical input passed to ${operator}:\n${res.join('\n')}`); | ||
|
|
||
| // Only print a single input. | ||
| if (res.length === 1) { | ||
| super(`${base} ${res[0]}`); | ||
| } else { | ||
| super(`${base}\n\n ${res.join('\n ')}\n`); | ||
| } | ||
| } else { | ||
| super(createErrDiff(actual, expected, operator)); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Trott @vsemozhetbyt PTAL.