Skip to content

Commit b5cf7e0

Browse files
committed
feat: errs.ID for semantic error testing
1 parent d08d0f0 commit b5cf7e0

File tree

2 files changed

+155
-0
lines changed

2 files changed

+155
-0
lines changed

internal/libevm/errs/errs.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2025 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
//
17+
// Package errs provides a mechanism for [testing error semantics] through
18+
// unique identifiers, instead of depending on error messages that may result in
19+
// change-detector tests.
20+
//
21+
// [testing error semantics]: https://google.github.io/styleguide/go/decisions#test-error-semantics
22+
package errs
23+
24+
import (
25+
"errors"
26+
"fmt"
27+
)
28+
29+
// An ID is a distinct numeric identifier for an error. It has no effect on the
30+
// error message and can only be accessed with this package.
31+
type ID int
32+
33+
// Error returns a new error with the ID.
34+
func (id ID) Error(msg string) error {
35+
return noWrap{errors.New(msg), id}
36+
}
37+
38+
type noWrap struct {
39+
error
40+
id ID
41+
}
42+
43+
// Errorf is the formatted equivalent of [ID.Error], supporting the same
44+
// wrapping semantics as [fmt.Errorf].
45+
func (id ID) Errorf(format string, a ...any) error {
46+
switch err := fmt.Errorf(format, a...).(type) {
47+
case singleWrapper:
48+
return single{err, id}
49+
case multiWrapper:
50+
return multi{err, id}
51+
default:
52+
return noWrap{err, id}
53+
}
54+
}
55+
56+
type singleWrapper interface {
57+
error
58+
Unwrap() error
59+
}
60+
61+
type single struct {
62+
singleWrapper
63+
id ID
64+
}
65+
66+
type multiWrapper interface {
67+
error
68+
Unwrap() []error
69+
}
70+
71+
type multi struct {
72+
multiWrapper
73+
id ID
74+
}
75+
76+
// IDOf returns the ID of the error, if one exists, and a flag to indicate as
77+
// such. IDOf does not unwrap errors.
78+
func IDOf(err error) (ID, bool) {
79+
switch err := err.(type) {
80+
case noWrap:
81+
return err.id, true
82+
case single:
83+
return err.id, true
84+
case multi:
85+
return err.id, true
86+
default:
87+
return 0, false
88+
}
89+
}

internal/libevm/errs/errs_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2025 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package errs
18+
19+
import (
20+
"errors"
21+
"fmt"
22+
"testing"
23+
24+
"github.com/stretchr/testify/assert"
25+
)
26+
27+
func TestIDOf(t *testing.T) {
28+
tests := []struct {
29+
err error
30+
wantID ID
31+
wantOK bool
32+
}{
33+
{
34+
err: errors.New("x"),
35+
},
36+
{
37+
err: fmt.Errorf("x"),
38+
},
39+
{
40+
err: ID(3).Error("x"),
41+
wantID: 3,
42+
wantOK: true,
43+
},
44+
{
45+
err: ID(42).Errorf("x"),
46+
wantID: 42,
47+
wantOK: true,
48+
},
49+
{
50+
err: ID(99).Errorf("%w", errors.New("x")),
51+
wantID: 99,
52+
wantOK: true,
53+
},
54+
{
55+
err: ID(0).Errorf("%w %w", errors.New("x"), errors.New("y")),
56+
wantID: 0,
57+
wantOK: true,
58+
},
59+
}
60+
61+
for _, tt := range tests {
62+
got, ok := IDOf(tt.err)
63+
assert.Equalf(t, tt.wantID, got, "IDOf(%T)", tt.err)
64+
assert.Equalf(t, tt.wantOK, ok, "IDOf(%T)", tt.err)
65+
}
66+
}

0 commit comments

Comments
 (0)