diff --git a/src/encoding/json/bench_test.go b/src/encoding/json/bench_test.go index 0f080acdbf2..f7bcf8073c0 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 5c34139d92f..a10c1e1ebb9 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, + CaseName: Name(""), + 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: `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 { - 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 + if tt.ptr == nil { + return } - if tt.golden && !bytes.Equal(enc, in) { - t.Errorf("#%d: remarshal mismatch:\nhave: %s\nwant: %s", i, enc, in) + + 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) } - vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) - dec = NewDecoder(bytes.NewReader(enc)) + 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) + } + + 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}, - }, + }{{ + 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) + } + }) } - - 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) - } - } -} - -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 79723488012..9c370280370 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}`}, + {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 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 - } + 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"]`}, + {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 _, 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) - } + 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}, + {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 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) + 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 3474b3e4810..068439dcaca 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 stripWhitespace(s string) string { + return strings.Map(func(r rune) rune { + if r == ' ' || r == '\n' || r == '\r' || r == '\t' { + return -1 + } + return r + }, s) } 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) - } + 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) + } + }) } } -// Tests of simple examples. - -type example struct { - compact string - indent string -} - -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 -} - -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 97f9fbd6d8d..32ede8cc7e6 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 -type tokenStreamCase struct { - json string - expTokens []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 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 { + 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('}')}}, - dec := NewDecoder(strings.NewReader(tcase.json)) - for i, etk := range tcase.expTokens { + // 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('}')}}, - var tk any - var err error + {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 := 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) + 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 6330efd3c21..d432cd7d8bd 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) - } - default: - t.Fatalf("Unexpected key: %#q, from %#q", i, b) + 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) + } + } + }) } } diff --git a/src/encoding/json/tags_test.go b/src/encoding/json/tags_test.go index 8ba8ddd5f80..1d2323dcee6 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) } } }