From b5cf7e04e415b2a7821fe5cc2dab8a58a51bc241 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 29 Jan 2025 11:21:39 +0000 Subject: [PATCH 1/5] feat: `errs.ID` for semantic error testing --- internal/libevm/errs/errs.go | 89 +++++++++++++++++++++++++++++++ internal/libevm/errs/errs_test.go | 66 +++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 internal/libevm/errs/errs.go create mode 100644 internal/libevm/errs/errs_test.go diff --git a/internal/libevm/errs/errs.go b/internal/libevm/errs/errs.go new file mode 100644 index 000000000000..5ba9e48bf212 --- /dev/null +++ b/internal/libevm/errs/errs.go @@ -0,0 +1,89 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . +// +// Package errs provides a mechanism for [testing error semantics] through +// unique identifiers, instead of depending on error messages that may result in +// change-detector tests. +// +// [testing error semantics]: https://google.github.io/styleguide/go/decisions#test-error-semantics +package errs + +import ( + "errors" + "fmt" +) + +// An ID is a distinct numeric identifier for an error. It has no effect on the +// error message and can only be accessed with this package. +type ID int + +// Error returns a new error with the ID. +func (id ID) Error(msg string) error { + return noWrap{errors.New(msg), id} +} + +type noWrap struct { + error + id ID +} + +// Errorf is the formatted equivalent of [ID.Error], supporting the same +// wrapping semantics as [fmt.Errorf]. +func (id ID) Errorf(format string, a ...any) error { + switch err := fmt.Errorf(format, a...).(type) { + case singleWrapper: + return single{err, id} + case multiWrapper: + return multi{err, id} + default: + return noWrap{err, id} + } +} + +type singleWrapper interface { + error + Unwrap() error +} + +type single struct { + singleWrapper + id ID +} + +type multiWrapper interface { + error + Unwrap() []error +} + +type multi struct { + multiWrapper + id ID +} + +// IDOf returns the ID of the error, if one exists, and a flag to indicate as +// such. IDOf does not unwrap errors. +func IDOf(err error) (ID, bool) { + switch err := err.(type) { + case noWrap: + return err.id, true + case single: + return err.id, true + case multi: + return err.id, true + default: + return 0, false + } +} diff --git a/internal/libevm/errs/errs_test.go b/internal/libevm/errs/errs_test.go new file mode 100644 index 000000000000..add0d2bd4bbd --- /dev/null +++ b/internal/libevm/errs/errs_test.go @@ -0,0 +1,66 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package errs + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIDOf(t *testing.T) { + tests := []struct { + err error + wantID ID + wantOK bool + }{ + { + err: errors.New("x"), + }, + { + err: fmt.Errorf("x"), + }, + { + err: ID(3).Error("x"), + wantID: 3, + wantOK: true, + }, + { + err: ID(42).Errorf("x"), + wantID: 42, + wantOK: true, + }, + { + err: ID(99).Errorf("%w", errors.New("x")), + wantID: 99, + wantOK: true, + }, + { + err: ID(0).Errorf("%w %w", errors.New("x"), errors.New("y")), + wantID: 0, + wantOK: true, + }, + } + + for _, tt := range tests { + got, ok := IDOf(tt.err) + assert.Equalf(t, tt.wantID, got, "IDOf(%T)", tt.err) + assert.Equalf(t, tt.wantOK, ok, "IDOf(%T)", tt.err) + } +} From e159180ab01b0f7f2c57035fa83f720282e724a7 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 29 Jan 2025 11:22:17 +0000 Subject: [PATCH 2/5] refactor(params): semantic error testing instead of regex --- params/json.libevm.go | 24 ++++++++++---- params/json.libevm_test.go | 67 ++++++++++++++------------------------ 2 files changed, 42 insertions(+), 49 deletions(-) diff --git a/params/json.libevm.go b/params/json.libevm.go index 639bfe18f67e..c9d9c06f8985 100644 --- a/params/json.libevm.go +++ b/params/json.libevm.go @@ -19,6 +19,8 @@ package params import ( "encoding/json" "fmt" + + "github.com/ava-labs/libevm/internal/libevm/errs" ) var _ interface { @@ -43,12 +45,22 @@ func (c *ChainConfig) UnmarshalJSON(data []byte) (err error) { return UnmarshalChainConfigJSON(data, c, c.extra, ec.reuseJSONRoot) } +// Internal error identifiers for precise testing. +const ( + errIDDecodeJSONIntoCombination errs.ID = iota + errIDDecodeJSONIntoExtra + errIDEncodeJSONCombination + errIDEncodeExtraToRawJSON + errIDEncodeDuplicateJSONKey + errIDNilExtra +) + // UnmarshalChainConfigJSON is equivalent to [ChainConfig.UnmarshalJSON] // had [Extras] with `C` been registered, but without the need to call // [RegisterExtras]. The `extra` argument MUST NOT be nil. func UnmarshalChainConfigJSON[C any](data []byte, config *ChainConfig, extra *C, reuseJSONRoot bool) (err error) { if extra == nil { - return fmt.Errorf("%T argument is nil; use %T.UnmarshalJSON() directly", extra, config) + return errIDNilExtra.Errorf("%T argument is nil; use %T.UnmarshalJSON() directly", extra, config) } if reuseJSONRoot { @@ -56,7 +68,7 @@ func UnmarshalChainConfigJSON[C any](data []byte, config *ChainConfig, extra *C, return fmt.Errorf("decoding JSON into %T: %s", config, err) } if err := json.Unmarshal(data, extra); err != nil { - return fmt.Errorf("decoding JSON into %T: %s", extra, err) + return errIDDecodeJSONIntoExtra.Errorf("decoding JSON into %T: %s", extra, err) } return nil } @@ -69,7 +81,7 @@ func UnmarshalChainConfigJSON[C any](data []byte, config *ChainConfig, extra *C, extra, } if err := json.Unmarshal(data, &combined); err != nil { - return fmt.Errorf(`decoding JSON into combination of %T and %T (as "extra" key): %s`, config, extra, err) + return errIDDecodeJSONIntoCombination.Errorf(`decoding JSON into combination of %T and %T (as "extra" key): %s`, config, extra, err) } return nil } @@ -100,7 +112,7 @@ func MarshalChainConfigJSON[C any](config ChainConfig, extra C, reuseJSONRoot bo } data, err = json.Marshal(jsonExtra) if err != nil { - return nil, fmt.Errorf(`encoding combination of %T and %T (as "extra" key) to JSON: %s`, config, extra, err) + return nil, errIDEncodeJSONCombination.Errorf(`encoding combination of %T and %T (as "extra" key) to JSON: %s`, config, extra, err) } return data, nil } @@ -116,13 +128,13 @@ func MarshalChainConfigJSON[C any](config ChainConfig, extra C, reuseJSONRoot bo } extraJSONRaw, err := toJSONRawMessages(extra) if err != nil { - return nil, fmt.Errorf("converting extra config to JSON raw messages: %s", err) + return nil, errIDEncodeExtraToRawJSON.Errorf("converting extra config to JSON raw messages: %s", err) } for k, v := range extraJSONRaw { _, ok := configJSONRaw[k] if ok { - return nil, fmt.Errorf("duplicate JSON key %q in ChainConfig and extra %T", k, extra) + return nil, errIDEncodeDuplicateJSONKey.Errorf("duplicate JSON key %q in ChainConfig and extra %T", k, extra) } configJSONRaw[k] = v } diff --git a/params/json.libevm_test.go b/params/json.libevm_test.go index 84b04b354278..93728e0f5415 100644 --- a/params/json.libevm_test.go +++ b/params/json.libevm_test.go @@ -13,6 +13,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see // . + package params import ( @@ -24,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/ava-labs/libevm/internal/libevm/errs" "github.com/ava-labs/libevm/libevm/pseudo" ) @@ -157,42 +159,33 @@ func TestUnmarshalChainConfigJSON_Errors(t *testing.T) { jsonData string // string for convenience extra *testExtra reuseJSONRoot bool - wantConfig ChainConfig - wantExtra any - wantErrRegex string + wantErrID errs.ID }{ "invalid_json": { - extra: &testExtra{}, - wantExtra: &testExtra{}, - wantErrRegex: `^decoding JSON into combination of \*.+\.ChainConfig and \*.+\.testExtra \(as "extra" key\): .+$`, + extra: &testExtra{}, + wantErrID: errIDDecodeJSONIntoCombination, }, "nil_extra_at_root_depth": { jsonData: `{"chainId": 1}`, extra: nil, reuseJSONRoot: true, - wantExtra: (*testExtra)(nil), - wantErrRegex: `^\*.+.testExtra argument is nil; use \*.+\.ChainConfig\.UnmarshalJSON\(\) directly$`, + wantErrID: errIDNilExtra, }, "nil_extra_at_extra_key": { - jsonData: `{"chainId": 1}`, - extra: nil, - wantExtra: (*testExtra)(nil), - wantErrRegex: `^\*.+\.testExtra argument is nil; use \*.+\.ChainConfig.UnmarshalJSON\(\) directly$`, + jsonData: `{"chainId": 1}`, + extra: nil, + wantErrID: errIDNilExtra, }, "wrong_extra_type_at_extra_key": { - jsonData: `{"chainId": 1, "extra": 1}`, - extra: &testExtra{}, - wantConfig: ChainConfig{ChainID: big.NewInt(1)}, - wantExtra: &testExtra{}, - wantErrRegex: `^decoding JSON into combination of \*.+\.ChainConfig and \*.+\.testExtra \(as "extra" key\): .+$`, + jsonData: `{"chainId": 1, "extra": 1}`, + extra: &testExtra{}, + wantErrID: errIDDecodeJSONIntoCombination, }, "wrong_extra_type_at_root_depth": { jsonData: `{"chainId": 1, "field": 1}`, extra: &testExtra{}, reuseJSONRoot: true, - wantConfig: ChainConfig{ChainID: big.NewInt(1)}, - wantExtra: &testExtra{}, - wantErrRegex: `^decoding JSON into \*.+\.testExtra: .+`, + wantErrID: errIDDecodeJSONIntoExtra, }, } @@ -204,14 +197,9 @@ func TestUnmarshalChainConfigJSON_Errors(t *testing.T) { data := []byte(testCase.jsonData) config := ChainConfig{} err := UnmarshalChainConfigJSON(data, &config, testCase.extra, testCase.reuseJSONRoot) - if testCase.wantErrRegex == "" { - require.NoError(t, err) - } else { - require.Error(t, err) - require.Regexp(t, testCase.wantErrRegex, err.Error()) - } - assert.Equal(t, testCase.wantConfig, config) - assert.Equal(t, testCase.wantExtra, testCase.extra) + got, ok := errs.IDOf(err) + require.True(t, ok, "errs.IDOf(UnmarshalChainConfigJSON())") + assert.Equalf(t, testCase.wantErrID, got, "UnmarshalChainConfigJSON() got error %v", err) }) } } @@ -223,31 +211,28 @@ func TestMarshalChainConfigJSON_Errors(t *testing.T) { config ChainConfig extra any reuseJSONRoot bool - wantJSONData string // string for convenience wantErrRegex string + wantErrID errs.ID }{ "invalid_extra_at_extra_key": { extra: struct { Field chan struct{} `json:"field"` }{}, - wantErrRegex: `^encoding combination of .+\.ChainConfig and .+ to JSON: .+$`, - }, - "nil_extra_at_extra_key": { - wantJSONData: `{"chainId":null}`, + wantErrID: errIDEncodeJSONCombination, }, "invalid_extra_at_root_depth": { extra: struct { Field chan struct{} `json:"field"` }{}, reuseJSONRoot: true, - wantErrRegex: "^converting extra config to JSON raw messages: .+$", + wantErrID: errIDEncodeExtraToRawJSON, }, "duplicate_key": { extra: struct { Field string `json:"chainId"` }{}, reuseJSONRoot: true, - wantErrRegex: `^duplicate JSON key "chainId" in ChainConfig and extra struct .+$`, + wantErrID: errIDEncodeDuplicateJSONKey, }, } @@ -257,14 +242,10 @@ func TestMarshalChainConfigJSON_Errors(t *testing.T) { t.Parallel() config := ChainConfig{} - data, err := MarshalChainConfigJSON(config, testCase.extra, testCase.reuseJSONRoot) - if testCase.wantErrRegex == "" { - require.NoError(t, err) - } else { - require.Error(t, err) - assert.Regexp(t, testCase.wantErrRegex, err.Error()) - } - assert.Equal(t, testCase.wantJSONData, string(data)) + _, err := MarshalChainConfigJSON(config, testCase.extra, testCase.reuseJSONRoot) + got, ok := errs.IDOf(err) + require.True(t, ok, "errs.IDOf(MarshalChainConfigJSON())") + assert.Equalf(t, testCase.wantErrID, got, "MarshalChainConfigJSON() got error %v", err) }) } } From 86043c43ce0c2bbd4b3dc12003d2426af711beca Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 3 Feb 2025 12:19:54 +0000 Subject: [PATCH 3/5] chore: separate package comment from copyright header --- internal/libevm/errs/errs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/libevm/errs/errs.go b/internal/libevm/errs/errs.go index 5ba9e48bf212..6c44d5e02474 100644 --- a/internal/libevm/errs/errs.go +++ b/internal/libevm/errs/errs.go @@ -13,7 +13,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see // . -// + // Package errs provides a mechanism for [testing error semantics] through // unique identifiers, instead of depending on error messages that may result in // change-detector tests. From 31d2f3e8d77b2c1e093c9c0b14444e26b6d968db Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 5 Feb 2025 11:43:12 +0000 Subject: [PATCH 4/5] refactor: use `errors.Is()` instead of `errs.IDOf()` --- internal/libevm/errs/errs.go | 60 +++++++++++++----------- internal/libevm/errs/errs_test.go | 77 ++++++++++++++++--------------- params/json.libevm.go | 12 ++--- params/json.libevm_test.go | 8 +--- 4 files changed, 81 insertions(+), 76 deletions(-) diff --git a/internal/libevm/errs/errs.go b/internal/libevm/errs/errs.go index 6c44d5e02474..0254537c255b 100644 --- a/internal/libevm/errs/errs.go +++ b/internal/libevm/errs/errs.go @@ -26,30 +26,49 @@ import ( "fmt" ) -// An ID is a distinct numeric identifier for an error. It has no effect on the -// error message and can only be accessed with this package. +// An ID is a distinct numeric identifier for an error. Any two errors with the +// same ID will result in [errors.Is] returning true, regardless of their +// messages. type ID int -// Error returns a new error with the ID. -func (id ID) Error(msg string) error { - return noWrap{errors.New(msg), id} +// An identifier performs ID comparison, for embedding in all error types to +// provide their Is() method. +type identifier struct { + id ID +} + +func (id identifier) errorID() ID { return id.id } + +func (id identifier) Is(target error) bool { + t, ok := target.(interface{ errorID() ID }) + if !ok { + return false + } + return t.errorID() == id.errorID() +} + +func (id ID) asIdentifier() identifier { return identifier{id} } + +// WithID returns a new error with the ID and message. +func WithID(id ID, msg string) error { + return noWrap{errors.New(msg), id.asIdentifier()} } type noWrap struct { error - id ID + identifier } -// Errorf is the formatted equivalent of [ID.Error], supporting the same +// WithIDf is the formatted equivalent of [WithID], supporting the same // wrapping semantics as [fmt.Errorf]. -func (id ID) Errorf(format string, a ...any) error { +func WithIDf(id ID, format string, a ...any) error { switch err := fmt.Errorf(format, a...).(type) { case singleWrapper: - return single{err, id} + return single{err, id.asIdentifier()} case multiWrapper: - return multi{err, id} + return multi{err, id.asIdentifier()} default: - return noWrap{err, id} + return noWrap{err, id.asIdentifier()} } } @@ -60,7 +79,7 @@ type singleWrapper interface { type single struct { singleWrapper - id ID + identifier } type multiWrapper interface { @@ -70,20 +89,5 @@ type multiWrapper interface { type multi struct { multiWrapper - id ID -} - -// IDOf returns the ID of the error, if one exists, and a flag to indicate as -// such. IDOf does not unwrap errors. -func IDOf(err error) (ID, bool) { - switch err := err.(type) { - case noWrap: - return err.id, true - case single: - return err.id, true - case multi: - return err.id, true - default: - return 0, false - } + identifier } diff --git a/internal/libevm/errs/errs_test.go b/internal/libevm/errs/errs_test.go index add0d2bd4bbd..ae1cdaaaa818 100644 --- a/internal/libevm/errs/errs_test.go +++ b/internal/libevm/errs/errs_test.go @@ -24,43 +24,48 @@ import ( "github.com/stretchr/testify/assert" ) -func TestIDOf(t *testing.T) { - tests := []struct { - err error - wantID ID - wantOK bool - }{ - { - err: errors.New("x"), - }, - { - err: fmt.Errorf("x"), - }, - { - err: ID(3).Error("x"), - wantID: 3, - wantOK: true, - }, - { - err: ID(42).Errorf("x"), - wantID: 42, - wantOK: true, - }, - { - err: ID(99).Errorf("%w", errors.New("x")), - wantID: 99, - wantOK: true, - }, - { - err: ID(0).Errorf("%w %w", errors.New("x"), errors.New("y")), - wantID: 0, - wantOK: true, - }, +func TestIs(t *testing.T) { + ids := []ID{0, 42} + errsByID := make(map[ID][]error) + for _, id := range ids { + errsByID[id] = []error{ + WithID(id, "WithID()"), + WithIDf(id, "WithIDf() no wrapping"), + WithIDf(id, "WithIDf() wrap one %w", errors.New("x")), + WithIDf(id, "WithIDf() wrap multi %w + %w", errors.New("x"), errors.New("y")), + } } - for _, tt := range tests { - got, ok := IDOf(tt.err) - assert.Equalf(t, tt.wantID, got, "IDOf(%T)", tt.err) - assert.Equalf(t, tt.wantOK, ok, "IDOf(%T)", tt.err) + unidentified := []error{ + errors.New("errors.New()"), + fmt.Errorf("fmt.Errorf()"), } + + for id, errs := range errsByID { + for _, err := range errs { + for targetID, targets := range errsByID { + want := id == targetID + for _, target := range targets { + assert.Equalf(t, want, errors.Is(err, target), "errors.Is(%v [ID %d], %v [ID %d])", err, id, target, targetID) + } + } + + for _, target := range unidentified { + assert.Falsef(t, errors.Is(err, target), "errors.Is(%v [ID %d], %v)", err, id, target) + } + } + } +} + +func Example() { + id42 := WithID(42, "hello") + alsoWithID42 := WithIDf(42, "%s", "world") + unidentified := errors.New("hello") + + fmt.Println(errors.Is(id42, alsoWithID42)) + fmt.Println(errors.Is(id42, unidentified)) + + // Output: + // true + // false } diff --git a/params/json.libevm.go b/params/json.libevm.go index c9d9c06f8985..376581b338f2 100644 --- a/params/json.libevm.go +++ b/params/json.libevm.go @@ -60,7 +60,7 @@ const ( // [RegisterExtras]. The `extra` argument MUST NOT be nil. func UnmarshalChainConfigJSON[C any](data []byte, config *ChainConfig, extra *C, reuseJSONRoot bool) (err error) { if extra == nil { - return errIDNilExtra.Errorf("%T argument is nil; use %T.UnmarshalJSON() directly", extra, config) + return errs.WithIDf(errIDNilExtra, "%T argument is nil; use %T.UnmarshalJSON() directly", extra, config) } if reuseJSONRoot { @@ -68,7 +68,7 @@ func UnmarshalChainConfigJSON[C any](data []byte, config *ChainConfig, extra *C, return fmt.Errorf("decoding JSON into %T: %s", config, err) } if err := json.Unmarshal(data, extra); err != nil { - return errIDDecodeJSONIntoExtra.Errorf("decoding JSON into %T: %s", extra, err) + return errs.WithIDf(errIDDecodeJSONIntoExtra, "decoding JSON into %T: %s", extra, err) } return nil } @@ -81,7 +81,7 @@ func UnmarshalChainConfigJSON[C any](data []byte, config *ChainConfig, extra *C, extra, } if err := json.Unmarshal(data, &combined); err != nil { - return errIDDecodeJSONIntoCombination.Errorf(`decoding JSON into combination of %T and %T (as "extra" key): %s`, config, extra, err) + return errs.WithIDf(errIDDecodeJSONIntoCombination, `decoding JSON into combination of %T and %T (as "extra" key): %s`, config, extra, err) } return nil } @@ -112,7 +112,7 @@ func MarshalChainConfigJSON[C any](config ChainConfig, extra C, reuseJSONRoot bo } data, err = json.Marshal(jsonExtra) if err != nil { - return nil, errIDEncodeJSONCombination.Errorf(`encoding combination of %T and %T (as "extra" key) to JSON: %s`, config, extra, err) + return nil, errs.WithIDf(errIDEncodeJSONCombination, `encoding combination of %T and %T (as "extra" key) to JSON: %s`, config, extra, err) } return data, nil } @@ -128,13 +128,13 @@ func MarshalChainConfigJSON[C any](config ChainConfig, extra C, reuseJSONRoot bo } extraJSONRaw, err := toJSONRawMessages(extra) if err != nil { - return nil, errIDEncodeExtraToRawJSON.Errorf("converting extra config to JSON raw messages: %s", err) + return nil, errs.WithIDf(errIDEncodeExtraToRawJSON, "converting extra config to JSON raw messages: %s", err) } for k, v := range extraJSONRaw { _, ok := configJSONRaw[k] if ok { - return nil, errIDEncodeDuplicateJSONKey.Errorf("duplicate JSON key %q in ChainConfig and extra %T", k, extra) + return nil, errs.WithIDf(errIDEncodeDuplicateJSONKey, "duplicate JSON key %q in ChainConfig and extra %T", k, extra) } configJSONRaw[k] = v } diff --git a/params/json.libevm_test.go b/params/json.libevm_test.go index 93728e0f5415..5fa21318bd4d 100644 --- a/params/json.libevm_test.go +++ b/params/json.libevm_test.go @@ -197,9 +197,7 @@ func TestUnmarshalChainConfigJSON_Errors(t *testing.T) { data := []byte(testCase.jsonData) config := ChainConfig{} err := UnmarshalChainConfigJSON(data, &config, testCase.extra, testCase.reuseJSONRoot) - got, ok := errs.IDOf(err) - require.True(t, ok, "errs.IDOf(UnmarshalChainConfigJSON())") - assert.Equalf(t, testCase.wantErrID, got, "UnmarshalChainConfigJSON() got error %v", err) + assert.ErrorIs(t, err, errs.WithID(testCase.wantErrID, "")) }) } } @@ -243,9 +241,7 @@ func TestMarshalChainConfigJSON_Errors(t *testing.T) { config := ChainConfig{} _, err := MarshalChainConfigJSON(config, testCase.extra, testCase.reuseJSONRoot) - got, ok := errs.IDOf(err) - require.True(t, ok, "errs.IDOf(MarshalChainConfigJSON())") - assert.Equalf(t, testCase.wantErrID, got, "MarshalChainConfigJSON() got error %v", err) + assert.ErrorIs(t, err, errs.WithID(testCase.wantErrID, "")) }) } } From 912e4180397f4ecaaff0b0870e64e1947d320990 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 5 Feb 2025 11:50:19 +0000 Subject: [PATCH 5/5] refactor: use `assert.NotErrorIs()` --- internal/libevm/errs/errs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/libevm/errs/errs_test.go b/internal/libevm/errs/errs_test.go index ae1cdaaaa818..bfa6597475dd 100644 --- a/internal/libevm/errs/errs_test.go +++ b/internal/libevm/errs/errs_test.go @@ -51,7 +51,7 @@ func TestIs(t *testing.T) { } for _, target := range unidentified { - assert.Falsef(t, errors.Is(err, target), "errors.Is(%v [ID %d], %v)", err, id, target) + assert.NotErrorIsf(t, err, target, "error ID %d", id) } } }