From 3b4d428ca0efaa309f7254ed378111cf76a1267d Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Thu, 24 Aug 2023 12:49:10 -0700 Subject: [PATCH] encoding/json: modernize tests MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit There are no changes to what is being tested. No test cases were removed or added. Changes made: * Use a local implementation of test case position marking. See #52751. * Use consistent names for all test tables and variables. * Generally speaking, follow modern Go style guide for tests. * Move global tables local to the test function if possible. * Make every table entry run in a distinct testing.T.Run. The purpose of this change is to make it easier to perform v1-to-v2 development where we want v2 to support close to bug-for-bug compatibility when running in v1 mode. Annotating each test case with the location of the test data makes it easier to jump directly to the test data itself and understand why this particular case is failing. Having every test case run in its own t.Run makes it easier to isolate a particular failing test and work on fixing the code until that test case starts to pass again. Unfortunately, many tests are annotated with an empty name. An empty name is better than nothing, since the testing framework auto assigns a numeric ID for duplicate names. It is not worth the trouble to give descriptive names to each of the thousands of test cases. Change-Id: I43905f35249b3d77dfca234b9c7808d40e225de8 Reviewed-on: https://go-review.googlesource.com/c/go/+/522880 Auto-Submit: Joseph Tsai Run-TryBot: Joseph Tsai TryBot-Result: Gopher Robot Reviewed-by: Daniel Martí Reviewed-by: Bryan Mills Reviewed-by: Damien Neil --- src/encoding/json/bench_test.go | 53 +- src/encoding/json/decode_test.go | 1644 +++++++++++++++-------------- src/encoding/json/encode_test.go | 565 +++++----- src/encoding/json/scanner_test.go | 223 ++-- src/encoding/json/stream_test.go | 403 +++---- src/encoding/json/tagkey_test.go | 83 +- src/encoding/json/tags_test.go | 2 +- 7 files changed, 1528 insertions(+), 1445 deletions(-) diff --git a/src/encoding/json/bench_test.go b/src/encoding/json/bench_test.go index 0f080acdbf..f7bcf8073c 100644 --- a/src/encoding/json/bench_test.go +++ b/src/encoding/json/bench_test.go @@ -93,7 +93,7 @@ func BenchmarkCodeEncoder(b *testing.B) { enc := NewEncoder(io.Discard) for pb.Next() { if err := enc.Encode(&codeStruct); err != nil { - b.Fatal("Encode:", err) + b.Fatalf("Encode error: %v", err) } } }) @@ -120,10 +120,10 @@ func BenchmarkCodeEncoderError(b *testing.B) { enc := NewEncoder(io.Discard) for pb.Next() { if err := enc.Encode(&codeStruct); err != nil { - b.Fatal("Encode:", err) + b.Fatalf("Encode error: %v", err) } if _, err := Marshal(dummy); err == nil { - b.Fatal("expect an error here") + b.Fatal("Marshal error: got nil, want non-nil") } } }) @@ -140,7 +140,7 @@ func BenchmarkCodeMarshal(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { if _, err := Marshal(&codeStruct); err != nil { - b.Fatal("Marshal:", err) + b.Fatalf("Marshal error: %v", err) } } }) @@ -166,10 +166,10 @@ func BenchmarkCodeMarshalError(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { if _, err := Marshal(&codeStruct); err != nil { - b.Fatal("Marshal:", err) + b.Fatalf("Marshal error: %v", err) } if _, err := Marshal(dummy); err == nil { - b.Fatal("expect an error here") + b.Fatal("Marshal error: got nil, want non-nil") } } }) @@ -188,7 +188,7 @@ func benchMarshalBytes(n int) func(*testing.B) { return func(b *testing.B) { for i := 0; i < b.N; i++ { if _, err := Marshal(v); err != nil { - b.Fatal("Marshal:", err) + b.Fatalf("Marshal error: %v", err) } } } @@ -215,10 +215,10 @@ func benchMarshalBytesError(n int) func(*testing.B) { return func(b *testing.B) { for i := 0; i < b.N; i++ { if _, err := Marshal(v); err != nil { - b.Fatal("Marshal:", err) + b.Fatalf("Marshal error: %v", err) } if _, err := Marshal(dummy); err == nil { - b.Fatal("expect an error here") + b.Fatal("Marshal error: got nil, want non-nil") } } } @@ -280,7 +280,7 @@ func BenchmarkCodeDecoder(b *testing.B) { buf.WriteByte('\n') buf.WriteByte('\n') if err := dec.Decode(&r); err != nil { - b.Fatal("Decode:", err) + b.Fatalf("Decode error: %v", err) } } }) @@ -297,7 +297,7 @@ func BenchmarkUnicodeDecoder(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { if err := dec.Decode(&out); err != nil { - b.Fatal("Decode:", err) + b.Fatalf("Decode error: %v", err) } r.Seek(0, 0) } @@ -311,7 +311,7 @@ func BenchmarkDecoderStream(b *testing.B) { buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n") var x any if err := dec.Decode(&x); err != nil { - b.Fatal("Decode:", err) + b.Fatalf("Decode error: %v", err) } ones := strings.Repeat(" 1\n", 300000) + "\n\n\n" b.StartTimer() @@ -320,8 +320,11 @@ func BenchmarkDecoderStream(b *testing.B) { buf.WriteString(ones) } x = nil - if err := dec.Decode(&x); err != nil || x != 1.0 { - b.Fatalf("Decode: %v after %d", err, i) + switch err := dec.Decode(&x); { + case err != nil: + b.Fatalf("Decode error: %v", err) + case x != 1.0: + b.Fatalf("Decode: got %v want 1.0", i) } } } @@ -337,7 +340,7 @@ func BenchmarkCodeUnmarshal(b *testing.B) { for pb.Next() { var r codeResponse if err := Unmarshal(codeJSON, &r); err != nil { - b.Fatal("Unmarshal:", err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -355,7 +358,7 @@ func BenchmarkCodeUnmarshalReuse(b *testing.B) { var r codeResponse for pb.Next() { if err := Unmarshal(codeJSON, &r); err != nil { - b.Fatal("Unmarshal:", err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -369,7 +372,7 @@ func BenchmarkUnmarshalString(b *testing.B) { var s string for pb.Next() { if err := Unmarshal(data, &s); err != nil { - b.Fatal("Unmarshal:", err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -382,7 +385,7 @@ func BenchmarkUnmarshalFloat64(b *testing.B) { var f float64 for pb.Next() { if err := Unmarshal(data, &f); err != nil { - b.Fatal("Unmarshal:", err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -395,7 +398,7 @@ func BenchmarkUnmarshalInt64(b *testing.B) { var x int64 for pb.Next() { if err := Unmarshal(data, &x); err != nil { - b.Fatal("Unmarshal:", err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -408,7 +411,7 @@ func BenchmarkUnmarshalMap(b *testing.B) { x := make(map[string]string, 3) for pb.Next() { if err := Unmarshal(data, &x); err != nil { - b.Fatal("Unmarshal:", err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -421,7 +424,7 @@ func BenchmarkIssue10335(b *testing.B) { var s struct{} for pb.Next() { if err := Unmarshal(j, &s); err != nil { - b.Fatal(err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -437,7 +440,7 @@ func BenchmarkIssue34127(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { if _, err := Marshal(&j); err != nil { - b.Fatal(err) + b.Fatalf("Marshal error: %v", err) } } }) @@ -450,7 +453,7 @@ func BenchmarkUnmapped(b *testing.B) { var s struct{} for pb.Next() { if err := Unmarshal(j, &s); err != nil { - b.Fatal(err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -533,7 +536,7 @@ func BenchmarkEncodeMarshaler(b *testing.B) { for pb.Next() { if err := enc.Encode(&m); err != nil { - b.Fatal("Encode:", err) + b.Fatalf("Encode error: %v", err) } } }) @@ -548,7 +551,7 @@ func BenchmarkEncoderEncode(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { if err := NewEncoder(io.Discard).Encode(v); err != nil { - b.Fatal(err) + b.Fatalf("Encode error: %v", err) } } }) diff --git a/src/encoding/json/decode_test.go b/src/encoding/json/decode_test.go index 5c34139d92..a10c1e1ebb 100644 --- a/src/encoding/json/decode_test.go +++ b/src/encoding/json/decode_test.go @@ -387,16 +387,6 @@ type mapStringToStringData struct { Data map[string]string `json:"data"` } -type unmarshalTest struct { - in string - ptr any // new(type) - out any - err error - useNumber bool - golden bool - disallowUnknownFields bool -} - type B struct { B bool `json:",string"` } @@ -406,179 +396,203 @@ type DoublePtr struct { J **int } -var unmarshalTests = []unmarshalTest{ +var unmarshalTests = []struct { + CaseName + in string + ptr any // new(type) + out any + err error + useNumber bool + golden bool + disallowUnknownFields bool +}{ // basic types - {in: `true`, ptr: new(bool), out: true}, - {in: `1`, ptr: new(int), out: 1}, - {in: `1.2`, ptr: new(float64), out: 1.2}, - {in: `-5`, ptr: new(int16), out: int16(-5)}, - {in: `2`, ptr: new(Number), out: Number("2"), useNumber: true}, - {in: `2`, ptr: new(Number), out: Number("2")}, - {in: `2`, ptr: new(any), out: float64(2.0)}, - {in: `2`, ptr: new(any), out: Number("2"), useNumber: true}, - {in: `"a\u1234"`, ptr: new(string), out: "a\u1234"}, - {in: `"http:\/\/"`, ptr: new(string), out: "http://"}, - {in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"}, - {in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, - {in: "null", ptr: new(any), out: nil}, - {in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeFor[string](), 7, "T", "X"}}, - {in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), 8, "T", "X"}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, - {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, - {in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, - {in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[SS](), 0, "W", "S"}}, - {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, - {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, - {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsFloat64}, - {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsNumber, useNumber: true}, + {CaseName: Name(""), in: `true`, ptr: new(bool), out: true}, + {CaseName: Name(""), in: `1`, ptr: new(int), out: 1}, + {CaseName: Name(""), in: `1.2`, ptr: new(float64), out: 1.2}, + {CaseName: Name(""), in: `-5`, ptr: new(int16), out: int16(-5)}, + {CaseName: Name(""), in: `2`, ptr: new(Number), out: Number("2"), useNumber: true}, + {CaseName: Name(""), in: `2`, ptr: new(Number), out: Number("2")}, + {CaseName: Name(""), in: `2`, ptr: new(any), out: float64(2.0)}, + {CaseName: Name(""), in: `2`, ptr: new(any), out: Number("2"), useNumber: true}, + {CaseName: Name(""), in: `"a\u1234"`, ptr: new(string), out: "a\u1234"}, + {CaseName: Name(""), in: `"http:\/\/"`, ptr: new(string), out: "http://"}, + {CaseName: Name(""), in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"}, + {CaseName: Name(""), in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, + {CaseName: Name(""), in: "null", ptr: new(any), out: nil}, + {CaseName: Name(""), in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeFor[string](), 7, "T", "X"}}, + {CaseName: Name(""), in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), 8, "T", "X"}}, + {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), out: tx{}}, + {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: `{"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}, + {CaseName: Name(""), in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsNumber, useNumber: true}, // raw values with whitespace - {in: "\n true ", ptr: new(bool), out: true}, - {in: "\t 1 ", ptr: new(int), out: 1}, - {in: "\r 1.2 ", ptr: new(float64), out: 1.2}, - {in: "\t -5 \n", ptr: new(int16), out: int16(-5)}, - {in: "\t \"a\\u1234\" \n", ptr: new(string), out: "a\u1234"}, + {CaseName: Name(""), in: "\n true ", ptr: new(bool), out: true}, + {CaseName: Name(""), in: "\t 1 ", ptr: new(int), out: 1}, + {CaseName: Name(""), in: "\r 1.2 ", ptr: new(float64), out: 1.2}, + {CaseName: Name(""), in: "\t -5 \n", ptr: new(int16), out: int16(-5)}, + {CaseName: Name(""), in: "\t \"a\\u1234\" \n", ptr: new(string), out: "a\u1234"}, // Z has a "-" tag. - {in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}}, - {in: `{"Y": 1, "Z": 2}`, ptr: new(T), err: fmt.Errorf("json: unknown field \"Z\""), disallowUnknownFields: true}, + {CaseName: Name(""), in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}}, + {CaseName: Name(""), in: `{"Y": 1, "Z": 2}`, ptr: new(T), err: fmt.Errorf("json: unknown field \"Z\""), disallowUnknownFields: true}, - {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}}, - {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, - {in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}}, - {in: `{"alphabet": "xyz"}`, ptr: new(U), out: U{}}, - {in: `{"alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, + {CaseName: Name(""), in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}}, + {CaseName: Name(""), in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, + {CaseName: Name(""), in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}}, + {CaseName: Name(""), in: `{"alphabet": "xyz"}`, ptr: new(U), out: U{}}, + {CaseName: Name(""), in: `{"alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, // syntax errors - {in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}}, - {in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element", 9}}, - {in: `{"X":12x}`, err: &SyntaxError{"invalid character 'x' after object key:value pair", 8}, useNumber: true}, - {in: `[2, 3`, err: &SyntaxError{msg: "unexpected end of JSON input", Offset: 5}}, - {in: `{"F3": -}`, ptr: new(V), out: V{F3: Number("-")}, err: &SyntaxError{msg: "invalid character '}' in numeric literal", Offset: 9}}, + {CaseName: Name(""), in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}}, + {CaseName: Name(""), in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element", 9}}, + {CaseName: Name(""), in: `{"X":12x}`, err: &SyntaxError{"invalid character 'x' after object key:value pair", 8}, useNumber: true}, + {CaseName: Name(""), in: `[2, 3`, err: &SyntaxError{msg: "unexpected end of JSON input", Offset: 5}}, + {CaseName: Name(""), in: `{"F3": -}`, ptr: new(V), out: V{F3: Number("-")}, err: &SyntaxError{msg: "invalid character '}' in numeric literal", Offset: 9}}, // raw value errors - {in: "\x01 42", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {in: " 42 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 5}}, - {in: "\x01 true", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {in: " false \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 8}}, - {in: "\x01 1.2", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {in: " 3.4 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 6}}, - {in: "\x01 \"string\"", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {in: " \"string\" \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 11}}, + {CaseName: Name(""), in: "\x01 42", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, + {CaseName: Name(""), in: " 42 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 5}}, + {CaseName: Name(""), in: "\x01 true", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, + {CaseName: Name(""), in: " false \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 8}}, + {CaseName: Name(""), in: "\x01 1.2", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, + {CaseName: Name(""), in: " 3.4 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 6}}, + {CaseName: Name(""), in: "\x01 \"string\"", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, + {CaseName: Name(""), in: " \"string\" \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 11}}, // array tests - {in: `[1, 2, 3]`, ptr: new([3]int), out: [3]int{1, 2, 3}}, - {in: `[1, 2, 3]`, ptr: new([1]int), out: [1]int{1}}, - {in: `[1, 2, 3]`, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}}, - {in: `[1, 2, 3]`, ptr: new(MustNotUnmarshalJSON), err: errors.New("MustNotUnmarshalJSON was used")}, + {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new([3]int), out: [3]int{1, 2, 3}}, + {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new([1]int), out: [1]int{1}}, + {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}}, + {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new(MustNotUnmarshalJSON), err: errors.New("MustNotUnmarshalJSON was used")}, // empty array to interface test - {in: `[]`, ptr: new([]any), out: []any{}}, - {in: `null`, ptr: new([]any), out: []any(nil)}, - {in: `{"T":[]}`, ptr: new(map[string]any), out: map[string]any{"T": []any{}}}, - {in: `{"T":null}`, ptr: new(map[string]any), out: map[string]any{"T": any(nil)}}, + {CaseName: Name(""), in: `[]`, ptr: new([]any), out: []any{}}, + {CaseName: Name(""), in: `null`, ptr: new([]any), out: []any(nil)}, + {CaseName: Name(""), in: `{"T":[]}`, ptr: new(map[string]any), out: map[string]any{"T": []any{}}}, + {CaseName: Name(""), in: `{"T":null}`, ptr: new(map[string]any), out: map[string]any{"T": any(nil)}}, // composite tests - {in: allValueIndent, ptr: new(All), out: allValue}, - {in: allValueCompact, ptr: new(All), out: allValue}, - {in: allValueIndent, ptr: new(*All), out: &allValue}, - {in: allValueCompact, ptr: new(*All), out: &allValue}, - {in: pallValueIndent, ptr: new(All), out: pallValue}, - {in: pallValueCompact, ptr: new(All), out: pallValue}, - {in: pallValueIndent, ptr: new(*All), out: &pallValue}, - {in: pallValueCompact, ptr: new(*All), out: &pallValue}, + {CaseName: Name(""), in: allValueIndent, ptr: new(All), out: allValue}, + {CaseName: Name(""), in: allValueCompact, ptr: new(All), out: allValue}, + {CaseName: Name(""), in: allValueIndent, ptr: new(*All), out: &allValue}, + {CaseName: Name(""), in: allValueCompact, ptr: new(*All), out: &allValue}, + {CaseName: Name(""), in: pallValueIndent, ptr: new(All), out: pallValue}, + {CaseName: Name(""), in: pallValueCompact, ptr: new(All), out: pallValue}, + {CaseName: Name(""), in: pallValueIndent, ptr: new(*All), out: &pallValue}, + {CaseName: Name(""), in: pallValueCompact, ptr: new(*All), out: &pallValue}, // unmarshal interface test - {in: `{"T":false}`, ptr: new(unmarshaler), out: umtrue}, // use "false" so test will fail if custom unmarshaler is not called - {in: `{"T":false}`, ptr: new(*unmarshaler), out: &umtrue}, - {in: `[{"T":false}]`, ptr: new([]unmarshaler), out: umslice}, - {in: `[{"T":false}]`, ptr: new(*[]unmarshaler), out: &umslice}, - {in: `{"M":{"T":"x:y"}}`, ptr: new(ustruct), out: umstruct}, + {CaseName: Name(""), in: `{"T":false}`, ptr: new(unmarshaler), out: umtrue}, // use "false" so test will fail if custom unmarshaler is not called + {CaseName: Name(""), in: `{"T":false}`, ptr: new(*unmarshaler), out: &umtrue}, + {CaseName: Name(""), in: `[{"T":false}]`, ptr: new([]unmarshaler), out: umslice}, + {CaseName: Name(""), in: `[{"T":false}]`, ptr: new(*[]unmarshaler), out: &umslice}, + {CaseName: Name(""), in: `{"M":{"T":"x:y"}}`, ptr: new(ustruct), out: umstruct}, // UnmarshalText interface test - {in: `"x:y"`, ptr: new(unmarshalerText), out: umtrueXY}, - {in: `"x:y"`, ptr: new(*unmarshalerText), out: &umtrueXY}, - {in: `["x:y"]`, ptr: new([]unmarshalerText), out: umsliceXY}, - {in: `["x:y"]`, ptr: new(*[]unmarshalerText), out: &umsliceXY}, - {in: `{"M":"x:y"}`, ptr: new(ustructText), out: umstructXY}, + {CaseName: Name(""), in: `"x:y"`, ptr: new(unmarshalerText), out: umtrueXY}, + {CaseName: Name(""), in: `"x:y"`, ptr: new(*unmarshalerText), out: &umtrueXY}, + {CaseName: Name(""), in: `["x:y"]`, ptr: new([]unmarshalerText), out: umsliceXY}, + {CaseName: Name(""), in: `["x:y"]`, ptr: new(*[]unmarshalerText), out: &umsliceXY}, + {CaseName: Name(""), in: `{"M":"x:y"}`, ptr: new(ustructText), out: umstructXY}, // integer-keyed map test { - in: `{"-1":"a","0":"b","1":"c"}`, - ptr: new(map[int]string), - out: map[int]string{-1: "a", 0: "b", 1: "c"}, + CaseName: Name(""), + in: `{"-1":"a","0":"b","1":"c"}`, + ptr: new(map[int]string), + out: map[int]string{-1: "a", 0: "b", 1: "c"}, }, { - in: `{"0":"a","10":"c","9":"b"}`, - ptr: new(map[u8]string), - out: map[u8]string{0: "a", 9: "b", 10: "c"}, + CaseName: Name(""), + in: `{"0":"a","10":"c","9":"b"}`, + ptr: new(map[u8]string), + out: map[u8]string{0: "a", 9: "b", 10: "c"}, }, { - in: `{"-9223372036854775808":"min","9223372036854775807":"max"}`, - ptr: new(map[int64]string), - out: map[int64]string{math.MinInt64: "min", math.MaxInt64: "max"}, + CaseName: Name(""), + in: `{"-9223372036854775808":"min","9223372036854775807":"max"}`, + ptr: new(map[int64]string), + out: map[int64]string{math.MinInt64: "min", math.MaxInt64: "max"}, }, { - in: `{"18446744073709551615":"max"}`, - ptr: new(map[uint64]string), - out: map[uint64]string{math.MaxUint64: "max"}, + CaseName: Name(""), + in: `{"18446744073709551615":"max"}`, + ptr: new(map[uint64]string), + out: map[uint64]string{math.MaxUint64: "max"}, }, { - in: `{"0":false,"10":true}`, - ptr: new(map[uintptr]bool), - out: map[uintptr]bool{0: false, 10: true}, + CaseName: Name(""), + in: `{"0":false,"10":true}`, + ptr: new(map[uintptr]bool), + out: map[uintptr]bool{0: false, 10: true}, }, // Check that MarshalText and UnmarshalText take precedence // over default integer handling in map keys. { - in: `{"u2":4}`, - ptr: new(map[u8marshal]int), - out: map[u8marshal]int{2: 4}, + CaseName: Name(""), + in: `{"u2":4}`, + ptr: new(map[u8marshal]int), + out: map[u8marshal]int{2: 4}, }, { - in: `{"2":4}`, - ptr: new(map[u8marshal]int), - err: errMissingU8Prefix, + CaseName: Name(""), + in: `{"2":4}`, + ptr: new(map[u8marshal]int), + err: errMissingU8Prefix, }, // integer-keyed map errors { - in: `{"abc":"abc"}`, - ptr: new(map[int]string), - err: &UnmarshalTypeError{Value: "number abc", Type: reflect.TypeFor[int](), Offset: 2}, + CaseName: Name(""), + in: `{"abc":"abc"}`, + ptr: new(map[int]string), + err: &UnmarshalTypeError{Value: "number abc", Type: reflect.TypeFor[int](), Offset: 2}, }, { - in: `{"256":"abc"}`, - ptr: new(map[uint8]string), - err: &UnmarshalTypeError{Value: "number 256", Type: reflect.TypeFor[uint8](), Offset: 2}, + CaseName: Name(""), + in: `{"256":"abc"}`, + ptr: new(map[uint8]string), + err: &UnmarshalTypeError{Value: "number 256", Type: reflect.TypeFor[uint8](), Offset: 2}, }, { - in: `{"128":"abc"}`, - ptr: new(map[int8]string), - err: &UnmarshalTypeError{Value: "number 128", Type: reflect.TypeFor[int8](), Offset: 2}, + CaseName: Name(""), + in: `{"128":"abc"}`, + ptr: new(map[int8]string), + err: &UnmarshalTypeError{Value: "number 128", Type: reflect.TypeFor[int8](), Offset: 2}, }, { - in: `{"-1":"abc"}`, - ptr: new(map[uint8]string), - err: &UnmarshalTypeError{Value: "number -1", Type: reflect.TypeFor[uint8](), Offset: 2}, + CaseName: Name(""), + in: `{"-1":"abc"}`, + ptr: new(map[uint8]string), + err: &UnmarshalTypeError{Value: "number -1", Type: reflect.TypeFor[uint8](), Offset: 2}, }, { - in: `{"F":{"a":2,"3":4}}`, - ptr: new(map[string]map[int]int), - err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeFor[int](), Offset: 7}, + CaseName: Name(""), + in: `{"F":{"a":2,"3":4}}`, + ptr: new(map[string]map[int]int), + err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeFor[int](), Offset: 7}, }, { - in: `{"F":{"a":2,"3":4}}`, - ptr: new(map[string]map[uint]int), - err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeFor[uint](), Offset: 7}, + CaseName: Name(""), + in: `{"F":{"a":2,"3":4}}`, + ptr: new(map[string]map[uint]int), + err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeFor[uint](), Offset: 7}, }, // Map keys can be encoding.TextUnmarshalers. - {in: `{"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, + {CaseName: Name(""), in: `{"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, // If multiple values for the same key exists, only the most recent value is used. - {in: `{"x:y":false,"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, + {CaseName: Name(""), in: `{"x:y":false,"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, { + CaseName: Name(""), in: `{ "Level0": 1, "Level1b": 2, @@ -634,93 +648,109 @@ var unmarshalTests = []unmarshalTest{ }, }, { - in: `{"hello": 1}`, - ptr: new(Ambig), - out: Ambig{First: 1}, + CaseName: Name(""), + in: `{"hello": 1}`, + ptr: new(Ambig), + out: Ambig{First: 1}, }, { - in: `{"X": 1,"Y":2}`, - ptr: new(S5), - out: S5{S8: S8{S9: S9{Y: 2}}}, + CaseName: Name(""), + in: `{"X": 1,"Y":2}`, + ptr: new(S5), + out: S5{S8: S8{S9: S9{Y: 2}}}, }, { + CaseName: Name(""), in: `{"X": 1,"Y":2}`, ptr: new(S5), err: fmt.Errorf("json: unknown field \"X\""), disallowUnknownFields: true, }, { - in: `{"X": 1,"Y":2}`, - ptr: new(S10), - out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, + CaseName: Name(""), + in: `{"X": 1,"Y":2}`, + ptr: new(S10), + out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, }, { + CaseName: Name(""), in: `{"X": 1,"Y":2}`, ptr: new(S10), err: fmt.Errorf("json: unknown field \"X\""), disallowUnknownFields: true, }, { - in: `{"I": 0, "I": null, "J": null}`, - ptr: new(DoublePtr), - out: DoublePtr{I: nil, J: nil}, + CaseName: Name(""), + in: `{"I": 0, "I": null, "J": null}`, + ptr: new(DoublePtr), + out: DoublePtr{I: nil, J: nil}, }, // invalid UTF-8 is coerced to valid UTF-8. { - in: "\"hello\xffworld\"", - ptr: new(string), - out: "hello\ufffdworld", + CaseName: Name(""), + in: "\"hello\xffworld\"", + ptr: new(string), + out: "hello\ufffdworld", }, { - in: "\"hello\xc2\xc2world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", + CaseName: Name(""), + in: "\"hello\xc2\xc2world\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", }, { - in: "\"hello\xc2\xffworld\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", + CaseName: Name(""), + in: "\"hello\xc2\xffworld\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", }, { - in: "\"hello\\ud800world\"", - ptr: new(string), - out: "hello\ufffdworld", + CaseName: Name(""), + in: "\"hello\\ud800world\"", + ptr: new(string), + out: "hello\ufffdworld", }, { - in: "\"hello\\ud800\\ud800world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", + CaseName: Name(""), + in: "\"hello\\ud800\\ud800world\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", }, { - in: "\"hello\\ud800\\ud800world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", + CaseName: Name(""), + in: "\"hello\\ud800\\ud800world\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", }, { - in: "\"hello\xed\xa0\x80\xed\xb0\x80world\"", - ptr: new(string), - out: "hello\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdworld", + CaseName: Name(""), + in: "\"hello\xed\xa0\x80\xed\xb0\x80world\"", + ptr: new(string), + out: "hello\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdworld", }, // Used to be issue 8305, but time.Time implements encoding.TextUnmarshaler so this works now. { - in: `{"2009-11-10T23:00:00Z": "hello world"}`, - ptr: new(map[time.Time]string), - out: map[time.Time]string{time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC): "hello world"}, + CaseName: Name(""), + in: `{"2009-11-10T23:00:00Z": "hello world"}`, + ptr: new(map[time.Time]string), + out: map[time.Time]string{time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC): "hello world"}, }, // issue 8305 { - in: `{"2009-11-10T23:00:00Z": "hello world"}`, - ptr: new(map[Point]string), - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[map[Point]string](), Offset: 1}, + CaseName: Name(""), + in: `{"2009-11-10T23:00:00Z": "hello world"}`, + ptr: new(map[Point]string), + err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[map[Point]string](), Offset: 1}, }, { - in: `{"asdf": "hello world"}`, - ptr: new(map[unmarshaler]string), - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[map[unmarshaler]string](), Offset: 1}, + CaseName: Name(""), + in: `{"asdf": "hello world"}`, + ptr: new(map[unmarshaler]string), + err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[map[unmarshaler]string](), Offset: 1}, }, // related to issue 13783. @@ -731,91 +761,104 @@ var unmarshalTests = []unmarshalTest{ // successfully unmarshaled. The custom unmarshalers were accessible in earlier // versions of Go, even though the custom marshaler was not. { - in: `"AQID"`, - ptr: new([]byteWithMarshalJSON), - out: []byteWithMarshalJSON{1, 2, 3}, + CaseName: Name(""), + in: `"AQID"`, + ptr: new([]byteWithMarshalJSON), + out: []byteWithMarshalJSON{1, 2, 3}, }, { - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithMarshalJSON), - out: []byteWithMarshalJSON{1, 2, 3}, - golden: true, + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithMarshalJSON), + out: []byteWithMarshalJSON{1, 2, 3}, + golden: true, }, { - in: `"AQID"`, - ptr: new([]byteWithMarshalText), - out: []byteWithMarshalText{1, 2, 3}, + CaseName: Name(""), + in: `"AQID"`, + ptr: new([]byteWithMarshalText), + out: []byteWithMarshalText{1, 2, 3}, }, { - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithMarshalText), - out: []byteWithMarshalText{1, 2, 3}, - golden: true, + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithMarshalText), + out: []byteWithMarshalText{1, 2, 3}, + golden: true, }, { - in: `"AQID"`, - ptr: new([]byteWithPtrMarshalJSON), - out: []byteWithPtrMarshalJSON{1, 2, 3}, + CaseName: Name(""), + in: `"AQID"`, + ptr: new([]byteWithPtrMarshalJSON), + out: []byteWithPtrMarshalJSON{1, 2, 3}, }, { - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithPtrMarshalJSON), - out: []byteWithPtrMarshalJSON{1, 2, 3}, - golden: true, + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithPtrMarshalJSON), + out: []byteWithPtrMarshalJSON{1, 2, 3}, + golden: true, }, { - in: `"AQID"`, - ptr: new([]byteWithPtrMarshalText), - out: []byteWithPtrMarshalText{1, 2, 3}, + CaseName: Name(""), + in: `"AQID"`, + ptr: new([]byteWithPtrMarshalText), + out: []byteWithPtrMarshalText{1, 2, 3}, }, { - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithPtrMarshalText), - out: []byteWithPtrMarshalText{1, 2, 3}, - golden: true, + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithPtrMarshalText), + out: []byteWithPtrMarshalText{1, 2, 3}, + golden: true, }, // ints work with the marshaler but not the base64 []byte case { - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithMarshalJSON), - out: []intWithMarshalJSON{1, 2, 3}, - golden: true, + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithMarshalJSON), + out: []intWithMarshalJSON{1, 2, 3}, + golden: true, }, { - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithMarshalText), - out: []intWithMarshalText{1, 2, 3}, - golden: true, + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithMarshalText), + out: []intWithMarshalText{1, 2, 3}, + golden: true, }, { - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithPtrMarshalJSON), - out: []intWithPtrMarshalJSON{1, 2, 3}, - golden: true, + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithPtrMarshalJSON), + out: []intWithPtrMarshalJSON{1, 2, 3}, + golden: true, }, { - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithPtrMarshalText), - out: []intWithPtrMarshalText{1, 2, 3}, - golden: true, - }, - - {in: `0.000001`, ptr: new(float64), out: 0.000001, golden: true}, - {in: `1e-7`, ptr: new(float64), out: 1e-7, golden: true}, - {in: `100000000000000000000`, ptr: new(float64), out: 100000000000000000000.0, golden: true}, - {in: `1e+21`, ptr: new(float64), out: 1e21, golden: true}, - {in: `-0.000001`, ptr: new(float64), out: -0.000001, golden: true}, - {in: `-1e-7`, ptr: new(float64), out: -1e-7, golden: true}, - {in: `-100000000000000000000`, ptr: new(float64), out: -100000000000000000000.0, golden: true}, - {in: `-1e+21`, ptr: new(float64), out: -1e21, golden: true}, - {in: `999999999999999900000`, ptr: new(float64), out: 999999999999999900000.0, golden: true}, - {in: `9007199254740992`, ptr: new(float64), out: 9007199254740992.0, golden: true}, - {in: `9007199254740993`, ptr: new(float64), out: 9007199254740992.0, golden: false}, + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithPtrMarshalText), + out: []intWithPtrMarshalText{1, 2, 3}, + golden: true, + }, + + {CaseName: Name(""), in: `0.000001`, ptr: new(float64), out: 0.000001, golden: true}, + {CaseName: Name(""), in: `1e-7`, ptr: new(float64), out: 1e-7, golden: true}, + {CaseName: Name(""), in: `100000000000000000000`, ptr: new(float64), out: 100000000000000000000.0, golden: true}, + {CaseName: Name(""), in: `1e+21`, ptr: new(float64), out: 1e21, golden: true}, + {CaseName: Name(""), in: `-0.000001`, ptr: new(float64), out: -0.000001, golden: true}, + {CaseName: Name(""), in: `-1e-7`, ptr: new(float64), out: -1e-7, golden: true}, + {CaseName: Name(""), in: `-100000000000000000000`, ptr: new(float64), out: -100000000000000000000.0, golden: true}, + {CaseName: Name(""), in: `-1e+21`, ptr: new(float64), out: -1e21, golden: true}, + {CaseName: Name(""), in: `999999999999999900000`, ptr: new(float64), out: 999999999999999900000.0, golden: true}, + {CaseName: Name(""), in: `9007199254740992`, ptr: new(float64), out: 9007199254740992.0, golden: true}, + {CaseName: Name(""), in: `9007199254740993`, ptr: new(float64), out: 9007199254740992.0, golden: false}, { - in: `{"V": {"F2": "hello"}}`, - ptr: new(VOuter), + CaseName: Name(""), + in: `{"V": {"F2": "hello"}}`, + ptr: new(VOuter), err: &UnmarshalTypeError{ Value: "string", Struct: "V", @@ -825,8 +868,9 @@ var unmarshalTests = []unmarshalTest{ }, }, { - in: `{"V": {"F4": {}, "F2": "hello"}}`, - ptr: new(VOuter), + CaseName: Name(""), + in: `{"V": {"F4": {}, "F2": "hello"}}`, + ptr: new(VOuter), err: &UnmarshalTypeError{ Value: "string", Struct: "V", @@ -838,17 +882,18 @@ var unmarshalTests = []unmarshalTest{ // issue 15146. // invalid inputs in wrongStringTests below. - {in: `{"B":"true"}`, ptr: new(B), out: B{true}, golden: true}, - {in: `{"B":"false"}`, ptr: new(B), out: B{false}, golden: true}, - {in: `{"B": "maybe"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "maybe" into bool`)}, - {in: `{"B": "tru"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "tru" into bool`)}, - {in: `{"B": "False"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "False" into bool`)}, - {in: `{"B": "null"}`, ptr: new(B), out: B{false}}, - {in: `{"B": "nul"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "nul" into bool`)}, - {in: `{"B": [2, 3]}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal unquoted value into bool`)}, + {CaseName: Name(""), in: `{"B":"true"}`, ptr: new(B), out: B{true}, golden: true}, + {CaseName: Name(""), in: `{"B":"false"}`, ptr: new(B), out: B{false}, golden: true}, + {CaseName: Name(""), in: `{"B": "maybe"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "maybe" into bool`)}, + {CaseName: Name(""), in: `{"B": "tru"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "tru" into bool`)}, + {CaseName: Name(""), in: `{"B": "False"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "False" into bool`)}, + {CaseName: Name(""), in: `{"B": "null"}`, ptr: new(B), out: B{false}}, + {CaseName: Name(""), in: `{"B": "nul"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "nul" into bool`)}, + {CaseName: Name(""), in: `{"B": [2, 3]}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal unquoted value into bool`)}, // additional tests for disallowUnknownFields { + CaseName: Name(""), in: `{ "Level0": 1, "Level1b": 2, @@ -876,6 +921,7 @@ var unmarshalTests = []unmarshalTest{ disallowUnknownFields: true, }, { + CaseName: Name(""), in: `{ "Level0": 1, "Level1b": 2, @@ -905,31 +951,36 @@ var unmarshalTests = []unmarshalTest{ // issue 26444 // UnmarshalTypeError without field & struct values { - in: `{"data":{"test1": "bob", "test2": 123}}`, - ptr: new(mapStringToStringData), - err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[string](), Offset: 37, Struct: "mapStringToStringData", Field: "data"}, + CaseName: Name(""), + in: `{"data":{"test1": "bob", "test2": 123}}`, + ptr: new(mapStringToStringData), + err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[string](), Offset: 37, Struct: "mapStringToStringData", Field: "data"}, }, { - in: `{"data":{"test1": 123, "test2": "bob"}}`, - ptr: new(mapStringToStringData), - err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[string](), Offset: 21, Struct: "mapStringToStringData", Field: "data"}, + CaseName: Name(""), + in: `{"data":{"test1": 123, "test2": "bob"}}`, + ptr: new(mapStringToStringData), + err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[string](), Offset: 21, Struct: "mapStringToStringData", Field: "data"}, }, // trying to decode JSON arrays or objects via TextUnmarshaler { - in: `[1, 2, 3]`, - ptr: new(MustNotUnmarshalText), - err: &UnmarshalTypeError{Value: "array", Type: reflect.TypeFor[*MustNotUnmarshalText](), Offset: 1}, + CaseName: Name(""), + in: `[1, 2, 3]`, + ptr: new(MustNotUnmarshalText), + err: &UnmarshalTypeError{Value: "array", Type: reflect.TypeFor[*MustNotUnmarshalText](), Offset: 1}, }, { - in: `{"foo": "bar"}`, - ptr: new(MustNotUnmarshalText), - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[*MustNotUnmarshalText](), Offset: 1}, + CaseName: Name(""), + in: `{"foo": "bar"}`, + ptr: new(MustNotUnmarshalText), + err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[*MustNotUnmarshalText](), Offset: 1}, }, // #22369 { - in: `{"PP": {"T": {"Y": "bad-type"}}}`, - ptr: new(P), + CaseName: Name(""), + in: `{"PP": {"T": {"Y": "bad-type"}}}`, + ptr: new(P), err: &UnmarshalTypeError{ Value: "string", Struct: "T", @@ -939,8 +990,9 @@ var unmarshalTests = []unmarshalTest{ }, }, { - in: `{"Ts": [{"Y": 1}, {"Y": 2}, {"Y": "bad-type"}]}`, - ptr: new(PP), + CaseName: Name(""), + in: `{"Ts": [{"Y": 1}, {"Y": 2}, {"Y": "bad-type"}]}`, + ptr: new(PP), err: &UnmarshalTypeError{ Value: "string", Struct: "T", @@ -951,76 +1003,84 @@ var unmarshalTests = []unmarshalTest{ }, // #14702 { - in: `invalid`, - ptr: new(Number), + CaseName: Name(""), + in: `invalid`, + ptr: new(Number), err: &SyntaxError{ msg: "invalid character 'i' looking for beginning of value", Offset: 1, }, }, { - in: `"invalid"`, - ptr: new(Number), - err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), + CaseName: Name(""), + in: `"invalid"`, + ptr: new(Number), + err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), }, { - in: `{"A":"invalid"}`, - ptr: new(struct{ A Number }), - err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), + CaseName: Name(""), + in: `{"A":"invalid"}`, + ptr: new(struct{ A Number }), + err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), }, { - in: `{"A":"invalid"}`, + CaseName: Name(""), + in: `{"A":"invalid"}`, ptr: new(struct { A Number `json:",string"` }), err: fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into json.Number", `invalid`), }, { - in: `{"A":"invalid"}`, - ptr: new(map[string]Number), - err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), + CaseName: Name(""), + in: `{"A":"invalid"}`, + ptr: new(map[string]Number), + err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), }, } func TestMarshal(t *testing.T) { b, err := Marshal(allValue) if err != nil { - t.Fatalf("Marshal allValue: %v", err) + t.Fatalf("Marshal error: %v", err) } if string(b) != allValueCompact { - t.Errorf("Marshal allValueCompact") + t.Errorf("Marshal:") diff(t, b, []byte(allValueCompact)) return } b, err = Marshal(pallValue) if err != nil { - t.Fatalf("Marshal pallValue: %v", err) + t.Fatalf("Marshal error: %v", err) } if string(b) != pallValueCompact { - t.Errorf("Marshal pallValueCompact") + t.Errorf("Marshal:") diff(t, b, []byte(pallValueCompact)) return } } -var badUTF8 = []struct { - in, out string -}{ - {"hello\xffworld", `"hello\ufffdworld"`}, - {"", `""`}, - {"\xff", `"\ufffd"`}, - {"\xff\xff", `"\ufffd\ufffd"`}, - {"a\xffb", `"a\ufffdb"`}, - {"\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"日本\ufffd\ufffd\ufffd"`}, -} - -func TestMarshalBadUTF8(t *testing.T) { - for _, tt := range badUTF8 { - b, err := Marshal(tt.in) - if string(b) != tt.out || err != nil { - t.Errorf("Marshal(%q) = %#q, %v, want %#q, nil", tt.in, b, err, tt.out) - } +func TestMarshalInvalidUTF8(t *testing.T) { + tests := []struct { + CaseName + in string + want string + }{ + {Name(""), "hello\xffworld", `"hello\ufffdworld"`}, + {Name(""), "", `""`}, + {Name(""), "\xff", `"\ufffd"`}, + {Name(""), "\xff\xff", `"\ufffd\ufffd"`}, + {Name(""), "a\xffb", `"a\ufffdb"`}, + {Name(""), "\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"日本\ufffd\ufffd\ufffd"`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + got, err := Marshal(tt.in) + if string(got) != tt.want || err != nil { + t.Errorf("%s: Marshal(%q):\n\tgot: (%q, %v)\n\twant: (%q, nil)", tt.Where, tt.in, got, err, tt.want) + } + }) } } @@ -1028,11 +1088,11 @@ func TestMarshalNumberZeroVal(t *testing.T) { var n Number out, err := Marshal(n) if err != nil { - t.Fatal(err) + t.Fatalf("Marshal error: %v", err) } - outStr := string(out) - if outStr != "0" { - t.Fatalf("Invalid zero val for Number: %q", outStr) + got := string(out) + if got != "0" { + t.Fatalf("Marshal: got %s, want 0", got) } } @@ -1068,109 +1128,98 @@ func TestMarshalEmbeds(t *testing.T) { Q: 18, }, } - b, err := Marshal(top) + got, err := Marshal(top) if err != nil { - t.Fatal(err) + t.Fatalf("Marshal error: %v", err) } want := "{\"Level0\":1,\"Level1b\":2,\"Level1c\":3,\"Level1a\":5,\"LEVEL1B\":6,\"e\":{\"Level1a\":8,\"Level1b\":9,\"Level1c\":10,\"Level1d\":11,\"x\":12},\"Loop1\":13,\"Loop2\":14,\"X\":15,\"Y\":16,\"Z\":17,\"Q\":18}" - if string(b) != want { - t.Errorf("Wrong marshal result.\n got: %q\nwant: %q", b, want) + if string(got) != want { + t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } func equalError(a, b error) bool { - if a == nil { - return b == nil - } - if b == nil { - return a == nil + if a == nil || b == nil { + return a == nil && b == nil } return a.Error() == b.Error() } func TestUnmarshal(t *testing.T) { - for i, tt := range unmarshalTests { - var scan scanner - in := []byte(tt.in) - if err := checkValid(in, &scan); err != nil { - if !equalError(err, tt.err) { - t.Errorf("#%d: checkValid: %#v", i, err) - continue + for _, tt := range unmarshalTests { + t.Run(tt.Name, func(t *testing.T) { + in := []byte(tt.in) + var scan scanner + if err := checkValid(in, &scan); err != nil { + if !equalError(err, tt.err) { + t.Fatalf("%s: checkValid error: %#v", tt.Where, err) + } + } + if tt.ptr == nil { + return } - } - if tt.ptr == nil { - continue - } - - typ := reflect.TypeOf(tt.ptr) - if typ.Kind() != reflect.Pointer { - t.Errorf("#%d: unmarshalTest.ptr %T is not a pointer type", i, tt.ptr) - continue - } - typ = typ.Elem() - - // v = new(right-type) - v := reflect.New(typ) - - if !reflect.DeepEqual(tt.ptr, v.Interface()) { - // There's no reason for ptr to point to non-zero data, - // as we decode into new(right-type), so the data is - // discarded. - // This can easily mean tests that silently don't test - // what they should. To test decoding into existing - // data, see TestPrefilled. - t.Errorf("#%d: unmarshalTest.ptr %#v is not a pointer to a zero value", i, tt.ptr) - continue - } - - dec := NewDecoder(bytes.NewReader(in)) - if tt.useNumber { - dec.UseNumber() - } - if tt.disallowUnknownFields { - dec.DisallowUnknownFields() - } - if err := dec.Decode(v.Interface()); !equalError(err, tt.err) { - t.Errorf("#%d: %v, want %v", i, err, tt.err) - continue - } else if err != nil { - continue - } - if !reflect.DeepEqual(v.Elem().Interface(), tt.out) { - t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), tt.out) - data, _ := Marshal(v.Elem().Interface()) - println(string(data)) - data, _ = Marshal(tt.out) - println(string(data)) - continue - } - // Check round trip also decodes correctly. - if tt.err == nil { - enc, err := Marshal(v.Interface()) - if err != nil { - t.Errorf("#%d: error re-marshaling: %v", i, err) - continue + typ := reflect.TypeOf(tt.ptr) + if typ.Kind() != reflect.Pointer { + t.Fatalf("%s: unmarshalTest.ptr %T is not a pointer type", tt.Where, tt.ptr) } - if tt.golden && !bytes.Equal(enc, in) { - t.Errorf("#%d: remarshal mismatch:\nhave: %s\nwant: %s", i, enc, in) + typ = typ.Elem() + + // v = new(right-type) + v := reflect.New(typ) + + if !reflect.DeepEqual(tt.ptr, v.Interface()) { + // There's no reason for ptr to point to non-zero data, + // as we decode into new(right-type), so the data is + // discarded. + // This can easily mean tests that silently don't test + // what they should. To test decoding into existing + // data, see TestPrefilled. + t.Fatalf("%s: unmarshalTest.ptr %#v is not a pointer to a zero value", tt.Where, tt.ptr) } - vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) - dec = NewDecoder(bytes.NewReader(enc)) + + dec := NewDecoder(bytes.NewReader(in)) if tt.useNumber { dec.UseNumber() } - if err := dec.Decode(vv.Interface()); err != nil { - t.Errorf("#%d: error re-unmarshaling %#q: %v", i, enc, err) - continue + if tt.disallowUnknownFields { + dec.DisallowUnknownFields() } - if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) { - t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), vv.Elem().Interface()) - t.Errorf(" In: %q", strings.Map(noSpace, string(in))) - t.Errorf("Marshal: %q", strings.Map(noSpace, string(enc))) - continue + 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) + } else if err != nil { + return } - } + if got := v.Elem().Interface(); !reflect.DeepEqual(got, tt.out) { + gotJSON, _ := Marshal(got) + wantJSON, _ := Marshal(tt.out) + t.Fatalf("%s: Decode:\n\tgot: %#+v\n\twant: %#+v\n\n\tgotJSON: %s\n\twantJSON: %s", tt.Where, got, tt.out, gotJSON, wantJSON) + } + + // Check round trip also decodes correctly. + if tt.err == nil { + enc, err := Marshal(v.Interface()) + if err != nil { + t.Fatalf("%s: Marshal error after roundtrip: %v", tt.Where, err) + } + if tt.golden && !bytes.Equal(enc, in) { + t.Errorf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, enc, in) + } + vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) + dec = NewDecoder(bytes.NewReader(enc)) + if tt.useNumber { + dec.UseNumber() + } + if err := dec.Decode(vv.Interface()); err != nil { + t.Fatalf("%s: Decode(%#q) error after roundtrip: %v", tt.Where, enc, err) + } + if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) { + t.Fatalf("%s: Decode:\n\tgot: %#+v\n\twant: %#+v\n\n\tgotJSON: %s\n\twantJSON: %s", + tt.Where, v.Elem().Interface(), vv.Elem().Interface(), + stripWhitespace(string(enc)), stripWhitespace(string(in))) + } + } + }) } } @@ -1178,48 +1227,50 @@ func TestUnmarshalMarshal(t *testing.T) { initBig() var v any if err := Unmarshal(jsonBig, &v); err != nil { - t.Fatalf("Unmarshal: %v", err) + t.Fatalf("Unmarshal error: %v", err) } b, err := Marshal(v) if err != nil { - t.Fatalf("Marshal: %v", err) + t.Fatalf("Marshal error: %v", err) } if !bytes.Equal(jsonBig, b) { - t.Errorf("Marshal jsonBig") + t.Errorf("Marshal:") diff(t, b, jsonBig) return } } -var numberTests = []struct { - in string - i int64 - intErr string - f float64 - floatErr string -}{ - {in: "-1.23e1", intErr: "strconv.ParseInt: parsing \"-1.23e1\": invalid syntax", f: -1.23e1}, - {in: "-12", i: -12, f: -12.0}, - {in: "1e1000", intErr: "strconv.ParseInt: parsing \"1e1000\": invalid syntax", floatErr: "strconv.ParseFloat: parsing \"1e1000\": value out of range"}, -} - // Independent of Decode, basic coverage of the accessors in Number func TestNumberAccessors(t *testing.T) { - for _, tt := range numberTests { - n := Number(tt.in) - if s := n.String(); s != tt.in { - t.Errorf("Number(%q).String() is %q", tt.in, s) - } - if i, err := n.Int64(); err == nil && tt.intErr == "" && i != tt.i { - t.Errorf("Number(%q).Int64() is %d", tt.in, i) - } else if (err == nil && tt.intErr != "") || (err != nil && err.Error() != tt.intErr) { - t.Errorf("Number(%q).Int64() wanted error %q but got: %v", tt.in, tt.intErr, err) - } - if f, err := n.Float64(); err == nil && tt.floatErr == "" && f != tt.f { - t.Errorf("Number(%q).Float64() is %g", tt.in, f) - } else if (err == nil && tt.floatErr != "") || (err != nil && err.Error() != tt.floatErr) { - t.Errorf("Number(%q).Float64() wanted error %q but got: %v", tt.in, tt.floatErr, err) - } + tests := []struct { + CaseName + in string + i int64 + intErr string + f float64 + floatErr string + }{ + {CaseName: Name(""), in: "-1.23e1", intErr: "strconv.ParseInt: parsing \"-1.23e1\": invalid syntax", f: -1.23e1}, + {CaseName: Name(""), in: "-12", i: -12, f: -12.0}, + {CaseName: Name(""), in: "1e1000", intErr: "strconv.ParseInt: parsing \"1e1000\": invalid syntax", floatErr: "strconv.ParseFloat: parsing \"1e1000\": value out of range"}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + n := Number(tt.in) + if got := n.String(); got != tt.in { + t.Errorf("%s: Number(%q).String() = %s, want %s", tt.Where, tt.in, got, tt.in) + } + if i, err := n.Int64(); err == nil && tt.intErr == "" && i != tt.i { + t.Errorf("%s: Number(%q).Int64() = %d, want %d", tt.Where, tt.in, i, tt.i) + } else if (err == nil && tt.intErr != "") || (err != nil && err.Error() != tt.intErr) { + t.Errorf("%s: Number(%q).Int64() error:\n\tgot: %v\n\twant: %v", tt.Where, tt.in, err, tt.intErr) + } + if f, err := n.Float64(); err == nil && tt.floatErr == "" && f != tt.f { + t.Errorf("%s: Number(%q).Float64() = %g, want %g", tt.Where, tt.in, f, tt.f) + } else if (err == nil && tt.floatErr != "") || (err != nil && err.Error() != tt.floatErr) { + t.Errorf("%s: Number(%q).Float64() error:\n\tgot %v\n\twant: %v", tt.Where, tt.in, err, tt.floatErr) + } + }) } } @@ -1230,14 +1281,14 @@ func TestLargeByteSlice(t *testing.T) { } b, err := Marshal(s0) if err != nil { - t.Fatalf("Marshal: %v", err) + t.Fatalf("Marshal error: %v", err) } var s1 []byte if err := Unmarshal(b, &s1); err != nil { - t.Fatalf("Unmarshal: %v", err) + t.Fatalf("Unmarshal error: %v", err) } if !bytes.Equal(s0, s1) { - t.Errorf("Marshal large byte slice") + t.Errorf("Marshal:") diff(t, s0, s1) } } @@ -1250,10 +1301,10 @@ func TestUnmarshalInterface(t *testing.T) { var xint Xint var i any = &xint if err := Unmarshal([]byte(`{"X":1}`), &i); err != nil { - t.Fatalf("Unmarshal: %v", err) + t.Fatalf("Unmarshal error: %v", err) } if xint.X != 1 { - t.Fatalf("Did not write to xint") + t.Fatalf("xint.X = %d, want 1", xint.X) } } @@ -1264,59 +1315,51 @@ func TestUnmarshalPtrPtr(t *testing.T) { t.Fatalf("Unmarshal: %v", err) } if xint.X != 1 { - t.Fatalf("Did not write to xint") + t.Fatalf("xint.X = %d, want 1", xint.X) } } func TestEscape(t *testing.T) { const input = `"foobar"` + " [\u2028 \u2029]" - const expected = `"\"foobar\"\u003chtml\u003e [\u2028 \u2029]"` - b, err := Marshal(input) + const want = `"\"foobar\"\u003chtml\u003e [\u2028 \u2029]"` + got, err := Marshal(input) if err != nil { t.Fatalf("Marshal error: %v", err) } - if s := string(b); s != expected { - t.Errorf("Encoding of [%s]:\n got [%s]\nwant [%s]", input, s, expected) + if string(got) != want { + t.Errorf("Marshal(%#q):\n\tgot: %s\n\twant: %s", input, got, want) } } -// WrongString is a struct that's misusing the ,string modifier. -type WrongString struct { - Message string `json:"result,string"` -} - -type wrongStringTest struct { - in, err string -} - -var wrongStringTests = []wrongStringTest{ - {`{"result":"x"}`, `json: invalid use of ,string struct tag, trying to unmarshal "x" into string`}, - {`{"result":"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "foo" into string`}, - {`{"result":"123"}`, `json: invalid use of ,string struct tag, trying to unmarshal "123" into string`}, - {`{"result":123}`, `json: invalid use of ,string struct tag, trying to unmarshal unquoted value into string`}, - {`{"result":"\""}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"" into string`}, - {`{"result":"\"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"foo" into string`}, -} - // If people misuse the ,string modifier, the error message should be // helpful, telling the user that they're doing it wrong. func TestErrorMessageFromMisusedString(t *testing.T) { - for n, tt := range wrongStringTests { - r := strings.NewReader(tt.in) - var s WrongString - err := NewDecoder(r).Decode(&s) - got := fmt.Sprintf("%v", err) - if got != tt.err { - t.Errorf("%d. got err = %q, want %q", n, got, tt.err) - } + // WrongString is a struct that's misusing the ,string modifier. + type WrongString struct { + Message string `json:"result,string"` } -} - -func noSpace(c rune) rune { - if isSpace(byte(c)) { //only used for ascii - return -1 + tests := []struct { + CaseName + in, err string + }{ + {Name(""), `{"result":"x"}`, `json: invalid use of ,string struct tag, trying to unmarshal "x" into string`}, + {Name(""), `{"result":"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "foo" into string`}, + {Name(""), `{"result":"123"}`, `json: invalid use of ,string struct tag, trying to unmarshal "123" into string`}, + {Name(""), `{"result":123}`, `json: invalid use of ,string struct tag, trying to unmarshal unquoted value into string`}, + {Name(""), `{"result":"\""}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"" into string`}, + {Name(""), `{"result":"\"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"foo" into string`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + r := strings.NewReader(tt.in) + var s WrongString + err := NewDecoder(r).Decode(&s) + got := fmt.Sprintf("%v", err) + if got != tt.err { + t.Errorf("%s: Decode error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.err) + } + }) } - return c } type All struct { @@ -1546,7 +1589,7 @@ var allValueIndent = `{ "PInterface": null }` -var allValueCompact = strings.Map(noSpace, allValueIndent) +var allValueCompact = stripWhitespace(allValueIndent) var pallValueIndent = `{ "Bool": false, @@ -1635,7 +1678,7 @@ var pallValueIndent = `{ "PInterface": 5.2 }` -var pallValueCompact = strings.Map(noSpace, pallValueIndent) +var pallValueCompact = stripWhitespace(pallValueIndent) func TestRefUnmarshal(t *testing.T) { type S struct { @@ -1656,10 +1699,10 @@ func TestRefUnmarshal(t *testing.T) { var got S if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref","R2":"ref","R3":"ref"}`), &got); err != nil { - t.Fatalf("Unmarshal: %v", err) + t.Fatalf("Unmarshal error: %v", err) } if !reflect.DeepEqual(got, want) { - t.Errorf("got %+v, want %+v", got, want) + t.Errorf("Unmarsha:\n\tgot: %+v\n\twant: %+v", got, want) } } @@ -1672,13 +1715,12 @@ func TestEmptyString(t *testing.T) { } data := `{"Number1":"1", "Number2":""}` dec := NewDecoder(strings.NewReader(data)) - var t2 T2 - err := dec.Decode(&t2) - if err == nil { - t.Fatal("Decode: did not return error") - } - if t2.Number1 != 1 { - t.Fatal("Decode: did not set Number1") + var got T2 + switch err := dec.Decode(&got); { + case err == nil: + t.Fatalf("Decode error: got nil, want non-nil") + case got.Number1 != 1: + t.Fatalf("Decode: got.Number1 = %d, want 1", got.Number1) } } @@ -1695,12 +1737,13 @@ func TestNullString(t *testing.T) { s.B = 1 s.C = new(int) *s.C = 2 - err := Unmarshal(data, &s) - if err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if s.B != 1 || s.C != nil { - t.Fatalf("after Unmarshal, s.B=%d, s.C=%p, want 1, nil", s.B, s.C) + switch err := Unmarshal(data, &s); { + case err != nil: + t.Fatalf("Unmarshal error: %v", err) + case s.B != 1: + t.Fatalf("Unmarshal: s.B = %d, want 1", s.B) + case s.C != nil: + t.Fatalf("Unmarshal: s.C = %d, want non-nil", s.C) } } @@ -1716,37 +1759,38 @@ func intpp(x *int) **int { return pp } -var interfaceSetTests = []struct { - pre any - json string - post any -}{ - {"foo", `"bar"`, "bar"}, - {"foo", `2`, 2.0}, - {"foo", `true`, true}, - {"foo", `null`, nil}, - - {nil, `null`, nil}, - {new(int), `null`, nil}, - {(*int)(nil), `null`, nil}, - {new(*int), `null`, new(*int)}, - {(**int)(nil), `null`, nil}, - {intp(1), `null`, nil}, - {intpp(nil), `null`, intpp(nil)}, - {intpp(intp(1)), `null`, intpp(nil)}, -} - func TestInterfaceSet(t *testing.T) { - for _, tt := range interfaceSetTests { - b := struct{ X any }{tt.pre} - blob := `{"X":` + tt.json + `}` - if err := Unmarshal([]byte(blob), &b); err != nil { - t.Errorf("Unmarshal %#q: %v", blob, err) - continue - } - if !reflect.DeepEqual(b.X, tt.post) { - t.Errorf("Unmarshal %#q into %#v: X=%#v, want %#v", blob, tt.pre, b.X, tt.post) - } + tests := []struct { + CaseName + pre any + json string + post any + }{ + {Name(""), "foo", `"bar"`, "bar"}, + {Name(""), "foo", `2`, 2.0}, + {Name(""), "foo", `true`, true}, + {Name(""), "foo", `null`, nil}, + + {Name(""), nil, `null`, nil}, + {Name(""), new(int), `null`, nil}, + {Name(""), (*int)(nil), `null`, nil}, + {Name(""), new(*int), `null`, new(*int)}, + {Name(""), (**int)(nil), `null`, nil}, + {Name(""), intp(1), `null`, nil}, + {Name(""), intpp(nil), `null`, intpp(nil)}, + {Name(""), intpp(intp(1)), `null`, intpp(nil)}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + b := struct{ X any }{tt.pre} + blob := `{"X":` + tt.json + `}` + if err := Unmarshal([]byte(blob), &b); err != nil { + t.Fatalf("%s: Unmarshal(%#q) error: %v", tt.Where, blob, err) + } + if !reflect.DeepEqual(b.X, tt.post) { + t.Errorf("%s: Unmarshal(%#q):\n\tpre.X: %#v\n\tgot.X: %#v\n\twant.X: %#v", tt.Where, blob, tt.pre, b.X, tt.post) + } + }) } } @@ -1924,24 +1968,18 @@ func (x MustNotUnmarshalText) UnmarshalText(text []byte) error { func TestStringKind(t *testing.T) { type stringKind string - - var m1, m2 map[stringKind]int - m1 = map[stringKind]int{ - "foo": 42, - } - - data, err := Marshal(m1) + want := map[stringKind]int{"foo": 42} + data, err := Marshal(want) if err != nil { - t.Errorf("Unexpected error marshaling: %v", err) + t.Fatalf("Marshal error: %v", err) } - - err = Unmarshal(data, &m2) + var got map[stringKind]int + err = Unmarshal(data, &got) if err != nil { - t.Errorf("Unexpected error unmarshaling: %v", err) + t.Fatalf("Unmarshal error: %v", err) } - - if !reflect.DeepEqual(m1, m2) { - t.Error("Items should be equal after encoding and then decoding") + if !reflect.DeepEqual(got, want) { + t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) } } @@ -1950,20 +1988,18 @@ func TestStringKind(t *testing.T) { // Issue 8962. func TestByteKind(t *testing.T) { type byteKind []byte - - a := byteKind("hello") - - data, err := Marshal(a) + want := byteKind("hello") + data, err := Marshal(want) if err != nil { - t.Error(err) + t.Fatalf("Marshal error: %v", err) } - var b byteKind - err = Unmarshal(data, &b) + var got byteKind + err = Unmarshal(data, &got) if err != nil { - t.Fatal(err) + t.Fatalf("Unmarshal error: %v", err) } - if !reflect.DeepEqual(a, b) { - t.Errorf("expected %v == %v", a, b) + if !reflect.DeepEqual(got, want) { + t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) } } @@ -1971,63 +2007,68 @@ func TestByteKind(t *testing.T) { // Issue 12921. func TestSliceOfCustomByte(t *testing.T) { type Uint8 uint8 - - a := []Uint8("hello") - - data, err := Marshal(a) + want := []Uint8("hello") + data, err := Marshal(want) if err != nil { - t.Fatal(err) + t.Fatalf("Marshal error: %v", err) } - var b []Uint8 - err = Unmarshal(data, &b) + var got []Uint8 + err = Unmarshal(data, &got) if err != nil { - t.Fatal(err) + t.Fatalf("Unmarshal error: %v", err) } - if !reflect.DeepEqual(a, b) { - t.Fatalf("expected %v == %v", a, b) + if !reflect.DeepEqual(got, want) { + t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) } } -var decodeTypeErrorTests = []struct { - dest any - src string -}{ - {new(string), `{"user": "name"}`}, // issue 4628. - {new(error), `{}`}, // issue 4222 - {new(error), `[]`}, - {new(error), `""`}, - {new(error), `123`}, - {new(error), `true`}, -} - func TestUnmarshalTypeError(t *testing.T) { - for _, item := range decodeTypeErrorTests { - err := Unmarshal([]byte(item.src), item.dest) - if _, ok := err.(*UnmarshalTypeError); !ok { - t.Errorf("expected type error for Unmarshal(%q, type %T): got %T", - item.src, item.dest, err) - } + tests := []struct { + CaseName + dest any + in string + }{ + {Name(""), new(string), `{"user": "name"}`}, // issue 4628. + {Name(""), new(error), `{}`}, // issue 4222 + {Name(""), new(error), `[]`}, + {Name(""), new(error), `""`}, + {Name(""), new(error), `123`}, + {Name(""), new(error), `true`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + err := Unmarshal([]byte(tt.in), tt.dest) + if _, ok := err.(*UnmarshalTypeError); !ok { + t.Errorf("%s: Unmarshal(%#q, %T):\n\tgot: %T\n\twant: %T", + tt.Where, tt.in, tt.dest, err, new(UnmarshalTypeError)) + } + }) } } -var unmarshalSyntaxTests = []string{ - "tru", - "fals", - "nul", - "123e", - `"hello`, - `[1,2,3`, - `{"key":1`, - `{"key":1,`, -} - func TestUnmarshalSyntax(t *testing.T) { var x any - for _, src := range unmarshalSyntaxTests { - err := Unmarshal([]byte(src), &x) - if _, ok := err.(*SyntaxError); !ok { - t.Errorf("expected syntax error for Unmarshal(%q): got %T", src, err) - } + tests := []struct { + CaseName + in string + }{ + {Name(""), "tru"}, + {Name(""), "fals"}, + {Name(""), "nul"}, + {Name(""), "123e"}, + {Name(""), `"hello`}, + {Name(""), `[1,2,3`}, + {Name(""), `{"key":1`}, + {Name(""), `{"key":1,`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + err := Unmarshal([]byte(tt.in), &x) + if _, ok := err.(*SyntaxError); !ok { + t.Errorf("%s: Unmarshal(%#q, any):\n\tgot: %T\n\twant: %T", + tt.Where, tt.in, err, new(SyntaxError)) + } + }) } } @@ -2048,10 +2089,10 @@ func TestUnmarshalUnexported(t *testing.T) { out := &unexportedFields{} err := Unmarshal([]byte(input), out) if err != nil { - t.Errorf("got error %v, expected nil", err) + t.Errorf("Unmarshal error: %v", err) } if !reflect.DeepEqual(out, want) { - t.Errorf("got %q, want %q", out, want) + t.Errorf("Unmarshal:\n\tgot: %+v\n\twant: %+v", out, want) } } @@ -2073,12 +2114,11 @@ func (t *Time3339) UnmarshalJSON(b []byte) error { func TestUnmarshalJSONLiteralError(t *testing.T) { var t3 Time3339 - err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3) - if err == nil { - t.Fatalf("expected error; got time %v", time.Time(t3)) - } - if !strings.Contains(err.Error(), "range") { - t.Errorf("got err = %v; want out of range error", err) + switch err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3); { + case err == nil: + t.Fatalf("Unmarshal error: got nil, want non-nil") + case !strings.Contains(err.Error(), "range"): + t.Errorf("Unmarshal error:\n\tgot: %v\n\twant: out of range", err) } } @@ -2091,7 +2131,7 @@ func TestSkipArrayObjects(t *testing.T) { err := Unmarshal([]byte(json), &dest) if err != nil { - t.Errorf("got error %q, want nil", err) + t.Errorf("Unmarshal error: %v", err) } } @@ -2100,99 +2140,102 @@ func TestSkipArrayObjects(t *testing.T) { // Issues 4900 and 8837, among others. func TestPrefilled(t *testing.T) { // Values here change, cannot reuse table across runs. - var prefillTests = []struct { + tests := []struct { + CaseName in string ptr any out any - }{ - { - in: `{"X": 1, "Y": 2}`, - ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, - out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, - }, - { - in: `{"X": 1, "Y": 2}`, - ptr: &map[string]any{"X": float32(3), "Y": int16(4), "Z": 1.5}, - out: &map[string]any{"X": float64(1), "Y": float64(2), "Z": 1.5}, - }, - { - in: `[2]`, - ptr: &[]int{1}, - out: &[]int{2}, - }, - { - in: `[2, 3]`, - ptr: &[]int{1}, - out: &[]int{2, 3}, - }, - { - in: `[2, 3]`, - ptr: &[...]int{1}, - out: &[...]int{2}, - }, - { - in: `[3]`, - ptr: &[...]int{1, 2}, - out: &[...]int{3, 0}, - }, - } - - for _, tt := range prefillTests { - ptrstr := fmt.Sprintf("%v", tt.ptr) - err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here - if err != nil { - t.Errorf("Unmarshal: %v", err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("Unmarshal(%#q, %s): have %v, want %v", tt.in, ptrstr, tt.ptr, tt.out) - } + }{{ + CaseName: Name(""), + in: `{"X": 1, "Y": 2}`, + ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, + out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, + }, { + CaseName: Name(""), + in: `{"X": 1, "Y": 2}`, + ptr: &map[string]any{"X": float32(3), "Y": int16(4), "Z": 1.5}, + out: &map[string]any{"X": float64(1), "Y": float64(2), "Z": 1.5}, + }, { + CaseName: Name(""), + in: `[2]`, + ptr: &[]int{1}, + out: &[]int{2}, + }, { + CaseName: Name(""), + in: `[2, 3]`, + ptr: &[]int{1}, + out: &[]int{2, 3}, + }, { + CaseName: Name(""), + in: `[2, 3]`, + ptr: &[...]int{1}, + out: &[...]int{2}, + }, { + CaseName: Name(""), + in: `[3]`, + ptr: &[...]int{1, 2}, + out: &[...]int{3, 0}, + }} + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + ptrstr := fmt.Sprintf("%v", tt.ptr) + err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here + if err != nil { + t.Errorf("%s: Unmarshal error: %v", tt.Where, err) + } + if !reflect.DeepEqual(tt.ptr, tt.out) { + t.Errorf("%s: Unmarshal(%#q, %T):\n\tgot: %v\n\twant: %v", tt.Where, tt.in, ptrstr, tt.ptr, tt.out) + } + }) } } -var invalidUnmarshalTests = []struct { - v any - want string -}{ - {nil, "json: Unmarshal(nil)"}, - {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, - {(*int)(nil), "json: Unmarshal(nil *int)"}, -} - func TestInvalidUnmarshal(t *testing.T) { buf := []byte(`{"a":"1"}`) - for _, tt := range invalidUnmarshalTests { - err := Unmarshal(buf, tt.v) - if err == nil { - t.Errorf("Unmarshal expecting error, got nil") - continue - } - if got := err.Error(); got != tt.want { - t.Errorf("Unmarshal = %q; want %q", got, tt.want) - } + 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)"}, + } + 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) + } + }) } } -var invalidUnmarshalTextTests = []struct { - v any - want string -}{ - {nil, "json: Unmarshal(nil)"}, - {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, - {(*int)(nil), "json: Unmarshal(nil *int)"}, - {new(net.IP), "json: cannot unmarshal number into Go value of type *net.IP"}, -} - func TestInvalidUnmarshalText(t *testing.T) { buf := []byte(`123`) - for _, tt := range invalidUnmarshalTextTests { - err := Unmarshal(buf, tt.v) - if err == nil { - t.Errorf("Unmarshal expecting error, got nil") - continue - } - if got := err.Error(); got != tt.want { - t.Errorf("Unmarshal = %q; want %q", got, tt.want) - } + 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) + } + }) } } @@ -2211,12 +2254,12 @@ func TestInvalidStringOption(t *testing.T) { data, err := Marshal(item) if err != nil { - t.Fatalf("Marshal: %v", err) + t.Fatalf("Marshal error: %v", err) } err = Unmarshal(data, &item) if err != nil { - t.Fatalf("Unmarshal: %v", err) + t.Fatalf("Unmarshal error: %v", err) } } @@ -2275,43 +2318,50 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) { ) tests := []struct { + CaseName in string ptr any out any err error }{{ // Error since we cannot set S1.embed1, but still able to set S1.R. - in: `{"R":2,"Q":1}`, - ptr: new(S1), - out: &S1{R: 2}, - err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed1"), + CaseName: Name(""), + in: `{"R":2,"Q":1}`, + ptr: new(S1), + out: &S1{R: 2}, + err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed1"), }, { // The top level Q field takes precedence. - in: `{"Q":1}`, - ptr: new(S2), - out: &S2{Q: 1}, + CaseName: Name(""), + in: `{"Q":1}`, + ptr: new(S2), + out: &S2{Q: 1}, }, { // No issue with non-pointer variant. - in: `{"R":2,"Q":1}`, - ptr: new(S3), - out: &S3{embed1: embed1{Q: 1}, R: 2}, + CaseName: Name(""), + in: `{"R":2,"Q":1}`, + ptr: new(S3), + out: &S3{embed1: embed1{Q: 1}, R: 2}, }, { // No error since both embedded structs have field R, which annihilate each other. // Thus, no attempt is made at setting S4.embed1. - in: `{"R":2}`, - ptr: new(S4), - out: new(S4), + CaseName: Name(""), + in: `{"R":2}`, + ptr: new(S4), + out: new(S4), }, { // Error since we cannot set S5.embed1, but still able to set S5.R. - in: `{"R":2,"Q":1}`, - ptr: new(S5), - out: &S5{R: 2}, - err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed3"), + CaseName: Name(""), + in: `{"R":2,"Q":1}`, + ptr: new(S5), + out: &S5{R: 2}, + err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed3"), }, { // Issue 24152, ensure decodeState.indirect does not panic. - in: `{"embed1": {"Q": 1}}`, - ptr: new(S6), - out: &S6{embed1{1}}, + CaseName: Name(""), + in: `{"embed1": {"Q": 1}}`, + ptr: new(S6), + out: &S6{embed1{1}}, }, { // Issue 24153, check that we can still set forwarded fields even in // the presence of a name conflict. @@ -2325,64 +2375,74 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) { // it should be impossible for an external package to set either Q. // // It is probably okay for a future reflect change to break this. - in: `{"embed1": {"Q": 1}, "Q": 2}`, - ptr: new(S7), - out: &S7{embed1{1}, embed2{2}}, + CaseName: Name(""), + in: `{"embed1": {"Q": 1}, "Q": 2}`, + ptr: new(S7), + out: &S7{embed1{1}, embed2{2}}, }, { // Issue 24153, similar to the S7 case. - in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`, - ptr: new(S8), - out: &S8{embed1{1}, embed2{2}, 3}, + CaseName: Name(""), + in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`, + ptr: new(S8), + out: &S8{embed1{1}, embed2{2}, 3}, }, { // Issue 228145, similar to the cases above. - in: `{"embed": {}}`, - ptr: new(S9), - out: &S9{}, + CaseName: Name(""), + in: `{"embed": {}}`, + ptr: new(S9), + out: &S9{}, }} - - for i, tt := range tests { - err := Unmarshal([]byte(tt.in), tt.ptr) - if !equalError(err, tt.err) { - t.Errorf("#%d: %v, want %v", i, err, tt.err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("#%d: mismatch\ngot: %#+v\nwant: %#+v", i, tt.ptr, tt.out) - } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + err := Unmarshal([]byte(tt.in), tt.ptr) + if !equalError(err, tt.err) { + t.Errorf("%s: Unmarshal error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) + } + if !reflect.DeepEqual(tt.ptr, tt.out) { + t.Errorf("%s: Unmarshal:\n\tgot: %#+v\n\twant: %#+v", tt.Where, tt.ptr, tt.out) + } + }) } } func TestUnmarshalErrorAfterMultipleJSON(t *testing.T) { tests := []struct { + CaseName in string err error }{{ - in: `1 false null :`, - err: &SyntaxError{"invalid character ':' looking for beginning of value", 14}, + CaseName: Name(""), + in: `1 false null :`, + err: &SyntaxError{"invalid character ':' looking for beginning of value", 14}, }, { - in: `1 [] [,]`, - err: &SyntaxError{"invalid character ',' looking for beginning of value", 7}, + CaseName: Name(""), + in: `1 [] [,]`, + err: &SyntaxError{"invalid character ',' looking for beginning of value", 7}, }, { - in: `1 [] [true:]`, - err: &SyntaxError{"invalid character ':' after array element", 11}, + CaseName: Name(""), + in: `1 [] [true:]`, + err: &SyntaxError{"invalid character ':' after array element", 11}, }, { - in: `1 {} {"x"=}`, - err: &SyntaxError{"invalid character '=' after object key", 14}, + CaseName: Name(""), + in: `1 {} {"x"=}`, + err: &SyntaxError{"invalid character '=' after object key", 14}, }, { - in: `falsetruenul#`, - err: &SyntaxError{"invalid character '#' in literal null (expecting 'l')", 13}, + CaseName: Name(""), + in: `falsetruenul#`, + err: &SyntaxError{"invalid character '#' in literal null (expecting 'l')", 13}, }} - for i, tt := range tests { - dec := NewDecoder(strings.NewReader(tt.in)) - var err error - for { - var v any - if err = dec.Decode(&v); err != nil { - break + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + dec := NewDecoder(strings.NewReader(tt.in)) + var err error + for err == nil { + var v any + err = dec.Decode(&v) } - } - if !reflect.DeepEqual(err, tt.err) { - t.Errorf("#%d: got %#v, want %#v", i, err, tt.err) - } + if !reflect.DeepEqual(err, tt.err) { + t.Errorf("%s: Decode error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) + } + }) } } @@ -2408,7 +2468,7 @@ func TestUnmarshalRecursivePointer(t *testing.T) { data := []byte(`{"a": "b"}`) if err := Unmarshal(data, v); err != nil { - t.Fatal(err) + t.Fatalf("Unmarshal error: %v", err) } } @@ -2424,11 +2484,11 @@ func (m *textUnmarshalerString) UnmarshalText(text []byte) error { func TestUnmarshalMapWithTextUnmarshalerStringKey(t *testing.T) { var p map[textUnmarshalerString]string if err := Unmarshal([]byte(`{"FOO": "1"}`), &p); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) + t.Fatalf("Unmarshal error: %v", err) } if _, ok := p["foo"]; !ok { - t.Errorf(`Key "foo" does not exist in map: %v`, p) + t.Errorf(`key "foo" missing in map: %v`, p) } } @@ -2436,28 +2496,28 @@ func TestUnmarshalRescanLiteralMangledUnquote(t *testing.T) { // See golang.org/issues/38105. var p map[textUnmarshalerString]string if err := Unmarshal([]byte(`{"开源":"12345开源"}`), &p); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) + t.Fatalf("Unmarshal error: %v", err) } if _, ok := p["开源"]; !ok { - t.Errorf(`Key "开源" does not exist in map: %v`, p) + t.Errorf(`key "开源" missing in map: %v`, p) } // See golang.org/issues/38126. type T struct { F1 string `json:"F1,string"` } - t1 := T{"aaa\tbbb"} + wantT := T{"aaa\tbbb"} - b, err := Marshal(t1) + b, err := Marshal(wantT) if err != nil { - t.Fatalf("Marshal unexpected error: %v", err) + t.Fatalf("Marshal error: %v", err) } - var t2 T - if err := Unmarshal(b, &t2); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) + var gotT T + if err := Unmarshal(b, &gotT); err != nil { + t.Fatalf("Unmarshal error: %v", err) } - if t1 != t2 { - t.Errorf("Marshal and Unmarshal roundtrip mismatch: want %q got %q", t1, t2) + if gotT != wantT { + t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %q\n\twant: %q", gotT, wantT) } // See golang.org/issues/39555. @@ -2465,107 +2525,93 @@ func TestUnmarshalRescanLiteralMangledUnquote(t *testing.T) { encoded, err := Marshal(input) if err != nil { - t.Fatalf("Marshal unexpected error: %v", err) + t.Fatalf("Marshal error: %v", err) } var got map[textUnmarshalerString]string if err := Unmarshal(encoded, &got); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) + t.Fatalf("Unmarshal error: %v", err) } want := map[textUnmarshalerString]string{"foo": "", `"`: ""} - if !reflect.DeepEqual(want, got) { - t.Fatalf("Unexpected roundtrip result:\nwant: %q\ngot: %q", want, got) + if !reflect.DeepEqual(got, want) { + t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %q\n\twant: %q", gotT, wantT) } } func TestUnmarshalMaxDepth(t *testing.T) { - testcases := []struct { - name string + tests := []struct { + CaseName data string errMaxDepth bool - }{ - { - name: "ArrayUnderMaxNestingDepth", - data: `{"a":` + strings.Repeat(`[`, 10000-1) + strings.Repeat(`]`, 10000-1) + `}`, - errMaxDepth: false, - }, - { - name: "ArrayOverMaxNestingDepth", - data: `{"a":` + strings.Repeat(`[`, 10000) + strings.Repeat(`]`, 10000) + `}`, - errMaxDepth: true, - }, - { - name: "ArrayOverStackDepth", - data: `{"a":` + strings.Repeat(`[`, 3000000) + strings.Repeat(`]`, 3000000) + `}`, - errMaxDepth: true, - }, - { - name: "ObjectUnderMaxNestingDepth", - data: `{"a":` + strings.Repeat(`{"a":`, 10000-1) + `0` + strings.Repeat(`}`, 10000-1) + `}`, - errMaxDepth: false, - }, - { - name: "ObjectOverMaxNestingDepth", - data: `{"a":` + strings.Repeat(`{"a":`, 10000) + `0` + strings.Repeat(`}`, 10000) + `}`, - errMaxDepth: true, - }, - { - name: "ObjectOverStackDepth", - data: `{"a":` + strings.Repeat(`{"a":`, 3000000) + `0` + strings.Repeat(`}`, 3000000) + `}`, - errMaxDepth: true, - }, - } + }{{ + CaseName: Name("ArrayUnderMaxNestingDepth"), + data: `{"a":` + strings.Repeat(`[`, 10000-1) + strings.Repeat(`]`, 10000-1) + `}`, + errMaxDepth: false, + }, { + CaseName: Name("ArrayOverMaxNestingDepth"), + data: `{"a":` + strings.Repeat(`[`, 10000) + strings.Repeat(`]`, 10000) + `}`, + errMaxDepth: true, + }, { + CaseName: Name("ArrayOverStackDepth"), + data: `{"a":` + strings.Repeat(`[`, 3000000) + strings.Repeat(`]`, 3000000) + `}`, + errMaxDepth: true, + }, { + CaseName: Name("ObjectUnderMaxNestingDepth"), + data: `{"a":` + strings.Repeat(`{"a":`, 10000-1) + `0` + strings.Repeat(`}`, 10000-1) + `}`, + errMaxDepth: false, + }, { + CaseName: Name("ObjectOverMaxNestingDepth"), + data: `{"a":` + strings.Repeat(`{"a":`, 10000) + `0` + strings.Repeat(`}`, 10000) + `}`, + errMaxDepth: true, + }, { + CaseName: Name("ObjectOverStackDepth"), + data: `{"a":` + strings.Repeat(`{"a":`, 3000000) + `0` + strings.Repeat(`}`, 3000000) + `}`, + errMaxDepth: true, + }} targets := []struct { - name string + CaseName newValue func() any - }{ - { - name: "unstructured", - newValue: func() any { - var v any - return &v - }, + }{{ + CaseName: Name("unstructured"), + newValue: func() any { + var v any + return &v }, - { - name: "typed named field", - newValue: func() any { - v := struct { - A any `json:"a"` - }{} - return &v - }, + }, { + CaseName: Name("typed named field"), + newValue: func() any { + v := struct { + A any `json:"a"` + }{} + return &v }, - { - name: "typed missing field", - newValue: func() any { - v := struct { - B any `json:"b"` - }{} - return &v - }, + }, { + CaseName: Name("typed missing field"), + newValue: func() any { + v := struct { + B any `json:"b"` + }{} + return &v }, - { - name: "custom unmarshaler", - newValue: func() any { - v := unmarshaler{} - return &v - }, + }, { + CaseName: Name("custom unmarshaler"), + newValue: func() any { + v := unmarshaler{} + return &v }, - } + }} - for _, tc := range testcases { + for _, tt := range tests { for _, target := range targets { - t.Run(target.name+"-"+tc.name, func(t *testing.T) { - err := Unmarshal([]byte(tc.data), target.newValue()) - if !tc.errMaxDepth { + t.Run(target.Name+"-"+tt.Name, func(t *testing.T) { + err := Unmarshal([]byte(tt.data), target.newValue()) + if !tt.errMaxDepth { if err != nil { - t.Errorf("unexpected error: %v", err) + t.Errorf("%s: %s: Unmarshal error: %v", tt.Where, target.Where, err) } } else { - if err == nil { - t.Errorf("expected error containing 'exceeded max depth', got none") - } else if !strings.Contains(err.Error(), "exceeded max depth") { - t.Errorf("expected error containing 'exceeded max depth', got: %v", err) + if err == nil || !strings.Contains(err.Error(), "exceeded max depth") { + t.Errorf("%s: %s: Unmarshal error:\n\tgot: %v\n\twant: exceeded max depth", tt.Where, target.Where, err) } } }) diff --git a/src/encoding/json/encode_test.go b/src/encoding/json/encode_test.go index 7972348801..9c37028037 100644 --- a/src/encoding/json/encode_test.go +++ b/src/encoding/json/encode_test.go @@ -44,7 +44,8 @@ type Optionals struct { Sto struct{} `json:"sto,omitempty"` } -var optionalsExpected = `{ +func TestOmitEmpty(t *testing.T) { + var want = `{ "sr": "", "omitempty": 0, "slr": null, @@ -55,8 +56,6 @@ var optionalsExpected = `{ "str": {}, "sto": {} }` - -func TestOmitEmpty(t *testing.T) { var o Optionals o.Sw = "something" o.Mr = map[string]any{} @@ -64,10 +63,10 @@ func TestOmitEmpty(t *testing.T) { got, err := MarshalIndent(&o, "", " ") if err != nil { - t.Fatal(err) + t.Fatalf("MarshalIndent error: %v", err) } - if got := string(got); got != optionalsExpected { - t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected) + if got := string(got); got != want { + t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want)) } } @@ -81,62 +80,57 @@ type StringTag struct { func TestRoundtripStringTag(t *testing.T) { tests := []struct { - name string + CaseName in StringTag want string // empty to just test that we roundtrip - }{ - { - name: "AllTypes", - in: StringTag{ - BoolStr: true, - IntStr: 42, - UintptrStr: 44, - StrStr: "xzbit", - NumberStr: "46", - }, - want: `{ - "BoolStr": "true", - "IntStr": "42", - "UintptrStr": "44", - "StrStr": "\"xzbit\"", - "NumberStr": "46" - }`, + }{{ + CaseName: Name("AllTypes"), + in: StringTag{ + BoolStr: true, + IntStr: 42, + UintptrStr: 44, + StrStr: "xzbit", + NumberStr: "46", }, - { - // See golang.org/issues/38173. - name: "StringDoubleEscapes", - in: StringTag{ - StrStr: "\b\f\n\r\t\"\\", - NumberStr: "0", // just to satisfy the roundtrip - }, - want: `{ - "BoolStr": "false", - "IntStr": "0", - "UintptrStr": "0", - "StrStr": "\"\\b\\f\\n\\r\\t\\\"\\\\\"", - "NumberStr": "0" - }`, + want: `{ + "BoolStr": "true", + "IntStr": "42", + "UintptrStr": "44", + "StrStr": "\"xzbit\"", + "NumberStr": "46" +}`, + }, { + // See golang.org/issues/38173. + CaseName: Name("StringDoubleEscapes"), + in: StringTag{ + StrStr: "\b\f\n\r\t\"\\", + NumberStr: "0", // just to satisfy the roundtrip }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // Indent with a tab prefix to make the multi-line string - // literals in the table nicer to read. - got, err := MarshalIndent(&test.in, "\t\t\t", "\t") + want: `{ + "BoolStr": "false", + "IntStr": "0", + "UintptrStr": "0", + "StrStr": "\"\\b\\f\\n\\r\\t\\\"\\\\\"", + "NumberStr": "0" +}`, + }} + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + got, err := MarshalIndent(&tt.in, "", "\t") if err != nil { - t.Fatal(err) + t.Fatalf("%s: MarshalIndent error: %v", tt.Where, err) } - if got := string(got); got != test.want { - t.Fatalf(" got: %s\nwant: %s\n", got, test.want) + if got := string(got); got != tt.want { + t.Fatalf("%s: MarshalIndent:\n\tgot: %s\n\twant: %s", tt.Where, stripWhitespace(got), stripWhitespace(tt.want)) } // Verify that it round-trips. var s2 StringTag if err := Unmarshal(got, &s2); err != nil { - t.Fatalf("Decode: %v", err) + t.Fatalf("%s: Decode error: %v", tt.Where, err) } - if !reflect.DeepEqual(test.in, s2) { - t.Fatalf("decode didn't match.\nsource: %#v\nEncoded as:\n%s\ndecode: %#v", test.in, string(got), s2) + if !reflect.DeepEqual(s2, tt.in) { + t.Fatalf("%s: Decode:\n\tinput: %s\n\tgot: %#v\n\twant: %#v", tt.Where, indentNewlines(string(got)), s2, tt.in) } }) } @@ -149,21 +143,21 @@ type renamedRenamedByteSlice []renamedByte func TestEncodeRenamedByteSlice(t *testing.T) { s := renamedByteSlice("abc") - result, err := Marshal(s) + got, err := Marshal(s) if err != nil { - t.Fatal(err) + t.Fatalf("Marshal error: %v", err) } - expect := `"YWJj"` - if string(result) != expect { - t.Errorf(" got %s want %s", result, expect) + want := `"YWJj"` + if string(got) != want { + t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } r := renamedRenamedByteSlice("abc") - result, err = Marshal(r) + got, err = Marshal(r) if err != nil { - t.Fatal(err) + t.Fatalf("Marshal error: %v", err) } - if string(result) != expect { - t.Errorf(" got %s want %s", result, expect) + if string(got) != want { + t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } @@ -212,36 +206,40 @@ func init() { func TestSamePointerNoCycle(t *testing.T) { if _, err := Marshal(samePointerNoCycle); err != nil { - t.Fatalf("unexpected error: %v", err) + t.Fatalf("Marshal error: %v", err) } } func TestSliceNoCycle(t *testing.T) { if _, err := Marshal(sliceNoCycle); err != nil { - t.Fatalf("unexpected error: %v", err) + t.Fatalf("Marshal error: %v", err) } } -var unsupportedValues = []any{ - math.NaN(), - math.Inf(-1), - math.Inf(1), - pointerCycle, - pointerCycleIndirect, - mapCycle, - sliceCycle, - recursiveSliceCycle, -} - func TestUnsupportedValues(t *testing.T) { - for _, v := range unsupportedValues { - if _, err := Marshal(v); err != nil { - if _, ok := err.(*UnsupportedValueError); !ok { - t.Errorf("for %v, got %T want UnsupportedValueError", v, err) + tests := []struct { + CaseName + in any + }{ + {Name(""), math.NaN()}, + {Name(""), math.Inf(-1)}, + {Name(""), math.Inf(1)}, + {Name(""), pointerCycle}, + {Name(""), pointerCycleIndirect}, + {Name(""), mapCycle}, + {Name(""), sliceCycle}, + {Name(""), recursiveSliceCycle}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + if _, err := Marshal(tt.in); err != nil { + if _, ok := err.(*UnsupportedValueError); !ok { + t.Errorf("%s: Marshal error:\n\tgot: %T\n\twant: %T", tt.Where, err, new(UnsupportedValueError)) + } + } else { + t.Errorf("%s: Marshal error: got nil, want non-nil", tt.Where) } - } else { - t.Errorf("for %v, expected error", v) - } + }) } } @@ -253,11 +251,11 @@ func TestMarshalTextFloatMap(t *testing.T) { } got, err := Marshal(m) if err != nil { - t.Errorf("Marshal() error: %v", err) + t.Errorf("Marshal error: %v", err) } want := `{"TF:NaN":"1","TF:NaN":"1"}` if string(got) != want { - t.Errorf("Marshal() = %s, want %s", got, want) + t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } @@ -322,10 +320,10 @@ func TestRefValMarshal(t *testing.T) { const want = `{"R0":"ref","R1":"ref","R2":"\"ref\"","R3":"\"ref\"","V0":"val","V1":"val","V2":"\"val\"","V3":"\"val\""}` b, err := Marshal(&s) if err != nil { - t.Fatalf("Marshal: %v", err) + t.Fatalf("Marshal error: %v", err) } if got := string(b); got != want { - t.Errorf("got %q, want %q", got, want) + t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } @@ -348,33 +346,33 @@ func TestMarshalerEscaping(t *testing.T) { want := `"\u003c\u0026\u003e"` b, err := Marshal(c) if err != nil { - t.Fatalf("Marshal(c): %v", err) + t.Fatalf("Marshal error: %v", err) } if got := string(b); got != want { - t.Errorf("Marshal(c) = %#q, want %#q", got, want) + t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } var ct CText want = `"\"\u003c\u0026\u003e\""` b, err = Marshal(ct) if err != nil { - t.Fatalf("Marshal(ct): %v", err) + t.Fatalf("Marshal error: %v", err) } if got := string(b); got != want { - t.Errorf("Marshal(ct) = %#q, want %#q", got, want) + t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } func TestAnonymousFields(t *testing.T) { tests := []struct { - label string // Test name + CaseName makeInput func() any // Function to create input value want string // Expected JSON output }{{ // Both S1 and S2 have a field named X. From the perspective of S, // it is ambiguous which one X refers to. // This should not serialize either field. - label: "AmbiguousField", + CaseName: Name("AmbiguousField"), makeInput: func() any { type ( S1 struct{ x, X int } @@ -388,7 +386,7 @@ func TestAnonymousFields(t *testing.T) { }, want: `{}`, }, { - label: "DominantField", + CaseName: Name("DominantField"), // Both S1 and S2 have a field named X, but since S has an X field as // well, it takes precedence over S1.X and S2.X. makeInput: func() any { @@ -406,7 +404,7 @@ func TestAnonymousFields(t *testing.T) { want: `{"X":6}`, }, { // Unexported embedded field of non-struct type should not be serialized. - label: "UnexportedEmbeddedInt", + CaseName: Name("UnexportedEmbeddedInt"), makeInput: func() any { type ( myInt int @@ -417,7 +415,7 @@ func TestAnonymousFields(t *testing.T) { want: `{}`, }, { // Exported embedded field of non-struct type should be serialized. - label: "ExportedEmbeddedInt", + CaseName: Name("ExportedEmbeddedInt"), makeInput: func() any { type ( MyInt int @@ -429,7 +427,7 @@ func TestAnonymousFields(t *testing.T) { }, { // Unexported embedded field of pointer to non-struct type // should not be serialized. - label: "UnexportedEmbeddedIntPointer", + CaseName: Name("UnexportedEmbeddedIntPointer"), makeInput: func() any { type ( myInt int @@ -443,7 +441,7 @@ func TestAnonymousFields(t *testing.T) { }, { // Exported embedded field of pointer to non-struct type // should be serialized. - label: "ExportedEmbeddedIntPointer", + CaseName: Name("ExportedEmbeddedIntPointer"), makeInput: func() any { type ( MyInt int @@ -458,7 +456,7 @@ func TestAnonymousFields(t *testing.T) { // Exported fields of embedded structs should have their // exported fields be serialized regardless of whether the struct types // themselves are exported. - label: "EmbeddedStruct", + CaseName: Name("EmbeddedStruct"), makeInput: func() any { type ( s1 struct{ x, X int } @@ -475,7 +473,7 @@ func TestAnonymousFields(t *testing.T) { // Exported fields of pointers to embedded structs should have their // exported fields be serialized regardless of whether the struct types // themselves are exported. - label: "EmbeddedStructPointer", + CaseName: Name("EmbeddedStructPointer"), makeInput: func() any { type ( s1 struct{ x, X int } @@ -491,7 +489,7 @@ func TestAnonymousFields(t *testing.T) { }, { // Exported fields on embedded unexported structs at multiple levels // of nesting should still be serialized. - label: "NestedStructAndInts", + CaseName: Name("NestedStructAndInts"), makeInput: func() any { type ( MyInt1 int @@ -518,7 +516,7 @@ func TestAnonymousFields(t *testing.T) { // If an anonymous struct pointer field is nil, we should ignore // the embedded fields behind it. Not properly doing so may // result in the wrong output or reflect panics. - label: "EmbeddedFieldBehindNilPointer", + CaseName: Name("EmbeddedFieldBehindNilPointer"), makeInput: func() any { type ( S2 struct{ Field string } @@ -530,13 +528,13 @@ func TestAnonymousFields(t *testing.T) { }} for _, tt := range tests { - t.Run(tt.label, func(t *testing.T) { + t.Run(tt.Name, func(t *testing.T) { b, err := Marshal(tt.makeInput()) if err != nil { - t.Fatalf("Marshal() = %v, want nil error", err) + t.Fatalf("%s: Marshal error: %v", tt.Where, err) } if string(b) != tt.want { - t.Fatalf("Marshal() = %q, want %q", b, tt.want) + t.Fatalf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, b, tt.want) } }) } @@ -588,31 +586,34 @@ func (nm *nilTextMarshaler) MarshalText() ([]byte, error) { // See golang.org/issue/16042 and golang.org/issue/34235. func TestNilMarshal(t *testing.T) { - testCases := []struct { - v any + tests := []struct { + CaseName + in any want string }{ - {v: nil, want: `null`}, - {v: new(float64), want: `0`}, - {v: []any(nil), want: `null`}, - {v: []string(nil), want: `null`}, - {v: map[string]string(nil), want: `null`}, - {v: []byte(nil), want: `null`}, - {v: struct{ M string }{"gopher"}, want: `{"M":"gopher"}`}, - {v: struct{ M Marshaler }{}, want: `{"M":null}`}, - {v: struct{ M Marshaler }{(*nilJSONMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, - {v: struct{ M any }{(*nilJSONMarshaler)(nil)}, want: `{"M":null}`}, - {v: struct{ M encoding.TextMarshaler }{}, want: `{"M":null}`}, - {v: struct{ M encoding.TextMarshaler }{(*nilTextMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, - {v: struct{ M any }{(*nilTextMarshaler)(nil)}, want: `{"M":null}`}, - } - - for _, tt := range testCases { - out, err := Marshal(tt.v) - if err != nil || string(out) != tt.want { - t.Errorf("Marshal(%#v) = %#q, %#v, want %#q, nil", tt.v, out, err, tt.want) - continue - } + {Name(""), nil, `null`}, + {Name(""), new(float64), `0`}, + {Name(""), []any(nil), `null`}, + {Name(""), []string(nil), `null`}, + {Name(""), map[string]string(nil), `null`}, + {Name(""), []byte(nil), `null`}, + {Name(""), struct{ M string }{"gopher"}, `{"M":"gopher"}`}, + {Name(""), struct{ M Marshaler }{}, `{"M":null}`}, + {Name(""), struct{ M Marshaler }{(*nilJSONMarshaler)(nil)}, `{"M":"0zenil0"}`}, + {Name(""), struct{ M any }{(*nilJSONMarshaler)(nil)}, `{"M":null}`}, + {Name(""), struct{ M encoding.TextMarshaler }{}, `{"M":null}`}, + {Name(""), struct{ M encoding.TextMarshaler }{(*nilTextMarshaler)(nil)}, `{"M":"0zenil0"}`}, + {Name(""), struct{ M any }{(*nilTextMarshaler)(nil)}, `{"M":null}`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + switch got, err := Marshal(tt.in); { + case err != nil: + t.Fatalf("%s: Marshal error: %v", tt.Where, err) + case string(got) != tt.want: + t.Fatalf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.want) + } + }) } } @@ -624,12 +625,12 @@ func TestEmbeddedBug(t *testing.T) { } b, err := Marshal(v) if err != nil { - t.Fatal("Marshal:", err) + t.Fatal("Marshal error:", err) } want := `{"S":"B"}` got := string(b) if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) + t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } // Now check that the duplicate field, S, does not appear. x := BugX{ @@ -637,12 +638,12 @@ func TestEmbeddedBug(t *testing.T) { } b, err = Marshal(x) if err != nil { - t.Fatal("Marshal:", err) + t.Fatal("Marshal error:", err) } want = `{"A":23}` got = string(b) if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) + t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } @@ -664,12 +665,12 @@ func TestTaggedFieldDominates(t *testing.T) { } b, err := Marshal(v) if err != nil { - t.Fatal("Marshal:", err) + t.Fatal("Marshal error:", err) } want := `{"S":"BugD"}` got := string(b) if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) + t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } @@ -691,12 +692,12 @@ func TestDuplicatedFieldDisappears(t *testing.T) { } b, err := Marshal(v) if err != nil { - t.Fatal("Marshal:", err) + t.Fatal("Marshal error:", err) } want := `{}` got := string(b) if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) + t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } @@ -706,9 +707,8 @@ func TestIssue10281(t *testing.T) { } x := Foo{Number(`invalid`)} - b, err := Marshal(&x) - if err == nil { - t.Errorf("Marshal(&x) = %#q; want error", b) + if _, err := Marshal(&x); err == nil { + t.Fatalf("Marshal error: got nil, want non-nil") } } @@ -724,26 +724,26 @@ func TestMarshalErrorAndReuseEncodeState(t *testing.T) { } dummy := Dummy{Name: "Dummy"} dummy.Next = &dummy - if b, err := Marshal(dummy); err == nil { - t.Errorf("Marshal(dummy) = %#q; want error", b) + if _, err := Marshal(dummy); err == nil { + t.Errorf("Marshal error: got nil, want non-nil") } type Data struct { A string I int } - data := Data{A: "a", I: 1} - b, err := Marshal(data) + want := Data{A: "a", I: 1} + b, err := Marshal(want) if err != nil { - t.Errorf("Marshal(%v) = %v", data, err) + t.Errorf("Marshal error: %v", err) } - var data2 Data - if err := Unmarshal(b, &data2); err != nil { - t.Errorf("Unmarshal(%v) = %v", data2, err) + var got Data + if err := Unmarshal(b, &got); err != nil { + t.Errorf("Unmarshal error: %v", err) } - if data2 != data { - t.Errorf("expect: %v, but get: %v", data, data2) + if got != want { + t.Errorf("Unmarshal:\n\tgot: %v\n\twant: %v", got, want) } } @@ -753,7 +753,7 @@ func TestHTMLEscape(t *testing.T) { want.Write([]byte(`{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`)) HTMLEscape(&b, []byte(m)) if !bytes.Equal(b.Bytes(), want.Bytes()) { - t.Errorf("HTMLEscape(&b, []byte(m)) = %s; want %s", b.Bytes(), want.Bytes()) + t.Errorf("HTMLEscape:\n\tgot: %s\n\twant: %s", b.Bytes(), want.Bytes()) } } @@ -765,21 +765,19 @@ func TestEncodePointerString(t *testing.T) { var n int64 = 42 b, err := Marshal(stringPointer{N: &n}) if err != nil { - t.Fatalf("Marshal: %v", err) + t.Fatalf("Marshal error: %v", err) } if got, want := string(b), `{"n":"42"}`; got != want { - t.Errorf("Marshal = %s, want %s", got, want) + t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } var back stringPointer - err = Unmarshal(b, &back) - if err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if back.N == nil { - t.Fatalf("Unmarshaled nil N field") - } - if *back.N != 42 { - t.Fatalf("*N = %d; want 42", *back.N) + switch err = Unmarshal(b, &back); { + case err != nil: + t.Fatalf("Unmarshal error: %v", err) + case back.N == nil: + t.Fatalf("Unmarshal: back.N = nil, want non-nil") + case *back.N != 42: + t.Fatalf("Unmarshal: *back.N = %d, want 42", *back.N) } } @@ -825,7 +823,7 @@ func TestEncodeString(t *testing.T) { for _, tt := range encodeStringTests { b, err := Marshal(tt.in) if err != nil { - t.Errorf("Marshal(%q): %v", tt.in, err) + t.Errorf("Marshal(%q) error: %v", tt.in, err) continue } out := string(b) @@ -863,65 +861,67 @@ func (f textfloat) MarshalText() ([]byte, error) { return tenc(`TF:%0.2f`, f) } // Issue 13783 func TestEncodeBytekind(t *testing.T) { - testdata := []struct { - data any + tests := []struct { + CaseName + in any want string }{ - {byte(7), "7"}, - {jsonbyte(7), `{"JB":7}`}, - {textbyte(4), `"TB:4"`}, - {jsonint(5), `{"JI":5}`}, - {textint(1), `"TI:1"`}, - {[]byte{0, 1}, `"AAE="`}, - {[]jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`}, - {[][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`}, - {[]textbyte{2, 3}, `["TB:2","TB:3"]`}, - {[]jsonint{5, 4}, `[{"JI":5},{"JI":4}]`}, - {[]textint{9, 3}, `["TI:9","TI:3"]`}, - {[]int{9, 3}, `[9,3]`}, - {[]textfloat{12, 3}, `["TF:12.00","TF:3.00"]`}, - } - for _, d := range testdata { - js, err := Marshal(d.data) - if err != nil { - t.Error(err) - continue - } - got, want := string(js), d.want - if got != want { - t.Errorf("got %s, want %s", got, want) - } + {Name(""), byte(7), "7"}, + {Name(""), jsonbyte(7), `{"JB":7}`}, + {Name(""), textbyte(4), `"TB:4"`}, + {Name(""), jsonint(5), `{"JI":5}`}, + {Name(""), textint(1), `"TI:1"`}, + {Name(""), []byte{0, 1}, `"AAE="`}, + {Name(""), []jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`}, + {Name(""), [][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`}, + {Name(""), []textbyte{2, 3}, `["TB:2","TB:3"]`}, + {Name(""), []jsonint{5, 4}, `[{"JI":5},{"JI":4}]`}, + {Name(""), []textint{9, 3}, `["TI:9","TI:3"]`}, + {Name(""), []int{9, 3}, `[9,3]`}, + {Name(""), []textfloat{12, 3}, `["TF:12.00","TF:3.00"]`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + b, err := Marshal(tt.in) + if err != nil { + t.Errorf("%s: Marshal error: %v", tt.Where, err) + } + got, want := string(b), tt.want + if got != want { + t.Errorf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, got, want) + } + }) } } func TestTextMarshalerMapKeysAreSorted(t *testing.T) { - b, err := Marshal(map[unmarshalerText]int{ + got, err := Marshal(map[unmarshalerText]int{ {"x", "y"}: 1, {"y", "x"}: 2, {"a", "z"}: 3, {"z", "a"}: 4, }) if err != nil { - t.Fatalf("Failed to Marshal text.Marshaler: %v", err) + t.Fatalf("Marshal error: %v", err) } const want = `{"a:z":3,"x:y":1,"y:x":2,"z:a":4}` - if string(b) != want { - t.Errorf("Marshal map with text.Marshaler keys: got %#q, want %#q", b, want) + if string(got) != want { + t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } // https://golang.org/issue/33675 func TestNilMarshalerTextMapKey(t *testing.T) { - b, err := Marshal(map[*unmarshalerText]int{ + got, err := Marshal(map[*unmarshalerText]int{ (*unmarshalerText)(nil): 1, {"A", "B"}: 2, }) if err != nil { - t.Fatalf("Failed to Marshal *text.Marshaler: %v", err) + t.Fatalf("Marshal error: %v", err) } const want = `{"":1,"A:B":2}` - if string(b) != want { - t.Errorf("Marshal map with *text.Marshaler keys: got %#q, want %#q", b, want) + if string(got) != want { + t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } @@ -960,7 +960,7 @@ func TestMarshalFloat(t *testing.T) { } bout, err := Marshal(vf) if err != nil { - t.Errorf("Marshal(%T(%g)): %v", vf, vf, err) + t.Errorf("Marshal(%T(%g)) error: %v", vf, vf, err) nfail++ return } @@ -969,12 +969,12 @@ func TestMarshalFloat(t *testing.T) { // result must convert back to the same float g, err := strconv.ParseFloat(out, bits) if err != nil { - t.Errorf("Marshal(%T(%g)) = %q, cannot parse back: %v", vf, vf, out, err) + t.Errorf("ParseFloat(%q) error: %v", out, err) nfail++ return } if f != g || fmt.Sprint(f) != fmt.Sprint(g) { // fmt.Sprint handles ±0 - t.Errorf("Marshal(%T(%g)) = %q (is %g, not %g)", vf, vf, out, float32(g), vf) + t.Errorf("ParseFloat(%q):\n\tgot: %g\n\twant: %g", out, float32(g), vf) nfail++ return } @@ -985,7 +985,7 @@ func TestMarshalFloat(t *testing.T) { } for _, re := range bad { if re.MatchString(out) { - t.Errorf("Marshal(%T(%g)) = %q, must not match /%s/", vf, vf, out, re) + t.Errorf("Marshal(%T(%g)) = %q; must not match /%s/", vf, vf, out, re) nfail++ return } @@ -1049,87 +1049,90 @@ func TestMarshalRawMessageValue(t *testing.T) { ) tests := []struct { + CaseName in any want string ok bool }{ // Test with nil RawMessage. - {rawNil, "null", true}, - {&rawNil, "null", true}, - {[]any{rawNil}, "[null]", true}, - {&[]any{rawNil}, "[null]", true}, - {[]any{&rawNil}, "[null]", true}, - {&[]any{&rawNil}, "[null]", true}, - {struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, - {&struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, - {struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, - {&struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, - {map[string]any{"M": rawNil}, `{"M":null}`, true}, - {&map[string]any{"M": rawNil}, `{"M":null}`, true}, - {map[string]any{"M": &rawNil}, `{"M":null}`, true}, - {&map[string]any{"M": &rawNil}, `{"M":null}`, true}, - {T1{rawNil}, "{}", true}, - {T2{&rawNil}, `{"M":null}`, true}, - {&T1{rawNil}, "{}", true}, - {&T2{&rawNil}, `{"M":null}`, true}, + {Name(""), rawNil, "null", true}, + {Name(""), &rawNil, "null", true}, + {Name(""), []any{rawNil}, "[null]", true}, + {Name(""), &[]any{rawNil}, "[null]", true}, + {Name(""), []any{&rawNil}, "[null]", true}, + {Name(""), &[]any{&rawNil}, "[null]", true}, + {Name(""), struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, + {Name(""), &struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, + {Name(""), struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, + {Name(""), &struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, + {Name(""), map[string]any{"M": rawNil}, `{"M":null}`, true}, + {Name(""), &map[string]any{"M": rawNil}, `{"M":null}`, true}, + {Name(""), map[string]any{"M": &rawNil}, `{"M":null}`, true}, + {Name(""), &map[string]any{"M": &rawNil}, `{"M":null}`, true}, + {Name(""), T1{rawNil}, "{}", true}, + {Name(""), T2{&rawNil}, `{"M":null}`, true}, + {Name(""), &T1{rawNil}, "{}", true}, + {Name(""), &T2{&rawNil}, `{"M":null}`, true}, // Test with empty, but non-nil, RawMessage. - {rawEmpty, "", false}, - {&rawEmpty, "", false}, - {[]any{rawEmpty}, "", false}, - {&[]any{rawEmpty}, "", false}, - {[]any{&rawEmpty}, "", false}, - {&[]any{&rawEmpty}, "", false}, - {struct{ X RawMessage }{rawEmpty}, "", false}, - {&struct{ X RawMessage }{rawEmpty}, "", false}, - {struct{ X *RawMessage }{&rawEmpty}, "", false}, - {&struct{ X *RawMessage }{&rawEmpty}, "", false}, - {map[string]any{"nil": rawEmpty}, "", false}, - {&map[string]any{"nil": rawEmpty}, "", false}, - {map[string]any{"nil": &rawEmpty}, "", false}, - {&map[string]any{"nil": &rawEmpty}, "", false}, - {T1{rawEmpty}, "{}", true}, - {T2{&rawEmpty}, "", false}, - {&T1{rawEmpty}, "{}", true}, - {&T2{&rawEmpty}, "", false}, + {Name(""), rawEmpty, "", false}, + {Name(""), &rawEmpty, "", false}, + {Name(""), []any{rawEmpty}, "", false}, + {Name(""), &[]any{rawEmpty}, "", false}, + {Name(""), []any{&rawEmpty}, "", false}, + {Name(""), &[]any{&rawEmpty}, "", false}, + {Name(""), struct{ X RawMessage }{rawEmpty}, "", false}, + {Name(""), &struct{ X RawMessage }{rawEmpty}, "", false}, + {Name(""), struct{ X *RawMessage }{&rawEmpty}, "", false}, + {Name(""), &struct{ X *RawMessage }{&rawEmpty}, "", false}, + {Name(""), map[string]any{"nil": rawEmpty}, "", false}, + {Name(""), &map[string]any{"nil": rawEmpty}, "", false}, + {Name(""), map[string]any{"nil": &rawEmpty}, "", false}, + {Name(""), &map[string]any{"nil": &rawEmpty}, "", false}, + {Name(""), T1{rawEmpty}, "{}", true}, + {Name(""), T2{&rawEmpty}, "", false}, + {Name(""), &T1{rawEmpty}, "{}", true}, + {Name(""), &T2{&rawEmpty}, "", false}, // Test with RawMessage with some text. // // The tests below marked with Issue6458 used to generate "ImZvbyI=" instead "foo". // This behavior was intentionally changed in Go 1.8. // See https://golang.org/issues/14493#issuecomment-255857318 - {rawText, `"foo"`, true}, // Issue6458 - {&rawText, `"foo"`, true}, - {[]any{rawText}, `["foo"]`, true}, // Issue6458 - {&[]any{rawText}, `["foo"]`, true}, // Issue6458 - {[]any{&rawText}, `["foo"]`, true}, - {&[]any{&rawText}, `["foo"]`, true}, - {struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 - {&struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, - {struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, - {&struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, - {map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 - {&map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 - {map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, - {&map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, - {T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 - {T2{&rawText}, `{"M":"foo"}`, true}, - {&T1{rawText}, `{"M":"foo"}`, true}, - {&T2{&rawText}, `{"M":"foo"}`, true}, - } - - for i, tt := range tests { - b, err := Marshal(tt.in) - if ok := (err == nil); ok != tt.ok { - if err != nil { - t.Errorf("test %d, unexpected failure: %v", i, err) - } else { - t.Errorf("test %d, unexpected success", i) + {Name(""), rawText, `"foo"`, true}, // Issue6458 + {Name(""), &rawText, `"foo"`, true}, + {Name(""), []any{rawText}, `["foo"]`, true}, // Issue6458 + {Name(""), &[]any{rawText}, `["foo"]`, true}, // Issue6458 + {Name(""), []any{&rawText}, `["foo"]`, true}, + {Name(""), &[]any{&rawText}, `["foo"]`, true}, + {Name(""), struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 + {Name(""), &struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, + {Name(""), struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, + {Name(""), &struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, + {Name(""), map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 + {Name(""), &map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 + {Name(""), map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, + {Name(""), &map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, + {Name(""), T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 + {Name(""), T2{&rawText}, `{"M":"foo"}`, true}, + {Name(""), &T1{rawText}, `{"M":"foo"}`, true}, + {Name(""), &T2{&rawText}, `{"M":"foo"}`, true}, + } + + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + b, err := Marshal(tt.in) + if ok := (err == nil); ok != tt.ok { + if err != nil { + t.Errorf("%s: Marshal error: %v", tt.Where, err) + } else { + t.Errorf("%s: Marshal error: got nil, want non-nil", tt.Where) + } } - } - if got := string(b); got != tt.want { - t.Errorf("test %d, Marshal(%#v) = %q, want %q", i, tt.in, got, tt.want) - } + if got := string(b); got != tt.want { + t.Errorf("%s: Marshal:\n\tinput: %#v\n\tgot: %s\n\twant: %s", tt.Where, tt.in, got, tt.want) + } + }) } } @@ -1153,12 +1156,12 @@ func TestMarshalUncommonFieldNames(t *testing.T) { }{} b, err := Marshal(v) if err != nil { - t.Fatal("Marshal:", err) + t.Fatal("Marshal error:", err) } want := `{"A0":0,"À":0,"Aβ":0}` got := string(b) if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) + t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } @@ -1168,23 +1171,25 @@ func TestMarshalerError(t *testing.T) { errText := "json: test error" tests := []struct { + CaseName err *MarshalerError want string - }{ - { - &MarshalerError{st, fmt.Errorf(errText), ""}, - "json: error calling MarshalJSON for type " + st.String() + ": " + errText, - }, - { - &MarshalerError{st, fmt.Errorf(errText), "TestMarshalerError"}, - "json: error calling TestMarshalerError for type " + st.String() + ": " + errText, - }, - } + }{{ + Name(""), + &MarshalerError{st, fmt.Errorf(errText), ""}, + "json: error calling MarshalJSON for type " + st.String() + ": " + errText, + }, { + Name(""), + &MarshalerError{st, fmt.Errorf(errText), "TestMarshalerError"}, + "json: error calling TestMarshalerError for type " + st.String() + ": " + errText, + }} - for i, tt := range tests { - got := tt.err.Error() - if got != tt.want { - t.Errorf("MarshalerError test %d, got: %s, want: %s", i, got, tt.want) - } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + got := tt.err.Error() + if got != tt.want { + t.Errorf("%s: Error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.want) + } + }) } } diff --git a/src/encoding/json/scanner_test.go b/src/encoding/json/scanner_test.go index 3474b3e481..068439dcac 100644 --- a/src/encoding/json/scanner_test.go +++ b/src/encoding/json/scanner_test.go @@ -9,51 +9,59 @@ import ( "math" "math/rand" "reflect" + "strings" "testing" ) -var validTests = []struct { - data string - ok bool -}{ - {`foo`, false}, - {`}{`, false}, - {`{]`, false}, - {`{}`, true}, - {`{"foo":"bar"}`, true}, - {`{"foo":"bar","bar":{"baz":["qux"]}}`, true}, +func indentNewlines(s string) string { + return strings.Join(strings.Split(s, "\n"), "\n\t") } -func TestValid(t *testing.T) { - for _, tt := range validTests { - if ok := Valid([]byte(tt.data)); ok != tt.ok { - t.Errorf("Valid(%#q) = %v, want %v", tt.data, ok, tt.ok) +func stripWhitespace(s string) string { + return strings.Map(func(r rune) rune { + if r == ' ' || r == '\n' || r == '\r' || r == '\t' { + return -1 } - } -} - -// Tests of simple examples. - -type example struct { - compact string - indent string + return r + }, s) } -var examples = []example{ - {`1`, `1`}, - {`{}`, `{}`}, - {`[]`, `[]`}, - {`{"":2}`, "{\n\t\"\": 2\n}"}, - {`[3]`, "[\n\t3\n]"}, - {`[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"}, - {`{"x":1}`, "{\n\t\"x\": 1\n}"}, - {ex1, ex1i}, - {"{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070 +func TestValid(t *testing.T) { + tests := []struct { + CaseName + data string + ok bool + }{ + {Name(""), `foo`, false}, + {Name(""), `}{`, false}, + {Name(""), `{]`, false}, + {Name(""), `{}`, true}, + {Name(""), `{"foo":"bar"}`, true}, + {Name(""), `{"foo":"bar","bar":{"baz":["qux"]}}`, true}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + if ok := Valid([]byte(tt.data)); ok != tt.ok { + t.Errorf("%s: Valid(`%s`) = %v, want %v", tt.Where, tt.data, ok, tt.ok) + } + }) + } } -var ex1 = `[true,false,null,"x",1,1.5,0,-5e+2]` - -var ex1i = `[ +func TestCompactAndIndent(t *testing.T) { + tests := []struct { + CaseName + compact string + indent string + }{ + {Name(""), `1`, `1`}, + {Name(""), `{}`, `{}`}, + {Name(""), `[]`, `[]`}, + {Name(""), `{"":2}`, "{\n\t\"\": 2\n}"}, + {Name(""), `[3]`, "[\n\t3\n]"}, + {Name(""), `[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"}, + {Name(""), `{"x":1}`, "{\n\t\"x\": 1\n}"}, + {Name(""), `[true,false,null,"x",1,1.5,0,-5e+2]`, `[ true, false, null, @@ -62,25 +70,40 @@ var ex1i = `[ 1.5, 0, -5e+2 -]` - -func TestCompact(t *testing.T) { +]`}, + {Name(""), "{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070 + } var buf bytes.Buffer - for _, tt := range examples { - buf.Reset() - if err := Compact(&buf, []byte(tt.compact)); err != nil { - t.Errorf("Compact(%#q): %v", tt.compact, err) - } else if s := buf.String(); s != tt.compact { - t.Errorf("Compact(%#q) = %#q, want original", tt.compact, s) - } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + buf.Reset() + if err := Compact(&buf, []byte(tt.compact)); err != nil { + t.Errorf("%s: Compact error: %v", tt.Where, err) + } else if got := buf.String(); got != tt.compact { + t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact)) + } - buf.Reset() - if err := Compact(&buf, []byte(tt.indent)); err != nil { - t.Errorf("Compact(%#q): %v", tt.indent, err) - continue - } else if s := buf.String(); s != tt.compact { - t.Errorf("Compact(%#q) = %#q, want %#q", tt.indent, s, tt.compact) - } + buf.Reset() + if err := Compact(&buf, []byte(tt.indent)); err != nil { + t.Errorf("%s: Compact error: %v", tt.Where, err) + } else if got := buf.String(); got != tt.compact { + t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact)) + } + + buf.Reset() + if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil { + t.Errorf("%s: Indent error: %v", tt.Where, err) + } else if got := buf.String(); got != tt.indent { + t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent)) + } + + buf.Reset() + if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil { + t.Errorf("%s: Indent error: %v", tt.Where, err) + } else if got := buf.String(); got != tt.indent { + t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent)) + } + }) } } @@ -88,38 +111,21 @@ func TestCompactSeparators(t *testing.T) { // U+2028 and U+2029 should be escaped inside strings. // They should not appear outside strings. tests := []struct { + CaseName in, compact string }{ - {"{\"\u2028\": 1}", "{\"\u2028\":1}"}, - {"{\"\u2029\" :2}", "{\"\u2029\":2}"}, + {Name(""), "{\"\u2028\": 1}", "{\"\u2028\":1}"}, + {Name(""), "{\"\u2029\" :2}", "{\"\u2029\":2}"}, } for _, tt := range tests { - var buf bytes.Buffer - if err := Compact(&buf, []byte(tt.in)); err != nil { - t.Errorf("Compact(%q): %v", tt.in, err) - } else if s := buf.String(); s != tt.compact { - t.Errorf("Compact(%q) = %q, want %q", tt.in, s, tt.compact) - } - } -} - -func TestIndent(t *testing.T) { - var buf bytes.Buffer - for _, tt := range examples { - buf.Reset() - if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil { - t.Errorf("Indent(%#q): %v", tt.indent, err) - } else if s := buf.String(); s != tt.indent { - t.Errorf("Indent(%#q) = %#q, want original", tt.indent, s) - } - - buf.Reset() - if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil { - t.Errorf("Indent(%#q): %v", tt.compact, err) - continue - } else if s := buf.String(); s != tt.indent { - t.Errorf("Indent(%#q) = %#q, want %#q", tt.compact, s, tt.indent) - } + t.Run(tt.Name, func(t *testing.T) { + var buf bytes.Buffer + if err := Compact(&buf, []byte(tt.in)); err != nil { + t.Errorf("%s: Compact error: %v", tt.Where, err) + } else if got := buf.String(); got != tt.compact { + t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact)) + } + }) } } @@ -129,11 +135,11 @@ func TestCompactBig(t *testing.T) { initBig() var buf bytes.Buffer if err := Compact(&buf, jsonBig); err != nil { - t.Fatalf("Compact: %v", err) + t.Fatalf("Compact error: %v", err) } b := buf.Bytes() if !bytes.Equal(b, jsonBig) { - t.Error("Compact(jsonBig) != jsonBig") + t.Error("Compact:") diff(t, b, jsonBig) return } @@ -144,23 +150,23 @@ func TestIndentBig(t *testing.T) { initBig() var buf bytes.Buffer if err := Indent(&buf, jsonBig, "", "\t"); err != nil { - t.Fatalf("Indent1: %v", err) + t.Fatalf("Indent error: %v", err) } b := buf.Bytes() if len(b) == len(jsonBig) { // jsonBig is compact (no unnecessary spaces); // indenting should make it bigger - t.Fatalf("Indent(jsonBig) did not get bigger") + t.Fatalf("Indent did not expand the input") } // should be idempotent var buf1 bytes.Buffer if err := Indent(&buf1, b, "", "\t"); err != nil { - t.Fatalf("Indent2: %v", err) + t.Fatalf("Indent error: %v", err) } b1 := buf1.Bytes() if !bytes.Equal(b1, b) { - t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig)") + t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig):") diff(t, b1, b) return } @@ -168,40 +174,40 @@ func TestIndentBig(t *testing.T) { // should get back to original buf1.Reset() if err := Compact(&buf1, b); err != nil { - t.Fatalf("Compact: %v", err) + t.Fatalf("Compact error: %v", err) } b1 = buf1.Bytes() if !bytes.Equal(b1, jsonBig) { - t.Error("Compact(Indent(jsonBig)) != jsonBig") + t.Error("Compact(Indent(jsonBig)) != jsonBig:") diff(t, b1, jsonBig) return } } -type indentErrorTest struct { - in string - err error -} - -var indentErrorTests = []indentErrorTest{ - {`{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}}, - {`{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}}, -} - func TestIndentErrors(t *testing.T) { - for i, tt := range indentErrorTests { - slice := make([]uint8, 0) - buf := bytes.NewBuffer(slice) - if err := Indent(buf, []uint8(tt.in), "", ""); err != nil { - if !reflect.DeepEqual(err, tt.err) { - t.Errorf("#%d: Indent: %#v", i, err) - continue + tests := []struct { + CaseName + in string + err error + }{ + {Name(""), `{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}}, + {Name(""), `{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + slice := make([]uint8, 0) + buf := bytes.NewBuffer(slice) + if err := Indent(buf, []uint8(tt.in), "", ""); err != nil { + if !reflect.DeepEqual(err, tt.err) { + t.Fatalf("%s: Indent error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) + } } - } + }) } } func diff(t *testing.T, a, b []byte) { + t.Helper() for i := 0; ; i++ { if i >= len(a) || i >= len(b) || a[i] != b[i] { j := i - 10 @@ -215,10 +221,7 @@ func diff(t *testing.T, a, b []byte) { } func trim(b []byte) []byte { - if len(b) > 20 { - return b[0:20] - } - return b + return b[:min(len(b), 20)] } // Generate a random JSON object. diff --git a/src/encoding/json/stream_test.go b/src/encoding/json/stream_test.go index 97f9fbd6d8..32ede8cc7e 100644 --- a/src/encoding/json/stream_test.go +++ b/src/encoding/json/stream_test.go @@ -6,17 +6,44 @@ package json import ( "bytes" + "fmt" "io" "log" "net" "net/http" "net/http/httptest" + "path" "reflect" + "runtime" "runtime/debug" "strings" "testing" ) +// TODO(https://go.dev/issue/52751): Replace with native testing support. + +// CaseName is a case name annotated with a file and line. +type CaseName struct { + Name string + Where CasePos +} + +// Name annotates a case name with the file and line of the caller. +func Name(s string) (c CaseName) { + c.Name = s + runtime.Callers(2, c.Where.pc[:]) + return c +} + +// CasePos represents a file and line number. +type CasePos struct{ pc [1]uintptr } + +func (pos CasePos) String() string { + frames := runtime.CallersFrames(pos.pc[:]) + frame, _ := frames.Next() + return fmt.Sprintf("%s:%d", path.Base(frame.File), frame.Line) +} + // Test values for the stream test. // One of each JSON kind. var streamTest = []any{ @@ -49,11 +76,11 @@ func TestEncoder(t *testing.T) { enc.SetIndent("", "") for j, v := range streamTest[0:i] { if err := enc.Encode(v); err != nil { - t.Fatalf("encode #%d: %v", j, err) + t.Fatalf("#%d.%d Encode error: %v", i, j, err) } } if have, want := buf.String(), nlines(streamEncoded, i); have != want { - t.Errorf("encoding %d items: mismatch", i) + t.Errorf("encoding %d items: mismatch:", i) diff(t, []byte(have), []byte(want)) break } @@ -76,24 +103,24 @@ func TestEncoderErrorAndReuseEncodeState(t *testing.T) { var buf bytes.Buffer enc := NewEncoder(&buf) if err := enc.Encode(dummy); err == nil { - t.Errorf("Encode(dummy) == nil; want error") + t.Errorf("Encode(dummy) error: got nil, want non-nil") } type Data struct { A string I int } - data := Data{A: "a", I: 1} - if err := enc.Encode(data); err != nil { - t.Errorf("Marshal(%v) = %v", data, err) + want := Data{A: "a", I: 1} + if err := enc.Encode(want); err != nil { + t.Errorf("Marshal error: %v", err) } - var data2 Data - if err := Unmarshal(buf.Bytes(), &data2); err != nil { - t.Errorf("Unmarshal(%v) = %v", data2, err) + var got Data + if err := Unmarshal(buf.Bytes(), &got); err != nil { + t.Errorf("Unmarshal error: %v", err) } - if data2 != data { - t.Errorf("expect: %v, but get: %v", data, data2) + if got != want { + t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %v\n\twant: %v", got, want) } } @@ -122,7 +149,7 @@ func TestEncoderIndent(t *testing.T) { enc.Encode(v) } if have, want := buf.String(), streamEncodedIndent; have != want { - t.Error("indented encoding mismatch") + t.Error("Encode mismatch:") diff(t, []byte(have), []byte(want)) } } @@ -160,50 +187,51 @@ func TestEncoderSetEscapeHTML(t *testing.T) { Bar string `json:"bar,string"` }{`foobar`} - for _, tt := range []struct { - name string + tests := []struct { + CaseName v any wantEscape string want string }{ - {"c", c, `"\u003c\u0026\u003e"`, `"<&>"`}, - {"ct", ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`}, - {`"<&>"`, "<&>", `"\u003c\u0026\u003e"`, `"<&>"`}, + {Name("c"), c, `"\u003c\u0026\u003e"`, `"<&>"`}, + {Name("ct"), ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`}, + {Name(`"<&>"`), "<&>", `"\u003c\u0026\u003e"`, `"<&>"`}, { - "tagStruct", tagStruct, + Name("tagStruct"), tagStruct, `{"\u003c\u003e\u0026#! ":0,"Invalid":0}`, `{"<>&#! ":0,"Invalid":0}`, }, { - `""`, marshalerStruct, + Name(`""`), marshalerStruct, `{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`, `{"NonPtr":"","Ptr":""}`, }, { - "stringOption", stringOption, + Name("stringOption"), stringOption, `{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`, `{"bar":"\"foobar\""}`, }, - } { - var buf strings.Builder - enc := NewEncoder(&buf) - if err := enc.Encode(tt.v); err != nil { - t.Errorf("Encode(%s): %s", tt.name, err) - continue - } - if got := strings.TrimSpace(buf.String()); got != tt.wantEscape { - t.Errorf("Encode(%s) = %#q, want %#q", tt.name, got, tt.wantEscape) - } - buf.Reset() - enc.SetEscapeHTML(false) - if err := enc.Encode(tt.v); err != nil { - t.Errorf("SetEscapeHTML(false) Encode(%s): %s", tt.name, err) - continue - } - if got := strings.TrimSpace(buf.String()); got != tt.want { - t.Errorf("SetEscapeHTML(false) Encode(%s) = %#q, want %#q", - tt.name, got, tt.want) - } + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + var buf strings.Builder + enc := NewEncoder(&buf) + if err := enc.Encode(tt.v); err != nil { + t.Fatalf("%s: Encode(%s) error: %s", tt.Where, tt.Name, err) + } + if got := strings.TrimSpace(buf.String()); got != tt.wantEscape { + t.Errorf("%s: Encode(%s):\n\tgot: %s\n\twant: %s", tt.Where, tt.Name, got, tt.wantEscape) + } + buf.Reset() + enc.SetEscapeHTML(false) + if err := enc.Encode(tt.v); err != nil { + t.Fatalf("%s: SetEscapeHTML(false) Encode(%s) error: %s", tt.Where, tt.Name, err) + } + if got := strings.TrimSpace(buf.String()); got != tt.want { + t.Errorf("%s: SetEscapeHTML(false) Encode(%s):\n\tgot: %s\n\twant: %s", + tt.Where, tt.Name, got, tt.want) + } + }) } } @@ -224,14 +252,14 @@ func TestDecoder(t *testing.T) { dec := NewDecoder(&buf) for j := range out { if err := dec.Decode(&out[j]); err != nil { - t.Fatalf("decode #%d/%d: %v", j, i, err) + t.Fatalf("decode #%d/%d error: %v", j, i, err) } } if !reflect.DeepEqual(out, streamTest[0:i]) { - t.Errorf("decoding %d items: mismatch", i) + t.Errorf("decoding %d items: mismatch:", i) for j := range out { if !reflect.DeepEqual(out[j], streamTest[j]) { - t.Errorf("#%d: have %v want %v", j, out[j], streamTest[j]) + t.Errorf("#%d:\n\tgot: %v\n\twant: %v", j, out[j], streamTest[j]) } } break @@ -250,14 +278,14 @@ func TestDecoderBuffered(t *testing.T) { t.Fatal(err) } if m.Name != "Gopher" { - t.Errorf("Name = %q; want Gopher", m.Name) + t.Errorf("Name = %s, want Gopher", m.Name) } rest, err := io.ReadAll(d.Buffered()) if err != nil { t.Fatal(err) } - if g, w := string(rest), " extra "; g != w { - t.Errorf("Remaining = %q; want %q", g, w) + if got, want := string(rest), " extra "; got != want { + t.Errorf("Remaining = %s, want %s", got, want) } } @@ -282,20 +310,20 @@ func TestRawMessage(t *testing.T) { Y float32 } const raw = `["\u0056",null]` - const msg = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}` - err := Unmarshal([]byte(msg), &data) + const want = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}` + err := Unmarshal([]byte(want), &data) if err != nil { - t.Fatalf("Unmarshal: %v", err) + t.Fatalf("Unmarshal error: %v", err) } if string([]byte(data.Id)) != raw { - t.Fatalf("Raw mismatch: have %#q want %#q", []byte(data.Id), raw) + t.Fatalf("Unmarshal:\n\tgot: %s\n\twant: %s", []byte(data.Id), raw) } - b, err := Marshal(&data) + got, err := Marshal(&data) if err != nil { - t.Fatalf("Marshal: %v", err) + t.Fatalf("Marshal error: %v", err) } - if string(b) != msg { - t.Fatalf("Marshal: have %#q want %#q", b, msg) + if string(got) != want { + t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } @@ -306,159 +334,156 @@ func TestNullRawMessage(t *testing.T) { IdPtr *RawMessage Y float32 } - const msg = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}` - err := Unmarshal([]byte(msg), &data) + const want = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}` + err := Unmarshal([]byte(want), &data) if err != nil { - t.Fatalf("Unmarshal: %v", err) + t.Fatalf("Unmarshal error: %v", err) } if want, got := "null", string(data.Id); want != got { - t.Fatalf("Raw mismatch: have %q, want %q", got, want) + t.Fatalf("Unmarshal:\n\tgot: %s\n\twant: %s", got, want) } if data.IdPtr != nil { - t.Fatalf("Raw pointer mismatch: have non-nil, want nil") + t.Fatalf("pointer mismatch: got non-nil, want nil") } - b, err := Marshal(&data) + got, err := Marshal(&data) if err != nil { - t.Fatalf("Marshal: %v", err) + t.Fatalf("Marshal error: %v", err) } - if string(b) != msg { - t.Fatalf("Marshal: have %#q want %#q", b, msg) + if string(got) != want { + t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) } } -var blockingTests = []string{ - `{"x": 1}`, - `[1, 2, 3]`, -} - func TestBlocking(t *testing.T) { - for _, enc := range blockingTests { - r, w := net.Pipe() - go w.Write([]byte(enc)) - var val any - - // If Decode reads beyond what w.Write writes above, - // it will block, and the test will deadlock. - if err := NewDecoder(r).Decode(&val); err != nil { - t.Errorf("decoding %s: %v", enc, err) - } - r.Close() - w.Close() + tests := []struct { + CaseName + in string + }{ + {Name(""), `{"x": 1}`}, + {Name(""), `[1, 2, 3]`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + r, w := net.Pipe() + go w.Write([]byte(tt.in)) + var val any + + // If Decode reads beyond what w.Write writes above, + // it will block, and the test will deadlock. + if err := NewDecoder(r).Decode(&val); err != nil { + t.Errorf("%s: NewDecoder(%s).Decode error: %v", tt.Where, tt.in, err) + } + r.Close() + w.Close() + }) } } -type tokenStreamCase struct { - json string - expTokens []any -} - type decodeThis struct { v any } -var tokenStreamCases = []tokenStreamCase{ - // streaming token cases - {json: `10`, expTokens: []any{float64(10)}}, - {json: ` [10] `, expTokens: []any{ - Delim('['), float64(10), Delim(']')}}, - {json: ` [false,10,"b"] `, expTokens: []any{ - Delim('['), false, float64(10), "b", Delim(']')}}, - {json: `{ "a": 1 }`, expTokens: []any{ - Delim('{'), "a", float64(1), Delim('}')}}, - {json: `{"a": 1, "b":"3"}`, expTokens: []any{ - Delim('{'), "a", float64(1), "b", "3", Delim('}')}}, - {json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ - Delim('['), - Delim('{'), "a", float64(1), Delim('}'), - Delim('{'), "a", float64(2), Delim('}'), - Delim(']')}}, - {json: `{"obj": {"a": 1}}`, expTokens: []any{ - Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'), - Delim('}')}}, - {json: `{"obj": [{"a": 1}]}`, expTokens: []any{ - Delim('{'), "obj", Delim('['), - Delim('{'), "a", float64(1), Delim('}'), - Delim(']'), Delim('}')}}, - - // streaming tokens with intermittent Decode() - {json: `{ "a": 1 }`, expTokens: []any{ - Delim('{'), "a", - decodeThis{float64(1)}, - Delim('}')}}, - {json: ` [ { "a" : 1 } ] `, expTokens: []any{ - Delim('['), - decodeThis{map[string]any{"a": float64(1)}}, - Delim(']')}}, - {json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ - Delim('['), - decodeThis{map[string]any{"a": float64(1)}}, - decodeThis{map[string]any{"a": float64(2)}}, - Delim(']')}}, - {json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{ - Delim('{'), "obj", Delim('['), - decodeThis{map[string]any{"a": float64(1)}}, - Delim(']'), Delim('}')}}, - - {json: `{"obj": {"a": 1}}`, expTokens: []any{ - Delim('{'), "obj", - decodeThis{map[string]any{"a": float64(1)}}, - Delim('}')}}, - {json: `{"obj": [{"a": 1}]}`, expTokens: []any{ - Delim('{'), "obj", - decodeThis{[]any{ - map[string]any{"a": float64(1)}, - }}, - Delim('}')}}, - {json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{ - Delim('['), - decodeThis{map[string]any{"a": float64(1)}}, - decodeThis{&SyntaxError{"expected comma after array element", 11}}, - }}, - {json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{ - Delim('{'), strings.Repeat("a", 513), - decodeThis{&SyntaxError{"expected colon after object key", 518}}, - }}, - {json: `{ "\a" }`, expTokens: []any{ - Delim('{'), - &SyntaxError{"invalid character 'a' in string escape code", 3}, - }}, - {json: ` \a`, expTokens: []any{ - &SyntaxError{"invalid character '\\\\' looking for beginning of value", 1}, - }}, -} - func TestDecodeInStream(t *testing.T) { - for ci, tcase := range tokenStreamCases { - - dec := NewDecoder(strings.NewReader(tcase.json)) - for i, etk := range tcase.expTokens { - - var tk any - var err error - - if dt, ok := etk.(decodeThis); ok { - etk = dt.v - err = dec.Decode(&tk) - } else { - tk, err = dec.Token() - } - if experr, ok := etk.(error); ok { - if err == nil || !reflect.DeepEqual(err, experr) { - t.Errorf("case %v: Expected error %#v in %q, but was %#v", ci, experr, tcase.json, err) + tests := []struct { + CaseName + json string + expTokens []any + }{ + // streaming token cases + {CaseName: Name(""), json: `10`, expTokens: []any{float64(10)}}, + {CaseName: Name(""), json: ` [10] `, expTokens: []any{ + Delim('['), float64(10), Delim(']')}}, + {CaseName: Name(""), json: ` [false,10,"b"] `, expTokens: []any{ + Delim('['), false, float64(10), "b", Delim(']')}}, + {CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{ + Delim('{'), "a", float64(1), Delim('}')}}, + {CaseName: Name(""), json: `{"a": 1, "b":"3"}`, expTokens: []any{ + Delim('{'), "a", float64(1), "b", "3", Delim('}')}}, + {CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ + Delim('['), + Delim('{'), "a", float64(1), Delim('}'), + Delim('{'), "a", float64(2), Delim('}'), + Delim(']')}}, + {CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{ + Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'), + Delim('}')}}, + {CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{ + Delim('{'), "obj", Delim('['), + Delim('{'), "a", float64(1), Delim('}'), + Delim(']'), Delim('}')}}, + + // streaming tokens with intermittent Decode() + {CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{ + Delim('{'), "a", + decodeThis{float64(1)}, + Delim('}')}}, + {CaseName: Name(""), json: ` [ { "a" : 1 } ] `, expTokens: []any{ + Delim('['), + decodeThis{map[string]any{"a": float64(1)}}, + Delim(']')}}, + {CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ + Delim('['), + decodeThis{map[string]any{"a": float64(1)}}, + decodeThis{map[string]any{"a": float64(2)}}, + Delim(']')}}, + {CaseName: Name(""), json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{ + Delim('{'), "obj", Delim('['), + decodeThis{map[string]any{"a": float64(1)}}, + Delim(']'), Delim('}')}}, + + {CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{ + Delim('{'), "obj", + decodeThis{map[string]any{"a": float64(1)}}, + Delim('}')}}, + {CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{ + Delim('{'), "obj", + decodeThis{[]any{ + map[string]any{"a": float64(1)}, + }}, + Delim('}')}}, + {CaseName: Name(""), json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{ + Delim('['), + decodeThis{map[string]any{"a": float64(1)}}, + decodeThis{&SyntaxError{"expected comma after array element", 11}}, + }}, + {CaseName: Name(""), json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{ + Delim('{'), strings.Repeat("a", 513), + decodeThis{&SyntaxError{"expected colon after object key", 518}}, + }}, + {CaseName: Name(""), json: `{ "\a" }`, expTokens: []any{ + Delim('{'), + &SyntaxError{"invalid character 'a' in string escape code", 3}, + }}, + {CaseName: Name(""), json: ` \a`, expTokens: []any{ + &SyntaxError{"invalid character '\\\\' looking for beginning of value", 1}, + }}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + dec := NewDecoder(strings.NewReader(tt.json)) + for i, want := range tt.expTokens { + var got any + var err error + + if dt, ok := want.(decodeThis); ok { + want = dt.v + err = dec.Decode(&got) + } else { + got, err = dec.Token() + } + if errWant, ok := want.(error); ok { + if err == nil || !reflect.DeepEqual(err, errWant) { + t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: %v", tt.Where, tt.json, err, errWant) + } + break + } else if err != nil { + t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: nil", tt.Where, tt.json, err) + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("%s: token %d:\n\tinput: %s\n\tgot: %T(%v)\n\twant: %T(%v)", tt.Where, i, tt.json, got, got, want, want) } - break - } else if err == io.EOF { - t.Errorf("case %v: Unexpected EOF in %q", ci, tcase.json) - break - } else if err != nil { - t.Errorf("case %v: Unexpected error '%#v' in %q", ci, err, tcase.json) - break - } - if !reflect.DeepEqual(tk, etk) { - t.Errorf(`case %v: %q @ %v expected %T(%v) was %T(%v)`, ci, tcase.json, i, etk, etk, tk, tk) - break } - } + }) } } @@ -472,7 +497,7 @@ func TestHTTPDecoding(t *testing.T) { defer ts.Close() res, err := http.Get(ts.URL) if err != nil { - log.Fatalf("GET failed: %v", err) + log.Fatalf("http.Get error: %v", err) } defer res.Body.Close() @@ -483,15 +508,15 @@ func TestHTTPDecoding(t *testing.T) { d := NewDecoder(res.Body) err = d.Decode(&foo) if err != nil { - t.Fatalf("Decode: %v", err) + t.Fatalf("Decode error: %v", err) } if foo.Foo != "bar" { - t.Errorf("decoded %q; want \"bar\"", foo.Foo) + t.Errorf(`Decode: got %q, want "bar"`, foo.Foo) } // make sure we get the EOF the second time err = d.Decode(&foo) if err != io.EOF { - t.Errorf("err = %v; want io.EOF", err) + t.Errorf("Decode error:\n\tgot: %v\n\twant: io.EOF", err) } } diff --git a/src/encoding/json/tagkey_test.go b/src/encoding/json/tagkey_test.go index 6330efd3c2..d432cd7d8b 100644 --- a/src/encoding/json/tagkey_test.go +++ b/src/encoding/json/tagkey_test.go @@ -72,49 +72,50 @@ type unicodeTag struct { W string `json:"Ελλάδα"` } -var structTagObjectKeyTests = []struct { - raw any - value string - key string -}{ - {basicLatin2xTag{"2x"}, "2x", "$%-/"}, - {basicLatin3xTag{"3x"}, "3x", "0123456789"}, - {basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"}, - {basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"}, - {basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"}, - {basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"}, - {miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"}, - {dashTag{"foo"}, "foo", "-"}, - {emptyTag{"Pour Moi"}, "Pour Moi", "W"}, - {misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"}, - {badFormatTag{"Orfevre"}, "Orfevre", "Y"}, - {badCodeTag{"Reliable Man"}, "Reliable Man", "Z"}, - {percentSlashTag{"brut"}, "brut", "text/html%"}, - {punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:;<=>?@[]^_{|}~ "}, - {spaceTag{"Perreddu"}, "Perreddu", "With space"}, - {unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"}, -} - func TestStructTagObjectKey(t *testing.T) { - for _, tt := range structTagObjectKeyTests { - b, err := Marshal(tt.raw) - if err != nil { - t.Fatalf("Marshal(%#q) failed: %v", tt.raw, err) - } - var f any - err = Unmarshal(b, &f) - if err != nil { - t.Fatalf("Unmarshal(%#q) failed: %v", b, err) - } - for i, v := range f.(map[string]any) { - switch i { - case tt.key: - if s, ok := v.(string); !ok || s != tt.value { - t.Fatalf("Unexpected value: %#q, want %v", s, tt.value) + tests := []struct { + CaseName + raw any + value string + key string + }{ + {Name(""), basicLatin2xTag{"2x"}, "2x", "$%-/"}, + {Name(""), basicLatin3xTag{"3x"}, "3x", "0123456789"}, + {Name(""), basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"}, + {Name(""), basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"}, + {Name(""), basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"}, + {Name(""), basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"}, + {Name(""), miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"}, + {Name(""), dashTag{"foo"}, "foo", "-"}, + {Name(""), emptyTag{"Pour Moi"}, "Pour Moi", "W"}, + {Name(""), misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"}, + {Name(""), badFormatTag{"Orfevre"}, "Orfevre", "Y"}, + {Name(""), badCodeTag{"Reliable Man"}, "Reliable Man", "Z"}, + {Name(""), percentSlashTag{"brut"}, "brut", "text/html%"}, + {Name(""), punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:;<=>?@[]^_{|}~ "}, + {Name(""), spaceTag{"Perreddu"}, "Perreddu", "With space"}, + {Name(""), unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + b, err := Marshal(tt.raw) + if err != nil { + t.Fatalf("%s: Marshal error: %v", tt.Where, err) + } + var f any + err = Unmarshal(b, &f) + if err != nil { + t.Fatalf("%s: Unmarshal error: %v", tt.Where, err) + } + for k, v := range f.(map[string]any) { + if k == tt.key { + if s, ok := v.(string); !ok || s != tt.value { + t.Fatalf("%s: Unmarshal(%#q) value:\n\tgot: %q\n\twant: %q", tt.Where, b, s, tt.value) + } + } else { + t.Fatalf("%s: Unmarshal(%#q): unexpected key: %q", tt.Where, b, k) } - default: - t.Fatalf("Unexpected key: %#q, from %#q", i, b) } - } + }) } } diff --git a/src/encoding/json/tags_test.go b/src/encoding/json/tags_test.go index 8ba8ddd5f8..1d2323dcee 100644 --- a/src/encoding/json/tags_test.go +++ b/src/encoding/json/tags_test.go @@ -22,7 +22,7 @@ func TestTagParsing(t *testing.T) { {"bar", false}, } { if opts.Contains(tt.opt) != tt.want { - t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) + t.Errorf("Contains(%q) = %v, want %v", tt.opt, !tt.want, tt.want) } } } -- 2.50.0