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)
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)
}
}
})

File diff suppressed because it is too large Load diff

View file

@ -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)
}
})
}
}

View file

@ -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.

View file

@ -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"`
}{`<html>foobar</html>`}
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}`,
},
{
`"<str>"`, marshalerStruct,
Name(`"<str>"`), marshalerStruct,
`{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`,
`{"NonPtr":"<str>","Ptr":"<str>"}`,
},
{
"stringOption", stringOption,
Name("stringOption"), stringOption,
`{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`,
`{"bar":"\"<html>foobar</html>\""}`,
},
} {
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)
}
}

View file

@ -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)
}
}
})
}
}

View file

@ -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)
}
}
}