From 99dad5281660c4e644602e0c8790dd24b3eb45f3 Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Mon, 18 Nov 2024 17:34:06 -0800 Subject: [PATCH] encoding/json: check exact structure of local error types in tests MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit During the development of error wrapping (#29934), the tests were modified to stop using reflect.DeepEqual since the prototype for error wrapping at the time included frame information of where the error was created. However, that change diminished the fidelity of the test so that it is no longer as strict, which affects the endeavor to implement v1 in terms of the v2 prototype. For locally declared error types, use reflect.DeepEqual to check that the exact structure of the error value matches. Change-Id: I443d418533866ab8d533bca3785fdc741e2c140e Reviewed-on: https://go-review.googlesource.com/c/go/+/629517 Auto-Submit: Damien Neil Reviewed-by: Ian Lance Taylor Reviewed-by: Daniel Martí Reviewed-by: Damien Neil LUCI-TryBot-Result: Go LUCI --- src/encoding/json/decode_test.go | 77 +++++++++++++++----------------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/src/encoding/json/decode_test.go b/src/encoding/json/decode_test.go index 71895a9bb1..de09fae50f 100644 --- a/src/encoding/json/decode_test.go +++ b/src/encoding/json/decode_test.go @@ -443,7 +443,7 @@ var unmarshalTests = []struct { {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), out: tx{}}, {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, {CaseName: Name(""), in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[SS](), 0, "W", "S"}}, - {CaseName: Name(""), in: `{"T": {"X": 23}}`, ptr: new(TOuter), out: TOuter{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), 0, "TOuter", "T.X"}}, + {CaseName: Name(""), in: `{"T": {"X": 23}}`, ptr: new(TOuter), out: TOuter{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), 8, "TOuter", "T.X"}}, {CaseName: Name(""), in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, {CaseName: Name(""), in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, {CaseName: Name(""), in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsFloat64}, @@ -907,7 +907,7 @@ var unmarshalTests = []struct { Struct: "Top", Field: "Embed0a.Level1a", Type: reflect.TypeFor[int](), - Offset: 10, + Offset: 19, }, }, @@ -1029,7 +1029,7 @@ var unmarshalTests = []struct { Struct: "T", Field: "Ts.Y", Type: reflect.TypeFor[int](), - Offset: 29, + Offset: 44, }, }, // #14702 @@ -1170,9 +1170,28 @@ func TestMarshalEmbeds(t *testing.T) { } func equalError(a, b error) bool { + isJSONError := func(err error) bool { + switch err.(type) { + case + *InvalidUTF8Error, + *InvalidUnmarshalError, + *MarshalerError, + *SyntaxError, + *UnmarshalFieldError, + *UnmarshalTypeError, + *UnsupportedTypeError, + *UnsupportedValueError: + return true + } + return false + } + if a == nil || b == nil { return a == nil && b == nil } + if isJSONError(a) || isJSONError(b) { + return reflect.DeepEqual(a, b) // safe for locally defined error types + } return a.Error() == b.Error() } @@ -1217,7 +1236,7 @@ func TestUnmarshal(t *testing.T) { dec.DisallowUnknownFields() } if err := dec.Decode(v.Interface()); !equalError(err, tt.err) { - t.Fatalf("%s: Decode error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) + t.Fatalf("%s: Decode error:\n\tgot: %#v\n\twant: %#v", tt.Where, err, tt.err) } else if err != nil { return } @@ -2222,49 +2241,27 @@ func TestPrefilled(t *testing.T) { } func TestInvalidUnmarshal(t *testing.T) { - buf := []byte(`{"a":"1"}`) tests := []struct { CaseName - v any - want string + in string + v any + wantErr error }{ - {Name(""), nil, "json: Unmarshal(nil)"}, - {Name(""), struct{}{}, "json: Unmarshal(non-pointer struct {})"}, - {Name(""), (*int)(nil), "json: Unmarshal(nil *int)"}, + {Name(""), `{"a":"1"}`, nil, &InvalidUnmarshalError{}}, + {Name(""), `{"a":"1"}`, struct{}{}, &InvalidUnmarshalError{reflect.TypeFor[struct{}]()}}, + {Name(""), `{"a":"1"}`, (*int)(nil), &InvalidUnmarshalError{reflect.TypeFor[*int]()}}, + {Name(""), `123`, nil, &InvalidUnmarshalError{}}, + {Name(""), `123`, struct{}{}, &InvalidUnmarshalError{reflect.TypeFor[struct{}]()}}, + {Name(""), `123`, (*int)(nil), &InvalidUnmarshalError{reflect.TypeFor[*int]()}}, + {Name(""), `123`, new(net.IP), &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[*net.IP](), Offset: 3}}, } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - err := Unmarshal(buf, tt.v) - if err == nil { + switch gotErr := Unmarshal([]byte(tt.in), tt.v); { + case gotErr == nil: t.Fatalf("%s: Unmarshal error: got nil, want non-nil", tt.Where) - } - if got := err.Error(); got != tt.want { - t.Errorf("%s: Unmarshal error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.want) - } - }) - } -} - -func TestInvalidUnmarshalText(t *testing.T) { - buf := []byte(`123`) - tests := []struct { - CaseName - v any - want string - }{ - {Name(""), nil, "json: Unmarshal(nil)"}, - {Name(""), struct{}{}, "json: Unmarshal(non-pointer struct {})"}, - {Name(""), (*int)(nil), "json: Unmarshal(nil *int)"}, - {Name(""), new(net.IP), "json: cannot unmarshal number into Go value of type *net.IP"}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - err := Unmarshal(buf, tt.v) - if err == nil { - t.Fatalf("%s: Unmarshal error: got nil, want non-nil", tt.Where) - } - if got := err.Error(); got != tt.want { - t.Errorf("%s: Unmarshal error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.want) + case !reflect.DeepEqual(gotErr, tt.wantErr): + t.Errorf("%s: Unmarshal error:\n\tgot: %#v\n\twant: %#v", tt.Where, gotErr, tt.wantErr) } }) } -- 2.48.1