blob: d3f0afd9ca65e0d1af3a2dac51c946e7d0dad7f4 [file] [log] [blame]
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package json_test
import (
"math"
"strings"
"testing"
"github.com/golang/protobuf/v2/internal/encoding/json"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
// splitLines is a cmpopts.Option for comparing strings with line breaks.
var splitLines = cmpopts.AcyclicTransformer("SplitLines", func(s string) []string {
return strings.Split(s, "\n")
})
func TestEncoder(t *testing.T) {
tests := []struct {
desc string
write func(*json.Encoder)
wantOut string
wantOutIndent string
}{
{
desc: "null",
write: func(e *json.Encoder) {
e.WriteNull()
},
wantOut: `null`,
wantOutIndent: `null`,
},
{
desc: "true",
write: func(e *json.Encoder) {
e.WriteBool(true)
},
wantOut: `true`,
wantOutIndent: `true`,
},
{
desc: "false",
write: func(e *json.Encoder) {
e.WriteBool(false)
},
wantOut: `false`,
wantOutIndent: `false`,
},
{
desc: "string",
write: func(e *json.Encoder) {
e.WriteString("hello world")
},
wantOut: `"hello world"`,
wantOutIndent: `"hello world"`,
},
{
desc: "string contains escaped characters",
write: func(e *json.Encoder) {
e.WriteString("\u0000\"\\/\b\f\n\r\t")
},
wantOut: `"\u0000\"\\/\b\f\n\r\t"`,
},
{
desc: "float64",
write: func(e *json.Encoder) {
e.WriteFloat(1.0199999809265137, 64)
},
wantOut: `1.0199999809265137`,
wantOutIndent: `1.0199999809265137`,
},
{
desc: "float64 max value",
write: func(e *json.Encoder) {
e.WriteFloat(math.MaxFloat64, 64)
},
wantOut: `1.7976931348623157e+308`,
wantOutIndent: `1.7976931348623157e+308`,
},
{
desc: "float64 min value",
write: func(e *json.Encoder) {
e.WriteFloat(-math.MaxFloat64, 64)
},
wantOut: `-1.7976931348623157e+308`,
wantOutIndent: `-1.7976931348623157e+308`,
},
{
desc: "float64 NaN",
write: func(e *json.Encoder) {
e.WriteFloat(math.NaN(), 64)
},
wantOut: `"NaN"`,
wantOutIndent: `"NaN"`,
},
{
desc: "float64 Infinity",
write: func(e *json.Encoder) {
e.WriteFloat(math.Inf(+1), 64)
},
wantOut: `"Infinity"`,
wantOutIndent: `"Infinity"`,
},
{
desc: "float64 -Infinity",
write: func(e *json.Encoder) {
e.WriteFloat(math.Inf(-1), 64)
},
wantOut: `"-Infinity"`,
wantOutIndent: `"-Infinity"`,
},
{
desc: "float32",
write: func(e *json.Encoder) {
e.WriteFloat(1.02, 32)
},
wantOut: `1.02`,
wantOutIndent: `1.02`,
},
{
desc: "float32 max value",
write: func(e *json.Encoder) {
e.WriteFloat(math.MaxFloat32, 32)
},
wantOut: `3.4028235e+38`,
wantOutIndent: `3.4028235e+38`,
},
{
desc: "float32 min value",
write: func(e *json.Encoder) {
e.WriteFloat(-math.MaxFloat32, 32)
},
wantOut: `-3.4028235e+38`,
wantOutIndent: `-3.4028235e+38`,
},
{
desc: "int",
write: func(e *json.Encoder) {
e.WriteInt(-math.MaxInt64)
},
wantOut: `-9223372036854775807`,
wantOutIndent: `-9223372036854775807`,
},
{
desc: "uint",
write: func(e *json.Encoder) {
e.WriteUint(math.MaxUint64)
},
wantOut: `18446744073709551615`,
wantOutIndent: `18446744073709551615`,
},
{
desc: "empty object",
write: func(e *json.Encoder) {
e.StartObject()
e.EndObject()
},
wantOut: `{}`,
wantOutIndent: `{}`,
},
{
desc: "empty array",
write: func(e *json.Encoder) {
e.StartArray()
e.EndArray()
},
wantOut: `[]`,
wantOutIndent: `[]`,
},
{
desc: "object with one member",
write: func(e *json.Encoder) {
e.StartObject()
e.WriteName("hello")
e.WriteString("world")
e.EndObject()
},
wantOut: `{"hello":"world"}`,
wantOutIndent: `{
"hello": "world"
}`,
},
{
desc: "array with one member",
write: func(e *json.Encoder) {
e.StartArray()
e.WriteNull()
e.EndArray()
},
wantOut: `[null]`,
wantOutIndent: `[
null
]`,
},
{
desc: "simple object",
write: func(e *json.Encoder) {
e.StartObject()
{
e.WriteName("null")
e.WriteNull()
}
{
e.WriteName("bool")
e.WriteBool(true)
}
{
e.WriteName("string")
e.WriteString("hello")
}
{
e.WriteName("float")
e.WriteFloat(6.28318, 64)
}
{
e.WriteName("int")
e.WriteInt(42)
}
{
e.WriteName("uint")
e.WriteUint(47)
}
e.EndObject()
},
wantOut: `{"null":null,"bool":true,"string":"hello","float":6.28318,"int":42,"uint":47}`,
wantOutIndent: `{
"null": null,
"bool": true,
"string": "hello",
"float": 6.28318,
"int": 42,
"uint": 47
}`,
},
{
desc: "simple array",
write: func(e *json.Encoder) {
e.StartArray()
{
e.WriteString("hello")
e.WriteFloat(6.28318, 32)
e.WriteInt(42)
e.WriteUint(47)
e.WriteBool(true)
e.WriteNull()
}
e.EndArray()
},
wantOut: `["hello",6.28318,42,47,true,null]`,
wantOutIndent: `[
"hello",
6.28318,
42,
47,
true,
null
]`,
},
{
desc: "fancy object",
write: func(e *json.Encoder) {
e.StartObject()
{
e.WriteName("object0")
e.StartObject()
e.EndObject()
}
{
e.WriteName("array0")
e.StartArray()
e.EndArray()
}
{
e.WriteName("object1")
e.StartObject()
{
e.WriteName("null")
e.WriteNull()
}
{
e.WriteName("object1-1")
e.StartObject()
{
e.WriteName("bool")
e.WriteBool(false)
}
{
e.WriteName("float")
e.WriteFloat(3.14159, 32)
}
e.EndObject()
}
e.EndObject()
}
{
e.WriteName("array1")
e.StartArray()
{
e.WriteNull()
e.StartObject()
e.EndObject()
e.StartObject()
{
e.WriteName("hello")
e.WriteString("world")
}
{
e.WriteName("hola")
e.WriteString("mundo")
}
e.EndObject()
e.StartArray()
{
e.WriteUint(1)
e.WriteUint(0)
e.WriteUint(1)
}
e.EndArray()
}
e.EndArray()
}
e.EndObject()
},
wantOutIndent: `{
"object0": {},
"array0": [],
"object1": {
"null": null,
"object1-1": {
"bool": false,
"float": 3.14159
}
},
"array1": [
null,
{},
{
"hello": "world",
"hola": "mundo"
},
[
1,
0,
1
]
]
}`,
},
{
desc: "string contains rune error",
write: func(e *json.Encoder) {
// WriteString returns non-fatal error for invalid UTF sequence, but
// should still output the written value. See TestWriteStringError
// below that checks for this.
e.StartObject()
e.WriteName("invalid rune")
e.WriteString("abc\xff")
e.EndObject()
},
wantOut: "{\"invalid rune\":\"abc\xff\"}",
}}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
if tc.wantOut != "" {
enc, err := json.NewEncoder("")
if err != nil {
t.Fatalf("NewEncoder() returned error: %v", err)
}
tc.write(enc)
got := string(enc.Bytes())
if got != tc.wantOut {
t.Errorf("%s:\n<got>:\n%v\n<want>\n%v\n", tc.desc, got, tc.wantOut)
}
}
if tc.wantOutIndent != "" {
enc, err := json.NewEncoder("\t")
if err != nil {
t.Fatalf("NewEncoder() returned error: %v", err)
}
tc.write(enc)
got, want := string(enc.Bytes()), tc.wantOutIndent
if got != want {
t.Errorf("%s(indent):\n<got>:\n%v\n<want>\n%v\n<diff -want +got>\n%v\n",
tc.desc, got, want, cmp.Diff(want, got, splitLines))
}
}
})
}
}
func TestWriteStringError(t *testing.T) {
tests := []string{"abc\xff"}
for _, in := range tests {
t.Run(in, func(t *testing.T) {
enc, err := json.NewEncoder("")
if err != nil {
t.Fatalf("NewEncoder() returned error: %v", err)
}
if err := enc.WriteString(in); err == nil {
t.Errorf("WriteString(%v): got nil error, want error", in)
}
})
}
}