assert.ErrorAs: log target type

pull/1345/head
Craig Davison 2024-12-31 15:09:40 -07:00
parent 7c367bb7bc
commit ca6698b8a1
2 changed files with 99 additions and 37 deletions

View File

@ -2102,7 +2102,7 @@ func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool {
expectedText = target.Error() expectedText = target.Error()
} }
chain := buildErrorChainString(err) chain := buildErrorChainString(err, false)
return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+ return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+
"expected: %q\n"+ "expected: %q\n"+
@ -2125,7 +2125,7 @@ func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool {
expectedText = target.Error() expectedText = target.Error()
} }
chain := buildErrorChainString(err) chain := buildErrorChainString(err, false)
return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+
"found: %q\n"+ "found: %q\n"+
@ -2143,10 +2143,10 @@ func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{
return true return true
} }
chain := buildErrorChainString(err) chain := buildErrorChainString(err, true)
return Fail(t, fmt.Sprintf("Should be in error chain:\n"+ return Fail(t, fmt.Sprintf("Should be in error chain:\n"+
"expected: %q\n"+ "expected: %T\n"+
"in chain: %s", target, chain, "in chain: %s", target, chain,
), msgAndArgs...) ), msgAndArgs...)
} }
@ -2161,24 +2161,49 @@ func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interfa
return true return true
} }
chain := buildErrorChainString(err) chain := buildErrorChainString(err, true)
return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ 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, "in chain: %s", target, chain,
), msgAndArgs...) ), 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 { if err == nil {
return "" return ""
} }
e := errors.Unwrap(err) var chain string
chain := fmt.Sprintf("%q", err.Error()) errs := unwrapAll(err)
for e != nil { for i := range errs {
chain += fmt.Sprintf("\n\t%q", e.Error()) if i != 0 {
e = errors.Unwrap(e) chain += "\n\t"
}
chain += fmt.Sprintf("%q", errs[i].Error())
if withType {
chain += fmt.Sprintf(" (%T)", errs[i])
}
} }
return chain return chain
} }

View File

@ -3175,11 +3175,13 @@ func parseLabeledOutput(output string) []labeledContent {
} }
type captureTestingT struct { type captureTestingT struct {
msg string failed bool
msg string
} }
func (ctt *captureTestingT) Errorf(format string, args ...interface{}) { func (ctt *captureTestingT) Errorf(format string, args ...interface{}) {
ctt.msg = fmt.Sprintf(format, args...) ctt.msg = fmt.Sprintf(format, args...)
ctt.failed = true
} }
func (ctt *captureTestingT) checkResultAndErrMsg(t *testing.T, expectedRes, res bool, expectedErrMsg string) { 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) t.Errorf("Should return %t", expectedRes)
return 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) contents := parseLabeledOutput(ctt.msg)
if res == true { if res == true {
if contents != nil { if contents != nil {
@ -3348,50 +3353,82 @@ func TestNotErrorIs(t *testing.T) {
func TestErrorAs(t *testing.T) { func TestErrorAs(t *testing.T) {
tests := []struct { tests := []struct {
err error err error
result bool result bool
resultErrMsg string
}{ }{
{fmt.Errorf("wrap: %w", &customError{}), true}, {
{io.EOF, false}, err: fmt.Errorf("wrap: %w", &customError{}),
{nil, false}, 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 { for _, tt := range tests {
tt := tt tt := tt
var target *customError var target *customError
t.Run(fmt.Sprintf("ErrorAs(%#v,%#v)", tt.err, target), func(t *testing.T) { 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) res := ErrorAs(mockT, tt.err, &target)
if res != tt.result { mockT.checkResultAndErrMsg(t, tt.result, res, tt.resultErrMsg)
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())
}
}) })
} }
} }
func TestNotErrorAs(t *testing.T) { func TestNotErrorAs(t *testing.T) {
tests := []struct { tests := []struct {
err error err error
result bool result bool
resultErrMsg string
}{ }{
{fmt.Errorf("wrap: %w", &customError{}), false}, {
{io.EOF, true}, err: fmt.Errorf("wrap: %w", &customError{}),
{nil, true}, 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 { for _, tt := range tests {
tt := tt tt := tt
var target *customError var target *customError
t.Run(fmt.Sprintf("NotErrorAs(%#v,%#v)", tt.err, target), func(t *testing.T) { 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) res := NotErrorAs(mockT, tt.err, &target)
if res != tt.result { mockT.checkResultAndErrMsg(t, tt.result, res, tt.resultErrMsg)
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())
}
}) })
} }
} }