From 2f8b16dab77716cf0ff62c3c22a6ff0aead0cb33 Mon Sep 17 00:00:00 2001 From: Bracken Dawson Date: Thu, 3 Oct 2024 16:38:42 +0100 Subject: [PATCH] Truncate very long objects in test failure messages --- assert/assertions.go | 85 +++++++------ assert/assertions_test.go | 257 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 293 insertions(+), 49 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index 44b854da6..e35948dc9 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -307,13 +307,15 @@ func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { func indentMessageLines(message string, longestLabelLen int) string { outBuf := new(bytes.Buffer) - for i, scanner := 0, bufio.NewScanner(strings.NewReader(message)); scanner.Scan(); i++ { - // no need to align first line because it starts at the correct location (after the label) - if i != 0 { - // append alignLen+1 spaces to align with "{{longestLabel}}:" before adding tab - outBuf.WriteString("\n\t" + strings.Repeat(" ", longestLabelLen+1) + "\t") + scanner := bufio.NewScanner(strings.NewReader(message)) + for firstLine := true; scanner.Scan(); firstLine = false { + if !firstLine { + fmt.Fprint(outBuf, "\n\t"+strings.Repeat(" ", longestLabelLen+1)+"\t") } - outBuf.WriteString(scanner.Text()) + fmt.Fprint(outBuf, scanner.Text()) + } + if err := scanner.Err(); err != nil { + return fmt.Sprintf("cannot display message: %s", err) } return outBuf.String() @@ -504,8 +506,8 @@ func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) b if !samePointers(expected, actual) { return Fail(t, fmt.Sprintf("Not same: \n"+ - "expected: %p %#v\n"+ - "actual : %p %#v", expected, expected, actual, actual), msgAndArgs...) + "expected: %p %s\n"+ + "actual : %p %s", expected, truncatingFormat("%#v", expected), actual, truncatingFormat("%#v", actual)), msgAndArgs...) } return true @@ -524,8 +526,8 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} if samePointers(expected, actual) { return Fail(t, fmt.Sprintf( - "Expected and actual point to the same object: %p %#v", - expected, expected), msgAndArgs...) + "Expected and actual point to the same object: %p %s", + expected, truncatingFormat("%#v", expected)), msgAndArgs...) } return true } @@ -555,23 +557,24 @@ func samePointers(first, second interface{}) bool { // to a type conversion in the Go grammar. func formatUnequalValues(expected, actual interface{}) (e string, a string) { if reflect.TypeOf(expected) != reflect.TypeOf(actual) { - return fmt.Sprintf("%T(%s)", expected, truncatingFormat(expected)), - fmt.Sprintf("%T(%s)", actual, truncatingFormat(actual)) + return fmt.Sprintf("%T(%s)", expected, truncatingFormat("%#v", expected)), + fmt.Sprintf("%T(%s)", actual, truncatingFormat("%#v", actual)) } switch expected.(type) { case time.Duration: return fmt.Sprintf("%v", expected), fmt.Sprintf("%v", actual) } - return truncatingFormat(expected), truncatingFormat(actual) + return truncatingFormat("%#v", expected), truncatingFormat("%#v", actual) } // 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. -func truncatingFormat(data interface{}) string { - value := fmt.Sprintf("%#v", data) - max := bufio.MaxScanTokenSize - 100 // Give us some space the type info too if needed. +func truncatingFormat(format string, data interface{}) string { + value := fmt.Sprintf(format, data) + // Give us space for two truncated objects and the surrounding sentence. + max := bufio.MaxScanTokenSize/2 - 100 if len(value) > max { value = value[0:max] + "<... truncated>" } @@ -711,7 +714,7 @@ func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } - return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...) + return Fail(t, fmt.Sprintf("Expected nil, but got: %s", truncatingFormat("%#v", object)), msgAndArgs...) } // isEmpty gets whether the specified object is considered empty or not. @@ -753,7 +756,7 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } - Fail(t, fmt.Sprintf("Should be empty, but was %v", object), msgAndArgs...) + Fail(t, fmt.Sprintf("Should be empty, but was %s", truncatingFormat("%v", object)), msgAndArgs...) } return pass @@ -799,11 +802,11 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) } l, ok := getLen(object) if !ok { - return Fail(t, fmt.Sprintf("\"%v\" could not be applied builtin len()", object), msgAndArgs...) + return Fail(t, fmt.Sprintf("%q could not be applied builtin len()", truncatingFormat("%v", object)), msgAndArgs...) } if l != length { - return Fail(t, fmt.Sprintf("\"%v\" should have %d item(s), but has %d", object, length, l), msgAndArgs...) + return Fail(t, fmt.Sprintf("%q should have %d item(s), but has %d", truncatingFormat("%v", object), length, l), msgAndArgs...) } return true } @@ -854,7 +857,7 @@ func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{ } if ObjectsAreEqual(expected, actual) { - return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...) + return Fail(t, fmt.Sprintf("Should not be: %s\n", truncatingFormat("%#v", actual)), msgAndArgs...) } return true @@ -870,7 +873,7 @@ func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...inte } if ObjectsAreEqualValues(expected, actual) { - return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...) + return Fail(t, fmt.Sprintf("Should not be: %s\n", truncatingFormat("%#v", actual)), msgAndArgs...) } return true @@ -932,10 +935,10 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo ok, found := containsElement(s, contains) if !ok { - return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...) + return Fail(t, fmt.Sprintf("%s could not be applied builtin len()", truncatingFormat("%#v", s)), msgAndArgs...) } if !found { - return Fail(t, fmt.Sprintf("%#v does not contain %#v", s, contains), msgAndArgs...) + return Fail(t, fmt.Sprintf("%s does not contain %#v", truncatingFormat("%#v", s), contains), msgAndArgs...) } return true @@ -955,10 +958,10 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) ok, found := containsElement(s, contains) if !ok { - return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...) + return Fail(t, fmt.Sprintf("%s could not be applied builtin len()", truncatingFormat("%#v", s)), msgAndArgs...) } if found { - return Fail(t, fmt.Sprintf("%#v should not contain %#v", s, contains), msgAndArgs...) + return Fail(t, fmt.Sprintf("%s should not contain %#v", truncatingFormat("%#v", s), contains), msgAndArgs...) } return true @@ -997,10 +1000,10 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok av := actualMap.MapIndex(k) if !av.IsValid() { - return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, subset), msgAndArgs...) + return Fail(t, fmt.Sprintf("%s does not contain %s", truncatingFormat("%#v", list), truncatingFormat("%#v", subset)), msgAndArgs...) } if !ObjectsAreEqual(ev.Interface(), av.Interface()) { - return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, subset), msgAndArgs...) + return Fail(t, fmt.Sprintf("%s does not contain %s", truncatingFormat("%#v", list), truncatingFormat("%#v", subset)), msgAndArgs...) } } @@ -1015,7 +1018,7 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", list), msgAndArgs...) } if !found { - return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, element), msgAndArgs...) + return Fail(t, fmt.Sprintf("%s does not contain %#v", truncatingFormat("%#v", list), element), msgAndArgs...) } } @@ -1062,7 +1065,7 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) } } - return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...) + return Fail(t, fmt.Sprintf("%s is a subset of %s", truncatingFormat("%#v", subset), truncatingFormat("%#v", list)), msgAndArgs...) } subsetList := reflect.ValueOf(subset) @@ -1077,7 +1080,7 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) } } - return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...) + return Fail(t, fmt.Sprintf("%s is a subset of %s", truncatingFormat("%#v", subset), truncatingFormat("%#v", list)), msgAndArgs...) } // ElementsMatch asserts that the specified listA(array, slice...) is equal to specified @@ -1581,7 +1584,7 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } - return Fail(t, fmt.Sprintf("Received unexpected error:\n%+v", err), msgAndArgs...) + return Fail(t, fmt.Sprintf("Received unexpected error:\n%s", truncatingFormat("%+v", err)), msgAndArgs...) } return true @@ -1622,7 +1625,7 @@ func EqualError(t TestingT, theError error, errString string, msgAndArgs ...inte if expected != actual { return Fail(t, fmt.Sprintf("Error message not equal:\n"+ "expected: %q\n"+ - "actual : %q", expected, actual), msgAndArgs...) + "actual : %s", expected, truncatingFormat("%q", actual)), msgAndArgs...) } return true } @@ -1642,7 +1645,7 @@ func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...in actual := theError.Error() if !strings.Contains(actual, contains) { - return Fail(t, fmt.Sprintf("Error %#v does not contain %#v", actual, contains), msgAndArgs...) + return Fail(t, fmt.Sprintf("Error %s does not contain %#v", truncatingFormat("%#v", actual), contains), msgAndArgs...) } return true @@ -1710,7 +1713,7 @@ func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { h.Helper() } if i != nil && !reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { - return Fail(t, fmt.Sprintf("Should be zero, but was %v", i), msgAndArgs...) + return Fail(t, fmt.Sprintf("Should be zero, but was %s", truncatingFormat("%v", i)), msgAndArgs...) } return true } @@ -2103,8 +2106,8 @@ func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { chain := buildErrorChainString(err) return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+ - "expected: %q\n"+ - "in chain: %s", expectedText, chain, + "expected: %s\n"+ + "in chain: %s", truncatingFormat("%q", expectedText), truncatingFormat("%s", chain), ), msgAndArgs...) } @@ -2126,8 +2129,8 @@ func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { chain := buildErrorChainString(err) return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ - "found: %q\n"+ - "in chain: %s", expectedText, chain, + "found: %s\n"+ + "in chain: %s", truncatingFormat("%q", expectedText), truncatingFormat("%s", chain), ), msgAndArgs...) } @@ -2145,7 +2148,7 @@ func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{ return Fail(t, fmt.Sprintf("Should be in error chain:\n"+ "expected: %q\n"+ - "in chain: %s", target, chain, + "in chain: %s", target, truncatingFormat("%s", chain), ), msgAndArgs...) } @@ -2163,7 +2166,7 @@ func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interfa return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ "found: %q\n"+ - "in chain: %s", target, chain, + "in chain: %s", target, truncatingFormat("%s", chain), ), msgAndArgs...) } diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 20d63b108..207f899cf 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -1060,10 +1060,10 @@ func TestSubsetNotSubset(t *testing.T) { }{ // cases that are expected to contain {[]int{1, 2, 3}, nil, true, `nil is the empty set which is a subset of every set`}, - {[]int{1, 2, 3}, []int{}, true, `[] is a subset of ['\x01' '\x02' '\x03']`}, - {[]int{1, 2, 3}, []int{1, 2}, true, `['\x01' '\x02'] is a subset of ['\x01' '\x02' '\x03']`}, - {[]int{1, 2, 3}, []int{1, 2, 3}, true, `['\x01' '\x02' '\x03'] is a subset of ['\x01' '\x02' '\x03']`}, - {[]string{"hello", "world"}, []string{"hello"}, true, `["hello"] is a subset of ["hello" "world"]`}, + {[]int{1, 2, 3}, []int{}, true, `[]int{} is a subset of []int{1, 2, 3}`}, + {[]int{1, 2, 3}, []int{1, 2}, true, `[]int{1, 2} is a subset of []int{1, 2, 3}`}, + {[]int{1, 2, 3}, []int{1, 2, 3}, true, `[]int{1, 2, 3} is a subset of []int{1, 2, 3}`}, + {[]string{"hello", "world"}, []string{"hello"}, true, `[]string{"hello"} is a subset of []string{"hello", "world"}`}, {map[string]string{ "a": "x", "c": "z", @@ -1071,7 +1071,7 @@ func TestSubsetNotSubset(t *testing.T) { }, map[string]string{ "a": "x", "b": "y", - }, true, `map["a":"x" "b":"y"] is a subset of map["a":"x" "b":"y" "c":"z"]`}, + }, true, `map[string]string{"a":"x", "b":"y"} is a subset of map[string]string{"a":"x", "b":"y", "c":"z"}`}, // cases that are expected not to contain {[]string{"hello", "world"}, []string{"hello", "testify"}, false, `[]string{"hello", "world"} does not contain "testify"`}, @@ -3046,12 +3046,12 @@ func Test_validateEqualArgs(t *testing.T) { func Test_truncatingFormat(t *testing.T) { - original := strings.Repeat("a", bufio.MaxScanTokenSize-102) - result := truncatingFormat(original) + original := strings.Repeat("a", bufio.MaxScanTokenSize/2-102) + result := truncatingFormat("%#v", original) Equal(t, fmt.Sprintf("%#v", original), result, "string should not be truncated") original = original + "x" - result = truncatingFormat(original) + result = truncatingFormat("%#v", original) NotEqual(t, fmt.Sprintf("%#v", original), result, "string should have been truncated.") if !strings.HasSuffix(result, "<... truncated>") { @@ -3317,3 +3317,244 @@ func TestNotErrorAs(t *testing.T) { }) } } + +func TestLenWithSliceTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + Len(mockT, longSlice, 1) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: "[0 0 0`) + Contains(t, mockT.errorString(), `<... truncated>" should have 1 item(s), but has 1000000`) +} + +func TestContainsWithSliceTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + Contains(mockT, longSlice, 1) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: []int{0, 0, 0,`) + Contains(t, mockT.errorString(), `<... truncated> does not contain 1`) +} + +func TestNotContainsWithSliceTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + NotContains(mockT, longSlice, 0) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: []int{0, 0, 0,`) + Contains(t, mockT.errorString(), `<... truncated> should not contain 0`) +} + +func TestSubsetWithSliceTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + Subset(mockT, longSlice, []int{1}) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: []int{0, 0, 0,`) + Contains(t, mockT.errorString(), `<... truncated> does not contain 1`) +} + +func TestSubsetWithMapTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + Subset(mockT, map[bool][]int{true: longSlice}, map[bool][]int{false: longSlice}) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: map[bool][]int{true:[]int{0, 0, 0,`) + Contains(t, mockT.errorString(), `<... truncated> does not contain map[bool][]int{false:[]int{0, 0, 0,`) +} + +func TestNotSubsetWithSliceTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + NotSubset(mockT, longSlice, longSlice) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: []int{0, 0, 0,`) + Contains(t, mockT.errorString(), `<... truncated> is a subset of []int{0, 0, 0,`) +} + +func TestNotSubsetWithMapTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + NotSubset(mockT, map[bool][]int{true: longSlice}, map[bool][]int{true: longSlice}) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: map[bool][]int{true:[]int{0, 0, 0,`) + Contains(t, mockT.errorString(), `<... truncated> is a subset of map[bool][]int{true:[]int{0, 0, 0,`) +} + +func TestSameWithSliceTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + Same(mockT, &[]int{}, &longSlice) + Contains(t, mockT.errorString(), `&[]int{0, 0, 0,`) +} + +func TestNotSameWithSliceTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + NotSame(mockT, &longSlice, &longSlice) + Contains(t, mockT.errorString(), `&[]int{0, 0, 0,`) +} + +func TestNilWithSliceTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + Nil(mockT, &longSlice) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: Expected nil, but got: &[]int{0, 0, 0,`) + Contains(t, mockT.errorString(), `<... truncated>`) +} + +func TestEmptyWithSliceTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + Empty(mockT, longSlice) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: Should be empty, but was [0 0 0`) + Contains(t, mockT.errorString(), `<... truncated>`) +} + +func TestNotEqualWithSliceTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + NotEqual(mockT, longSlice, longSlice) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: Should not be: []int{0, 0, 0,`) + Contains(t, mockT.errorString(), `<... truncated>`) +} + +func TestNotEqualValuesWithSliceTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + NotEqualValues(mockT, longSlice, longSlice) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: Should not be: []int{0, 0, 0,`) + Contains(t, mockT.errorString(), `<... truncated>`) +} + +func TestNoErrorWithErrorTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + NoError(mockT, fmt.Errorf("long: %v", longSlice)) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: Received unexpected error: + long: [0 0 0`) + Contains(t, mockT.errorString(), `<... truncated>`) +} + +func TestEqualErrorWithErrorTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + EqualError(mockT, fmt.Errorf("long: %v", longSlice), "EOF") + Contains(t, mockT.errorString(), ` + Error Trace: + Error: Error message not equal: + expected: "EOF" + actual : "long: [0 0 0`) + Contains(t, mockT.errorString(), `<... truncated>`) +} + +func TestErrorContainsWithErrorTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + ErrorContains(mockT, fmt.Errorf("long: %v", longSlice), "EOF") + Contains(t, mockT.errorString(), ` + Error Trace: + Error: Error "long: [0 0 0`) + Contains(t, mockT.errorString(), `<... truncated> does not contain "EOF"`) +} + +func TestZeroWithSliceTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + Zero(mockT, longSlice) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: Should be zero, but was [0 0 0`) + Contains(t, mockT.errorString(), `<... truncated>`) +} + +func TestErrorIsWithErrorTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + ErrorIs(mockT, fmt.Errorf("long: %v", longSlice), fmt.Errorf("also: %v", longSlice)) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: Target error should be in err chain: + expected: "also: [0 0 0`) + Contains(t, mockT.errorString(), `<... truncated> + in chain: "long: [0 0 0`) +} + +func TestNotErrorIsWithErrorTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + err := fmt.Errorf("long: %v", longSlice) + NotErrorIs(mockT, err, err) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: Target error should not be in err chain: + found: "long: [0 0 0`) + Contains(t, mockT.errorString(), `<... truncated> + in chain: "long: [0 0 0`) +} + +func TestErrorAsWithErrorTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + var target *customError + ErrorAs(mockT, fmt.Errorf("long: %v", longSlice), &target) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: Should be in error chain: + expected: %!q(**assert.customError=0x`) + Contains(t, mockT.errorString(), ` + in chain: "long: [0 0 0`) + Contains(t, mockT.errorString(), "<... truncated>") +} + +func TestNotErrorAsWithErrorTooLongToPrint(t *testing.T) { + t.Parallel() + mockT := new(mockTestingT) + longSlice := make([]int, 1_000_000) + var target *customError + NotErrorAs(mockT, fmt.Errorf("long: %v %w", longSlice, &customError{}), &target) + Contains(t, mockT.errorString(), ` + Error Trace: + Error: Target error should not be in err chain: + found: %!q(**assert.customError=0x`) + Contains(t, mockT.errorString(), ` + in chain: "long: [0 0 0`) + Contains(t, mockT.errorString(), "<... truncated>") +}