From ca6698b8a1c4617c5ca4333ce0c878e91c506702 Mon Sep 17 00:00:00 2001 From: Craig Davison Date: Tue, 31 Dec 2024 15:09:40 -0700 Subject: [PATCH 1/4] assert.ErrorAs: log target type --- assert/assertions.go | 49 ++++++++++++++++------ assert/assertions_test.go | 87 ++++++++++++++++++++++++++++----------- 2 files changed, 99 insertions(+), 37 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index 4e91332bb..466554e5a 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2102,7 +2102,7 @@ func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { expectedText = target.Error() } - chain := buildErrorChainString(err) + chain := buildErrorChainString(err, false) return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+ "expected: %q\n"+ @@ -2125,7 +2125,7 @@ func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { expectedText = target.Error() } - chain := buildErrorChainString(err) + chain := buildErrorChainString(err, false) return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ "found: %q\n"+ @@ -2143,10 +2143,10 @@ func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{ return true } - chain := buildErrorChainString(err) + chain := buildErrorChainString(err, true) return Fail(t, fmt.Sprintf("Should be in error chain:\n"+ - "expected: %q\n"+ + "expected: %T\n"+ "in chain: %s", target, chain, ), msgAndArgs...) } @@ -2161,24 +2161,49 @@ func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interfa return true } - chain := buildErrorChainString(err) + chain := buildErrorChainString(err, true) return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ - "found: %q\n"+ + "found: %T\n"+ "in chain: %s", target, chain, ), msgAndArgs...) } -func buildErrorChainString(err error) string { +func unwrapAll(err error) (errs []error) { + errs = append(errs, err) + switch x := err.(type) { + case interface{ Unwrap() error }: + err = x.Unwrap() + if err == nil { + return + } + errs = append(errs, unwrapAll(err)...) + case interface{ Unwrap() []error }: + for _, err := range x.Unwrap() { + errs = append(errs, unwrapAll(err)...) + } + return + default: + return + } + return +} + +func buildErrorChainString(err error, withType bool) string { if err == nil { return "" } - e := errors.Unwrap(err) - chain := fmt.Sprintf("%q", err.Error()) - for e != nil { - chain += fmt.Sprintf("\n\t%q", e.Error()) - e = errors.Unwrap(e) + var chain string + errs := unwrapAll(err) + for i := range errs { + if i != 0 { + chain += "\n\t" + } + chain += fmt.Sprintf("%q", errs[i].Error()) + if withType { + chain += fmt.Sprintf(" (%T)", errs[i]) + } } return chain } diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 9f859fa8d..18911ba83 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3175,11 +3175,13 @@ func parseLabeledOutput(output string) []labeledContent { } type captureTestingT struct { - msg string + failed bool + msg string } func (ctt *captureTestingT) Errorf(format string, args ...interface{}) { ctt.msg = fmt.Sprintf(format, args...) + ctt.failed = true } func (ctt *captureTestingT) checkResultAndErrMsg(t *testing.T, expectedRes, res bool, expectedErrMsg string) { @@ -3188,6 +3190,9 @@ func (ctt *captureTestingT) checkResultAndErrMsg(t *testing.T, expectedRes, res t.Errorf("Should return %t", expectedRes) return } + if res == ctt.failed { + t.Errorf("The test result (%t) should be reflected in the testing.T type (%t)", res, !ctt.failed) + } contents := parseLabeledOutput(ctt.msg) if res == true { if contents != nil { @@ -3348,50 +3353,82 @@ func TestNotErrorIs(t *testing.T) { func TestErrorAs(t *testing.T) { tests := []struct { - err error - result bool + err error + result bool + resultErrMsg string }{ - {fmt.Errorf("wrap: %w", &customError{}), true}, - {io.EOF, false}, - {nil, false}, + { + err: fmt.Errorf("wrap: %w", &customError{}), + result: true, + }, + { + err: io.EOF, + result: false, + resultErrMsg: "" + + "Should be in error chain:\n" + + "expected: **assert.customError\n" + + "in chain: \"EOF\" (*errors.errorString)\n", + }, + { + err: nil, + result: false, + resultErrMsg: "" + + "Should be in error chain:\n" + + "expected: **assert.customError\n" + + "in chain: \n", + }, + { + err: fmt.Errorf("abc: %w", errors.New("def")), + result: false, + resultErrMsg: "" + + "Should be in error chain:\n" + + "expected: **assert.customError\n" + + "in chain: \"abc: def\" (*fmt.wrapError)\n" + + "\t\"def\" (*errors.errorString)\n", + }, } for _, tt := range tests { tt := tt var target *customError t.Run(fmt.Sprintf("ErrorAs(%#v,%#v)", tt.err, target), func(t *testing.T) { - mockT := new(testing.T) + mockT := new(captureTestingT) res := ErrorAs(mockT, tt.err, &target) - if res != tt.result { - t.Errorf("ErrorAs(%#v,%#v) should return %t", tt.err, target, tt.result) - } - if res == mockT.Failed() { - t.Errorf("The test result (%t) should be reflected in the testing.T type (%t)", res, !mockT.Failed()) - } + mockT.checkResultAndErrMsg(t, tt.result, res, tt.resultErrMsg) }) } } func TestNotErrorAs(t *testing.T) { tests := []struct { - err error - result bool + err error + result bool + resultErrMsg string }{ - {fmt.Errorf("wrap: %w", &customError{}), false}, - {io.EOF, true}, - {nil, true}, + { + err: fmt.Errorf("wrap: %w", &customError{}), + result: false, + resultErrMsg: "" + + "Target error should not be in err chain:\n" + + "found: **assert.customError\n" + + "in chain: \"wrap: fail\" (*fmt.wrapError)\n" + + "\t\"fail\" (*assert.customError)\n", + }, + { + err: io.EOF, + result: true, + }, + { + err: nil, + result: true, + }, } for _, tt := range tests { tt := tt var target *customError t.Run(fmt.Sprintf("NotErrorAs(%#v,%#v)", tt.err, target), func(t *testing.T) { - mockT := new(testing.T) + mockT := new(captureTestingT) res := NotErrorAs(mockT, tt.err, &target) - if res != tt.result { - t.Errorf("NotErrorAs(%#v,%#v) should not return %t", tt.err, target, tt.result) - } - if res == mockT.Failed() { - t.Errorf("The test result (%t) should be reflected in the testing.T type (%t)", res, !mockT.Failed()) - } + mockT.checkResultAndErrMsg(t, tt.result, res, tt.resultErrMsg) }) } } From ccb5e7f656ccfbd55ab31f98b4a7aa8d5255d30a Mon Sep 17 00:00:00 2001 From: Craig Davison Date: Tue, 31 Dec 2024 15:26:24 -0700 Subject: [PATCH 2/4] Remove redundant returns --- assert/assertions.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index 466554e5a..4efe1de47 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2182,9 +2182,6 @@ func unwrapAll(err error) (errs []error) { for _, err := range x.Unwrap() { errs = append(errs, unwrapAll(err)...) } - return - default: - return } return } From 1c717c00c126f37ce24492e0e34ca7921bf01b5d Mon Sep 17 00:00:00 2001 From: Craig Davison Date: Tue, 31 Dec 2024 15:35:31 -0700 Subject: [PATCH 3/4] Add return --- assert/assertions_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 18911ba83..bb6af1b6f 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3192,6 +3192,7 @@ func (ctt *captureTestingT) checkResultAndErrMsg(t *testing.T, expectedRes, res } if res == ctt.failed { t.Errorf("The test result (%t) should be reflected in the testing.T type (%t)", res, !ctt.failed) + return } contents := parseLabeledOutput(ctt.msg) if res == true { From c60c3bd7fb8aa1f50fe1e91c9136060a4cbd9657 Mon Sep 17 00:00:00 2001 From: Craig Davison Date: Fri, 3 Jan 2025 14:34:16 -0700 Subject: [PATCH 4/4] dereference target --- assert/assertions.go | 8 ++++---- assert/assertions_test.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index 4efe1de47..be821614b 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2146,8 +2146,8 @@ func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{ chain := buildErrorChainString(err, true) return Fail(t, fmt.Sprintf("Should be in error chain:\n"+ - "expected: %T\n"+ - "in chain: %s", target, chain, + "expected: %s\n"+ + "in chain: %s", reflect.ValueOf(target).Elem().Type(), chain, ), msgAndArgs...) } @@ -2164,8 +2164,8 @@ func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interfa chain := buildErrorChainString(err, true) return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ - "found: %T\n"+ - "in chain: %s", target, chain, + "found: %s\n"+ + "in chain: %s", reflect.ValueOf(target).Elem().Type(), chain, ), msgAndArgs...) } diff --git a/assert/assertions_test.go b/assert/assertions_test.go index bb6af1b6f..503eadb3a 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3367,7 +3367,7 @@ func TestErrorAs(t *testing.T) { result: false, resultErrMsg: "" + "Should be in error chain:\n" + - "expected: **assert.customError\n" + + "expected: *assert.customError\n" + "in chain: \"EOF\" (*errors.errorString)\n", }, { @@ -3375,7 +3375,7 @@ func TestErrorAs(t *testing.T) { result: false, resultErrMsg: "" + "Should be in error chain:\n" + - "expected: **assert.customError\n" + + "expected: *assert.customError\n" + "in chain: \n", }, { @@ -3383,7 +3383,7 @@ func TestErrorAs(t *testing.T) { result: false, resultErrMsg: "" + "Should be in error chain:\n" + - "expected: **assert.customError\n" + + "expected: *assert.customError\n" + "in chain: \"abc: def\" (*fmt.wrapError)\n" + "\t\"def\" (*errors.errorString)\n", }, @@ -3410,7 +3410,7 @@ func TestNotErrorAs(t *testing.T) { result: false, resultErrMsg: "" + "Target error should not be in err chain:\n" + - "found: **assert.customError\n" + + "found: *assert.customError\n" + "in chain: \"wrap: fail\" (*fmt.wrapError)\n" + "\t\"fail\" (*assert.customError)\n", },