encoding/json: modernize tests

There are no changes to what is being tested.
No test cases were removed or added.

Changes made:
* Use a local implementation of test case position marking. See #52751.
* Use consistent names for all test tables and variables.
* Generally speaking, follow modern Go style guide for tests.
* Move global tables local to the test function if possible.
* Make every table entry run in a distinct testing.T.Run.

The purpose of this change is to make it easier to perform
v1-to-v2 development where we want v2 to support close to
bug-for-bug compatibility when running in v1 mode.

Annotating each test case with the location of the test data
makes it easier to jump directly to the test data itself
and understand why this particular case is failing.

Having every test case run in its own t.Run makes it easier
to isolate a particular failing test and work on fixing the code
until that test case starts to pass again.

Unfortunately, many tests are annotated with an empty name.
An empty name is better than nothing, since the testing framework
auto assigns a numeric ID for duplicate names.
It is not worth the trouble to give descriptive names to each
of the thousands of test cases.

Change-Id: I43905f35249b3d77dfca234b9c7808d40e225de8
Reviewed-on: https://go-review.googlesource.com/c/go/+/522880
Auto-Submit: Joseph Tsai <joetsai@digital-static.net>
Run-TryBot: Joseph Tsai <joetsai@digital-static.net>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Reviewed-by: Bryan Mills <bcmills@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Joe Tsai 2023-08-24 12:49:10 -07:00 committed by Damien Neil
parent 882a356ec0
commit 3b4d428ca0
7 changed files with 1521 additions and 1438 deletions

View file

