From 13b2f655401a00670f089a068ffa872fd4c1eed6 Mon Sep 17 00:00:00 2001 From: Giovanni Date: Mon, 9 Sep 2024 09:58:25 +0200 Subject: [PATCH] assert: make assertion_error user mysers diff algorithm Fixes: https://github.com/nodejs/node/issues/51733 Co-Authored-By: Pietro Marchini --- lib/internal/assert/assertion_error.js | 310 +++-------------- lib/internal/assert/myers_diff.js | 269 +++++++++++++++ lib/internal/util/inspect.js | 35 +- test/parallel/test-assert-checktag.js | 6 +- test/parallel/test-assert-deep.js | 181 ++++++++-- test/parallel/test-assert.js | 325 ++++++++++-------- test/pseudo-tty/test-assert-colors.js | 20 +- test/pseudo-tty/test-assert-no-color.js | 2 +- .../test-assert-position-indicator.js | 17 +- 9 files changed, 727 insertions(+), 438 deletions(-) create mode 100644 lib/internal/assert/myers_diff.js diff --git a/lib/internal/assert/assertion_error.js b/lib/internal/assert/assertion_error.js index 5d8c45040af0fe..450e566304b7e1 100644 --- a/lib/internal/assert/assertion_error.js +++ b/lib/internal/assert/assertion_error.js @@ -1,31 +1,27 @@ 'use strict'; const { + ArrayIsArray, ArrayPrototypeJoin, ArrayPrototypePop, Error, ErrorCaptureStackTrace, - MathMax, + Map, + Object, ObjectAssign, ObjectDefineProperty, ObjectGetPrototypeOf, + ObjectKeys, 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 } = require('internal/assert/myers_diff'); const kReadableOperator = { deepStrictEqual: 'Expected values to be strictly deep-equal:', @@ -41,269 +37,77 @@ 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; - 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 createErrDiff(actual, expected, operator) { - let other = ''; - let res = ''; - let end = ''; - let skipped = false; - const actualInspected = inspectValue(actual); - const actualLines = StringPrototypeSplit(actualInspected, '\n'); - const expectedLines = StringPrototypeSplit(inspectValue(expected), '\n'); +function showSimpleDiff(test) { + const isPrimitive = test !== Object(test); + const isEmptyArray = ArrayIsArray(test) && test.length === 0; + const isSimpleObject = !isPrimitive && !(test instanceof Map) && ObjectKeys(test).length === 0; - let i = 0; - let indicator = ''; + return isPrimitive || isEmptyArray || isSimpleObject; +} +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 `${kReadableOperator[operator]}\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; - } - } - } - } - - // 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}`; - } else { - other = a; - } - ArrayPrototypePop(actualLines); - ArrayPrototypePop(expectedLines); - if (actualLines.length === 0 || expectedLines.length === 0) - break; - a = actualLines[actualLines.length - 1]; - b = expectedLines[expectedLines.length - 1]; - } - - 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'); - - // 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 = ''; - } + return operator; +} - let printedLines = 0; - let identical = 0; - const msg = kReadableOperator[operator] + - `\n${colors.green}+ actual${colors.white} ${colors.red}- expected${colors.white}`; - const skippedMsg = ` ${colors.blue}...${colors.white} Lines skipped`; +function createErrDiff(actual, expected, operator) { + operator = checkOperator(actual, expected, operator); + const nopSkippedMessage = `\n${colors.blue}...${colors.white} Lines skipped which didn't differ`; + const insertedSkippedMessage = `\n${colors.green}...${colors.white} Lines skipped which were inserted`; + const deletedSkippedMessage = `\n${colors.red}...${colors.white} Lines skipped which were deleted`; - 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; - } + const simpleDiff = showSimpleDiff(actual) && showSimpleDiff(expected); + const isStringComparison = typeof actual === 'string' && typeof expected === 'string'; + const header = simpleDiff ? '' : `${colors.green}+ actual${colors.white} ${colors.red}- expected${colors.white}`; + const headerMessage = `${kReadableOperator[operator]}\n${header}`; - 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}`; - } - } + const diff = myersDiff(actual, expected, true); + const { message, skipped } = printMyersDiff(diff, simpleDiff, isStringComparison); + const skippedMessahe = skipped ? `${nopSkippedMessage}${insertedSkippedMessage}${deletedSkippedMessage}` : ''; - return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}${indicator}`; + return `${headerMessage}${skippedMessahe}\n${message}\n`; } function addEllipsis(string) { diff --git a/lib/internal/assert/myers_diff.js b/lib/internal/assert/myers_diff.js new file mode 100644 index 00000000000000..d05fb7bcb7d105 --- /dev/null +++ b/lib/internal/assert/myers_diff.js @@ -0,0 +1,269 @@ +'use strict'; + +const { + Array, + ArrayPrototypeFill, + ArrayPrototypePush, + ArrayPrototypeSlice, + ArrayPrototypeSplice, + Object, + StringPrototypeEndsWith, + StringPrototypeSlice, + StringPrototypeSplit, +} = primordials; + +const colors = require('internal/util/colors'); +const { inspect } = require('internal/util/inspect'); + +const kStartSingleArrayPlaceholder = '|**~__'; +const kEndSingleArrayPlaceholder = '__~**|'; +const kMaxStringLength = 512; +const kNopLinesToCollapse = 5; + +function inspectValue(val, additionalConfig = {}) { + return inspect(val, { + compact: false, + customInspect: false, + depth: 1000, + maxArrayLength: Infinity, + showHidden: false, + showProxy: false, + sorted: true, + getters: true, + ...additionalConfig, + }); +} + +function isObject(val) { + return val != null && val.constructor === Object; +} + +function areLinesEqual(actual, expected, checkCommaDisparity) { + if (!checkCommaDisparity) { + return actual === expected; + } + return actual === expected || `${actual},` === expected || actual === `${expected},`; +} + +function getSingularizeArrayValuesString(value) { + return `${kStartSingleArrayPlaceholder}${value}${kEndSingleArrayPlaceholder}`; +} + +function removePlaceholder(value) { + if (typeof value !== 'string') { + return value; + } + const startIndex = value.indexOf(kStartSingleArrayPlaceholder); + const endIndex = value.indexOf(kEndSingleArrayPlaceholder) + kEndSingleArrayPlaceholder.length; + + if (startIndex !== -1 && endIndex !== -1) { + return value.slice(0, startIndex) + value.slice(endIndex); + } + + return value; +} + +function cleanDiffFromSingularizedArrays(diff) { + for (let i = 0; i < diff.length; i++) { + diff[i].value = removePlaceholder(diff[i].value); + } + return diff; +} + +const inspectOptions = { getSingularizeArrayValuesString }; + +function myersDiff(actual, expected, stringify) { + const checkCommaDisparity = actual != null && typeof actual === 'object'; + + if (stringify || isObject(actual)) { + actual = StringPrototypeSplit(inspectValue(actual, inspectOptions), '\n'); + } + if (stringify || isObject(expected)) { + expected = StringPrototypeSplit(inspectValue(expected, inspectOptions), '\n'); + } + + 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) { + const diff = backtrack(trace, actual, expected, checkCommaDisparity); + return cleanDiffFromSingularizedArrays(diff); + } + } + } +} + +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, { type: 'nop', value }); + x--; + y--; + } + + if (diffLevel > 0) { + if (x > prevX) { + ArrayPrototypePush(result, { type: 'insert', value: actual[x - 1] }); + x--; + } else { + ArrayPrototypePush(result, { type: 'delete', value: expected[y - 1] }); + y--; + } + } + } + + return result.reverse(); +} + +function formatValue(value) { + if (value.length > kMaxStringLength) { + return `${StringPrototypeSlice(value, 0, kMaxStringLength + 1)}...`; + } + return value; +} + +function pushGroupedLinesMessage(message, color) { + ArrayPrototypeSplice(message, message.length - 1, 0, `${colors[color]}...${colors.white}`); +} + +function getSimpleDiff(diff, isStringComparison) { + const actual = formatValue(diff[0].value); + const expected = formatValue((diff[1] || diff[0]).value); + + let message = `${actual} !== ${expected}`; + const maxTerminalLength = process.stderr.isTTY ? process.stderr.columns : 80; + const comparisonLength = (actual.slice(1, -1).length + expected.slice(1, -1).length); + const showIndicator = isStringComparison && (comparisonLength <= maxTerminalLength); + + if (showIndicator) { + let indicator = ''; + for (let i = 0; i < actual.length; i++) { + indicator += actual[i] !== expected[i] ? '^' : ' '; + } + message += `\n${indicator}`; + } + + return message; +} + +function printMyersDiff(diff, simpleDiff, isStringComparison) { + if (simpleDiff) { + return { message: getSimpleDiff(diff, isStringComparison), skipped: false }; + } + + const message = []; + let skipped = false; + let previousType = 'null'; + let nopCount = 0; + let lastInserted = null; + let lastDeleted = null; + let identicalInsertedCount = 0; + let identicalDeletedCount = 0; + + for (let diffIdx = 0; diffIdx < diff.length; diffIdx++) { + const { type, value } = diff[diffIdx]; + const typeChanged = previousType && (type !== previousType); + + if (type === 'insert') { + if (!typeChanged && (lastInserted === value)) { + identicalInsertedCount++; + } else { + ArrayPrototypePush(message, `${colors.green}+${colors.white} ${formatValue(value)}`); + } + } else if (type === 'delete') { + if (!typeChanged && (lastDeleted === value)) { + identicalDeletedCount++; + } else { + ArrayPrototypePush(message, `${colors.red}-${colors.white} ${formatValue(value)}`); + } + } else if (type === 'nop') { + if (nopCount <= kNopLinesToCollapse) { + ArrayPrototypePush(message, `${colors.white} ${formatValue(value)}`); + } + nopCount++; + } + + const shouldGroupInsertedLines = ((previousType === 'insert' && typeChanged) || + (type === 'insert' && lastInserted !== value)) && identicalInsertedCount; + const shouldGroupDeletedLines = ((previousType === 'delete' && typeChanged) || + (type === 'delete' && lastDeleted !== value)) && identicalDeletedCount; + + if (typeChanged && previousType === 'nop') { + if (nopCount > kNopLinesToCollapse) { + pushGroupedLinesMessage(message, 'blue'); + skipped = true; + } + nopCount = 0; + } else if (shouldGroupInsertedLines) { + pushGroupedLinesMessage(message, 'green'); + identicalInsertedCount = 0; + skipped = true; + } else if (shouldGroupDeletedLines) { + pushGroupedLinesMessage(message, 'red'); + identicalDeletedCount = 0; + skipped = true; + } + + if (type === 'insert') { + lastInserted = value; + } else if (type === 'delete') { + lastDeleted = value; + } + + previousType = type; + } + + return { message: `\n${message.join('\n')}`, skipped }; +} + +module.exports = { myersDiff, printMyersDiff }; diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 674cb43ab1f01f..a3230b5288beed 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -1739,6 +1739,32 @@ function formatArrayBuffer(ctx, value) { return [`${ctx.stylize('[Uint8Contents]', 'special')}: <${str}>`]; } +function getParentName(ctx) { + if (ctx.seen.length === 0) { + return ''; + } + + let parentName = ''; + for (let parentIdx = ctx.seen.length - 2; parentIdx >= 0; parentIdx--) { + const value = ctx.seen[parentIdx + 1]; + + if (typeof ctx.seen[parentIdx] === 'object') { + for (const key in ctx.seen[parentIdx]) { + if (ctx.seen[parentIdx][key] === value) { + if (!parentName) { + parentName = key; + } else { + parentName = `${key}.${parentName}`; + } + break; + } + } + } + } + + return parentName; +} + function formatArray(ctx, value, recurseTimes) { const valLen = value.length; const len = MathMin(MathMax(0, ctx.maxArrayLength), valLen); @@ -1750,7 +1776,14 @@ function formatArray(ctx, value, recurseTimes) { if (!ObjectPrototypeHasOwnProperty(value, i)) { return formatSpecialArray(ctx, value, recurseTimes, len, output, i); } - ArrayPrototypePush(output, formatProperty(ctx, value, recurseTimes, i, kArrayType)); + const parentName = getParentName(ctx); + let formattedValue = formatProperty(ctx, value, recurseTimes, i, kArrayType); + + if (ctx?.userOptions?.getSingularizeArrayValuesString) { + formattedValue = `${ctx.userOptions.getSingularizeArrayValuesString(parentName)}${formattedValue}`; + } + + ArrayPrototypePush(output, formattedValue); } if (remaining > 0) { ArrayPrototypePush(output, remainingText(remaining)); diff --git a/test/parallel/test-assert-checktag.js b/test/parallel/test-assert-checktag.js index 7ea12b89d75127..c35533a8f625fe 100644 --- a/test/parallel/test-assert-checktag.js +++ b/test/parallel/test-assert-checktag.js @@ -29,14 +29,16 @@ 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 {}' + '\n' + + '2016-01-01T00:00:00.000Z !== 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' + '\n' + + 'Date {} !== 2016-01-01T00:00:00.000Z\n' } ); } diff --git a/test/parallel/test-assert-deep.js b/test/parallel/test-assert-deep.js index a8bc5d3cf4e815..9e2fd3d01ba208 100644 --- a/test/parallel/test-assert-deep.js +++ b/test/parallel/test-assert-deep.js @@ -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,35 @@ test('Additional tests', () => { { code: 'ERR_ASSERTION', name: 'AssertionError', - message: `${defaultMsgStartFull}\n\n+ /ab/\n- /a/` + message: 'Expected values to be strictly deep-equal:\n\n/ab/ !== /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\n/a/g !== /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\n/a/i !== /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\n/a/m !== /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\n/aa/gim !== /aa/im\n' }); { @@ -909,7 +916,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 +924,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 +932,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 +971,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 +1002,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; @@ -1026,7 +1033,15 @@ test('Strict equal with identical objects that are not identical ' + { code: 'ERR_ASSERTION', name: 'AssertionError', - message: /\.\.\./g + message: 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + ' {\n' + + ' symbol0: Symbol(),\n' + + ' symbol10: Symbol(),\n' + + ' symbol11: Symbol(),\n' + + ' symbol12: Symbol(),\n' + + ' symbol13: Symbol(),\n' } ); }); @@ -1052,7 +1067,7 @@ test('Basic array out of bounds check', () => { ' 1,\n' + ' 2,\n' + '+ 3\n' + - ' ]' + ' ]\n' } ); }); @@ -1346,3 +1361,107 @@ test('Comparing two different WeakSet instances', () => { const weakSet2 = new WeakSet(); 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' + + '+ 3\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' + + "... Lines skipped which didn't differ\n" + + '... Lines skipped which were inserted\n' + + '... Lines skipped which were deleted\n' + + '\n' + + ' [\n' + + ' {\n' + + ' a: 1\n' + + ' },\n' + + ' 2,\n' + + ' 3,\n' + + '...\n' + + '+ 1,\n' + + '+ 2,\n' + + ' 3,\n' + + '- 4,\n' + + '- 5\n' + + ' ]\n' + + ' }\n' + + ' ]\n' + } + ); +}); diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index b5e5bb1c9b0a29..c6702e9b2f6a79 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -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,8 @@ 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]' + '\n' + + '[Error: foo] !== [Error: foobar]\n' } ); }); @@ -342,15 +343,14 @@ 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 testShortAssertionMessage(actual, expected) { + function testShortAssertionMessage(actual, expected, end = '') { testAssertionMessage(actual, expected, strictEqualMessageStart + - `\n${inspect(actual)} !== ''\n`); + `\n${inspect(actual)} !== ''\n${end}`); } testShortAssertionMessage(null, 'null'); @@ -359,31 +359,86 @@ test('Test assertion messages', () => { testShortAssertionMessage(100, '100'); testShortAssertionMessage(NaN, 'NaN'); testShortAssertionMessage(Infinity, 'Infinity'); - testShortAssertionMessage('a', '"a"'); - testShortAssertionMessage('foo', '\'foo\''); + testShortAssertionMessage('a', '\'a\'', ' ^^\n'); + testShortAssertionMessage('foo', '\'foo\'', ' ^^^^\n'); 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, - ' {\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+ }'); + + 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' + + '+ {\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 } ); @@ -470,9 +525,9 @@ test('Long values should be truncated for display', () => { assert.strictEqual('A'.repeat(1000), ''); }, (err) => { assert.strictEqual(err.code, 'ERR_ASSERTION'); - assert.strictEqual(err.message, - `${strictEqualMessageStart}+ actual - expected\n\n` + - `+ '${'A'.repeat(1000)}'\n- ''`); + assert.strictEqual(err.message, 'Expected values to be strictly equal:\n' + + '\n' + + `'${'A'.repeat(512)}... !== ''\n`); assert.strictEqual(err.actual.length, 1000); assert.ok(inspect(err).includes(`actual: '${'A'.repeat(488)}...'`)); return true; @@ -485,7 +540,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, 5); assert.strictEqual(err.actual.split('\n').length, 16); assert.ok(inspect(err).includes( "actual: 'fhqwhgads\\n' +\n" + @@ -566,84 +621,80 @@ 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' + + "... Lines skipped which didn't differ\n" + + '... Lines skipped which were inserted\n' + + '... Lines skipped which were deleted\n' + + '\n' + + ' [\n' + + ' 1,\n' + + ' 1,\n' + + ' 1,\n' + + ' 1,\n' + + ' 1,\n' + + '...\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]), { 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' + + "... Lines skipped which didn't differ\n" + + '... Lines skipped which were inserted\n' + + '... Lines skipped which were deleted\n' + + '\n' + + ' [\n' + + ' 1,\n' + + ' 1,\n' + + ' 1,\n' + + ' 0,\n' + + ' 1,\n' + + '...\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, 0, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1]), { message }); - message = [ - start, - actExp, - '', - ' [', - ' 1,', - '+ 2,', - '- 1,', - ' 1,', - ' 1,', - ' 0,', - '+ 1,', - ' 1', - ' ]', - ].join('\n'); + 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 +710,7 @@ test('Test strict assert', () => { '+ 2,', '+ 1', '+ ]', - '- undefined', + '- undefined\n', ].join('\n'); strict.throws( () => strict.deepEqual([1, 2, 1], undefined), @@ -673,22 +724,48 @@ 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` + + message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + "... Lines skipped which didn't differ\n" + + '... Lines skipped which were inserted\n' + + '... Lines skipped which were deleted\n' + + '\n' + + ' [\n' + + '+ 1,\n' + + '...\n' + + '+ 1\n' + + '- 2,\n' + + '...\n' + + '- 2\n' + + ' ]\n'; + strict.throws( + () => strict.deepEqual(Array(28).fill(1), Array(28).fill(2)), + { message }); + + message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + "... Lines skipped which didn't differ\n" + + '... Lines skipped which were inserted\n' + + '... Lines skipped which were deleted\n' + '\n' + ' [\n' + - '+ 1,\n'.repeat(25) + + '+ 1,\n' + + '...\n' + + '+ 3\n' + + '- 2,\n' + + '...\n' + + '- 4,\n' + '...\n' + - '- 2,\n'.repeat(25) + - '...'; + '- 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 +780,7 @@ test('Test strict assert', () => { '- {\n' + '- [Symbol(nodejs.util.inspect.custom)]: [Function (anonymous)],\n' + "- loop: 'forever'\n" + - '- }' + '- }\n' }); // notDeepEqual tests @@ -819,11 +896,7 @@ test('Additional asserts', () => { { code: 'ERR_ASSERTION', constructor: assert.AssertionError, - message: 'Expected values to be strictly equal:\n' + - '+ actual - expected\n' + - '\n' + - "+ 'string'\n" + - '- false' + message: "Expected values to be strictly equal:\n\n'string' !== false\n" } ); @@ -835,11 +908,7 @@ test('Additional asserts', () => { { code: 'ERR_ASSERTION', constructor: assert.AssertionError, - message: 'Expected values to be strictly equal:\n' + - '+ actual - expected\n' + - '\n' + - "+ 'string'\n" + - '- false' + message: "Expected values to be strictly equal:\n\n'string' !== false\n" } ); @@ -851,11 +920,7 @@ test('Additional asserts', () => { }, { code: 'ERR_ASSERTION', constructor: assert.AssertionError, - message: 'Expected values to be strictly equal:\n' + - '+ actual - expected\n' + - '\n' + - "+ 'string'\n" + - '- false' + message: "Expected values to be strictly equal:\n\n'string' !== false\n" } ); /* eslint-enable @stylistic/js/indent */ @@ -1008,7 +1073,7 @@ test('Throws accepts objects', () => { '- foo: undefined,\n' + " message: 'Wrong value',\n" + " name: 'TypeError'\n" + - ' }' + ' }\n' } ); @@ -1026,7 +1091,7 @@ test('Throws accepts objects', () => { '- foo: undefined,\n' + " message: 'Wrong value',\n" + " name: 'TypeError'\n" + - ' }' + ' }\n' } ); @@ -1060,7 +1125,7 @@ test('Throws accepts objects', () => { " message: 'e',\n" + "+ name: 'TypeError'\n" + "- name: 'Error'\n" + - ' }' + ' }\n' } ); assert.throws( @@ -1074,7 +1139,7 @@ test('Throws accepts objects', () => { "+ message: 'foo',\n" + "- message: '',\n" + " name: 'Error'\n" + - ' }' + ' }\n' } ); @@ -1131,7 +1196,7 @@ test('Additional assert', () => { assert.throws( () => assert.strictEqual([], []), { - message: 'Values have same structure but are not reference-equal:\n\n[]\n' + message: 'Expected "actual" to be reference-equal to "expected":\n\n[] !== []\n' } ); @@ -1142,7 +1207,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 +1234,7 @@ test('Additional assert', () => { "+ message: 'foobar',\n" + '- message: /fooa/,\n' + " name: 'TypeError'\n" + - ' }' + ' }\n' } ); @@ -1190,7 +1255,7 @@ test('Additional assert', () => { '+ null\n' + '- {\n' + "- message: 'foo'\n" + - '- }' + '- }\n' } ); @@ -1217,11 +1282,10 @@ 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' + + '\n' + + "'test test' !== 'test foobar'\n" + + ' ^^^^^\n', } ); @@ -1478,19 +1542,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', () => { diff --git a/test/pseudo-tty/test-assert-colors.js b/test/pseudo-tty/test-assert-colors.js index 9d5a923aa0f8e7..d43dd60224a06f 100644 --- a/test/pseudo-tty/test-assert-colors.js +++ b/test/pseudo-tty/test-assert-colors.js @@ -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; }); diff --git a/test/pseudo-tty/test-assert-no-color.js b/test/pseudo-tty/test-assert-no-color.js index 698d29ab8dbceb..d488f4e6bfcbe7 100644 --- a/test/pseudo-tty/test-assert-no-color.js +++ b/test/pseudo-tty/test-assert-no-color.js @@ -15,5 +15,5 @@ assert.throws( '+ {}\n' + '- {\n' + '- foo: \'bar\'\n' + - '- }', + '- }\n', }); diff --git a/test/pseudo-tty/test-assert-position-indicator.js b/test/pseudo-tty/test-assert-position-indicator.js index 9909fee2b4ae72..e5c5e180606e55 100644 --- a/test/pseudo-tty/test-assert-position-indicator.js +++ b/test/pseudo-tty/test-assert-position-indicator.js @@ -5,14 +5,25 @@ const assert = require('assert'); process.env.NODE_DISABLE_COLORS = true; process.stderr.columns = 20; +function checkStrings() { + assert.strictEqual('123456789ABCDEFGHI', '1!3!5!7!9!BC!!!GHI'); +} + // Confirm that there is no position indicator. assert.throws( - () => { assert.strictEqual('a'.repeat(30), 'a'.repeat(31)); }, + () => { checkStrings(); }, (err) => !err.message.includes('^'), ); +process.stderr.columns = 80; + // Confirm that there is a position indicator. assert.throws( - () => { assert.strictEqual('aaaa', 'aaaaa'); }, - (err) => err.message.includes('^'), + () => { checkStrings(); }, + { + message: 'Expected values to be strictly equal:\n' + + '\n' + + "'123456789ABCDEFGHI' !== '1!3!5!7!9!BC!!!GHI'\n" + + ' ^ ^ ^ ^ ^ ^^^ \n', + }, );