mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 13:09:21 +01:00
assert: make assertion_error use Myers diff algorithm
Fixes: https://github.com/nodejs/node/issues/51733 Co-Authored-By: Pietro Marchini <pietro.marchini94@gmail.com> PR-URL: https://github.com/nodejs/node/pull/54862 Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
parent
d458b933ed
commit
4d6d7d644b
@ -3,29 +3,23 @@
|
||||
const {
|
||||
ArrayPrototypeJoin,
|
||||
ArrayPrototypePop,
|
||||
ArrayPrototypeSlice,
|
||||
Error,
|
||||
ErrorCaptureStackTrace,
|
||||
MathMax,
|
||||
ObjectAssign,
|
||||
ObjectDefineProperty,
|
||||
ObjectGetPrototypeOf,
|
||||
String,
|
||||
StringPrototypeEndsWith,
|
||||
StringPrototypeRepeat,
|
||||
StringPrototypeSlice,
|
||||
StringPrototypeSplit,
|
||||
} = primordials;
|
||||
|
||||
const { inspect } = require('internal/util/inspect');
|
||||
const {
|
||||
removeColors,
|
||||
} = require('internal/util');
|
||||
const colors = require('internal/util/colors');
|
||||
const {
|
||||
validateObject,
|
||||
} = require('internal/validators');
|
||||
const { validateObject } = require('internal/validators');
|
||||
const { isErrorStackTraceLimitWritable } = require('internal/errors');
|
||||
|
||||
const { myersDiff, printMyersDiff, printSimpleMyersDiff } = require('internal/assert/myers_diff');
|
||||
|
||||
const kReadableOperator = {
|
||||
deepStrictEqual: 'Expected values to be strictly deep-equal:',
|
||||
@ -41,272 +35,174 @@ const kReadableOperator = {
|
||||
notDeepEqualUnequal: 'Expected values not to be loosely deep-equal:',
|
||||
};
|
||||
|
||||
// Comparing short primitives should just show === / !== instead of using the
|
||||
// diff.
|
||||
const kMaxShortLength = 12;
|
||||
const kMaxShortStringLength = 12;
|
||||
const kMaxLongStringLength = 512;
|
||||
|
||||
function copyError(source) {
|
||||
const target = ObjectAssign({ __proto__: ObjectGetPrototypeOf(source) }, source);
|
||||
ObjectDefineProperty(target, 'message', { __proto__: null, value: source.message });
|
||||
const target = ObjectAssign(
|
||||
{ __proto__: ObjectGetPrototypeOf(source) },
|
||||
source,
|
||||
);
|
||||
ObjectDefineProperty(target, 'message', {
|
||||
__proto__: null,
|
||||
value: source.message,
|
||||
});
|
||||
return target;
|
||||
}
|
||||
|
||||
function inspectValue(val) {
|
||||
// The util.inspect default values could be changed. This makes sure the
|
||||
// error messages contain the necessary information nevertheless.
|
||||
return inspect(
|
||||
val,
|
||||
{
|
||||
compact: false,
|
||||
customInspect: false,
|
||||
depth: 1000,
|
||||
maxArrayLength: Infinity,
|
||||
// Assert compares only enumerable properties (with a few exceptions).
|
||||
showHidden: false,
|
||||
// Assert does not detect proxies currently.
|
||||
showProxy: false,
|
||||
sorted: true,
|
||||
// Inspect getters as we also check them when comparing entries.
|
||||
getters: true,
|
||||
},
|
||||
);
|
||||
return inspect(val, {
|
||||
compact: false,
|
||||
customInspect: false,
|
||||
depth: 1000,
|
||||
maxArrayLength: Infinity,
|
||||
// Assert compares only enumerable properties (with a few exceptions).
|
||||
showHidden: false,
|
||||
// Assert does not detect proxies currently.
|
||||
showProxy: false,
|
||||
sorted: true,
|
||||
// Inspect getters as we also check them when comparing entries.
|
||||
getters: true,
|
||||
});
|
||||
}
|
||||
|
||||
function getErrorMessage(operator, message) {
|
||||
return message || kReadableOperator[operator];
|
||||
}
|
||||
|
||||
function createErrDiff(actual, expected, operator, message = '') {
|
||||
let other = '';
|
||||
let res = '';
|
||||
let end = '';
|
||||
let skipped = false;
|
||||
const actualInspected = inspectValue(actual);
|
||||
const actualLines = StringPrototypeSplit(actualInspected, '\n');
|
||||
const expectedLines = StringPrototypeSplit(inspectValue(expected), '\n');
|
||||
|
||||
let i = 0;
|
||||
let indicator = '';
|
||||
|
||||
function checkOperator(actual, expected, operator) {
|
||||
// In case both values are objects or functions explicitly mark them as not
|
||||
// reference equal for the `strictEqual` operator.
|
||||
if (operator === 'strictEqual' &&
|
||||
((typeof actual === 'object' && actual !== null &&
|
||||
typeof expected === 'object' && expected !== null) ||
|
||||
(typeof actual === 'function' && typeof expected === 'function'))) {
|
||||
if (
|
||||
operator === 'strictEqual' &&
|
||||
((typeof actual === 'object' &&
|
||||
actual !== null &&
|
||||
typeof expected === 'object' &&
|
||||
expected !== null) ||
|
||||
(typeof actual === 'function' && typeof expected === 'function'))
|
||||
) {
|
||||
operator = 'strictEqualObject';
|
||||
}
|
||||
|
||||
// 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]) {
|
||||
// Check for the visible length using the `removeColors()` function, if
|
||||
// appropriate.
|
||||
const c = inspect.defaultOptions.colors;
|
||||
const actualRaw = c ? removeColors(actualLines[0]) : actualLines[0];
|
||||
const expectedRaw = c ? removeColors(expectedLines[0]) : expectedLines[0];
|
||||
const inputLength = actualRaw.length + expectedRaw.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 `${getErrorMessage(operator, message)}\n\n` +
|
||||
`${actualLines[0]} !== ${expectedLines[0]}\n`;
|
||||
}
|
||||
} else if (operator !== 'strictEqualObject') {
|
||||
// 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 (actualRaw[i] === expectedRaw[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 ${StringPrototypeRepeat(' ', i)}^`;
|
||||
i = 0;
|
||||
return operator;
|
||||
}
|
||||
|
||||
function getColoredMyersDiff(actual, expected) {
|
||||
const header = `${colors.green}actual${colors.white} ${colors.red}expected${colors.white}`;
|
||||
const skipped = false;
|
||||
|
||||
const diff = myersDiff(StringPrototypeSplit(actual, ''), StringPrototypeSplit(expected, ''));
|
||||
let message = printSimpleMyersDiff(diff);
|
||||
|
||||
if (skipped) {
|
||||
message += '...';
|
||||
}
|
||||
|
||||
return { message, header, skipped };
|
||||
}
|
||||
|
||||
function getStackedDiff(actual, expected) {
|
||||
const isStringComparison = typeof actual === 'string' && typeof expected === 'string';
|
||||
|
||||
let message = `\n${colors.green}+${colors.white} ${actual}\n${colors.red}- ${colors.white}${expected}`;
|
||||
const stringsLen = actual.length + expected.length;
|
||||
const maxTerminalLength = process.stderr.isTTY ? process.stderr.columns : 80;
|
||||
const showIndicator = isStringComparison && (stringsLen <= maxTerminalLength);
|
||||
|
||||
if (showIndicator) {
|
||||
let indicatorIdx = -1;
|
||||
|
||||
for (let i = 0; i < actual.length; i++) {
|
||||
if (actual[i] !== expected[i]) {
|
||||
// Skip the indicator for the first 2 characters because the diff is immediately apparent
|
||||
// It is 3 instead of 2 to account for the quotes
|
||||
if (i >= 3) {
|
||||
indicatorIdx = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (indicatorIdx !== -1) {
|
||||
message += `\n${StringPrototypeRepeat(' ', indicatorIdx + 2)}^`;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all ending lines that match (this optimizes the output for
|
||||
// readability by reducing the number of total changed lines).
|
||||
let a = actualLines[actualLines.length - 1];
|
||||
let b = expectedLines[expectedLines.length - 1];
|
||||
while (a === b) {
|
||||
if (i++ < 3) {
|
||||
end = `\n ${a}${end}`;
|
||||
return { message };
|
||||
}
|
||||
|
||||
function getSimpleDiff(originalActual, actual, originalExpected, expected) {
|
||||
const stringsLen = actual.length + expected.length;
|
||||
if (stringsLen <= kMaxShortStringLength && (originalActual !== 0 || originalExpected !== 0)) {
|
||||
return { message: `${actual} !== ${expected}`, header: '' };
|
||||
}
|
||||
|
||||
const isStringComparison = typeof originalActual === 'string' && typeof originalExpected === 'string';
|
||||
// colored myers diff
|
||||
if (isStringComparison && colors.hasColors) {
|
||||
return getColoredMyersDiff(actual, expected);
|
||||
}
|
||||
|
||||
return getStackedDiff(actual, expected);
|
||||
}
|
||||
|
||||
function isSimpleDiff(actual, inspectedActual, expected, inspectedExpected) {
|
||||
if (inspectedActual.length > 1 || inspectedExpected.length > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return typeof actual !== 'object' || actual === null || typeof expected !== 'object' || expected === null;
|
||||
}
|
||||
|
||||
function createErrDiff(actual, expected, operator, customMessage) {
|
||||
operator = checkOperator(actual, expected, operator);
|
||||
|
||||
let skipped = false;
|
||||
let message = '';
|
||||
const inspectedActual = inspectValue(actual);
|
||||
const inspectedExpected = inspectValue(expected);
|
||||
const inspectedSplitActual = StringPrototypeSplit(inspectedActual, '\n');
|
||||
const inspectedSplitExpected = StringPrototypeSplit(inspectedExpected, '\n');
|
||||
const showSimpleDiff = isSimpleDiff(actual, inspectedSplitActual, expected, inspectedSplitExpected);
|
||||
let header = `${colors.green}+ actual${colors.white} ${colors.red}- expected${colors.white}`;
|
||||
|
||||
if (showSimpleDiff) {
|
||||
const simpleDiff = getSimpleDiff(actual, inspectedSplitActual[0], expected, inspectedSplitExpected[0]);
|
||||
message = simpleDiff.message;
|
||||
if (typeof simpleDiff.header !== 'undefined') {
|
||||
header = simpleDiff.header;
|
||||
}
|
||||
if (simpleDiff.skipped) {
|
||||
skipped = true;
|
||||
}
|
||||
} else if (inspectedActual === inspectedExpected) {
|
||||
// Handles the case where the objects are structurally the same but different references
|
||||
operator = 'notIdentical';
|
||||
if (inspectedSplitActual.length > 50) {
|
||||
message = `${ArrayPrototypeJoin(ArrayPrototypeSlice(inspectedSplitActual, 0, 50), '\n')}\n...}`;
|
||||
skipped = true;
|
||||
} else {
|
||||
other = a;
|
||||
message = ArrayPrototypeJoin(inspectedSplitActual, '\n');
|
||||
}
|
||||
ArrayPrototypePop(actualLines);
|
||||
ArrayPrototypePop(expectedLines);
|
||||
if (actualLines.length === 0 || expectedLines.length === 0)
|
||||
break;
|
||||
a = actualLines[actualLines.length - 1];
|
||||
b = expectedLines[expectedLines.length - 1];
|
||||
}
|
||||
header = '';
|
||||
} else {
|
||||
const checkCommaDisparity = actual != null && typeof actual === 'object';
|
||||
const diff = myersDiff(inspectedSplitActual, inspectedSplitExpected, checkCommaDisparity);
|
||||
|
||||
const maxLines = MathMax(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 = StringPrototypeSplit(actualInspected, '\n');
|
||||
const myersDiffMessage = printMyersDiff(diff);
|
||||
message = myersDiffMessage.message;
|
||||
|
||||
// Only remove lines in case it makes sense to collapse those.
|
||||
// TODO: Accept env to always show the full error.
|
||||
if (actualLines.length > 50) {
|
||||
actualLines[46] = `${colors.blue}...${colors.white}`;
|
||||
while (actualLines.length > 47) {
|
||||
ArrayPrototypePop(actualLines);
|
||||
}
|
||||
}
|
||||
|
||||
return `${kReadableOperator.notIdentical}\n\n` +
|
||||
`${ArrayPrototypeJoin(actualLines, '\n')}\n`;
|
||||
}
|
||||
|
||||
// There were at least five identical lines at the end. Mark a couple of
|
||||
// skipped.
|
||||
if (i >= 5) {
|
||||
end = `\n${colors.blue}...${colors.white}${end}`;
|
||||
skipped = true;
|
||||
}
|
||||
if (other !== '') {
|
||||
end = `\n ${other}${end}`;
|
||||
other = '';
|
||||
}
|
||||
|
||||
let printedLines = 0;
|
||||
let identical = 0;
|
||||
const msg = `${getErrorMessage(operator, message)}\n${colors.green}+ actual${colors.white} ${colors.red}- expected${colors.white}`;
|
||||
const skippedMsg = ` ${colors.blue}...${colors.white} Lines skipped`;
|
||||
|
||||
let lines = actualLines;
|
||||
let plusMinus = `${colors.green}+${colors.white}`;
|
||||
let maxLength = expectedLines.length;
|
||||
if (actualLines.length < maxLines) {
|
||||
lines = expectedLines;
|
||||
plusMinus = `${colors.red}-${colors.white}`;
|
||||
maxLength = actualLines.length;
|
||||
}
|
||||
|
||||
for (i = 0; i < maxLines; i++) {
|
||||
if (maxLength < i + 1) {
|
||||
// If more than two former lines are identical, print them. Collapse them
|
||||
// in case more than five lines were identical.
|
||||
if (identical > 2) {
|
||||
if (identical > 3) {
|
||||
if (identical > 4) {
|
||||
if (identical === 5) {
|
||||
res += `\n ${lines[i - 3]}`;
|
||||
printedLines++;
|
||||
} else {
|
||||
res += `\n${colors.blue}...${colors.white}`;
|
||||
skipped = true;
|
||||
}
|
||||
}
|
||||
res += `\n ${lines[i - 2]}`;
|
||||
printedLines++;
|
||||
}
|
||||
res += `\n ${lines[i - 1]}`;
|
||||
printedLines++;
|
||||
}
|
||||
// No identical lines before.
|
||||
identical = 0;
|
||||
// Add the expected line to the cache.
|
||||
if (lines === actualLines) {
|
||||
res += `\n${plusMinus} ${lines[i]}`;
|
||||
} else {
|
||||
other += `\n${plusMinus} ${lines[i]}`;
|
||||
}
|
||||
printedLines++;
|
||||
// Only extra actual lines exist
|
||||
// Lines diverge
|
||||
} else {
|
||||
const expectedLine = expectedLines[i];
|
||||
let actualLine = actualLines[i];
|
||||
// If the lines diverge, specifically check for lines that only diverge by
|
||||
// a trailing comma. In that case it is actually identical and we should
|
||||
// mark it as such.
|
||||
let divergingLines =
|
||||
actualLine !== expectedLine &&
|
||||
(!StringPrototypeEndsWith(actualLine, ',') ||
|
||||
StringPrototypeSlice(actualLine, 0, -1) !== expectedLine);
|
||||
// If the expected line has a trailing comma but is otherwise identical,
|
||||
// add a comma at the end of the actual line. Otherwise the output could
|
||||
// look weird as in:
|
||||
//
|
||||
// [
|
||||
// 1 // No comma at the end!
|
||||
// + 2
|
||||
// ]
|
||||
//
|
||||
if (divergingLines &&
|
||||
StringPrototypeEndsWith(expectedLine, ',') &&
|
||||
StringPrototypeSlice(expectedLine, 0, -1) === actualLine) {
|
||||
divergingLines = false;
|
||||
actualLine += ',';
|
||||
}
|
||||
if (divergingLines) {
|
||||
// If more than two former lines are identical, print them. Collapse
|
||||
// them in case more than five lines were identical.
|
||||
if (identical > 2) {
|
||||
if (identical > 3) {
|
||||
if (identical > 4) {
|
||||
if (identical === 5) {
|
||||
res += `\n ${actualLines[i - 3]}`;
|
||||
printedLines++;
|
||||
} else {
|
||||
res += `\n${colors.blue}...${colors.white}`;
|
||||
skipped = true;
|
||||
}
|
||||
}
|
||||
res += `\n ${actualLines[i - 2]}`;
|
||||
printedLines++;
|
||||
}
|
||||
res += `\n ${actualLines[i - 1]}`;
|
||||
printedLines++;
|
||||
}
|
||||
// No identical lines before.
|
||||
identical = 0;
|
||||
// Add the actual line to the result and cache the expected diverging
|
||||
// line so consecutive diverging lines show up as +++--- and not +-+-+-.
|
||||
res += `\n${colors.green}+${colors.white} ${actualLine}`;
|
||||
other += `\n${colors.red}-${colors.white} ${expectedLine}`;
|
||||
printedLines += 2;
|
||||
// Lines are identical
|
||||
} else {
|
||||
// Add all cached information to the result before adding other things
|
||||
// and reset the cache.
|
||||
res += other;
|
||||
other = '';
|
||||
identical++;
|
||||
// The very first identical line since the last diverging line is be
|
||||
// added to the result.
|
||||
if (identical <= 2) {
|
||||
res += `\n ${actualLine}`;
|
||||
printedLines++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Inspected object to big (Show ~50 rows max)
|
||||
if (printedLines > 50 && i < maxLines - 2) {
|
||||
return `${msg}${skippedMsg}\n${res}\n${colors.blue}...${colors.white}${other}\n` +
|
||||
`${colors.blue}...${colors.white}`;
|
||||
if (myersDiffMessage.skipped) {
|
||||
skipped = true;
|
||||
}
|
||||
}
|
||||
|
||||
return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}${indicator}`;
|
||||
const headerMessage = `${getErrorMessage(operator, customMessage)}\n${header}`;
|
||||
const skippedMessage = skipped ? '\n... Skipped lines' : '';
|
||||
|
||||
return `${headerMessage}${skippedMessage}\n${message}\n`;
|
||||
}
|
||||
|
||||
function addEllipsis(string) {
|
||||
@ -314,8 +210,8 @@ function addEllipsis(string) {
|
||||
if (lines.length > 10) {
|
||||
lines.length = 10;
|
||||
return `${ArrayPrototypeJoin(lines, '\n')}\n...`;
|
||||
} else if (string.length > 512) {
|
||||
return `${StringPrototypeSlice(string, 512)}...`;
|
||||
} else if (string.length > kMaxLongStringLength) {
|
||||
return `${StringPrototypeSlice(string, kMaxLongStringLength)}...`;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
@ -361,7 +257,7 @@ class AssertionError extends Error {
|
||||
}
|
||||
|
||||
if (operator === 'deepStrictEqual' || operator === 'strictEqual') {
|
||||
super(createErrDiff(actual, expected, operator));
|
||||
super(createErrDiff(actual, expected, operator, message));
|
||||
} else if (operator === 'notDeepStrictEqual' ||
|
||||
operator === 'notStrictEqual') {
|
||||
// In case the objects are equal but the operator requires unequal, show
|
||||
@ -403,10 +299,10 @@ class AssertionError extends Error {
|
||||
}
|
||||
super(res);
|
||||
} else {
|
||||
if (res.length > 512) {
|
||||
if (res.length > kMaxLongStringLength) {
|
||||
res = `${StringPrototypeSlice(res, 0, 509)}...`;
|
||||
}
|
||||
if (other.length > 512) {
|
||||
if (other.length > kMaxLongStringLength) {
|
||||
other = `${StringPrototypeSlice(other, 0, 509)}...`;
|
||||
}
|
||||
if (operator === 'deepEqual') {
|
||||
|
167
lib/internal/assert/myers_diff.js
Normal file
167
lib/internal/assert/myers_diff.js
Normal file
@ -0,0 +1,167 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
Array,
|
||||
ArrayPrototypeFill,
|
||||
ArrayPrototypePush,
|
||||
ArrayPrototypeSlice,
|
||||
StringPrototypeEndsWith,
|
||||
} = primordials;
|
||||
|
||||
const colors = require('internal/util/colors');
|
||||
|
||||
const kNopLinesToCollapse = 5;
|
||||
|
||||
function areLinesEqual(actual, expected, checkCommaDisparity) {
|
||||
if (actual === expected) {
|
||||
return true;
|
||||
}
|
||||
if (checkCommaDisparity) {
|
||||
return `${actual},` === expected || actual === `${expected},`;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function myersDiff(actual, expected, checkCommaDisparity = false) {
|
||||
const actualLength = actual.length;
|
||||
const expectedLength = expected.length;
|
||||
const max = actualLength + expectedLength;
|
||||
const v = ArrayPrototypeFill(Array(2 * max + 1), 0);
|
||||
|
||||
const trace = [];
|
||||
|
||||
for (let diffLevel = 0; diffLevel <= max; diffLevel++) {
|
||||
const newTrace = ArrayPrototypeSlice(v);
|
||||
ArrayPrototypePush(trace, newTrace);
|
||||
|
||||
for (let diagonalIndex = -diffLevel; diagonalIndex <= diffLevel; diagonalIndex += 2) {
|
||||
let x;
|
||||
if (diagonalIndex === -diffLevel ||
|
||||
(diagonalIndex !== diffLevel && v[diagonalIndex - 1 + max] < v[diagonalIndex + 1 + max])) {
|
||||
x = v[diagonalIndex + 1 + max];
|
||||
} else {
|
||||
x = v[diagonalIndex - 1 + max] + 1;
|
||||
}
|
||||
|
||||
let y = x - diagonalIndex;
|
||||
|
||||
while (x < actualLength && y < expectedLength && areLinesEqual(actual[x], expected[y], checkCommaDisparity)) {
|
||||
x++;
|
||||
y++;
|
||||
}
|
||||
|
||||
v[diagonalIndex + max] = x;
|
||||
|
||||
if (x >= actualLength && y >= expectedLength) {
|
||||
return backtrack(trace, actual, expected, checkCommaDisparity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function backtrack(trace, actual, expected, checkCommaDisparity) {
|
||||
const actualLength = actual.length;
|
||||
const expectedLength = expected.length;
|
||||
const max = actualLength + expectedLength;
|
||||
|
||||
let x = actualLength;
|
||||
let y = expectedLength;
|
||||
const result = [];
|
||||
|
||||
for (let diffLevel = trace.length - 1; diffLevel >= 0; diffLevel--) {
|
||||
const v = trace[diffLevel];
|
||||
const diagonalIndex = x - y;
|
||||
let prevDiagonalIndex;
|
||||
|
||||
if (diagonalIndex === -diffLevel ||
|
||||
(diagonalIndex !== diffLevel && v[diagonalIndex - 1 + max] < v[diagonalIndex + 1 + max])) {
|
||||
prevDiagonalIndex = diagonalIndex + 1;
|
||||
} else {
|
||||
prevDiagonalIndex = diagonalIndex - 1;
|
||||
}
|
||||
|
||||
const prevX = v[prevDiagonalIndex + max];
|
||||
const prevY = prevX - prevDiagonalIndex;
|
||||
|
||||
while (x > prevX && y > prevY) {
|
||||
const value = !checkCommaDisparity ||
|
||||
StringPrototypeEndsWith(actual[x - 1], ',') ? actual[x - 1] : expected[y - 1];
|
||||
ArrayPrototypePush(result, { __proto__: null, type: 'nop', value });
|
||||
x--;
|
||||
y--;
|
||||
}
|
||||
|
||||
if (diffLevel > 0) {
|
||||
if (x > prevX) {
|
||||
ArrayPrototypePush(result, { __proto__: null, type: 'insert', value: actual[x - 1] });
|
||||
x--;
|
||||
} else {
|
||||
ArrayPrototypePush(result, { __proto__: null, type: 'delete', value: expected[y - 1] });
|
||||
y--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function printSimpleMyersDiff(diff) {
|
||||
let message = '';
|
||||
|
||||
for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) {
|
||||
const { type, value } = diff[diffIdx];
|
||||
if (type === 'insert') {
|
||||
message += `${colors.green}${value}${colors.white}`;
|
||||
} else if (type === 'delete') {
|
||||
message += `${colors.red}${value}${colors.white}`;
|
||||
} else {
|
||||
message += `${colors.white}${value}${colors.white}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `\n${message}`;
|
||||
}
|
||||
|
||||
function printMyersDiff(diff, simple = false) {
|
||||
let message = '';
|
||||
let skipped = false;
|
||||
let nopCount = 0;
|
||||
|
||||
for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) {
|
||||
const { type, value } = diff[diffIdx];
|
||||
const previousType = (diffIdx < (diff.length - 1)) ? diff[diffIdx + 1].type : null;
|
||||
const typeChanged = previousType && (type !== previousType);
|
||||
|
||||
if (typeChanged && previousType === 'nop') {
|
||||
// Avoid grouping if only one line would have been grouped otherwise
|
||||
if (nopCount === kNopLinesToCollapse + 1) {
|
||||
message += `${colors.white} ${diff[diffIdx + 1].value}\n`;
|
||||
} else if (nopCount === kNopLinesToCollapse + 2) {
|
||||
message += `${colors.white} ${diff[diffIdx + 2].value}\n`;
|
||||
message += `${colors.white} ${diff[diffIdx + 1].value}\n`;
|
||||
} if (nopCount >= (kNopLinesToCollapse + 3)) {
|
||||
message += `${colors.blue}...${colors.white}\n`;
|
||||
message += `${colors.white} ${diff[diffIdx + 1].value}\n`;
|
||||
skipped = true;
|
||||
}
|
||||
nopCount = 0;
|
||||
}
|
||||
|
||||
if (type === 'insert') {
|
||||
message += `${colors.green}+${colors.white} ${value}\n`;
|
||||
} else if (type === 'delete') {
|
||||
message += `${colors.red}-${colors.white} ${value}\n`;
|
||||
} else if (type === 'nop') {
|
||||
if (nopCount < kNopLinesToCollapse) {
|
||||
message += `${colors.white} ${value}\n`;
|
||||
}
|
||||
nopCount++;
|
||||
}
|
||||
}
|
||||
|
||||
message = message.trimEnd();
|
||||
|
||||
return { message: `\n${message}`, skipped };
|
||||
}
|
||||
|
||||
module.exports = { myersDiff, printMyersDiff, printSimpleMyersDiff };
|
@ -29,14 +29,20 @@ test('', { skip: !hasCrypto }, () => {
|
||||
() => assert.deepStrictEqual(date, fake),
|
||||
{
|
||||
message: 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n\n+ 2016-01-01T00:00:00.000Z\n- Date {}'
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ 2016-01-01T00:00:00.000Z\n' +
|
||||
'- Date {}\n'
|
||||
}
|
||||
);
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual(fake, date),
|
||||
{
|
||||
message: 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n\n+ Date {}\n- 2016-01-01T00:00:00.000Z'
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ Date {}\n' +
|
||||
'- 2016-01-01T00:00:00.000Z\n'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -70,9 +70,16 @@ test('deepEqual', () => {
|
||||
() => assert.deepStrictEqual(arr, buf),
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
message: `${defaultMsgStartFull} ... Lines skipped\n\n` +
|
||||
'+ Uint8Array(4) [\n' +
|
||||
'- Buffer(4) [Uint8Array] [\n 120,\n...\n 122,\n 10\n ]'
|
||||
message: 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ Uint8Array(4) [\n' +
|
||||
'- Buffer(4) [Uint8Array] [\n' +
|
||||
' 120,\n' +
|
||||
' 121,\n' +
|
||||
' 122,\n' +
|
||||
' 10\n' +
|
||||
' ]\n'
|
||||
}
|
||||
);
|
||||
assert.deepEqual(arr, buf);
|
||||
@ -92,7 +99,7 @@ test('deepEqual', () => {
|
||||
' 122,\n' +
|
||||
' 10,\n' +
|
||||
'+ prop: 1\n' +
|
||||
' ]'
|
||||
' ]\n'
|
||||
}
|
||||
);
|
||||
assert.notDeepEqual(buf2, buf);
|
||||
@ -112,7 +119,7 @@ test('deepEqual', () => {
|
||||
' 122,\n' +
|
||||
' 10,\n' +
|
||||
'- prop: 5\n' +
|
||||
' ]'
|
||||
' ]\n'
|
||||
}
|
||||
);
|
||||
assert.notDeepEqual(arr, arr2);
|
||||
@ -127,7 +134,7 @@ test('date', () => {
|
||||
code: 'ERR_ASSERTION',
|
||||
message: `${defaultMsgStartFull}\n\n` +
|
||||
'+ 2016-01-01T00:00:00.000Z\n- MyDate 2016-01-01T00:00:00.000Z' +
|
||||
" {\n- '0': '1'\n- }"
|
||||
" {\n- '0': '1'\n- }\n"
|
||||
}
|
||||
);
|
||||
assert.throws(
|
||||
@ -136,7 +143,7 @@ test('date', () => {
|
||||
code: 'ERR_ASSERTION',
|
||||
message: `${defaultMsgStartFull}\n\n` +
|
||||
'+ MyDate 2016-01-01T00:00:00.000Z {\n' +
|
||||
"+ '0': '1'\n+ }\n- 2016-01-01T00:00:00.000Z"
|
||||
"+ '0': '1'\n+ }\n- 2016-01-01T00:00:00.000Z\n"
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -151,7 +158,7 @@ test('regexp', () => {
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
message: `${defaultMsgStartFull}\n\n` +
|
||||
"+ /test/\n- MyRegExp /test/ {\n- '0': '1'\n- }"
|
||||
"+ /test/\n- MyRegExp /test/ {\n- '0': '1'\n- }\n"
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -474,7 +481,7 @@ test('es6 Maps and Sets', () => {
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
message: `${defaultMsgStartFull}\n\n` +
|
||||
" Map(1) {\n+ 1 => 1\n- 1 => '1'\n }"
|
||||
" Map(1) {\n+ 1 => 1\n- 1 => '1'\n }\n"
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -846,35 +853,55 @@ test('Additional tests', () => {
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: `${defaultMsgStartFull}\n\n+ /ab/\n- /a/`
|
||||
message: 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ /ab/\n' +
|
||||
'- /a/\n'
|
||||
});
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual(/a/g, /a/),
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: `${defaultMsgStartFull}\n\n+ /a/g\n- /a/`
|
||||
message: 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ /a/g\n' +
|
||||
'- /a/\n'
|
||||
});
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual(/a/i, /a/),
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: `${defaultMsgStartFull}\n\n+ /a/i\n- /a/`
|
||||
message: 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ /a/i\n' +
|
||||
'- /a/\n'
|
||||
});
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual(/a/m, /a/),
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: `${defaultMsgStartFull}\n\n+ /a/m\n- /a/`
|
||||
message: 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ /a/m\n' +
|
||||
'- /a/\n'
|
||||
});
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual(/aa/igm, /aa/im),
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: `${defaultMsgStartFull}\n\n+ /aa/gim\n- /aa/im\n ^`
|
||||
message: 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ /aa/gim\n' +
|
||||
'- /aa/im\n'
|
||||
});
|
||||
|
||||
{
|
||||
@ -909,7 +936,7 @@ test('Having the same number of owned properties && the same set of keys', () =>
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: `${defaultMsgStartFull}\n\n [\n+ 4\n- '4'\n ]`
|
||||
message: `${defaultMsgStartFull}\n\n [\n+ 4\n- '4'\n ]\n`
|
||||
});
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual({ a: 4 }, { a: 4, b: true }),
|
||||
@ -917,7 +944,7 @@ test('Having the same number of owned properties && the same set of keys', () =>
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: `${defaultMsgStartFull}\n\n ` +
|
||||
'{\n a: 4,\n- b: true\n }'
|
||||
'{\n a: 4,\n- b: true\n }\n'
|
||||
});
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual(['a'], { 0: 'a' }),
|
||||
@ -925,7 +952,7 @@ test('Having the same number of owned properties && the same set of keys', () =>
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: `${defaultMsgStartFull}\n\n` +
|
||||
"+ [\n+ 'a'\n+ ]\n- {\n- '0': 'a'\n- }"
|
||||
"+ [\n+ 'a'\n+ ]\n- {\n- '0': 'a'\n- }\n"
|
||||
});
|
||||
});
|
||||
|
||||
@ -964,25 +991,25 @@ test('Check extra properties on errors', () => {
|
||||
() => assert.deepStrictEqual(a, b),
|
||||
{
|
||||
operator: 'throws',
|
||||
message: `${defaultMsgStartFull}\n\n` +
|
||||
' [TypeError: foo] {\n+ foo: \'bar\'\n- foo: \'baz\'\n }',
|
||||
message: '',
|
||||
}
|
||||
),
|
||||
{
|
||||
message: 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected ... Lines skipped\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
' Comparison {\n' +
|
||||
" message: 'Expected values to be strictly deep-equal:\\n' +\n" +
|
||||
'...\n' +
|
||||
" ' [TypeError: foo] {\\n' +\n" +
|
||||
" \"+ foo: 'bar'\\n\" +\n" +
|
||||
"+ \"- foo: 'baz.'\\n\" +\n" +
|
||||
"- \"- foo: 'baz'\\n\" +\n" +
|
||||
" ' }',\n" +
|
||||
"+ message: 'Expected values to be strictly deep-equal:\\n' +\n" +
|
||||
"+ '+ actual - expected\\n' +\n" +
|
||||
"+ '\\n' +\n" +
|
||||
"+ ' [TypeError: foo] {\\n' +\n" +
|
||||
`+ "+ foo: 'bar'\\n" +\n` +
|
||||
`+ "- foo: 'baz.'\\n" +\n` +
|
||||
"+ ' }\\n',\n" +
|
||||
"+ operator: 'deepStrictEqual'\n" +
|
||||
"- message: '',\n" +
|
||||
"- operator: 'throws'\n" +
|
||||
' }'
|
||||
' }\n'
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -995,7 +1022,7 @@ test('Check proxies', () => {
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual(arrProxy, [1, 2, 3]),
|
||||
{ message: `${defaultMsgStartFull}\n\n` +
|
||||
' [\n 1,\n 2,\n- 3\n ]' }
|
||||
' [\n 1,\n 2,\n- 3\n ]\n' }
|
||||
);
|
||||
util.inspect.defaultOptions = tmp;
|
||||
|
||||
@ -1052,7 +1079,7 @@ test('Basic array out of bounds check', () => {
|
||||
' 1,\n' +
|
||||
' 2,\n' +
|
||||
'+ 3\n' +
|
||||
' ]'
|
||||
' ]\n'
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -1347,6 +1374,107 @@ test('Comparing two different WeakSet instances', () => {
|
||||
assertNotDeepOrStrict(weakSet1, weakSet2);
|
||||
});
|
||||
|
||||
test('Comparing two arrays nested inside object, with overlapping elements', () => {
|
||||
const actual = { a: { b: [1, 2, 3] } };
|
||||
const expected = { a: { b: [3, 4, 5] } };
|
||||
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual(actual, expected),
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
' {\n' +
|
||||
' a: {\n' +
|
||||
' b: [\n' +
|
||||
'+ 1,\n' +
|
||||
'+ 2,\n' +
|
||||
' 3,\n' +
|
||||
'- 4,\n' +
|
||||
'- 5\n' +
|
||||
' ]\n' +
|
||||
' }\n' +
|
||||
' }\n'
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('Comparing two arrays nested inside object, with overlapping elements, swapping keys', () => {
|
||||
const actual = { a: { b: [1, 2, 3], c: 2 } };
|
||||
const expected = { a: { b: 1, c: [3, 4, 5] } };
|
||||
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual(actual, expected),
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
' {\n' +
|
||||
' a: {\n' +
|
||||
'+ b: [\n' +
|
||||
'+ 1,\n' +
|
||||
'+ 2,\n' +
|
||||
'- b: 1,\n' +
|
||||
'- c: [\n' +
|
||||
' 3,\n' +
|
||||
'- 4,\n' +
|
||||
'- 5\n' +
|
||||
' ],\n' +
|
||||
'+ c: 2\n' +
|
||||
' }\n' +
|
||||
' }\n'
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('Detects differences in deeply nested arrays instead of seeing a new object', () => {
|
||||
const actual = [
|
||||
{ a: 1 },
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
{ c: [1, 2, 3] },
|
||||
];
|
||||
const expected = [
|
||||
{ a: 1 },
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
{ c: [3, 4, 5] },
|
||||
];
|
||||
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual(actual, expected),
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'... Skipped lines\n' +
|
||||
'\n' +
|
||||
' [\n' +
|
||||
' {\n' +
|
||||
' a: 1\n' +
|
||||
' },\n' +
|
||||
' 2,\n' +
|
||||
'...\n' +
|
||||
' c: [\n' +
|
||||
'+ 1,\n' +
|
||||
'+ 2,\n' +
|
||||
' 3,\n' +
|
||||
'- 4,\n' +
|
||||
'- 5\n' +
|
||||
' ]\n' +
|
||||
' }\n' +
|
||||
' ]\n'
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// check URL
|
||||
{
|
||||
const a = new URL('http://foo');
|
||||
|
@ -30,8 +30,9 @@ const vm = require('vm');
|
||||
// Disable colored output to prevent color codes from breaking assertion
|
||||
// message comparisons. This should only be an issue when process.stdout
|
||||
// is a TTY.
|
||||
if (process.stdout.isTTY)
|
||||
if (process.stdout.isTTY) {
|
||||
process.env.NODE_DISABLE_COLORS = '1';
|
||||
}
|
||||
|
||||
const strictEqualMessageStart = 'Expected values to be strictly equal:\n';
|
||||
const start = 'Expected values to be strictly deep-equal:';
|
||||
@ -277,8 +278,10 @@ test('assert.throws()', () => {
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: 'Expected "actual" to be reference-equal to "expected":\n' +
|
||||
'+ actual - expected\n\n' +
|
||||
'+ [Error: foo]\n- [Error: foobar]'
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ [Error: foo]\n' +
|
||||
'- [Error: foobar]\n'
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -342,15 +345,21 @@ test('Test assertion messages', () => {
|
||||
() => assert.strictEqual(actual, ''),
|
||||
{
|
||||
generatedMessage: true,
|
||||
message: msg || strictEqualMessageStart +
|
||||
`+ actual - expected\n\n+ ${expected}\n- ''`
|
||||
message: msg || `Expected values to be strictly equal:\n\n${expected} !== ''\n`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function testLongAssertionMessage(actual, expected) {
|
||||
testAssertionMessage(actual, expected, 'Expected values to be strictly equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
`+ ${expected}\n` +
|
||||
"- ''\n");
|
||||
}
|
||||
|
||||
function testShortAssertionMessage(actual, expected) {
|
||||
testAssertionMessage(actual, expected, strictEqualMessageStart +
|
||||
`\n${inspect(actual)} !== ''\n`);
|
||||
testAssertionMessage(actual, expected, strictEqualMessageStart + `\n${inspect(actual)} !== ''\n`);
|
||||
}
|
||||
|
||||
testShortAssertionMessage(null, 'null');
|
||||
@ -359,31 +368,86 @@ test('Test assertion messages', () => {
|
||||
testShortAssertionMessage(100, '100');
|
||||
testShortAssertionMessage(NaN, 'NaN');
|
||||
testShortAssertionMessage(Infinity, 'Infinity');
|
||||
testShortAssertionMessage('a', '"a"');
|
||||
testShortAssertionMessage('a', '\'a\'');
|
||||
testShortAssertionMessage('foo', '\'foo\'');
|
||||
testShortAssertionMessage(0, '0');
|
||||
testShortAssertionMessage(Symbol(), 'Symbol()');
|
||||
testShortAssertionMessage(undefined, 'undefined');
|
||||
testShortAssertionMessage(-Infinity, '-Infinity');
|
||||
testAssertionMessage([], '[]');
|
||||
testShortAssertionMessage([], '[]');
|
||||
testShortAssertionMessage({}, '{}');
|
||||
testAssertionMessage(/a/, '/a/');
|
||||
testAssertionMessage(/abc/gim, '/abc/gim');
|
||||
testAssertionMessage({}, '{}');
|
||||
testAssertionMessage([1, 2, 3], '[\n+ 1,\n+ 2,\n+ 3\n+ ]');
|
||||
testAssertionMessage(function f() {}, '[Function: f]');
|
||||
testAssertionMessage(function() {}, '[Function (anonymous)]');
|
||||
testAssertionMessage(circular,
|
||||
'<ref *1> {\n+ x: [Circular *1],\n+ y: 1\n+ }');
|
||||
testAssertionMessage({ a: undefined, b: null },
|
||||
'{\n+ a: undefined,\n+ b: null\n+ }');
|
||||
testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity },
|
||||
'{\n+ a: NaN,\n+ b: Infinity,\n+ c: -Infinity\n+ }');
|
||||
testLongAssertionMessage(function f() {}, '[Function: f]');
|
||||
testLongAssertionMessage(function() {}, '[Function (anonymous)]');
|
||||
|
||||
assert.throws(
|
||||
() => assert.strictEqual([1, 2, 3], ''),
|
||||
{
|
||||
message: 'Expected values to be strictly equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ [\n' +
|
||||
'+ 1,\n' +
|
||||
'+ 2,\n' +
|
||||
'+ 3\n' +
|
||||
'+ ]\n' +
|
||||
"- ''\n",
|
||||
generatedMessage: true
|
||||
}
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => assert.strictEqual(circular, ''),
|
||||
{
|
||||
message: 'Expected values to be strictly equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ <ref *1> {\n' +
|
||||
'+ x: [Circular *1],\n' +
|
||||
'+ y: 1\n' +
|
||||
'+ }\n' +
|
||||
"- ''\n",
|
||||
generatedMessage: true
|
||||
}
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => assert.strictEqual({ a: undefined, b: null }, ''),
|
||||
{
|
||||
message: 'Expected values to be strictly equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ {\n' +
|
||||
'+ a: undefined,\n' +
|
||||
'+ b: null\n' +
|
||||
'+ }\n' +
|
||||
"- ''\n",
|
||||
generatedMessage: true
|
||||
}
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => assert.strictEqual({ a: NaN, b: Infinity, c: -Infinity }, ''),
|
||||
{
|
||||
message: 'Expected values to be strictly equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
'+ {\n' +
|
||||
'+ a: NaN,\n' +
|
||||
'+ b: Infinity,\n' +
|
||||
'+ c: -Infinity\n' +
|
||||
'+ }\n' +
|
||||
"- ''\n",
|
||||
generatedMessage: true
|
||||
}
|
||||
);
|
||||
|
||||
// https://github.com/nodejs/node-v0.x-archive/issues/5292
|
||||
assert.throws(
|
||||
() => assert.strictEqual(1, 2),
|
||||
{
|
||||
message: `${strictEqualMessageStart}\n1 !== 2\n`,
|
||||
message: 'Expected values to be strictly equal:\n\n1 !== 2\n',
|
||||
generatedMessage: true
|
||||
}
|
||||
);
|
||||
@ -472,7 +536,7 @@ test('Long values should be truncated for display', () => {
|
||||
assert.strictEqual(err.code, 'ERR_ASSERTION');
|
||||
assert.strictEqual(err.message,
|
||||
`${strictEqualMessageStart}+ actual - expected\n\n` +
|
||||
`+ '${'A'.repeat(1000)}'\n- ''`);
|
||||
`+ '${'A'.repeat(1000)}'\n- ''\n`);
|
||||
assert.strictEqual(err.actual.length, 1000);
|
||||
assert.ok(inspect(err).includes(`actual: '${'A'.repeat(488)}...'`));
|
||||
return true;
|
||||
@ -485,7 +549,7 @@ test('Output that extends beyond 10 lines should also be truncated for display',
|
||||
assert.strictEqual(multilineString, '');
|
||||
}, (err) => {
|
||||
assert.strictEqual(err.code, 'ERR_ASSERTION');
|
||||
assert.strictEqual(err.message.split('\n').length, 19);
|
||||
assert.strictEqual(err.message.split('\n').length, 20);
|
||||
assert.strictEqual(err.actual.split('\n').length, 16);
|
||||
assert.ok(inspect(err).includes(
|
||||
"actual: 'fhqwhgads\\n' +\n" +
|
||||
@ -566,84 +630,117 @@ test('Test strict assert', () => {
|
||||
Error.stackTraceLimit = tmpLimit;
|
||||
|
||||
// Test error diffs.
|
||||
let message = [
|
||||
start,
|
||||
`${actExp} ... Lines skipped`,
|
||||
'',
|
||||
' [',
|
||||
' [',
|
||||
' [',
|
||||
' 1,',
|
||||
' 2,',
|
||||
'+ 3',
|
||||
"- '3'",
|
||||
' ]',
|
||||
'...',
|
||||
' 4,',
|
||||
' 5',
|
||||
' ]'].join('\n');
|
||||
let message = 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
' [\n' +
|
||||
' [\n' +
|
||||
' [\n' +
|
||||
' 1,\n' +
|
||||
' 2,\n' +
|
||||
'+ 3\n' +
|
||||
"- '3'\n" +
|
||||
' ]\n' +
|
||||
' ],\n' +
|
||||
' 4,\n' +
|
||||
' 5\n' +
|
||||
' ]\n';
|
||||
strict.throws(
|
||||
() => strict.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]),
|
||||
{ message });
|
||||
|
||||
message = [
|
||||
start,
|
||||
`${actExp} ... Lines skipped`,
|
||||
'',
|
||||
' [',
|
||||
' 1,',
|
||||
'...',
|
||||
' 1,',
|
||||
' 0,',
|
||||
'- 1,',
|
||||
' 1,',
|
||||
'...',
|
||||
' 1,',
|
||||
' 1',
|
||||
' ]',
|
||||
].join('\n');
|
||||
message = 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'... Skipped lines\n' +
|
||||
'\n' +
|
||||
' [\n' +
|
||||
' 1,\n' +
|
||||
' 1,\n' +
|
||||
' 1,\n' +
|
||||
' 0,\n' +
|
||||
'...\n' +
|
||||
' 1,\n' +
|
||||
'+ 1\n' +
|
||||
' ]\n';
|
||||
strict.throws(
|
||||
() => strict.deepEqual(
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1]),
|
||||
[1, 1, 1, 0, 1, 1, 1, 1],
|
||||
[1, 1, 1, 0, 1, 1, 1]),
|
||||
{ message });
|
||||
|
||||
message = [
|
||||
start,
|
||||
`${actExp} ... Lines skipped`,
|
||||
'',
|
||||
' [',
|
||||
' 1,',
|
||||
'...',
|
||||
' 1,',
|
||||
' 0,',
|
||||
'+ 1,',
|
||||
' 1,',
|
||||
' 1,',
|
||||
' 1',
|
||||
' ]',
|
||||
].join('\n');
|
||||
strict.throws(
|
||||
() => strict.deepEqual(
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1]),
|
||||
{ message });
|
||||
message = 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
' [\n' +
|
||||
' 1,\n' +
|
||||
' 2,\n' +
|
||||
' 3,\n' +
|
||||
' 4,\n' +
|
||||
' 5,\n' +
|
||||
'+ 6,\n' +
|
||||
'- 9,\n' +
|
||||
' 7\n' +
|
||||
' ]\n';
|
||||
|
||||
message = [
|
||||
start,
|
||||
actExp,
|
||||
'',
|
||||
' [',
|
||||
' 1,',
|
||||
'+ 2,',
|
||||
'- 1,',
|
||||
' 1,',
|
||||
' 1,',
|
||||
' 0,',
|
||||
'+ 1,',
|
||||
' 1',
|
||||
' ]',
|
||||
].join('\n');
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual([1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 9, 7]),
|
||||
{ message }
|
||||
);
|
||||
|
||||
message = 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
' [\n' +
|
||||
' 1,\n' +
|
||||
' 2,\n' +
|
||||
' 3,\n' +
|
||||
' 4,\n' +
|
||||
' 5,\n' +
|
||||
' 6,\n' +
|
||||
'+ 7,\n' +
|
||||
'- 9,\n' +
|
||||
' 8\n' +
|
||||
' ]\n';
|
||||
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual([1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 9, 8]),
|
||||
{ message }
|
||||
);
|
||||
|
||||
message = 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'... Skipped lines\n' +
|
||||
'\n' +
|
||||
' [\n' +
|
||||
' 1,\n' +
|
||||
' 2,\n' +
|
||||
' 3,\n' +
|
||||
' 4,\n' +
|
||||
'...\n' +
|
||||
' 7,\n' +
|
||||
'+ 8,\n' +
|
||||
'- 0,\n' +
|
||||
' 9\n' +
|
||||
' ]\n';
|
||||
|
||||
assert.throws(
|
||||
() => assert.deepStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 0, 9]),
|
||||
{ message }
|
||||
);
|
||||
|
||||
message = 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
' [\n' +
|
||||
' 1,\n' +
|
||||
'+ 2,\n' +
|
||||
' 1,\n' +
|
||||
' 1,\n' +
|
||||
'- 1,\n' +
|
||||
' 0,\n' +
|
||||
' 1,\n' +
|
||||
'+ 1\n' +
|
||||
' ]\n';
|
||||
strict.throws(
|
||||
() => strict.deepEqual(
|
||||
[1, 2, 1, 1, 0, 1, 1],
|
||||
@ -659,7 +756,7 @@ test('Test strict assert', () => {
|
||||
'+ 2,',
|
||||
'+ 1',
|
||||
'+ ]',
|
||||
'- undefined',
|
||||
'- undefined\n',
|
||||
].join('\n');
|
||||
strict.throws(
|
||||
() => strict.deepEqual([1, 2, 1], undefined),
|
||||
@ -673,22 +770,25 @@ test('Test strict assert', () => {
|
||||
'+ 1,',
|
||||
' 2,',
|
||||
' 1',
|
||||
' ]',
|
||||
' ]\n',
|
||||
].join('\n');
|
||||
strict.throws(
|
||||
() => strict.deepEqual([1, 2, 1], [2, 1]),
|
||||
{ message });
|
||||
|
||||
message = `${start}\n` +
|
||||
`${actExp} ... Lines skipped\n` +
|
||||
'\n' +
|
||||
' [\n' +
|
||||
'+ 1,\n'.repeat(25) +
|
||||
'...\n' +
|
||||
'- 2,\n'.repeat(25) +
|
||||
'...';
|
||||
message = 'Expected values to be strictly deep-equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
' [\n' +
|
||||
'+ 1,\n'.repeat(10) +
|
||||
'+ 3\n' +
|
||||
'- 2,\n'.repeat(11) +
|
||||
'- 4,\n' +
|
||||
'- 4,\n' +
|
||||
'- 4\n' +
|
||||
' ]\n';
|
||||
strict.throws(
|
||||
() => strict.deepEqual(Array(28).fill(1), Array(28).fill(2)),
|
||||
() => strict.deepEqual([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4]),
|
||||
{ message });
|
||||
|
||||
const obj1 = {};
|
||||
@ -703,7 +803,7 @@ test('Test strict assert', () => {
|
||||
'- {\n' +
|
||||
'- [Symbol(nodejs.util.inspect.custom)]: [Function (anonymous)],\n' +
|
||||
"- loop: 'forever'\n" +
|
||||
'- }'
|
||||
'- }\n'
|
||||
});
|
||||
|
||||
// notDeepEqual tests
|
||||
@ -823,7 +923,7 @@ test('Additional asserts', () => {
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
"+ 'string'\n" +
|
||||
'- false'
|
||||
'- false\n'
|
||||
}
|
||||
);
|
||||
|
||||
@ -839,7 +939,7 @@ test('Additional asserts', () => {
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
"+ 'string'\n" +
|
||||
'- false'
|
||||
'- false\n'
|
||||
}
|
||||
);
|
||||
|
||||
@ -855,7 +955,7 @@ test('Additional asserts', () => {
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
"+ 'string'\n" +
|
||||
'- false'
|
||||
'- false\n'
|
||||
}
|
||||
);
|
||||
/* eslint-enable @stylistic/js/indent */
|
||||
@ -1008,7 +1108,7 @@ test('Throws accepts objects', () => {
|
||||
'- foo: undefined,\n' +
|
||||
" message: 'Wrong value',\n" +
|
||||
" name: 'TypeError'\n" +
|
||||
' }'
|
||||
' }\n'
|
||||
}
|
||||
);
|
||||
|
||||
@ -1026,7 +1126,7 @@ test('Throws accepts objects', () => {
|
||||
'- foo: undefined,\n' +
|
||||
" message: 'Wrong value',\n" +
|
||||
" name: 'TypeError'\n" +
|
||||
' }'
|
||||
' }\n'
|
||||
}
|
||||
);
|
||||
|
||||
@ -1060,7 +1160,7 @@ test('Throws accepts objects', () => {
|
||||
" message: 'e',\n" +
|
||||
"+ name: 'TypeError'\n" +
|
||||
"- name: 'Error'\n" +
|
||||
' }'
|
||||
' }\n'
|
||||
}
|
||||
);
|
||||
assert.throws(
|
||||
@ -1074,7 +1174,7 @@ test('Throws accepts objects', () => {
|
||||
"+ message: 'foo',\n" +
|
||||
"- message: '',\n" +
|
||||
" name: 'Error'\n" +
|
||||
' }'
|
||||
' }\n'
|
||||
}
|
||||
);
|
||||
|
||||
@ -1142,7 +1242,7 @@ test('Additional assert', () => {
|
||||
{
|
||||
message: 'Expected "actual" to be reference-equal to "expected":\n' +
|
||||
'+ actual - expected\n\n' +
|
||||
"+ [Arguments] {\n- {\n '0': 'a'\n }"
|
||||
"+ [Arguments] {\n- {\n '0': 'a'\n }\n"
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -1169,7 +1269,7 @@ test('Additional assert', () => {
|
||||
"+ message: 'foobar',\n" +
|
||||
'- message: /fooa/,\n' +
|
||||
" name: 'TypeError'\n" +
|
||||
' }'
|
||||
' }\n'
|
||||
}
|
||||
);
|
||||
|
||||
@ -1190,7 +1290,7 @@ test('Additional assert', () => {
|
||||
'+ null\n' +
|
||||
'- {\n' +
|
||||
"- message: 'foo'\n" +
|
||||
'- }'
|
||||
'- }\n'
|
||||
}
|
||||
);
|
||||
|
||||
@ -1204,7 +1304,7 @@ test('Additional assert', () => {
|
||||
),
|
||||
{
|
||||
actual,
|
||||
message: "message\n+ actual - expected\n\n+ 'foobar'\n- {\n- message: 'foobar'\n- }",
|
||||
message: "message\n+ actual - expected\n\n+ 'foobar'\n- {\n- message: 'foobar'\n- }\n",
|
||||
operator: 'throws',
|
||||
generatedMessage: false
|
||||
}
|
||||
@ -1217,11 +1317,12 @@ test('Additional assert', () => {
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: strictEqualMessageStart +
|
||||
'+ actual - expected\n\n' +
|
||||
"+ 'test test'\n" +
|
||||
"- 'test foobar'\n" +
|
||||
' ^'
|
||||
message: 'Expected values to be strictly equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
"+ 'test test'\n" +
|
||||
"- 'test foobar'\n" +
|
||||
' ^\n',
|
||||
}
|
||||
);
|
||||
|
||||
@ -1258,7 +1359,7 @@ test('Additional assert', () => {
|
||||
{
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: 'custom message\n+ actual - expected\n\n {\n+ a: true\n- a: false\n }'
|
||||
message: 'custom message\n+ actual - expected\n\n {\n+ a: true\n- a: false\n }\n'
|
||||
}
|
||||
);
|
||||
|
||||
@ -1489,19 +1590,6 @@ test('Additional assert', () => {
|
||||
);
|
||||
assert.doesNotMatch('I will pass', /different$/);
|
||||
}
|
||||
|
||||
{
|
||||
const tempColor = inspect.defaultOptions.colors;
|
||||
assert.throws(() => {
|
||||
inspect.defaultOptions.colors = true;
|
||||
// Guarantee the position indicator is placed correctly.
|
||||
assert.strictEqual(111554n, 11111115);
|
||||
}, (err) => {
|
||||
assert.strictEqual(inspect(err).split('\n')[5], ' ^');
|
||||
inspect.defaultOptions.colors = tempColor;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('assert/strict exists', () => {
|
||||
|
@ -9,16 +9,16 @@ assert.throws(() => {
|
||||
assert.deepStrictEqual([1, 2, 2, 2, 2], [2, 2, 2, 2, 2]);
|
||||
}, (err) => {
|
||||
const expected = 'Expected values to be strictly deep-equal:\n' +
|
||||
'\u001b[32m+ actual\u001b[39m \u001b[31m- expected\u001b[39m' +
|
||||
' \u001b[34m...\u001b[39m Lines skipped\n\n' +
|
||||
' [\n' +
|
||||
'\u001b[32m+\u001b[39m 1,\n' +
|
||||
'\u001b[31m-\u001b[39m 2,\n' +
|
||||
' 2,\n' +
|
||||
'\u001b[34m...\u001b[39m\n' +
|
||||
' 2,\n' +
|
||||
' 2\n' +
|
||||
' ]';
|
||||
'\x1B[32m+ actual\x1B[39m \x1B[31m- expected\x1B[39m\n' +
|
||||
'\n' +
|
||||
'\x1B[39m [\n' +
|
||||
'\x1B[32m+\x1B[39m 1,\n' +
|
||||
'\x1B[39m 2,\n' +
|
||||
'\x1B[39m 2,\n' +
|
||||
'\x1B[39m 2,\n' +
|
||||
'\x1B[39m 2,\n' +
|
||||
'\x1B[31m-\x1B[39m 2\n' +
|
||||
'\x1B[39m ]\n';
|
||||
assert.strictEqual(err.message, expected);
|
||||
return true;
|
||||
});
|
||||
|
@ -15,5 +15,5 @@ assert.throws(
|
||||
'+ {}\n' +
|
||||
'- {\n' +
|
||||
'- foo: \'bar\'\n' +
|
||||
'- }',
|
||||
'- }\n',
|
||||
});
|
||||
|
@ -2,17 +2,39 @@
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
process.env.NODE_DISABLE_COLORS = true;
|
||||
process.env.NODE_DISABLE_COLORS = '1';
|
||||
|
||||
// Does not show the indicator when the terminal is too narrow
|
||||
process.stderr.columns = 20;
|
||||
|
||||
// Confirm that there is no position indicator.
|
||||
assert.throws(
|
||||
() => { assert.strictEqual('a'.repeat(30), 'a'.repeat(31)); },
|
||||
() => { assert.strictEqual('123456789ABCDEFGHI', '1!3!5!7!9!BC!!!GHI'); },
|
||||
(err) => !err.message.includes('^'),
|
||||
);
|
||||
|
||||
// Confirm that there is a position indicator.
|
||||
// Does not show the indicator because the first difference is in the first 2 chars
|
||||
process.stderr.columns = 80;
|
||||
assert.throws(
|
||||
() => { assert.strictEqual('aaaa', 'aaaaa'); },
|
||||
(err) => err.message.includes('^'),
|
||||
() => { assert.strictEqual('123456789ABCDEFGHI', '1!3!5!7!9!BC!!!GHI'); },
|
||||
{
|
||||
message: 'Expected values to be strictly equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
"+ '123456789ABCDEFGHI'\n" +
|
||||
"- '1!3!5!7!9!BC!!!GHI'\n",
|
||||
},
|
||||
);
|
||||
|
||||
// Show the indicator because the first difference is in the 3 char
|
||||
process.stderr.columns = 80;
|
||||
assert.throws(
|
||||
() => { assert.strictEqual('123456789ABCDEFGHI', '12!!5!7!9!BC!!!GHI'); },
|
||||
{
|
||||
message: 'Expected values to be strictly equal:\n' +
|
||||
'+ actual - expected\n' +
|
||||
'\n' +
|
||||
"+ '123456789ABCDEFGHI'\n" +
|
||||
"- '12!!5!7!9!BC!!!GHI'\n" +
|
||||
' ^\n',
|
||||
},
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user