From deebfa36e6912d7bcd56a522f3d029a28a7745e6 Mon Sep 17 00:00:00 2001 From: Oaksprout Date: Tue, 28 Jul 2026 12:58:25 +0100 Subject: [PATCH] assert: reduce truncatingFormat limit for readable failure output truncatingFormat's limit was sized (~32KB) purely to keep a formatted value from making the assembled failure message exceed the bufio.MaxScanTokenSize line-length limit go test's output scanner imposes, which was enough to stop failure output being dropped entirely (#1525) but still let a single assertion swamp the console with tens of kilobytes of unreadable output. As suggested by @brackendawson on #1801, reduce the limit significantly instead of special-casing Len: 4000 bytes, matching the default MaxLength used by Gomega's format package for the same purpose, while staying comfortably below bufio.MaxScanTokenSize so the original guarantee still holds. Anyone who needs the untruncated value can print it with t.Logf. This affects every assertion that shares truncatingFormat (Equal, EqualValues, EqualExportedValues, NotEqual, NotEqualValues, Nil, Empty, Len, Contains, NotContains, Subset, NotSubset, Same, NotSame, NoError, EqualError, ErrorContains, ErrorIs, NotErrorIs, ErrorAs, NotErrorAs, Zero), which is the intended scope per the issue thread. ElementsMatch is out of scope: it does not use truncatingFormat. Fixes #1801 --- assert/assertions.go | 22 ++++++++++++++++++---- assert/assertions_test.go | 22 ++++++++++++++++++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index 1419e4776..78139b4d3 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -614,14 +614,28 @@ func formatUnequalValues(expected, actual interface{}) (e string, a string) { return truncatingFormat("%#v", expected), truncatingFormat("%#v", actual) } +// maxMessageSize is the maximum length, in bytes, of a single formatted +// value that truncatingFormat will print before appending "<... truncated>". +// +// This used to be derived from bufio.MaxScanTokenSize, sized just small +// enough that two truncated values plus their surrounding sentence couldn't +// exceed the line-length limit go test's output scanner imposes; that +// avoided losing failure output entirely (#1525), but a limit of ~32KB per +// value still let a single assertion swamp the console with output no one +// can read (#1801). 4000 keeps failure output readable, matches the default +// MaxLength used by Gomega's format package (github.com/onsi/gomega/format) +// for the same purpose, and remains comfortably below bufio.MaxScanTokenSize +// so the original line-length guarantee still holds. Anyone who needs the +// untruncated value can print it themselves, e.g. via t.Logf. +const maxMessageSize = 4000 + // truncatingFormat formats the data and truncates it if it's too long. // -// This helps keep formatted error messages lines from exceeding the -// bufio.MaxScanTokenSize max line length that the go testing framework imposes. +// This helps keep formatted error messages readable and ensures they don't +// exceed the bufio.MaxScanTokenSize max line length that the go testing +// framework imposes. func truncatingFormat(format string, data interface{}) string { value := fmt.Sprintf(format, data) - // Give us space for two truncated objects and the surrounding sentence. - maxMessageSize := bufio.MaxScanTokenSize/2 - 100 if len(value) > maxMessageSize { value = value[0:maxMessageSize] + "<... truncated>" } diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 11642e096..f9b45f0a0 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -1,7 +1,6 @@ package assert import ( - "bufio" "bytes" "encoding/json" "errors" @@ -3653,7 +3652,7 @@ func Test_validateEqualArgs(t *testing.T) { func Test_truncatingFormat(t *testing.T) { t.Parallel() - original := strings.Repeat("a", bufio.MaxScanTokenSize/2-102) + original := strings.Repeat("a", maxMessageSize-2) result := truncatingFormat("%#v", original) Equal(t, fmt.Sprintf("%#v", original), result, "string should not be truncated") @@ -3981,6 +3980,25 @@ func TestLenWithSliceTooLongToPrint(t *testing.T) { Contains(t, mockT.errorString(), `<... truncated>" should have 1 item(s), but has 1000000`) } +// TestLenWithSliceTooLongToPrintIsReadable is a regression test for +// https://github.com/stretchr/testify/issues/1801. Truncating the printed +// value at all (TestLenWithSliceTooLongToPrint above) is not enough on its +// own: the pre-#1801 limit still let a single assertion dump tens of +// kilobytes into the console, which is just as unreadable as no output at +// all. The failure message must be truncated to a size a human can actually +// read in a terminal. +func TestLenWithSliceTooLongToPrintIsReadable(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + Len(mockT, longSlice, 1) + errStr := mockT.errorString() + Contains(t, errStr, "<... truncated>") + if len(errStr) > 8000 { + t.Errorf("Len failure message on a very large slice is %d bytes, want a readable size (<=8000 bytes):\n%s", len(errStr), errStr) + } +} + func TestContainsWithSliceTooLongToPrint(t *testing.T) { t.Parallel() mockT := new(mockTestingT)