@ -93,7 +93,7 @@ func BenchmarkCodeEncoder(b *testing.B) {
enc := NewEncoder(io.Discard) enc := NewEncoder(io.Discard)
for pb.Next() { for pb.Next() {
if err := enc.Encode(&codeStruct); err != nil { 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) enc := NewEncoder(io.Discard)
for pb.Next() { for pb.Next() {
if err := enc.Encode(&codeStruct); err != nil { if err := enc.Encode(&codeStruct); err != nil {
b.Fatal("Encode:", err) b.Fatalf("Encode error: %v", err)
} }
if _, err := Marshal(dummy); err == nil { 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) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
if _, err := Marshal(&codeStruct); err != nil { 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) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
if _, err := Marshal(&codeStruct); err != nil { if _, err := Marshal(&codeStruct); err != nil {
b.Fatal("Marshal:", err) b.Fatalf("Marshal error: %v", err)
} }
if _, err := Marshal(dummy); err == nil { 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) { return func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if _, err := Marshal(v); err != nil { 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) { return func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if _, err := Marshal(v); err != nil { if _, err := Marshal(v); err != nil {
b.Fatal("Marshal:", err) b.Fatalf("Marshal error: %v", err)
} }
if _, err := Marshal(dummy); err == nil { 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')
buf.WriteByte('\n') buf.WriteByte('\n')
if err := dec.Decode(&r); err != nil { 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() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if err := dec.Decode(&out); err != nil { if err := dec.Decode(&out); err != nil {
b.Fatal("Decode:", err) b.Fatalf("Decode error: %v", err)
} }
r.Seek(0, 0) r.Seek(0, 0)
} }
@ -311,7 +311,7 @@ func BenchmarkDecoderStream(b *testing.B) {
buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n") buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n")
var x any var x any
if err := dec.Decode(&x); err != nil { 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" ones := strings.Repeat(" 1\n", 300000) + "\n\n\n"
b.StartTimer() b.StartTimer()
@ -320,8 +320,11 @@ func BenchmarkDecoderStream(b *testing.B) {
buf.WriteString(ones) buf.WriteString(ones)
} }
x = nil x = nil
if err := dec.Decode(&x); err != nil || x != 1.0 { switch err := dec.Decode(&x); {
b.Fatalf("Decode: %v after %d", err, i) 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() { for pb.Next() {
var r codeResponse var r codeResponse
if err := Unmarshal(codeJSON, &r); err != nil { 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 var r codeResponse
for pb.Next() { for pb.Next() {
if err := Unmarshal(codeJSON, &r); err != nil { 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 var s string
for pb.Next() { for pb.Next() {
if err := Unmarshal(data, &s); err != nil { 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 var f float64
for pb.Next() { for pb.Next() {
if err := Unmarshal(data, &f); err != nil { 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 var x int64
for pb.Next() { for pb.Next() {
if err := Unmarshal(data, &x); err != nil { 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) x := make(map[string]string, 3)
for pb.Next() { for pb.Next() {
if err := Unmarshal(data, &x); err != nil { 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{} var s struct{}
for pb.Next() { for pb.Next() {
if err := Unmarshal(j, &s); err != nil { 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) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
if _, err := Marshal(&j); err != nil { 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{} var s struct{}
for pb.Next() { for pb.Next() {
if err := Unmarshal(j, &s); err != nil { 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() { for pb.Next() {
if err := enc.Encode(&m); err != nil { 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) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
if err := NewEncoder(io.Discard).Encode(v); err != nil { if err := NewEncoder(io.Discard).Encode(v); err != nil {
b.Fatal(err) b.Fatalf("Encode error: %v", err)
} }
} }
}) })

File diff suppressed because it is too large Load diff

View file

@ -44,7 +44,8 @@ type Optionals struct {
Sto struct{} `json:"sto,omitempty"` Sto struct{} `json:"sto,omitempty"`
} }
var optionalsExpected = `{ func TestOmitEmpty(t *testing.T) {
var want = `{
"sr": "", "sr": "",
"omitempty": 0, "omitempty": 0,
"slr": null, "slr": null,
@ -55,8 +56,6 @@ var optionalsExpected = `{
"str": {}, "str": {},
"sto": {} "sto": {}
}` }`
func TestOmitEmpty(t *testing.T) {
var o Optionals var o Optionals
o.Sw = "something" o.Sw = "something"
o.Mr = map[string]any{} o.Mr = map[string]any{}
@ -64,10 +63,10 @@ func TestOmitEmpty(t *testing.T) {
got, err := MarshalIndent(&o, "", " ") got, err := MarshalIndent(&o, "", " ")
if err != nil { if err != nil {
t.Fatal(err) t.Fatalf("MarshalIndent error: %v", err)
} }
if got := string(got); got != optionalsExpected { if got := string(got); got != want {
t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected) t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want))
} }
} }
@ -81,12 +80,11 @@ type StringTag struct {
func TestRoundtripStringTag(t *testing.T) { func TestRoundtripStringTag(t *testing.T) {
tests := []struct { tests := []struct {
name string CaseName
in StringTag in StringTag
want string // empty to just test that we roundtrip want string // empty to just test that we roundtrip
}{ }{{
{ CaseName: Name("AllTypes"),
name: "AllTypes",
in: StringTag{ in: StringTag{
BoolStr: true, BoolStr: true,
IntStr: 42, IntStr: 42,
@ -100,11 +98,10 @@ func TestRoundtripStringTag(t *testing.T) {
"UintptrStr": "44", "UintptrStr": "44",
"StrStr": "\"xzbit\"", "StrStr": "\"xzbit\"",
"NumberStr": "46" "NumberStr": "46"
}`, }`,
}, }, {
{
// See golang.org/issues/38173. // See golang.org/issues/38173.
name: "StringDoubleEscapes", CaseName: Name("StringDoubleEscapes"),
in: StringTag{ in: StringTag{
StrStr: "\b\f\n\r\t\"\\", StrStr: "\b\f\n\r\t\"\\",
NumberStr: "0", // just to satisfy the roundtrip NumberStr: "0", // just to satisfy the roundtrip
@ -115,28 +112,25 @@ func TestRoundtripStringTag(t *testing.T) {
"UintptrStr": "0", "UintptrStr": "0",
"StrStr": "\"\\b\\f\\n\\r\\t\\\"\\\\\"", "StrStr": "\"\\b\\f\\n\\r\\t\\\"\\\\\"",
"NumberStr": "0" "NumberStr": "0"
}`, }`,
}, }}
} for _, tt := range tests {
for _, test := range tests { t.Run(tt.Name, func(t *testing.T) {
t.Run(test.name, func(t *testing.T) { got, err := MarshalIndent(&tt.in, "", "\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")
if err != nil { if err != nil {
t.Fatal(err) t.Fatalf("%s: MarshalIndent error: %v", tt.Where, err)
} }
if got := string(got); got != test.want { if got := string(got); got != tt.want {
t.Fatalf(" got: %s\nwant: %s\n", got, test.want) t.Fatalf("%s: MarshalIndent:\n\tgot: %s\n\twant: %s", tt.Where, stripWhitespace(got), stripWhitespace(tt.want))
} }
// Verify that it round-trips. // Verify that it round-trips.
var s2 StringTag var s2 StringTag
if err := Unmarshal(got, &s2); err != nil { 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) { if !reflect.DeepEqual(s2, tt.in) {
t.Fatalf("decode didn't match.\nsource: %#v\nEncoded as:\n%s\ndecode: %#v", test.in, string(got), s2) 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) { func TestEncodeRenamedByteSlice(t *testing.T) {
s := renamedByteSlice("abc") s := renamedByteSlice("abc")
result, err := Marshal(s) got, err := Marshal(s)
if err != nil { if err != nil {
t.Fatal(err) t.Fatalf("Marshal error: %v", err)
} }
expect := `"YWJj"` want := `"YWJj"`
if string(result) != expect { if string(got) != want {
t.Errorf(" got %s want %s", result, expect) t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want)
} }
r := renamedRenamedByteSlice("abc") r := renamedRenamedByteSlice("abc")
result, err = Marshal(r) got, err = Marshal(r)
if err != nil { if err != nil {
t.Fatal(err) t.Fatalf("Marshal error: %v", err)
} }
if string(result) != expect { if string(got) != want {
t.Errorf(" got %s want %s", result, expect) t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want)
} }
} }
@ -212,36 +206,40 @@ func init() {
func TestSamePointerNoCycle(t *testing.T) { func TestSamePointerNoCycle(t *testing.T) {
if _, err := Marshal(samePointerNoCycle); err != nil { if _, err := Marshal(samePointerNoCycle); err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("Marshal error: %v", err)
} }
} }
func TestSliceNoCycle(t *testing.T) { func TestSliceNoCycle(t *testing.T) {
if _, err := Marshal(sliceNoCycle); err != nil { 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) { func TestUnsupportedValues(t *testing.T) {
for _, v := range unsupportedValues { tests := []struct {
if _, err := Marshal(v); err != nil { 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 { if _, ok := err.(*UnsupportedValueError); !ok {
t.Errorf("for %v, got %T want UnsupportedValueError", v, err) t.Errorf("%s: Marshal error:\n\tgot: %T\n\twant: %T", tt.Where, err, new(UnsupportedValueError))
} }
} else { } else {
t.Errorf("for %v, expected error", v) t.Errorf("%s: Marshal error: got nil, want non-nil", tt.Where)
} }
})
} }
} }
@ -253,11 +251,11 @@ func TestMarshalTextFloatMap(t *testing.T) {
} }
got, err := Marshal(m) got, err := Marshal(m)
if err != nil { if err != nil {
t.Errorf("Marshal() error: %v", err) t.Errorf("Marshal error: %v", err)
} }
want := `{"TF:NaN":"1","TF:NaN":"1"}` want := `{"TF:NaN":"1","TF:NaN":"1"}`
if string(got) != want { 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\""}` const want = `{"R0":"ref","R1":"ref","R2":"\"ref\"","R3":"\"ref\"","V0":"val","V1":"val","V2":"\"val\"","V3":"\"val\""}`
b, err := Marshal(&s) b, err := Marshal(&s)
if err != nil { if err != nil {
t.Fatalf("Marshal: %v", err) t.Fatalf("Marshal error: %v", err)
} }
if got := string(b); got != want { 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"` want := `"\u003c\u0026\u003e"`
b, err := Marshal(c) b, err := Marshal(c)
if err != nil { if err != nil {
t.Fatalf("Marshal(c): %v", err) t.Fatalf("Marshal error: %v", err)
} }
if got := string(b); got != want { 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 var ct CText
want = `"\"\u003c\u0026\u003e\""` want = `"\"\u003c\u0026\u003e\""`
b, err = Marshal(ct) b, err = Marshal(ct)
if err != nil { if err != nil {
t.Fatalf("Marshal(ct): %v", err) t.Fatalf("Marshal error: %v", err)
} }
if got := string(b); got != want { 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) { func TestAnonymousFields(t *testing.T) {
tests := []struct { tests := []struct {
label string // Test name CaseName
makeInput func() any // Function to create input value makeInput func() any // Function to create input value
want string // Expected JSON output want string // Expected JSON output
}{{ }{{
// Both S1 and S2 have a field named X. From the perspective of S, // Both S1 and S2 have a field named X. From the perspective of S,
// it is ambiguous which one X refers to. // it is ambiguous which one X refers to.
// This should not serialize either field. // This should not serialize either field.
label: "AmbiguousField", CaseName: Name("AmbiguousField"),
makeInput: func() any { makeInput: func() any {
type ( type (
S1 struct{ x, X int } S1 struct{ x, X int }
@ -388,7 +386,7 @@ func TestAnonymousFields(t *testing.T) {
}, },
want: `{}`, want: `{}`,
}, { }, {
label: "DominantField", CaseName: Name("DominantField"),
// Both S1 and S2 have a field named X, but since S has an X field as // 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. // well, it takes precedence over S1.X and S2.X.
makeInput: func() any { makeInput: func() any {
@ -406,7 +404,7 @@ func TestAnonymousFields(t *testing.T) {
want: `{"X":6}`, want: `{"X":6}`,
}, { }, {
// Unexported embedded field of non-struct type should not be serialized. // Unexported embedded field of non-struct type should not be serialized.
label: "UnexportedEmbeddedInt", CaseName: Name("UnexportedEmbeddedInt"),
makeInput: func() any { makeInput: func() any {
type ( type (
myInt int myInt int
@ -417,7 +415,7 @@ func TestAnonymousFields(t *testing.T) {
want: `{}`, want: `{}`,
}, { }, {
// Exported embedded field of non-struct type should be serialized. // Exported embedded field of non-struct type should be serialized.
label: "ExportedEmbeddedInt", CaseName: Name("ExportedEmbeddedInt"),
makeInput: func() any { makeInput: func() any {
type ( type (
MyInt int MyInt int
@ -429,7 +427,7 @@ func TestAnonymousFields(t *testing.T) {
}, { }, {
// Unexported embedded field of pointer to non-struct type // Unexported embedded field of pointer to non-struct type
// should not be serialized. // should not be serialized.
label: "UnexportedEmbeddedIntPointer", CaseName: Name("UnexportedEmbeddedIntPointer"),
makeInput: func() any { makeInput: func() any {
type ( type (
myInt int myInt int
@ -443,7 +441,7 @@ func TestAnonymousFields(t *testing.T) {
}, { }, {
// Exported embedded field of pointer to non-struct type // Exported embedded field of pointer to non-struct type
// should be serialized. // should be serialized.
label: "ExportedEmbeddedIntPointer", CaseName: Name("ExportedEmbeddedIntPointer"),
makeInput: func() any { makeInput: func() any {
type ( type (
MyInt int MyInt int
@ -458,7 +456,7 @@ func TestAnonymousFields(t *testing.T) {
// Exported fields of embedded structs should have their // Exported fields of embedded structs should have their
// exported fields be serialized regardless of whether the struct types // exported fields be serialized regardless of whether the struct types
// themselves are exported. // themselves are exported.
label: "EmbeddedStruct", CaseName: Name("EmbeddedStruct"),
makeInput: func() any { makeInput: func() any {
type ( type (
s1 struct{ x, X int } 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 of pointers to embedded structs should have their
// exported fields be serialized regardless of whether the struct types // exported fields be serialized regardless of whether the struct types
// themselves are exported. // themselves are exported.
label: "EmbeddedStructPointer", CaseName: Name("EmbeddedStructPointer"),
makeInput: func() any { makeInput: func() any {
type ( type (
s1 struct{ x, X int } s1 struct{ x, X int }
@ -491,7 +489,7 @@ func TestAnonymousFields(t *testing.T) {
}, { }, {
// Exported fields on embedded unexported structs at multiple levels // Exported fields on embedded unexported structs at multiple levels
// of nesting should still be serialized. // of nesting should still be serialized.
label: "NestedStructAndInts", CaseName: Name("NestedStructAndInts"),
makeInput: func() any { makeInput: func() any {
type ( type (
MyInt1 int MyInt1 int
@ -518,7 +516,7 @@ func TestAnonymousFields(t *testing.T) {
// If an anonymous struct pointer field is nil, we should ignore // If an anonymous struct pointer field is nil, we should ignore
// the embedded fields behind it. Not properly doing so may // the embedded fields behind it. Not properly doing so may
// result in the wrong output or reflect panics. // result in the wrong output or reflect panics.
label: "EmbeddedFieldBehindNilPointer", CaseName: Name("EmbeddedFieldBehindNilPointer"),
makeInput: func() any { makeInput: func() any {
type ( type (
S2 struct{ Field string } S2 struct{ Field string }
@ -530,13 +528,13 @@ func TestAnonymousFields(t *testing.T) {
}} }}
for _, tt := range tests { 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()) b, err := Marshal(tt.makeInput())
if err != nil { 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 { 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. // See golang.org/issue/16042 and golang.org/issue/34235.
func TestNilMarshal(t *testing.T) { func TestNilMarshal(t *testing.T) {
testCases := []struct { tests := []struct {
v any CaseName
in any
want string want string
}{ }{
{v: nil, want: `null`}, {Name(""), nil, `null`},
{v: new(float64), want: `0`}, {Name(""), new(float64), `0`},
{v: []any(nil), want: `null`}, {Name(""), []any(nil), `null`},
{v: []string(nil), want: `null`}, {Name(""), []string(nil), `null`},
{v: map[string]string(nil), want: `null`}, {Name(""), map[string]string(nil), `null`},
{v: []byte(nil), want: `null`}, {Name(""), []byte(nil), `null`},
{v: struct{ M string }{"gopher"}, want: `{"M":"gopher"}`}, {Name(""), struct{ M string }{"gopher"}, `{"M":"gopher"}`},
{v: struct{ M Marshaler }{}, want: `{"M":null}`}, {Name(""), struct{ M Marshaler }{}, `{"M":null}`},
{v: struct{ M Marshaler }{(*nilJSONMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, {Name(""), struct{ M Marshaler }{(*nilJSONMarshaler)(nil)}, `{"M":"0zenil0"}`},
{v: struct{ M any }{(*nilJSONMarshaler)(nil)}, want: `{"M":null}`}, {Name(""), struct{ M any }{(*nilJSONMarshaler)(nil)}, `{"M":null}`},
{v: struct{ M encoding.TextMarshaler }{}, want: `{"M":null}`}, {Name(""), struct{ M encoding.TextMarshaler }{}, `{"M":null}`},
{v: struct{ M encoding.TextMarshaler }{(*nilTextMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, {Name(""), struct{ M encoding.TextMarshaler }{(*nilTextMarshaler)(nil)}, `{"M":"0zenil0"}`},
{v: struct{ M any }{(*nilTextMarshaler)(nil)}, want: `{"M":null}`}, {Name(""), struct{ M any }{(*nilTextMarshaler)(nil)}, `{"M":null}`},
} }
for _, tt := range tests {
for _, tt := range testCases { t.Run(tt.Name, func(t *testing.T) {
out, err := Marshal(tt.v) switch got, err := Marshal(tt.in); {
if err != nil || string(out) != tt.want { case err != nil:
t.Errorf("Marshal(%#v) = %#q, %#v, want %#q, nil", tt.v, out, err, tt.want) t.Fatalf("%s: Marshal error: %v", tt.Where, err)
continue 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) b, err := Marshal(v)
if err != nil { if err != nil {
t.Fatal("Marshal:", err) t.Fatal("Marshal error:", err)
} }
want := `{"S":"B"}` want := `{"S":"B"}`
got := string(b) got := string(b)
if got != want { 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. // Now check that the duplicate field, S, does not appear.
x := BugX{ x := BugX{
@ -637,12 +638,12 @@ func TestEmbeddedBug(t *testing.T) {
} }
b, err = Marshal(x) b, err = Marshal(x)
if err != nil { if err != nil {
t.Fatal("Marshal:", err) t.Fatal("Marshal error:", err)
} }
want = `{"A":23}` want = `{"A":23}`
got = string(b) got = string(b)
if got != want { 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) b, err := Marshal(v)
if err != nil { if err != nil {
t.Fatal("Marshal:", err) t.Fatal("Marshal error:", err)
} }
want := `{"S":"BugD"}` want := `{"S":"BugD"}`
got := string(b) got := string(b)
if got != want { 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) b, err := Marshal(v)
if err != nil { if err != nil {
t.Fatal("Marshal:", err) t.Fatal("Marshal error:", err)
} }
want := `{}` want := `{}`
got := string(b) got := string(b)
if got != want { 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`)} x := Foo{Number(`invalid`)}
b, err := Marshal(&x) if _, err := Marshal(&x); err == nil {
if err == nil { t.Fatalf("Marshal error: got nil, want non-nil")
t.Errorf("Marshal(&x) = %#q; want error", b)
} }
} }
@ -724,26 +724,26 @@ func TestMarshalErrorAndReuseEncodeState(t *testing.T) {
} }
dummy := Dummy{Name: "Dummy"} dummy := Dummy{Name: "Dummy"}
dummy.Next = &dummy dummy.Next = &dummy
if b, err := Marshal(dummy); err == nil { if _, err := Marshal(dummy); err == nil {
t.Errorf("Marshal(dummy) = %#q; want error", b) t.Errorf("Marshal error: got nil, want non-nil")
} }
type Data struct { type Data struct {
A string A string
I int I int
} }
data := Data{A: "a", I: 1} want := Data{A: "a", I: 1}
b, err := Marshal(data) b, err := Marshal(want)
if err != nil { if err != nil {
t.Errorf("Marshal(%v) = %v", data, err) t.Errorf("Marshal error: %v", err)
} }
var data2 Data var got Data
if err := Unmarshal(b, &data2); err != nil { if err := Unmarshal(b, &got); err != nil {
t.Errorf("Unmarshal(%v) = %v", data2, err) t.Errorf("Unmarshal error: %v", err)
} }
if data2 != data { if got != want {
t.Errorf("expect: %v, but get: %v", data, data2) 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"}`)) want.Write([]byte(`{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`))
HTMLEscape(&b, []byte(m)) HTMLEscape(&b, []byte(m))
if !bytes.Equal(b.Bytes(), want.Bytes()) { 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 var n int64 = 42
b, err := Marshal(stringPointer{N: &n}) b, err := Marshal(stringPointer{N: &n})
if err != nil { if err != nil {
t.Fatalf("Marshal: %v", err) t.Fatalf("Marshal error: %v", err)
} }
if got, want := string(b), `{"n":"42"}`; got != want { 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 var back stringPointer
err = Unmarshal(b, &back) switch err = Unmarshal(b, &back); {
if err != nil { case err != nil:
t.Fatalf("Unmarshal: %v", err) t.Fatalf("Unmarshal error: %v", err)
} case back.N == nil:
if back.N == nil { t.Fatalf("Unmarshal: back.N = nil, want non-nil")
t.Fatalf("Unmarshaled nil N field") case *back.N != 42:
} t.Fatalf("Unmarshal: *back.N = %d, want 42", *back.N)
if *back.N != 42 {
t.Fatalf("*N = %d; want 42", *back.N)
} }
} }
@ -825,7 +823,7 @@ func TestEncodeString(t *testing.T) {
for _, tt := range encodeStringTests { for _, tt := range encodeStringTests {
b, err := Marshal(tt.in) b, err := Marshal(tt.in)
if err != nil { if err != nil {
t.Errorf("Marshal(%q): %v", tt.in, err) t.Errorf("Marshal(%q) error: %v", tt.in, err)
continue continue
} }
out := string(b) out := string(b)
@ -863,65 +861,67 @@ func (f textfloat) MarshalText() ([]byte, error) { return tenc(`TF:%0.2f`, f) }
// Issue 13783 // Issue 13783
func TestEncodeBytekind(t *testing.T) { func TestEncodeBytekind(t *testing.T) {
testdata := []struct { tests := []struct {
data any CaseName
in any
want string want string
}{ }{
{byte(7), "7"}, {Name(""), byte(7), "7"},
{jsonbyte(7), `{"JB":7}`}, {Name(""), jsonbyte(7), `{"JB":7}`},
{textbyte(4), `"TB:4"`}, {Name(""), textbyte(4), `"TB:4"`},
{jsonint(5), `{"JI":5}`}, {Name(""), jsonint(5), `{"JI":5}`},
{textint(1), `"TI:1"`}, {Name(""), textint(1), `"TI:1"`},
{[]byte{0, 1}, `"AAE="`}, {Name(""), []byte{0, 1}, `"AAE="`},
{[]jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`}, {Name(""), []jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`},
{[][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`}, {Name(""), [][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`},
{[]textbyte{2, 3}, `["TB:2","TB:3"]`}, {Name(""), []textbyte{2, 3}, `["TB:2","TB:3"]`},
{[]jsonint{5, 4}, `[{"JI":5},{"JI":4}]`}, {Name(""), []jsonint{5, 4}, `[{"JI":5},{"JI":4}]`},
{[]textint{9, 3}, `["TI:9","TI:3"]`}, {Name(""), []textint{9, 3}, `["TI:9","TI:3"]`},
{[]int{9, 3}, `[9,3]`}, {Name(""), []int{9, 3}, `[9,3]`},
{[]textfloat{12, 3}, `["TF:12.00","TF:3.00"]`}, {Name(""), []textfloat{12, 3}, `["TF:12.00","TF:3.00"]`},
} }
for _, d := range testdata { for _, tt := range tests {
js, err := Marshal(d.data) t.Run(tt.Name, func(t *testing.T) {
b, err := Marshal(tt.in)
if err != nil { if err != nil {
t.Error(err) t.Errorf("%s: Marshal error: %v", tt.Where, err)
continue
} }
got, want := string(js), d.want got, want := string(b), tt.want
if got != want { if got != want {
t.Errorf("got %s, want %s", got, want) t.Errorf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, got, want)
} }
})
} }
} }
func TestTextMarshalerMapKeysAreSorted(t *testing.T) { func TestTextMarshalerMapKeysAreSorted(t *testing.T) {
b, err := Marshal(map[unmarshalerText]int{ got, err := Marshal(map[unmarshalerText]int{
{"x", "y"}: 1, {"x", "y"}: 1,
{"y", "x"}: 2, {"y", "x"}: 2,
{"a", "z"}: 3, {"a", "z"}: 3,
{"z", "a"}: 4, {"z", "a"}: 4,
}) })
if err != nil { 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}` const want = `{"a:z":3,"x:y":1,"y:x":2,"z:a":4}`
if string(b) != want { if string(got) != want {
t.Errorf("Marshal map with text.Marshaler keys: got %#q, want %#q", b, want) t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want)
} }
} }
// https://golang.org/issue/33675 // https://golang.org/issue/33675
func TestNilMarshalerTextMapKey(t *testing.T) { func TestNilMarshalerTextMapKey(t *testing.T) {
b, err := Marshal(map[*unmarshalerText]int{ got, err := Marshal(map[*unmarshalerText]int{
(*unmarshalerText)(nil): 1, (*unmarshalerText)(nil): 1,
{"A", "B"}: 2, {"A", "B"}: 2,
}) })
if err != nil { if err != nil {
t.Fatalf("Failed to Marshal *text.Marshaler: %v", err) t.Fatalf("Marshal error: %v", err)
} }
const want = `{"":1,"A:B":2}` const want = `{"":1,"A:B":2}`
if string(b) != want { if string(got) != want {
t.Errorf("Marshal map with *text.Marshaler keys: got %#q, want %#q", b, 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) bout, err := Marshal(vf)
if err != nil { if err != nil {
t.Errorf("Marshal(%T(%g)): %v", vf, vf, err) t.Errorf("Marshal(%T(%g)) error: %v", vf, vf, err)
nfail++ nfail++
return return
} }
@ -969,12 +969,12 @@ func TestMarshalFloat(t *testing.T) {
// result must convert back to the same float // result must convert back to the same float
g, err := strconv.ParseFloat(out, bits) g, err := strconv.ParseFloat(out, bits)
if err != nil { 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++ nfail++
return return
} }
if f != g || fmt.Sprint(f) != fmt.Sprint(g) { // fmt.Sprint handles ±0 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++ nfail++
return return
} }
@ -985,7 +985,7 @@ func TestMarshalFloat(t *testing.T) {
} }
for _, re := range bad { for _, re := range bad {
if re.MatchString(out) { 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++ nfail++
return return
} }
@ -1049,87 +1049,90 @@ func TestMarshalRawMessageValue(t *testing.T) {
) )
tests := []struct { tests := []struct {
CaseName
in any in any
want string want string
ok bool ok bool
}{ }{
// Test with nil RawMessage. // Test with nil RawMessage.
{rawNil, "null", true}, {Name(""), rawNil, "null", true},
{&rawNil, "null", true}, {Name(""), &rawNil, "null", true},
{[]any{rawNil}, "[null]", true}, {Name(""), []any{rawNil}, "[null]", true},
{&[]any{rawNil}, "[null]", true}, {Name(""), &[]any{rawNil}, "[null]", true},
{[]any{&rawNil}, "[null]", true}, {Name(""), []any{&rawNil}, "[null]", true},
{&[]any{&rawNil}, "[null]", true}, {Name(""), &[]any{&rawNil}, "[null]", true},
{struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, {Name(""), struct{ M RawMessage }{rawNil}, `{"M":null}`, true},
{&struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, {Name(""), &struct{ M RawMessage }{rawNil}, `{"M":null}`, true},
{struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, {Name(""), struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true},
{&struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, {Name(""), &struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true},
{map[string]any{"M": rawNil}, `{"M":null}`, true}, {Name(""), map[string]any{"M": rawNil}, `{"M":null}`, true},
{&map[string]any{"M": rawNil}, `{"M":null}`, true}, {Name(""), &map[string]any{"M": rawNil}, `{"M":null}`, true},
{map[string]any{"M": &rawNil}, `{"M":null}`, true}, {Name(""), map[string]any{"M": &rawNil}, `{"M":null}`, true},
{&map[string]any{"M": &rawNil}, `{"M":null}`, true}, {Name(""), &map[string]any{"M": &rawNil}, `{"M":null}`, true},
{T1{rawNil}, "{}", true}, {Name(""), T1{rawNil}, "{}", true},
{T2{&rawNil}, `{"M":null}`, true}, {Name(""), T2{&rawNil}, `{"M":null}`, true},
{&T1{rawNil}, "{}", true}, {Name(""), &T1{rawNil}, "{}", true},
{&T2{&rawNil}, `{"M":null}`, true}, {Name(""), &T2{&rawNil}, `{"M":null}`, true},
// Test with empty, but non-nil, RawMessage. // Test with empty, but non-nil, RawMessage.
{rawEmpty, "", false}, {Name(""), rawEmpty, "", false},
{&rawEmpty, "", false}, {Name(""), &rawEmpty, "", false},
{[]any{rawEmpty}, "", false}, {Name(""), []any{rawEmpty}, "", false},
{&[]any{rawEmpty}, "", false}, {Name(""), &[]any{rawEmpty}, "", false},
{[]any{&rawEmpty}, "", false}, {Name(""), []any{&rawEmpty}, "", false},
{&[]any{&rawEmpty}, "", false}, {Name(""), &[]any{&rawEmpty}, "", false},
{struct{ X RawMessage }{rawEmpty}, "", false}, {Name(""), struct{ X RawMessage }{rawEmpty}, "", false},
{&struct{ X RawMessage }{rawEmpty}, "", false}, {Name(""), &struct{ X RawMessage }{rawEmpty}, "", false},
{struct{ X *RawMessage }{&rawEmpty}, "", false}, {Name(""), struct{ X *RawMessage }{&rawEmpty}, "", false},
{&struct{ X *RawMessage }{&rawEmpty}, "", false}, {Name(""), &struct{ X *RawMessage }{&rawEmpty}, "", false},
{map[string]any{"nil": rawEmpty}, "", false}, {Name(""), map[string]any{"nil": rawEmpty}, "", false},
{&map[string]any{"nil": rawEmpty}, "", false}, {Name(""), &map[string]any{"nil": rawEmpty}, "", false},
{map[string]any{"nil": &rawEmpty}, "", false}, {Name(""), map[string]any{"nil": &rawEmpty}, "", false},
{&map[string]any{"nil": &rawEmpty}, "", false}, {Name(""), &map[string]any{"nil": &rawEmpty}, "", false},
{T1{rawEmpty}, "{}", true}, {Name(""), T1{rawEmpty}, "{}", true},
{T2{&rawEmpty}, "", false}, {Name(""), T2{&rawEmpty}, "", false},
{&T1{rawEmpty}, "{}", true}, {Name(""), &T1{rawEmpty}, "{}", true},
{&T2{&rawEmpty}, "", false}, {Name(""), &T2{&rawEmpty}, "", false},
// Test with RawMessage with some text. // Test with RawMessage with some text.
// //
// The tests below marked with Issue6458 used to generate "ImZvbyI=" instead "foo". // The tests below marked with Issue6458 used to generate "ImZvbyI=" instead "foo".
// This behavior was intentionally changed in Go 1.8. // This behavior was intentionally changed in Go 1.8.
// See https://golang.org/issues/14493#issuecomment-255857318 // See https://golang.org/issues/14493#issuecomment-255857318
{rawText, `"foo"`, true}, // Issue6458 {Name(""), rawText, `"foo"`, true}, // Issue6458
{&rawText, `"foo"`, true}, {Name(""), &rawText, `"foo"`, true},
{[]any{rawText}, `["foo"]`, true}, // Issue6458 {Name(""), []any{rawText}, `["foo"]`, true}, // Issue6458
{&[]any{rawText}, `["foo"]`, true}, // Issue6458 {Name(""), &[]any{rawText}, `["foo"]`, true}, // Issue6458
{[]any{&rawText}, `["foo"]`, true}, {Name(""), []any{&rawText}, `["foo"]`, true},
{&[]any{&rawText}, `["foo"]`, true}, {Name(""), &[]any{&rawText}, `["foo"]`, true},
{struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 {Name(""), struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458
{&struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, {Name(""), &struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true},
{struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, {Name(""), struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true},
{&struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, {Name(""), &struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true},
{map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 {Name(""), map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458
{&map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 {Name(""), &map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458
{map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, {Name(""), map[string]any{"M": &rawText}, `{"M":"foo"}`, true},
{&map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, {Name(""), &map[string]any{"M": &rawText}, `{"M":"foo"}`, true},
{T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 {Name(""), T1{rawText}, `{"M":"foo"}`, true}, // Issue6458
{T2{&rawText}, `{"M":"foo"}`, true}, {Name(""), T2{&rawText}, `{"M":"foo"}`, true},
{&T1{rawText}, `{"M":"foo"}`, true}, {Name(""), &T1{rawText}, `{"M":"foo"}`, true},
{&T2{&rawText}, `{"M":"foo"}`, true}, {Name(""), &T2{&rawText}, `{"M":"foo"}`, true},
} }
for i, tt := range tests { for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
b, err := Marshal(tt.in) b, err := Marshal(tt.in)
if ok := (err == nil); ok != tt.ok { if ok := (err == nil); ok != tt.ok {
if err != nil { if err != nil {
t.Errorf("test %d, unexpected failure: %v", i, err) t.Errorf("%s: Marshal error: %v", tt.Where, err)
} else { } else {
t.Errorf("test %d, unexpected success", i) t.Errorf("%s: Marshal error: got nil, want non-nil", tt.Where)
} }
} }
if got := string(b); got != tt.want { if got := string(b); got != tt.want {
t.Errorf("test %d, Marshal(%#v) = %q, want %q", i, tt.in, 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) b, err := Marshal(v)
if err != nil { if err != nil {
t.Fatal("Marshal:", err) t.Fatal("Marshal error:", err)
} }
want := `{"A0":0,"À":0,"Aβ":0}` want := `{"A0":0,"À":0,"Aβ":0}`
got := string(b) got := string(b)
if got != want { 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" errText := "json: test error"
tests := []struct { tests := []struct {
CaseName
err *MarshalerError err *MarshalerError
want string want string
}{ }{{
{ Name(""),
&MarshalerError{st, fmt.Errorf(errText), ""}, &MarshalerError{st, fmt.Errorf(errText), ""},
"json: error calling MarshalJSON for type " + st.String() + ": " + errText, "json: error calling MarshalJSON for type " + st.String() + ": " + errText,
}, }, {
{ Name(""),
&MarshalerError{st, fmt.Errorf(errText), "TestMarshalerError"}, &MarshalerError{st, fmt.Errorf(errText), "TestMarshalerError"},
"json: error calling TestMarshalerError for type " + st.String() + ": " + errText, "json: error calling TestMarshalerError for type " + st.String() + ": " + errText,
}, }}
}
for i, tt := range tests { for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
got := tt.err.Error() got := tt.err.Error()
if got != tt.want { if got != tt.want {
t.Errorf("MarshalerError test %d, got: %s, want: %s", i, got, tt.want) t.Errorf("%s: Error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.want)
} }
})
} }
} }

View file

@ -9,51 +9,59 @@ import (
"math" "math"
"math/rand" "math/rand"
"reflect" "reflect"
"strings"
"testing" "testing"
) )
var validTests = []struct { func indentNewlines(s string) string {
data string return strings.Join(strings.Split(s, "\n"), "\n\t")
ok bool }
}{
{`foo`, false}, func stripWhitespace(s string) string {
{`}{`, false}, return strings.Map(func(r rune) rune {
{`{]`, false}, if r == ' ' || r == '\n' || r == '\r' || r == '\t' {
{`{}`, true}, return -1
{`{"foo":"bar"}`, true}, }
{`{"foo":"bar","bar":{"baz":["qux"]}}`, true}, return r
}, s)
} }
func TestValid(t *testing.T) { func TestValid(t *testing.T) {
for _, tt := range validTests { tests := []struct {
if ok := Valid([]byte(tt.data)); ok != tt.ok { CaseName
t.Errorf("Valid(%#q) = %v, want %v", tt.data, ok, tt.ok) 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. func TestCompactAndIndent(t *testing.T) {
tests := []struct {
type example struct { CaseName
compact string compact string
indent string indent string
} }{
{Name(""), `1`, `1`},
var examples = []example{ {Name(""), `{}`, `{}`},
{`1`, `1`}, {Name(""), `[]`, `[]`},
{`{}`, `{}`}, {Name(""), `{"":2}`, "{\n\t\"\": 2\n}"},
{`[]`, `[]`}, {Name(""), `[3]`, "[\n\t3\n]"},
{`{"":2}`, "{\n\t\"\": 2\n}"}, {Name(""), `[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"},
{`[3]`, "[\n\t3\n]"}, {Name(""), `{"x":1}`, "{\n\t\"x\": 1\n}"},
{`[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"}, {Name(""), `[true,false,null,"x",1,1.5,0,-5e+2]`, `[
{`{"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 = `[
true, true,
false, false,
null, null,
@ -62,25 +70,40 @@ var ex1i = `[
1.5, 1.5,
0, 0,
-5e+2 -5e+2
]` ]`},
{Name(""), "{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070
func TestCompact(t *testing.T) { }
var buf bytes.Buffer var buf bytes.Buffer
for _, tt := range examples { for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
buf.Reset() buf.Reset()
if err := Compact(&buf, []byte(tt.compact)); err != nil { if err := Compact(&buf, []byte(tt.compact)); err != nil {
t.Errorf("Compact(%#q): %v", tt.compact, err) t.Errorf("%s: Compact error: %v", tt.Where, err)
} else if s := buf.String(); s != tt.compact { } else if got := buf.String(); got != tt.compact {
t.Errorf("Compact(%#q) = %#q, want original", tt.compact, s) t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
} }
buf.Reset() buf.Reset()
if err := Compact(&buf, []byte(tt.indent)); err != nil { if err := Compact(&buf, []byte(tt.indent)); err != nil {
t.Errorf("Compact(%#q): %v", tt.indent, err) t.Errorf("%s: Compact error: %v", tt.Where, err)
continue } else if got := buf.String(); got != tt.compact {
} else if s := buf.String(); s != tt.compact { t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
t.Errorf("Compact(%#q) = %#q, want %#q", tt.indent, s, 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. // U+2028 and U+2029 should be escaped inside strings.
// They should not appear outside strings. // They should not appear outside strings.
tests := []struct { tests := []struct {
CaseName
in, compact string in, compact string
}{ }{
{"{\"\u2028\": 1}", "{\"\u2028\":1}"}, {Name(""), "{\"\u2028\": 1}", "{\"\u2028\":1}"},
{"{\"\u2029\" :2}", "{\"\u2029\":2}"}, {Name(""), "{\"\u2029\" :2}", "{\"\u2029\":2}"},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer
if err := Compact(&buf, []byte(tt.in)); err != nil { if err := Compact(&buf, []byte(tt.in)); err != nil {
t.Errorf("Compact(%q): %v", tt.in, err) t.Errorf("%s: Compact error: %v", tt.Where, err)
} else if s := buf.String(); s != tt.compact { } else if got := buf.String(); got != tt.compact {
t.Errorf("Compact(%q) = %q, want %q", tt.in, s, tt.compact) t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(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)
} }
})
} }
} }
@ -129,11 +135,11 @@ func TestCompactBig(t *testing.T) {
initBig() initBig()
var buf bytes.Buffer var buf bytes.Buffer
if err := Compact(&buf, jsonBig); err != nil { if err := Compact(&buf, jsonBig); err != nil {
t.Fatalf("Compact: %v", err) t.Fatalf("Compact error: %v", err)
} }
b := buf.Bytes() b := buf.Bytes()
if !bytes.Equal(b, jsonBig) { if !bytes.Equal(b, jsonBig) {
t.Error("Compact(jsonBig) != jsonBig") t.Error("Compact:")
diff(t, b, jsonBig) diff(t, b, jsonBig)
return return
} }
@ -144,23 +150,23 @@ func TestIndentBig(t *testing.T) {
initBig() initBig()
var buf bytes.Buffer var buf bytes.Buffer
if err := Indent(&buf, jsonBig, "", "\t"); err != nil { if err := Indent(&buf, jsonBig, "", "\t"); err != nil {
t.Fatalf("Indent1: %v", err) t.Fatalf("Indent error: %v", err)
} }
b := buf.Bytes() b := buf.Bytes()
if len(b) == len(jsonBig) { if len(b) == len(jsonBig) {
// jsonBig is compact (no unnecessary spaces); // jsonBig is compact (no unnecessary spaces);
// indenting should make it bigger // indenting should make it bigger
t.Fatalf("Indent(jsonBig) did not get bigger") t.Fatalf("Indent did not expand the input")
} }
// should be idempotent // should be idempotent
var buf1 bytes.Buffer var buf1 bytes.Buffer
if err := Indent(&buf1, b, "", "\t"); err != nil { if err := Indent(&buf1, b, "", "\t"); err != nil {
t.Fatalf("Indent2: %v", err) t.Fatalf("Indent error: %v", err)
} }
b1 := buf1.Bytes() b1 := buf1.Bytes()
if !bytes.Equal(b1, b) { if !bytes.Equal(b1, b) {
t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig)") t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig):")
diff(t, b1, b) diff(t, b1, b)
return return
} }
@ -168,40 +174,40 @@ func TestIndentBig(t *testing.T) {
// should get back to original // should get back to original
buf1.Reset() buf1.Reset()
if err := Compact(&buf1, b); err != nil { if err := Compact(&buf1, b); err != nil {
t.Fatalf("Compact: %v", err) t.Fatalf("Compact error: %v", err)
} }
b1 = buf1.Bytes() b1 = buf1.Bytes()
if !bytes.Equal(b1, jsonBig) { if !bytes.Equal(b1, jsonBig) {
t.Error("Compact(Indent(jsonBig)) != jsonBig") t.Error("Compact(Indent(jsonBig)) != jsonBig:")
diff(t, b1, jsonBig) diff(t, b1, jsonBig)
return return
} }
} }
type indentErrorTest struct { func TestIndentErrors(t *testing.T) {
tests := []struct {
CaseName
in string in string
err error err error
} }{
{Name(""), `{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}},
var indentErrorTests = []indentErrorTest{ {Name(""), `{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}},
{`{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}}, }
{`{"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) {
func TestIndentErrors(t *testing.T) {
for i, tt := range indentErrorTests {
slice := make([]uint8, 0) slice := make([]uint8, 0)
buf := bytes.NewBuffer(slice) buf := bytes.NewBuffer(slice)
if err := Indent(buf, []uint8(tt.in), "", ""); err != nil { if err := Indent(buf, []uint8(tt.in), "", ""); err != nil {
if !reflect.DeepEqual(err, tt.err) { if !reflect.DeepEqual(err, tt.err) {
t.Errorf("#%d: Indent: %#v", i, err) t.Fatalf("%s: Indent error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err)
continue
} }
} }
})
} }
} }
func diff(t *testing.T, a, b []byte) { func diff(t *testing.T, a, b []byte) {
t.Helper()
for i := 0; ; i++ { for i := 0; ; i++ {
if i >= len(a) || i >= len(b) || a[i] != b[i] { if i >= len(a) || i >= len(b) || a[i] != b[i] {
j := i - 10 j := i - 10
@ -215,10 +221,7 @@ func diff(t *testing.T, a, b []byte) {
} }
func trim(b []byte) []byte { func trim(b []byte) []byte {
if len(b) > 20 { return b[:min(len(b), 20)]
return b[0:20]
}
return b
} }
// Generate a random JSON object. // Generate a random JSON object.

View file

@ -6,17 +6,44 @@ package json
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"log" "log"
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"path"
"reflect" "reflect"
"runtime"
"runtime/debug" "runtime/debug"
"strings" "strings"
"testing" "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. // Test values for the stream test.
// One of each JSON kind. // One of each JSON kind.
var streamTest = []any{ var streamTest = []any{
@ -49,11 +76,11 @@ func TestEncoder(t *testing.T) {
enc.SetIndent("", "") enc.SetIndent("", "")
for j, v := range streamTest[0:i] { for j, v := range streamTest[0:i] {
if err := enc.Encode(v); err != nil { 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 { 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)) diff(t, []byte(have), []byte(want))
break break
} }
@ -76,24 +103,24 @@ func TestEncoderErrorAndReuseEncodeState(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer
enc := NewEncoder(&buf) enc := NewEncoder(&buf)
if err := enc.Encode(dummy); err == nil { 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 { type Data struct {
A string A string
I int I int
} }
data := Data{A: "a", I: 1} want := Data{A: "a", I: 1}
if err := enc.Encode(data); err != nil { if err := enc.Encode(want); err != nil {
t.Errorf("Marshal(%v) = %v", data, err) t.Errorf("Marshal error: %v", err)
} }
var data2 Data var got Data
if err := Unmarshal(buf.Bytes(), &data2); err != nil { if err := Unmarshal(buf.Bytes(), &got); err != nil {
t.Errorf("Unmarshal(%v) = %v", data2, err) t.Errorf("Unmarshal error: %v", err)
} }
if data2 != data { if got != want {
t.Errorf("expect: %v, but get: %v", data, data2) 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) enc.Encode(v)
} }
if have, want := buf.String(), streamEncodedIndent; have != want { if have, want := buf.String(), streamEncodedIndent; have != want {
t.Error("indented encoding mismatch") t.Error("Encode mismatch:")
diff(t, []byte(have), []byte(want)) diff(t, []byte(have), []byte(want))
} }
} }
@ -160,50 +187,51 @@ func TestEncoderSetEscapeHTML(t *testing.T) {
Bar string `json:"bar,string"` Bar string `json:"bar,string"`
}{`<html>foobar</html>`} }{`<html>foobar</html>`}
for _, tt := range []struct { tests := []struct {
name string CaseName
v any v any
wantEscape string wantEscape string
want string want string
}{ }{
{"c", c, `"\u003c\u0026\u003e"`, `"<&>"`}, {Name("c"), c, `"\u003c\u0026\u003e"`, `"<&>"`},
{"ct", ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`}, {Name("ct"), ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`},
{`"<&>"`, "<&>", `"\u003c\u0026\u003e"`, `"<&>"`}, {Name(`"<&>"`), "<&>", `"\u003c\u0026\u003e"`, `"<&>"`},
{ {
"tagStruct", tagStruct, Name("tagStruct"), tagStruct,
`{"\u003c\u003e\u0026#! ":0,"Invalid":0}`, `{"\u003c\u003e\u0026#! ":0,"Invalid":0}`,
`{"<>&#! ":0,"Invalid":0}`, `{"<>&#! ":0,"Invalid":0}`,
}, },
{ {
`"<str>"`, marshalerStruct, Name(`"<str>"`), marshalerStruct,
`{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`, `{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`,
`{"NonPtr":"<str>","Ptr":"<str>"}`, `{"NonPtr":"<str>","Ptr":"<str>"}`,
}, },
{ {
"stringOption", stringOption, Name("stringOption"), stringOption,
`{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`, `{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`,
`{"bar":"\"<html>foobar</html>\""}`, `{"bar":"\"<html>foobar</html>\""}`,
}, },
} { }
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
var buf strings.Builder var buf strings.Builder
enc := NewEncoder(&buf) enc := NewEncoder(&buf)
if err := enc.Encode(tt.v); err != nil { if err := enc.Encode(tt.v); err != nil {
t.Errorf("Encode(%s): %s", tt.name, err) t.Fatalf("%s: Encode(%s) error: %s", tt.Where, tt.Name, err)
continue
} }
if got := strings.TrimSpace(buf.String()); got != tt.wantEscape { if got := strings.TrimSpace(buf.String()); got != tt.wantEscape {
t.Errorf("Encode(%s) = %#q, want %#q", tt.name, got, tt.wantEscape) t.Errorf("%s: Encode(%s):\n\tgot: %s\n\twant: %s", tt.Where, tt.Name, got, tt.wantEscape)
} }
buf.Reset() buf.Reset()
enc.SetEscapeHTML(false) enc.SetEscapeHTML(false)
if err := enc.Encode(tt.v); err != nil { if err := enc.Encode(tt.v); err != nil {
t.Errorf("SetEscapeHTML(false) Encode(%s): %s", tt.name, err) t.Fatalf("%s: SetEscapeHTML(false) Encode(%s) error: %s", tt.Where, tt.Name, err)
continue
} }
if got := strings.TrimSpace(buf.String()); got != tt.want { if got := strings.TrimSpace(buf.String()); got != tt.want {
t.Errorf("SetEscapeHTML(false) Encode(%s) = %#q, want %#q", t.Errorf("%s: SetEscapeHTML(false) Encode(%s):\n\tgot: %s\n\twant: %s",
tt.name, got, tt.want) tt.Where, tt.Name, got, tt.want)
} }
})
} }
} }
@ -224,14 +252,14 @@ func TestDecoder(t *testing.T) {
dec := NewDecoder(&buf) dec := NewDecoder(&buf)
for j := range out { for j := range out {
if err := dec.Decode(&out[j]); err != nil { 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]) { 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 { for j := range out {
if !reflect.DeepEqual(out[j], streamTest[j]) { 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 break
@ -250,14 +278,14 @@ func TestDecoderBuffered(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if m.Name != "Gopher" { 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()) rest, err := io.ReadAll(d.Buffered())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if g, w := string(rest), " extra "; g != w { if got, want := string(rest), " extra "; got != want {
t.Errorf("Remaining = %q; want %q", g, w) t.Errorf("Remaining = %s, want %s", got, want)
} }
} }
@ -282,20 +310,20 @@ func TestRawMessage(t *testing.T) {
Y float32 Y float32
} }
const raw = `["\u0056",null]` const raw = `["\u0056",null]`
const msg = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}` const want = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}`
err := Unmarshal([]byte(msg), &data) err := Unmarshal([]byte(want), &data)
if err != nil { if err != nil {
t.Fatalf("Unmarshal: %v", err) t.Fatalf("Unmarshal error: %v", err)
} }
if string([]byte(data.Id)) != raw { 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 { if err != nil {
t.Fatalf("Marshal: %v", err) t.Fatalf("Marshal error: %v", err)
} }
if string(b) != msg { if string(got) != want {
t.Fatalf("Marshal: have %#q want %#q", b, msg) t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want)
} }
} }
@ -306,159 +334,156 @@ func TestNullRawMessage(t *testing.T) {
IdPtr *RawMessage IdPtr *RawMessage
Y float32 Y float32
} }
const msg = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}` const want = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}`
err := Unmarshal([]byte(msg), &data) err := Unmarshal([]byte(want), &data)
if err != nil { if err != nil {
t.Fatalf("Unmarshal: %v", err) t.Fatalf("Unmarshal error: %v", err)
} }
if want, got := "null", string(data.Id); want != got { 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 { 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 { if err != nil {
t.Fatalf("Marshal: %v", err) t.Fatalf("Marshal error: %v", err)
} }
if string(b) != msg { if string(got) != want {
t.Fatalf("Marshal: have %#q want %#q", b, msg) t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want)
} }
} }
var blockingTests = []string{
`{"x": 1}`,
`[1, 2, 3]`,
}
func TestBlocking(t *testing.T) { func TestBlocking(t *testing.T) {
for _, enc := range blockingTests { 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() r, w := net.Pipe()
go w.Write([]byte(enc)) go w.Write([]byte(tt.in))
var val any var val any
// If Decode reads beyond what w.Write writes above, // If Decode reads beyond what w.Write writes above,
// it will block, and the test will deadlock. // it will block, and the test will deadlock.
if err := NewDecoder(r).Decode(&val); err != nil { if err := NewDecoder(r).Decode(&val); err != nil {
t.Errorf("decoding %s: %v", enc, err) t.Errorf("%s: NewDecoder(%s).Decode error: %v", tt.Where, tt.in, err)
} }
r.Close() r.Close()
w.Close() w.Close()
})
} }
} }
type tokenStreamCase struct {
json string
expTokens []any
}
type decodeThis struct { type decodeThis struct {
v any v any
} }
var tokenStreamCases = []tokenStreamCase{ func TestDecodeInStream(t *testing.T) {
tests := []struct {
CaseName
json string
expTokens []any
}{
// streaming token cases // streaming token cases
{json: `10`, expTokens: []any{float64(10)}}, {CaseName: Name(""), json: `10`, expTokens: []any{float64(10)}},
{json: ` [10] `, expTokens: []any{ {CaseName: Name(""), json: ` [10] `, expTokens: []any{
Delim('['), float64(10), Delim(']')}}, Delim('['), float64(10), Delim(']')}},
{json: ` [false,10,"b"] `, expTokens: []any{ {CaseName: Name(""), json: ` [false,10,"b"] `, expTokens: []any{
Delim('['), false, float64(10), "b", Delim(']')}}, Delim('['), false, float64(10), "b", Delim(']')}},
{json: `{ "a": 1 }`, expTokens: []any{ {CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{
Delim('{'), "a", float64(1), Delim('}')}}, Delim('{'), "a", float64(1), Delim('}')}},
{json: `{"a": 1, "b":"3"}`, expTokens: []any{ {CaseName: Name(""), json: `{"a": 1, "b":"3"}`, expTokens: []any{
Delim('{'), "a", float64(1), "b", "3", Delim('}')}}, Delim('{'), "a", float64(1), "b", "3", Delim('}')}},
{json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ {CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
Delim('['), Delim('['),
Delim('{'), "a", float64(1), Delim('}'), Delim('{'), "a", float64(1), Delim('}'),
Delim('{'), "a", float64(2), Delim('}'), Delim('{'), "a", float64(2), Delim('}'),
Delim(']')}}, Delim(']')}},
{json: `{"obj": {"a": 1}}`, expTokens: []any{ {CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{
Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'), Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'),
Delim('}')}}, Delim('}')}},
{json: `{"obj": [{"a": 1}]}`, expTokens: []any{ {CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{
Delim('{'), "obj", Delim('['), Delim('{'), "obj", Delim('['),
Delim('{'), "a", float64(1), Delim('}'), Delim('{'), "a", float64(1), Delim('}'),
Delim(']'), Delim('}')}}, Delim(']'), Delim('}')}},
// streaming tokens with intermittent Decode() // streaming tokens with intermittent Decode()
{json: `{ "a": 1 }`, expTokens: []any{ {CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{
Delim('{'), "a", Delim('{'), "a",
decodeThis{float64(1)}, decodeThis{float64(1)},
Delim('}')}}, Delim('}')}},
{json: ` [ { "a" : 1 } ] `, expTokens: []any{ {CaseName: Name(""), json: ` [ { "a" : 1 } ] `, expTokens: []any{
Delim('['), Delim('['),
decodeThis{map[string]any{"a": float64(1)}}, decodeThis{map[string]any{"a": float64(1)}},
Delim(']')}}, Delim(']')}},
{json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ {CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
Delim('['), Delim('['),
decodeThis{map[string]any{"a": float64(1)}}, decodeThis{map[string]any{"a": float64(1)}},
decodeThis{map[string]any{"a": float64(2)}}, decodeThis{map[string]any{"a": float64(2)}},
Delim(']')}}, Delim(']')}},
{json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{ {CaseName: Name(""), json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{
Delim('{'), "obj", Delim('['), Delim('{'), "obj", Delim('['),
decodeThis{map[string]any{"a": float64(1)}}, decodeThis{map[string]any{"a": float64(1)}},
Delim(']'), Delim('}')}}, Delim(']'), Delim('}')}},
{json: `{"obj": {"a": 1}}`, expTokens: []any{ {CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{
Delim('{'), "obj", Delim('{'), "obj",
decodeThis{map[string]any{"a": float64(1)}}, decodeThis{map[string]any{"a": float64(1)}},
Delim('}')}}, Delim('}')}},
{json: `{"obj": [{"a": 1}]}`, expTokens: []any{ {CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{
Delim('{'), "obj", Delim('{'), "obj",
decodeThis{[]any{ decodeThis{[]any{
map[string]any{"a": float64(1)}, map[string]any{"a": float64(1)},
}}, }},
Delim('}')}}, Delim('}')}},
{json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{ {CaseName: Name(""), json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{
Delim('['), Delim('['),
decodeThis{map[string]any{"a": float64(1)}}, decodeThis{map[string]any{"a": float64(1)}},
decodeThis{&SyntaxError{"expected comma after array element", 11}}, decodeThis{&SyntaxError{"expected comma after array element", 11}},
}}, }},
{json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{ {CaseName: Name(""), json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{
Delim('{'), strings.Repeat("a", 513), Delim('{'), strings.Repeat("a", 513),
decodeThis{&SyntaxError{"expected colon after object key", 518}}, decodeThis{&SyntaxError{"expected colon after object key", 518}},
}}, }},
{json: `{ "\a" }`, expTokens: []any{ {CaseName: Name(""), json: `{ "\a" }`, expTokens: []any{
Delim('{'), Delim('{'),
&SyntaxError{"invalid character 'a' in string escape code", 3}, &SyntaxError{"invalid character 'a' in string escape code", 3},
}}, }},
{json: ` \a`, expTokens: []any{ {CaseName: Name(""), json: ` \a`, expTokens: []any{
&SyntaxError{"invalid character '\\\\' looking for beginning of value", 1}, &SyntaxError{"invalid character '\\\\' looking for beginning of value", 1},
}}, }},
} }
for _, tt := range tests {
func TestDecodeInStream(t *testing.T) { t.Run(tt.Name, func(t *testing.T) {
for ci, tcase := range tokenStreamCases { dec := NewDecoder(strings.NewReader(tt.json))
for i, want := range tt.expTokens {
dec := NewDecoder(strings.NewReader(tcase.json)) var got any
for i, etk := range tcase.expTokens {
var tk any
var err error var err error
if dt, ok := etk.(decodeThis); ok { if dt, ok := want.(decodeThis); ok {
etk = dt.v want = dt.v
err = dec.Decode(&tk) err = dec.Decode(&got)
} else { } else {
tk, err = dec.Token() got, err = dec.Token()
} }
if experr, ok := etk.(error); ok { if errWant, ok := want.(error); ok {
if err == nil || !reflect.DeepEqual(err, experr) { if err == nil || !reflect.DeepEqual(err, errWant) {
t.Errorf("case %v: Expected error %#v in %q, but was %#v", ci, experr, tcase.json, err) t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: %v", tt.Where, tt.json, err, errWant)
} }
break break
} else if err == io.EOF {
t.Errorf("case %v: Unexpected EOF in %q", ci, tcase.json)
break
} else if err != nil { } else if err != nil {
t.Errorf("case %v: Unexpected error '%#v' in %q", ci, err, tcase.json) t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: nil", tt.Where, tt.json, err)
break
} }
if !reflect.DeepEqual(tk, etk) { if !reflect.DeepEqual(got, want) {
t.Errorf(`case %v: %q @ %v expected %T(%v) was %T(%v)`, ci, tcase.json, i, etk, etk, tk, tk) 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
} }
} }
})
} }
} }
@ -472,7 +497,7 @@ func TestHTTPDecoding(t *testing.T) {
defer ts.Close() defer ts.Close()
res, err := http.Get(ts.URL) res, err := http.Get(ts.URL)
if err != nil { if err != nil {
log.Fatalf("GET failed: %v", err) log.Fatalf("http.Get error: %v", err)
} }
defer res.Body.Close() defer res.Body.Close()
@ -483,15 +508,15 @@ func TestHTTPDecoding(t *testing.T) {
d := NewDecoder(res.Body) d := NewDecoder(res.Body)
err = d.Decode(&foo) err = d.Decode(&foo)
if err != nil { if err != nil {
t.Fatalf("Decode: %v", err) t.Fatalf("Decode error: %v", err)
} }
if foo.Foo != "bar" { 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 // make sure we get the EOF the second time
err = d.Decode(&foo) err = d.Decode(&foo)
if err != io.EOF { if err != io.EOF {
t.Errorf("err = %v; want io.EOF", err) t.Errorf("Decode error:\n\tgot: %v\n\twant: io.EOF", err)
} }
} }

View file

@ -72,49 +72,50 @@ type unicodeTag struct {
W string `json:"Ελλάδα"` W string `json:"Ελλάδα"`
} }
var structTagObjectKeyTests = []struct { func TestStructTagObjectKey(t *testing.T) {
tests := []struct {
CaseName
raw any raw any
value string value string
key string key string
}{ }{
{basicLatin2xTag{"2x"}, "2x", "$%-/"}, {Name(""), basicLatin2xTag{"2x"}, "2x", "$%-/"},
{basicLatin3xTag{"3x"}, "3x", "0123456789"}, {Name(""), basicLatin3xTag{"3x"}, "3x", "0123456789"},
{basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"}, {Name(""), basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"},
{basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"}, {Name(""), basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"},
{basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"}, {Name(""), basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"},
{basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"}, {Name(""), basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"},
{miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"}, {Name(""), miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"},
{dashTag{"foo"}, "foo", "-"}, {Name(""), dashTag{"foo"}, "foo", "-"},
{emptyTag{"Pour Moi"}, "Pour Moi", "W"}, {Name(""), emptyTag{"Pour Moi"}, "Pour Moi", "W"},
{misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"}, {Name(""), misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"},
{badFormatTag{"Orfevre"}, "Orfevre", "Y"}, {Name(""), badFormatTag{"Orfevre"}, "Orfevre", "Y"},
{badCodeTag{"Reliable Man"}, "Reliable Man", "Z"}, {Name(""), badCodeTag{"Reliable Man"}, "Reliable Man", "Z"},
{percentSlashTag{"brut"}, "brut", "text/html%"}, {Name(""), percentSlashTag{"brut"}, "brut", "text/html%"},
{punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:;<=>?@[]^_{|}~ "}, {Name(""), punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:;<=>?@[]^_{|}~ "},
{spaceTag{"Perreddu"}, "Perreddu", "With space"}, {Name(""), spaceTag{"Perreddu"}, "Perreddu", "With space"},
{unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"}, {Name(""), unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"},
} }
for _, tt := range tests {
func TestStructTagObjectKey(t *testing.T) { t.Run(tt.Name, func(t *testing.T) {
for _, tt := range structTagObjectKeyTests {
b, err := Marshal(tt.raw) b, err := Marshal(tt.raw)
if err != nil { if err != nil {
t.Fatalf("Marshal(%#q) failed: %v", tt.raw, err) t.Fatalf("%s: Marshal error: %v", tt.Where, err)
} }
var f any var f any
err = Unmarshal(b, &f) err = Unmarshal(b, &f)
if err != nil { if err != nil {
t.Fatalf("Unmarshal(%#q) failed: %v", b, err) t.Fatalf("%s: Unmarshal error: %v", tt.Where, err)
} }
for i, v := range f.(map[string]any) { for k, v := range f.(map[string]any) {
switch i { if k == tt.key {
case tt.key:
if s, ok := v.(string); !ok || s != tt.value { if s, ok := v.(string); !ok || s != tt.value {
t.Fatalf("Unexpected value: %#q, want %v", s, tt.value) t.Fatalf("%s: Unmarshal(%#q) value:\n\tgot: %q\n\twant: %q", tt.Where, b, s, tt.value)
} }
default: } else {
t.Fatalf("Unexpected key: %#q, from %#q", i, b) t.Fatalf("%s: Unmarshal(%#q): unexpected key: %q", tt.Where, b, k)
} }
} }
})
} }
} }

View file

@ -22,7 +22,7 @@ func TestTagParsing(t *testing.T) {
{"bar", false}, {"bar", false},
} { } {
if opts.Contains(tt.opt) != tt.want { 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)
} }
} }
} }