diff options
author | Ruben Bridgewater <ruben@bridgewater.de> | 2018-07-03 03:06:57 +0300 |
---|---|---|
committer | Ruben Bridgewater <ruben@bridgewater.de> | 2018-08-04 11:04:32 +0300 |
commit | 0518b9edf33bfffac53ac5f706694a205ff754a2 (patch) | |
tree | 23b152f429dfadeb65b7ec97ab78b3214547c7ec /lib/internal/assert.js | |
parent | a2ec80851ceff8ba6745d6909c8a2434ddfdf568 (diff) |
assert: multiple improvements
1) Switched + / - and red / green in diffs. It seems like that style
is more natural to most people.
2) Short primitives do not use the diff anymore. Especially short
numbers can be read well like 1 !== 2. Cases that can not be
displayed like that (e.g., -0 and +0) use the regular diff output.
3) Improved error descriptions. It was not always clear what the
messages stood for. That should now be resolved.
4) Added a position indicator for single lines in case a tty is used
and the line is shorter than the visual columns.
5) Color detection is now done by checking stderr instead of stdout.
PR-URL: https://github.com/nodejs/node/pull/21628
Reviewed-By: Michaƫl Zasso <targos@protonmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com>
Diffstat (limited to 'lib/internal/assert.js')
-rw-r--r-- | lib/internal/assert.js | 134 |
1 files changed, 86 insertions, 48 deletions
diff --git a/lib/internal/assert.js b/lib/internal/assert.js index 74f7e3f7c05..894b36d17fa 100644 --- a/lib/internal/assert.js +++ b/lib/internal/assert.js @@ -10,13 +10,18 @@ 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 kReadableOperator = { + deepStrictEqual: 'Expected inputs to be strictly deep-equal:', + notDeepStrictEqual: 'Expected "actual" not to be strictly deep-equal to:', + strictEqual: 'Expected inputs to be strictly equal:', + notStrictEqual: 'Expected "actual" to be strictly unequal to:', + notIdentical: 'Inputs identical but not reference equal:', }; +// Comparing short primitives should just show === / !== instead of using the +// diff. +const kMaxShortLength = 10; + function copyError(source) { const keys = Object.keys(source); const target = Object.create(Object.getPrototypeOf(source)); @@ -49,22 +54,59 @@ function inspectValue(val) { } function createErrDiff(actual, expected, operator) { - var other = ''; - var res = ''; - var lastPos = 0; - var end = ''; - var skipped = false; + let other = ''; + let res = ''; + let lastPos = 0; + let end = ''; + let skipped = false; const actualLines = inspectValue(actual); const expectedLines = inspectValue(expected); - const msg = READABLE_OPERATOR[operator] + - `:\n${green}+ expected${white} ${red}- actual${white}`; + const msg = kReadableOperator[operator] + + `\n${green}+ actual${white} ${red}- expected${white}`; const skippedMsg = ` ${blue}...${white} Lines skipped`; + let i = 0; + let indicator = ''; + + // If "actual" and "expected" fit on a single line and they are not strictly + // equal, check further special handling. + if (actualLines.length === 1 && expectedLines.length === 1 && + actualLines[0] !== expectedLines[0]) { + const inputLength = actualLines[0].length + expectedLines[0].length; + // If the character length of "actual" and "expected" together is less than + // kMaxShortLength and if neither is an object and at least one of them is + // not `zero`, use the strict equal comparison to visualize the output. + if (inputLength <= kMaxShortLength) { + if ((typeof actual !== 'object' || actual === null) && + (typeof expected !== 'object' || expected === null) && + (actual !== 0 || expected !== 0)) { // -0 === +0 + return `${kReadableOperator[operator]}\n\n` + + `${actualLines[0]} !== ${expectedLines[0]}\n`; + } + } else { + // If the stderr is a tty and the input length is lower than the current + // columns per line, add a mismatch indicator below the output. If it is + // not a tty, use a default value of 80 characters. + const maxLength = process.stderr.isTTY ? process.stderr.columns : 80; + if (inputLength < maxLength) { + while (actualLines[0][i] === expectedLines[0][i]) { + i++; + } + // Ignore the first characters. + if (i > 2) { + // Add position indicator for the first mismatch in case it is a + // single line and the input length is less than the column length. + indicator = `\n ${' '.repeat(i)}^`; + i = 0; + } + } + } + } + // Remove all ending lines that match (this optimizes the output for // readability by reducing the number of total changed lines). - var a = actualLines[actualLines.length - 1]; - var b = expectedLines[expectedLines.length - 1]; - var i = 0; + let a = actualLines[actualLines.length - 1]; + let b = expectedLines[expectedLines.length - 1]; while (a === b) { if (i++ < 2) { end = `\n ${a}${end}`; @@ -78,6 +120,26 @@ function createErrDiff(actual, expected, operator) { a = actualLines[actualLines.length - 1]; b = expectedLines[expectedLines.length - 1]; } + + const maxLines = Math.max(actualLines.length, expectedLines.length); + // Strict equal with identical objects that are not identical by reference. + // E.g., assert.deepStrictEqual({ a: Symbol() }, { a: Symbol() }) + if (maxLines === 0) { + // 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] = `${blue}...${white}`; + while (actualLines.length > 27) { + actualLines.pop(); + } + } + + return `${kReadableOperator.notIdentical}\n\n${actualLines.join('\n')}\n`; + } + if (i > 3) { end = `\n${blue}...${white}${end}`; skipped = true; @@ -87,9 +149,7 @@ function createErrDiff(actual, expected, operator) { other = ''; } - const maxLines = Math.max(actualLines.length, expectedLines.length); - var printedLines = 0; - var identical = 0; + let printedLines = 0; for (i = 0; i < maxLines; i++) { // Only extra expected lines exist const cur = i - lastPos; @@ -106,7 +166,7 @@ function createErrDiff(actual, expected, operator) { printedLines++; } lastPos = i; - other += `\n${green}+${white} ${expectedLines[i]}`; + other += `\n${red}-${white} ${expectedLines[i]}`; printedLines++; // Only extra actual lines exist } else if (expectedLines.length < i + 1) { @@ -122,7 +182,7 @@ function createErrDiff(actual, expected, operator) { printedLines++; } lastPos = i; - res += `\n${red}-${white} ${actualLines[i]}`; + res += `\n${green}+${white} ${actualLines[i]}`; printedLines++; // Lines diverge } else if (actualLines[i] !== expectedLines[i]) { @@ -138,8 +198,8 @@ function createErrDiff(actual, expected, operator) { printedLines++; } lastPos = i; - res += `\n${red}-${white} ${actualLines[i]}`; - other += `\n${green}+${white} ${expectedLines[i]}`; + res += `\n${green}+${white} ${actualLines[i]}`; + other += `\n${red}-${white} ${expectedLines[i]}`; printedLines += 2; // Lines are identical } else { @@ -149,7 +209,6 @@ 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) { @@ -158,28 +217,7 @@ function createErrDiff(actual, expected, operator) { } } - // Strict equal with identical objects that are not identical by reference. - if (identical === maxLines) { - // E.g., assert.deepStrictEqual(Symbol(), Symbol()) - const base = operator === 'strictEqual' ? - 'Input objects identical but not reference equal:' : - 'Input objects not identical:'; - - // 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] = `${blue}...${white}`; - while (actualLines.length > 27) { - actualLines.pop(); - } - } - - return `${base}\n\n${actualLines.join('\n')}\n`; - } - return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}`; + return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}${indicator}`; } class AssertionError extends Error { @@ -198,10 +236,10 @@ class AssertionError extends Error { if (message != null) { super(String(message)); } else { - if (process.stdout.isTTY) { + if (process.stderr.isTTY) { // Reset on each call to make sure we handle dynamically set environment // variables correct. - if (process.stdout.getColorDepth() !== 1) { + if (process.stderr.getColorDepth() !== 1) { blue = '\u001b[34m'; green = '\u001b[32m'; white = '\u001b[39m'; @@ -231,7 +269,7 @@ 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}:`; + const base = kReadableOperator[operator]; // Only remove lines in case it makes sense to collapse those. // TODO: Accept env to always show the full error. |