diff options
| author | Stefan Majewsky <majewsky@gmx.net> | 2026-06-27 23:09:47 +0200 |
|---|---|---|
| committer | Stefan Majewsky <majewsky@gmx.net> | 2026-06-27 23:09:47 +0200 |
| commit | 62132b9680cbd8cbe6e50a346cc71922812dac00 (patch) | |
| tree | 6e06a1beee0f088f3834a1db6651b004e00a67e6 | |
| parent | 8e9889d3e5b2874854832d1d882d124132546f49 (diff) | |
| download | go-gg-62132b9680cbd8cbe6e50a346cc71922812dac00.tar.gz | |
add func assert.ErrsEqual
| -rw-r--r-- | .golangci.yaml | 2 | ||||
| -rw-r--r-- | CHANGELOG.md | 4 | ||||
| -rw-r--r-- | assert/errequal.go | 77 | ||||
| -rw-r--r-- | assert/errequal_test.go | 24 |
4 files changed, 87 insertions, 20 deletions
diff --git a/.golangci.yaml b/.golangci.yaml index 6e6ffc1..9767b3a 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -39,7 +39,7 @@ linters: - whitespace settings: goconst: - min-occurrences: 5 + min-occurrences: 6 gocritic: disable-all: true enabled-checks: diff --git a/CHANGELOG.md b/CHANGELOG.md index 989e607..fc79189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ SPDX-License-Identifier: Apache-2.0 Changes: - Add package pathrouter. -- In assert.Equal output, present string literals with backticks instead of quotes when it makes the output more readable. +- Add `assert.ErrsEqual()`. +- In `assert.Equal()` output, present string literals with backticks instead of quotes when it makes the output more readable. +- Fixed `assert.ErrEqual()` not calling `t.Helper()`. # v1.10.1 (2026-06-20) diff --git a/assert/errequal.go b/assert/errequal.go index 86cd719..d7bd8ba 100644 --- a/assert/errequal.go +++ b/assert/errequal.go @@ -17,6 +17,46 @@ import ( // - If expected is of type [*regexp.Regexp], the actual error must have a message matching that regexp. // - If expected is of any other type, ErrEqual will panic. func ErrEqual(t TestingTB, actual error, expected any) bool { + t.Helper() + err := errEqual(t, actual, expected) + if err == nil { + return true + } else { + t.Error(err) + return false + } +} + +// ErrsEqual checks if a list of actual errors matches the expectation. +// Both lists must be of equal length, and each individual entry must match according to [ErrEqual]. +func ErrsEqual[A ~[]error, E ~[]V, V any](t TestingTB, actual A, expected E) bool { + ok := true + for idx := range max(len(actual), len(expected)) { + var err error + switch { + case idx >= len(actual): + err = errEqual(t, missingError{}, expected[idx]) + case idx >= len(expected): + err = errEqual(t, actual[idx], missingError{}) + default: + err = errEqual(t, actual[idx], expected[idx]) + } + if err != nil { + t.Errorf("in actual[%d]: %s", idx, err) + ok = false + } + } + return ok +} + +// missingError is used by ErrsEqual() to mark a missing error on one side of the match. +type missingError struct{} + +func (missingError) Error() string { + return "<missing>" +} + +func errEqual(t TestingTB, actual error, expected any) error { // coerce all types that implement `error` into the interface type `error`, // and also coerce all nil values of concrete `error` types into untyped nil if expectedErr, ok := expected.(error); ok { @@ -33,42 +73,43 @@ func ErrEqual(t TestingTB, actual error, expected any) bool { switch expected := expected.(type) { case nil: if actual == nil { - return true + return nil } else { - t.Errorf("expected no error, but got %q", actual.Error()) - return false + return fmt.Errorf("expected no error, but got %s", formatErrorMessage(actual)) } case error: if actual == nil { - t.Errorf("expected %q, but got no error", expected.Error()) - return false + return fmt.Errorf("expected %s, but got no error", formatErrorMessage(expected)) } else if errors.Is(actual, expected) { - return true + return nil } else { - t.Errorf("expected %q, but got %q", expected.Error(), actual.Error()) - return false + return fmt.Errorf("expected %s, but got %q", formatErrorMessage(expected), actual.Error()) } case string: if actual == nil { - t.Errorf("expected %q, but got no error", expected) - return false + return fmt.Errorf("expected %q, but got no error", expected) } else if actual.Error() == expected { - return true + return nil } else { - t.Errorf("expected %q, but got %q", expected, actual.Error()) - return false + return fmt.Errorf("expected %q, but got %s", expected, formatErrorMessage(actual)) } case *regexp.Regexp: if actual == nil { - t.Errorf("expected an error matching /%s/, but got no error", expected.String()) - return false + return fmt.Errorf("expected an error matching /%s/, but got no error", expected.String()) } else if expected.MatchString(actual.Error()) { - return true + return nil } else { - t.Errorf("expected an error matching /%s/, but got %q", expected.String(), actual.Error()) - return false + return fmt.Errorf("expected an error matching /%s/, but got %s", expected.String(), formatErrorMessage(actual)) } default: panic(fmt.Sprintf("cannot handle `expected` of type %T", expected)) } } + +func formatErrorMessage(err error) string { + if _, ok := err.(missingError); ok { //nolint:errorlint // this error is never wrapped, so errors.Is() is unnecessary + return err.Error() + } else { + return fmt.Sprintf("%q", err.Error()) + } +} diff --git a/assert/errequal_test.go b/assert/errequal_test.go index 4bb9080..b97342a 100644 --- a/assert/errequal_test.go +++ b/assert/errequal_test.go @@ -89,3 +89,27 @@ type structTypedError struct { func (e structTypedError) Error() string { return e.Message } + +func TestErrsEqual(t *testing.T) { + // NOTE: ErrsEqual() uses the same basic machinery as ErrEqual(), so we're not comparing all types here. + // We just need to check the machinery of iterating over `actual` and `expected` and dealing with slice length mismatches. + + errs := []error{ + errors.New("first error"), + errors.New("second error"), + } + + assert.ErrsEqual(t, errs, []string{"first error", "second error"}) + expectErrors(t, func(t assert.TestingTB) { + assert.ErrsEqual(t, errs, []string{"second error", "first error"}) + }, ` + in actual[0]: expected "second error", but got "first error" + in actual[1]: expected "first error", but got "second error" + `) + expectErrors(t, func(t assert.TestingTB) { + assert.ErrsEqual(t, errs, []string{"first error"}) + }, `in actual[1]: expected <missing>, but got "second error"`) + expectErrors(t, func(t assert.TestingTB) { + assert.ErrsEqual(t, errs, []string{"first error", "second error", "third error"}) + }, `in actual[2]: expected "third error", but got <missing>`) +} |
