From f6f68cb2db1cb489ddef139e269da2201c4df196 Mon Sep 17 00:00:00 2001 From: semimikoh Date: Sat, 18 Apr 2026 12:34:15 +0900 Subject: [PATCH 1/2] assert: avoid expensive diff for large values --- lib/internal/assert/assertion_error.js | 16 ++++++++++++++++ test/parallel/test-assert-deep.js | 13 +++++++++++++ 2 files changed, 29 insertions(+) diff --git a/lib/internal/assert/assertion_error.js b/lib/internal/assert/assertion_error.js index 5dbf1e7a341380..8fb8134ac1d6cd 100644 --- a/lib/internal/assert/assertion_error.js +++ b/lib/internal/assert/assertion_error.js @@ -42,6 +42,8 @@ const kReadableOperator = { const kMaxShortStringLength = 12; const kMaxLongStringLength = 512; +const kMaxDiffLineCount = 1000; +const kMaxDiffLinesToShow = 50; const kMethodsWithCustomMessageDiff = new SafeSet() .add('deepStrictEqual') @@ -182,6 +184,13 @@ function isSimpleDiff(actual, inspectedActual, expected, inspectedExpected) { return typeof actual !== 'object' || actual === null || typeof expected !== 'object' || expected === null; } +function getTruncatedDiffValue(lines) { + if (lines.length > kMaxDiffLinesToShow) { + return `${ArrayPrototypeJoin(ArrayPrototypeSlice(lines, 0, kMaxDiffLinesToShow), '\n')}\n...`; + } + return ArrayPrototypeJoin(lines, '\n'); +} + function createErrDiff(actual, expected, operator, customMessage, diffType = 'simple') { operator = checkOperator(actual, expected, operator); @@ -213,6 +222,13 @@ function createErrDiff(actual, expected, operator, customMessage, diffType = 'si message = ArrayPrototypeJoin(inspectedSplitActual, '\n'); } header = ''; + } else if ( + diffType !== 'full' && + inspectedSplitActual.length + inspectedSplitExpected.length > kMaxDiffLineCount + ) { + message = `\n${colors.green}+${colors.white} ${getTruncatedDiffValue(inspectedSplitActual)}\n` + + `${colors.red}-${colors.white} ${getTruncatedDiffValue(inspectedSplitExpected)}`; + skipped = true; } else { const checkCommaDisparity = actual != null && typeof actual === 'object'; const diff = myersDiff(inspectedSplitActual, inspectedSplitExpected, checkCommaDisparity); diff --git a/test/parallel/test-assert-deep.js b/test/parallel/test-assert-deep.js index 5d856bf1f378b9..dc72f5c7654394 100644 --- a/test/parallel/test-assert-deep.js +++ b/test/parallel/test-assert-deep.js @@ -1180,6 +1180,19 @@ test('Strict equal with identical objects that are not identical ' + ); }); +test('Strict equal skips line diff for very large objects', () => { + const buffer = Buffer.alloc(1_000); + + assert.throws( + () => assert.strictEqual(buffer, [buffer]), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: /Skipped lines[\s\S]*Buffer\(1000\)/ + } + ); +}); + test('Basic valueOf check', () => { const a = new String(1); a.valueOf = undefined; From f41f6cf72372df096af26466b13714f0268e405d Mon Sep 17 00:00:00 2001 From: semimikoh Date: Mon, 20 Apr 2026 15:40:03 +0900 Subject: [PATCH 2/2] assert: address large diff review feedback --- lib/internal/assert/assertion_error.js | 24 +++++++++++++++--------- test/parallel/test-assert-deep.js | 15 +++++++++++++++ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/lib/internal/assert/assertion_error.js b/lib/internal/assert/assertion_error.js index 8fb8134ac1d6cd..1561f7804efbb1 100644 --- a/lib/internal/assert/assertion_error.js +++ b/lib/internal/assert/assertion_error.js @@ -191,6 +191,12 @@ function getTruncatedDiffValue(lines) { return ArrayPrototypeJoin(lines, '\n'); } +function hasLargeRootMismatch(inspectedActual, inspectedExpected, diffType) { + return diffType !== 'full' && + inspectedActual.length + inspectedExpected.length > kMaxDiffLineCount && + inspectedActual[0] !== inspectedExpected[0]; +} + function createErrDiff(actual, expected, operator, customMessage, diffType = 'simple') { operator = checkOperator(actual, expected, operator); @@ -222,11 +228,11 @@ function createErrDiff(actual, expected, operator, customMessage, diffType = 'si message = ArrayPrototypeJoin(inspectedSplitActual, '\n'); } header = ''; - } else if ( - diffType !== 'full' && - inspectedSplitActual.length + inspectedSplitExpected.length > kMaxDiffLineCount - ) { - message = `\n${colors.green}+${colors.white} ${getTruncatedDiffValue(inspectedSplitActual)}\n` + + } else if (hasLargeRootMismatch(inspectedSplitActual, inspectedSplitExpected, diffType)) { + const actualPrefix = operator === 'partialDeepStrictEqual' ? + `${colors.gray}${colors.hasColors ? ' ' : '+'}` : + `${colors.green}+${colors.white}`; + message = `\n${actualPrefix} ${getTruncatedDiffValue(inspectedSplitActual)}\n` + `${colors.red}-${colors.white} ${getTruncatedDiffValue(inspectedSplitExpected)}`; skipped = true; } else { @@ -236,15 +242,15 @@ function createErrDiff(actual, expected, operator, customMessage, diffType = 'si const myersDiffMessage = printMyersDiff(diff, operator); message = myersDiffMessage.message; - if (operator === 'partialDeepStrictEqual') { - header = `${colors.gray}${colors.hasColors ? '' : '+ '}actual${colors.white} ${colors.red}- expected${colors.white}`; - } - if (myersDiffMessage.skipped) { skipped = true; } } + if (operator === 'partialDeepStrictEqual') { + header = `${colors.gray}${colors.hasColors ? '' : '+ '}actual${colors.white} ${colors.red}- expected${colors.white}`; + } + const headerMessage = `${getErrorMessage(operator, customMessage)}\n${header}`; const skippedMessage = skipped ? '\n... Skipped lines' : ''; diff --git a/test/parallel/test-assert-deep.js b/test/parallel/test-assert-deep.js index dc72f5c7654394..28cfb31e368899 100644 --- a/test/parallel/test-assert-deep.js +++ b/test/parallel/test-assert-deep.js @@ -1193,6 +1193,21 @@ test('Strict equal skips line diff for very large objects', () => { ); }); +test('Deep strict equal preserves line diff for large values with the same root', () => { + const actual = new Array(1_000).fill(0); + const expected = new Array(1_000).fill(0); + expected[10] = 1; + + assert.throws( + () => assert.deepStrictEqual(actual, expected), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: /- {3}1/ + } + ); +}); + test('Basic valueOf check', () => { const a = new String(1); a.valueOf = undefined;