aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-06-27 23:09:47 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-06-27 23:09:47 +0200
commit62132b9680cbd8cbe6e50a346cc71922812dac00 (patch)
tree6e06a1beee0f088f3834a1db6651b004e00a67e6
parent8e9889d3e5b2874854832d1d882d124132546f49 (diff)
downloadgo-gg-62132b9680cbd8cbe6e50a346cc71922812dac00.tar.gz
add func assert.ErrsEqual
-rw-r--r--.golangci.yaml2
-rw-r--r--CHANGELOG.md4
-rw-r--r--assert/errequal.go77
-rw-r--r--assert/errequal_test.go24
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>`)
+}