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)