mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-14 09:44:39 +00:00
5c1f700acc
This test was accidentally duplicated in CL 569356. Change-Id: I0e2f81fbebe9b4c37bf6d28615e0bf51d28cfc4c Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/607777 Auto-Submit: Michael Stapelberg <stapelberg@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Christian Höppner <hoeppi@google.com>
1423 lines
25 KiB
Go
1423 lines
25 KiB
Go
// 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 (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"testing"
|
|
"unicode/utf8"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
"google.golang.org/protobuf/internal/encoding/json"
|
|
)
|
|
|
|
type R struct {
|
|
// E is expected error substring from calling Decoder.Read if set.
|
|
E string
|
|
// V is one of the checker implementations that validates the token value.
|
|
V checker
|
|
// P is expected Token.Pos() if set > 0.
|
|
P int
|
|
// RS is expected result from Token.RawString() if not empty.
|
|
RS string
|
|
}
|
|
|
|
// checker defines API for Token validation.
|
|
type checker interface {
|
|
// check checks and expects for token API call to return and compare
|
|
// against implementation-stored value. Returns empty string if success,
|
|
// else returns error message describing the error.
|
|
check(json.Token) string
|
|
}
|
|
|
|
// checkers that checks the token kind only.
|
|
var (
|
|
EOF = kindOnly{json.EOF}
|
|
Null = kindOnly{json.Null}
|
|
ObjectOpen = kindOnly{json.ObjectOpen}
|
|
ObjectClose = kindOnly{json.ObjectClose}
|
|
ArrayOpen = kindOnly{json.ArrayOpen}
|
|
ArrayClose = kindOnly{json.ArrayClose}
|
|
)
|
|
|
|
type kindOnly struct {
|
|
want json.Kind
|
|
}
|
|
|
|
func (x kindOnly) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != x.want {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, x.want)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Name struct {
|
|
val string
|
|
}
|
|
|
|
func (x Name) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Name {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Name)
|
|
}
|
|
|
|
if got := tok.Name(); got != x.val {
|
|
return fmt.Sprintf("Token.Name(): got %v, want %v", got, x.val)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Bool struct {
|
|
val bool
|
|
}
|
|
|
|
func (x Bool) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Bool {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Bool)
|
|
}
|
|
|
|
if got := tok.Bool(); got != x.val {
|
|
return fmt.Sprintf("Token.Bool(): got %v, want %v", got, x.val)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Str struct {
|
|
val string
|
|
}
|
|
|
|
func (x Str) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.String {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.String)
|
|
}
|
|
|
|
if got := tok.ParsedString(); got != x.val {
|
|
return fmt.Sprintf("Token.ParsedString(): got %v, want %v", got, x.val)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type F64 struct {
|
|
val float64
|
|
}
|
|
|
|
func (x F64) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Number {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Number)
|
|
}
|
|
|
|
got, ok := tok.Float(64)
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Float(64): returned not ok")
|
|
}
|
|
if got != x.val {
|
|
return fmt.Sprintf("Token.Float(64): got %v, want %v", got, x.val)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type F32 struct {
|
|
val float32
|
|
}
|
|
|
|
func (x F32) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Number {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Number)
|
|
}
|
|
|
|
got, ok := tok.Float(32)
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Float(32): returned not ok")
|
|
}
|
|
if float32(got) != x.val {
|
|
return fmt.Sprintf("Token.Float(32): got %v, want %v", got, x.val)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// NotF64 is a checker to validate a Number token where Token.Float(64) returns not ok.
|
|
var NotF64 = xf64{}
|
|
|
|
type xf64 struct{}
|
|
|
|
func (x xf64) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Number {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Number)
|
|
}
|
|
|
|
_, ok := tok.Float(64)
|
|
if ok {
|
|
return fmt.Sprintf("Token.Float(64): returned ok")
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// NotF32 is a checker to validate a Number token where Token.Float(32) returns not ok.
|
|
var NotF32 = xf32{}
|
|
|
|
type xf32 struct{}
|
|
|
|
func (x xf32) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Number {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Number)
|
|
}
|
|
|
|
_, ok := tok.Float(32)
|
|
if ok {
|
|
return fmt.Sprintf("Token.Float(32): returned ok")
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type I64 struct {
|
|
val int64
|
|
}
|
|
|
|
func (x I64) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Number {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Number)
|
|
}
|
|
|
|
got, ok := tok.Int(64)
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Int(64): returned not ok")
|
|
}
|
|
if got != x.val {
|
|
return fmt.Sprintf("Token.Int(64): got %v, want %v", got, x.val)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type I32 struct {
|
|
val int32
|
|
}
|
|
|
|
func (x I32) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Number {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Number)
|
|
}
|
|
|
|
got, ok := tok.Int(32)
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Int(32): returned not ok")
|
|
}
|
|
if int32(got) != x.val {
|
|
return fmt.Sprintf("Token.Int(32): got %v, want %v", got, x.val)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// NotI64 is a checker to validate a Number token where Token.Int(64) returns not ok.
|
|
var NotI64 = xi64{}
|
|
|
|
type xi64 struct{}
|
|
|
|
func (x xi64) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Number {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Number)
|
|
}
|
|
|
|
_, ok := tok.Int(64)
|
|
if ok {
|
|
return fmt.Sprintf("Token.Int(64): returned ok")
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// NotI32 is a checker to validate a Number token where Token.Int(32) returns not ok.
|
|
var NotI32 = xi32{}
|
|
|
|
type xi32 struct{}
|
|
|
|
func (x xi32) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Number {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Number)
|
|
}
|
|
|
|
_, ok := tok.Int(32)
|
|
if ok {
|
|
return fmt.Sprintf("Token.Int(32): returned ok")
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Ui64 struct {
|
|
val uint64
|
|
}
|
|
|
|
func (x Ui64) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Number {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Number)
|
|
}
|
|
|
|
got, ok := tok.Uint(64)
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Uint(64): returned not ok")
|
|
}
|
|
if got != x.val {
|
|
return fmt.Sprintf("Token.Uint(64): got %v, want %v", got, x.val)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Ui32 struct {
|
|
val uint32
|
|
}
|
|
|
|
func (x Ui32) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Number {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Number)
|
|
}
|
|
|
|
got, ok := tok.Uint(32)
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Uint(32): returned not ok")
|
|
}
|
|
if uint32(got) != x.val {
|
|
return fmt.Sprintf("Token.Uint(32): got %v, want %v", got, x.val)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// NotUi64 is a checker to validate a Number token where Token.Uint(64) returns not ok.
|
|
var NotUi64 = xui64{}
|
|
|
|
type xui64 struct{}
|
|
|
|
func (x xui64) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Number {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Number)
|
|
}
|
|
|
|
_, ok := tok.Uint(64)
|
|
if ok {
|
|
return fmt.Sprintf("Token.Uint(64): returned ok")
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// NotI32 is a checker to validate a Number token where Token.Uint(32) returns not ok.
|
|
var NotUi32 = xui32{}
|
|
|
|
type xui32 struct{}
|
|
|
|
func (x xui32) check(tok json.Token) string {
|
|
if got := tok.Kind(); got != json.Number {
|
|
return fmt.Sprintf("Token.Kind(): got %v, want %v", got, json.Number)
|
|
}
|
|
|
|
_, ok := tok.Uint(32)
|
|
if ok {
|
|
return fmt.Sprintf("Token.Uint(32): returned ok")
|
|
}
|
|
return ""
|
|
}
|
|
|
|
var errEOF = json.ErrUnexpectedEOF.Error()
|
|
|
|
func TestDecoder(t *testing.T) {
|
|
const space = " \n\r\t"
|
|
|
|
tests := []struct {
|
|
in string
|
|
// want is a list of expected values returned from calling
|
|
// Decoder.Read. An item makes the test code invoke
|
|
// Decoder.Read and compare against R.E for error returned or use R.V to
|
|
// validate the returned Token object.
|
|
want []R
|
|
}{
|
|
{
|
|
in: ``,
|
|
want: []R{{V: EOF}},
|
|
},
|
|
{
|
|
in: space,
|
|
want: []R{{V: EOF}},
|
|
},
|
|
{
|
|
// Calling Read after EOF will keep returning EOF for
|
|
// succeeding Read calls.
|
|
in: space,
|
|
want: []R{
|
|
{V: EOF},
|
|
{V: EOF},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
|
|
// JSON literals.
|
|
{
|
|
in: space + `null` + space,
|
|
want: []R{
|
|
{V: Null, P: len(space), RS: `null`},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `true` + space,
|
|
want: []R{
|
|
{V: Bool{true}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `false` + space,
|
|
want: []R{
|
|
{V: Bool{false}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Error returned will produce the same error again.
|
|
in: space + `foo` + space,
|
|
want: []R{
|
|
{E: `invalid value foo`},
|
|
{E: `invalid value foo`},
|
|
},
|
|
},
|
|
|
|
// JSON strings.
|
|
{
|
|
in: space + `""` + space,
|
|
want: []R{
|
|
{V: Str{}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `"hello"` + space,
|
|
want: []R{
|
|
{V: Str{"hello"}, RS: `"hello"`},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `"hello`,
|
|
want: []R{{E: errEOF}},
|
|
},
|
|
{
|
|
in: "\"\x00\"",
|
|
want: []R{{E: `invalid character '\x00' in string`}},
|
|
},
|
|
{
|
|
in: "\"\u0031\u0032\"",
|
|
want: []R{
|
|
{V: Str{"12"}, RS: "\"\u0031\u0032\""},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Invalid UTF-8 error is returned in ReadString instead of Read.
|
|
in: "\"\xff\"",
|
|
want: []R{{E: `syntax error (line 1:1): invalid UTF-8 in string`}},
|
|
},
|
|
{
|
|
in: `"` + string(utf8.RuneError) + `"`,
|
|
want: []R{
|
|
{V: Str{string(utf8.RuneError)}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `"\uFFFD"`,
|
|
want: []R{
|
|
{V: Str{string(utf8.RuneError)}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `"\x"`,
|
|
want: []R{{E: `invalid escape code "\\x" in string`}},
|
|
},
|
|
{
|
|
in: `"\uXXXX"`,
|
|
want: []R{{E: `invalid escape code "\\uXXXX" in string`}},
|
|
},
|
|
{
|
|
in: `"\uDEAD"`, // unmatched surrogate pair
|
|
want: []R{{E: errEOF}},
|
|
},
|
|
{
|
|
in: `"\uDEAD\uBEEF"`, // invalid surrogate half
|
|
want: []R{{E: `invalid escape code "\\uBEEF" in string`}},
|
|
},
|
|
{
|
|
in: `"\uD800\udead"`, // valid surrogate pair
|
|
want: []R{
|
|
{V: Str{`𐊭`}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `"\u0000\"\\\/\b\f\n\r\t"`,
|
|
want: []R{
|
|
{V: Str{"\u0000\"\\/\b\f\n\r\t"}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
|
|
// Invalid JSON numbers.
|
|
{
|
|
in: `-`,
|
|
want: []R{{E: `invalid value -`}},
|
|
},
|
|
{
|
|
in: `+0`,
|
|
want: []R{{E: `invalid value +0`}},
|
|
},
|
|
{
|
|
in: `-+`,
|
|
want: []R{{E: `invalid value -+`}},
|
|
},
|
|
{
|
|
in: `0.`,
|
|
want: []R{{E: `invalid value 0.`}},
|
|
},
|
|
{
|
|
in: `.1`,
|
|
want: []R{{E: `invalid value .1`}},
|
|
},
|
|
{
|
|
in: `1.0.1`,
|
|
want: []R{{E: `invalid value 1.0.1`}},
|
|
},
|
|
{
|
|
in: `1..1`,
|
|
want: []R{{E: `invalid value 1..1`}},
|
|
},
|
|
{
|
|
in: `-1-2`,
|
|
want: []R{{E: `invalid value -1-2`}},
|
|
},
|
|
{
|
|
in: `01`,
|
|
want: []R{{E: `invalid value 01`}},
|
|
},
|
|
{
|
|
in: `1e`,
|
|
want: []R{{E: `invalid value 1e`}},
|
|
},
|
|
{
|
|
in: `1e1.2`,
|
|
want: []R{{E: `invalid value 1e1.2`}},
|
|
},
|
|
{
|
|
in: `1Ee`,
|
|
want: []R{{E: `invalid value 1Ee`}},
|
|
},
|
|
{
|
|
in: `1.e1`,
|
|
want: []R{{E: `invalid value 1.e1`}},
|
|
},
|
|
{
|
|
in: `1.e+`,
|
|
want: []R{{E: `invalid value 1.e+`}},
|
|
},
|
|
{
|
|
in: `1e+-2`,
|
|
want: []R{{E: `invalid value 1e+-2`}},
|
|
},
|
|
{
|
|
in: `1e--2`,
|
|
want: []R{{E: `invalid value 1e--2`}},
|
|
},
|
|
{
|
|
in: `1.0true`,
|
|
want: []R{{E: `invalid value 1.0true`}},
|
|
},
|
|
|
|
// JSON numbers as floating point.
|
|
{
|
|
in: space + `0.0` + space,
|
|
want: []R{
|
|
{V: F32{0}, P: len(space), RS: `0.0`},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `0` + space,
|
|
want: []R{
|
|
{V: F32{0}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `-0` + space,
|
|
want: []R{
|
|
{V: F32{float32(math.Copysign(0, -1))}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `-0`,
|
|
want: []R{
|
|
{V: F64{math.Copysign(0, -1)}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `-0.0`,
|
|
want: []R{
|
|
{V: F32{float32(math.Copysign(0, -1))}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `-0.0`,
|
|
want: []R{
|
|
{V: F64{math.Copysign(0, -1)}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `-1.02`,
|
|
want: []R{
|
|
{V: F32{-1.02}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `1.020000`,
|
|
want: []R{
|
|
{V: F32{1.02}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `-1.0e0`,
|
|
want: []R{
|
|
{V: F32{-1}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `1.0e-000`,
|
|
want: []R{
|
|
{V: F32{1}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `1e+00`,
|
|
want: []R{
|
|
{V: F32{1}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `1.02e3`,
|
|
want: []R{
|
|
{V: F32{1.02e3}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `-1.02E03`,
|
|
want: []R{
|
|
{V: F32{-1.02e3}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `1.0200e+3`,
|
|
want: []R{
|
|
{V: F32{1.02e3}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `-1.0200E+03`,
|
|
want: []R{
|
|
{V: F32{-1.02e3}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `1.0200e-3`,
|
|
want: []R{
|
|
{V: F32{1.02e-3}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `-1.0200E-03`,
|
|
want: []R{
|
|
{V: F32{-1.02e-3}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Exceeds max float32 limit, but should be ok for float64.
|
|
in: `3.4e39`,
|
|
want: []R{
|
|
{V: F64{3.4e39}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
|
|
{
|
|
// Exceeds max float32 limit.
|
|
in: `3.4e39`,
|
|
want: []R{
|
|
{V: NotF32},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Less than negative max float32 limit.
|
|
in: `-3.4e39`,
|
|
want: []R{
|
|
{V: NotF32},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Exceeds max float64 limit.
|
|
in: `1.79e+309`,
|
|
want: []R{
|
|
{V: NotF64},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Less than negative max float64 limit.
|
|
in: `-1.79e+309`,
|
|
want: []R{
|
|
{V: NotF64},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
|
|
// JSON numbers as signed integers.
|
|
{
|
|
in: space + `0` + space,
|
|
want: []R{
|
|
{V: I32{0}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `-0` + space,
|
|
want: []R{
|
|
{V: I32{0}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Fractional part equals 0 is ok.
|
|
in: `1.00000`,
|
|
want: []R{
|
|
{V: I32{1}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Fractional part not equals 0 returns error.
|
|
in: `1.0000000001`,
|
|
want: []R{
|
|
{V: NotI32},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `0e0`,
|
|
want: []R{
|
|
{V: I32{0}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `0.0E0`,
|
|
want: []R{
|
|
{V: I32{0}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `0.0E10`,
|
|
want: []R{
|
|
{V: I32{0}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `-1`,
|
|
want: []R{
|
|
{V: I32{-1}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `1.0e+0`,
|
|
want: []R{
|
|
{V: I32{1}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `-1E-0`,
|
|
want: []R{
|
|
{V: I32{-1}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `1E1`,
|
|
want: []R{
|
|
{V: I32{10}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `-100.00e-02`,
|
|
want: []R{
|
|
{V: I32{-1}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `0.1200E+02`,
|
|
want: []R{
|
|
{V: I64{12}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `0.012e2`,
|
|
want: []R{
|
|
{V: NotI32},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `12e-2`,
|
|
want: []R{
|
|
{V: NotI32},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Exceeds math.MaxInt32.
|
|
in: `2147483648`,
|
|
want: []R{
|
|
{V: NotI32},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Exceeds math.MinInt32.
|
|
in: `-2147483649`,
|
|
want: []R{
|
|
{V: NotI32},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Exceeds math.MaxInt32, but ok for int64.
|
|
in: `2147483648`,
|
|
want: []R{
|
|
{V: I64{2147483648}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Exceeds math.MinInt32, but ok for int64.
|
|
in: `-2147483649`,
|
|
want: []R{
|
|
{V: I64{-2147483649}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Exceeds math.MaxInt64.
|
|
in: `9223372036854775808`,
|
|
want: []R{
|
|
{V: NotI64},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Exceeds math.MinInt64.
|
|
in: `-9223372036854775809`,
|
|
want: []R{
|
|
{V: NotI64},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
|
|
// JSON numbers as unsigned integers.
|
|
{
|
|
in: space + `0` + space,
|
|
want: []R{
|
|
{V: Ui32{0}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `-0` + space,
|
|
want: []R{
|
|
{V: Ui32{0}},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `-1`,
|
|
want: []R{
|
|
{V: NotUi32},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Exceeds math.MaxUint32.
|
|
in: `4294967296`,
|
|
want: []R{
|
|
{V: NotUi32},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
// Exceeds math.MaxUint64.
|
|
in: `18446744073709551616`,
|
|
want: []R{
|
|
{V: NotUi64},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
|
|
// JSON sequence of values.
|
|
{
|
|
in: `true null`,
|
|
want: []R{
|
|
{V: Bool{true}},
|
|
{E: `(line 1:6): unexpected token null`},
|
|
},
|
|
},
|
|
{
|
|
in: "null false",
|
|
want: []R{
|
|
{V: Null},
|
|
{E: `unexpected token false`},
|
|
},
|
|
},
|
|
{
|
|
in: `true,false`,
|
|
want: []R{
|
|
{V: Bool{true}},
|
|
{E: `unexpected token ,`},
|
|
},
|
|
},
|
|
{
|
|
in: `47"hello"`,
|
|
want: []R{
|
|
{V: I32{47}},
|
|
{E: `unexpected token "hello"`},
|
|
},
|
|
},
|
|
{
|
|
in: `47 "hello"`,
|
|
want: []R{
|
|
{V: I32{47}},
|
|
{E: `unexpected token "hello"`},
|
|
},
|
|
},
|
|
{
|
|
in: `true 42`,
|
|
want: []R{
|
|
{V: Bool{true}},
|
|
{E: `unexpected token 42`},
|
|
},
|
|
},
|
|
|
|
// JSON arrays.
|
|
{
|
|
in: space + `[]` + space,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: ArrayClose},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `[` + space + `]` + space,
|
|
want: []R{
|
|
{V: ArrayOpen, P: len(space), RS: `[`},
|
|
{V: ArrayClose},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `[` + space,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{E: errEOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `]` + space,
|
|
want: []R{{E: `unexpected token ]`}},
|
|
},
|
|
{
|
|
in: `[null,true,false, 1e1, "hello" ]`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: Null},
|
|
{V: Bool{true}},
|
|
{V: Bool{false}},
|
|
{V: I32{10}},
|
|
{V: Str{"hello"}},
|
|
{V: ArrayClose},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `[` + space + `true` + space + `,` + space + `"hello"` + space + `]`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: Bool{true}},
|
|
{V: Str{"hello"}},
|
|
{V: ArrayClose},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `[` + space + `true` + space + `,` + space + `]`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: Bool{true}},
|
|
{E: `unexpected token ]`},
|
|
},
|
|
},
|
|
{
|
|
in: `[` + space + `false` + space + `]`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: Bool{false}},
|
|
{V: ArrayClose},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `[` + space + `1` + space + `0` + space + `]`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: I64{1}},
|
|
{E: `unexpected token 0`},
|
|
},
|
|
},
|
|
{
|
|
in: `[null`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: Null},
|
|
{E: errEOF},
|
|
},
|
|
},
|
|
{
|
|
in: `[foo]`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{E: `invalid value foo`},
|
|
},
|
|
},
|
|
{
|
|
in: `[{}, "hello", [true, false], null]`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: ObjectOpen},
|
|
{V: ObjectClose},
|
|
{V: Str{"hello"}},
|
|
{V: ArrayOpen},
|
|
{V: Bool{true}},
|
|
{V: Bool{false}},
|
|
{V: ArrayClose},
|
|
{V: Null},
|
|
{V: ArrayClose},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `[{ ]`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: ObjectOpen},
|
|
{E: `unexpected token ]`},
|
|
},
|
|
},
|
|
{
|
|
in: `[[ ]`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: ArrayOpen},
|
|
{V: ArrayClose},
|
|
{E: errEOF},
|
|
},
|
|
},
|
|
{
|
|
in: `[,]`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{E: `unexpected token ,`},
|
|
},
|
|
},
|
|
{
|
|
in: `[true "hello"]`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: Bool{true}},
|
|
{E: `unexpected token "hello"`},
|
|
},
|
|
},
|
|
{
|
|
in: `[] null`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: ArrayClose},
|
|
{E: `unexpected token null`},
|
|
},
|
|
},
|
|
{
|
|
in: `true []`,
|
|
want: []R{
|
|
{V: Bool{true}},
|
|
{E: `unexpected token [`},
|
|
},
|
|
},
|
|
|
|
// JSON objects.
|
|
{
|
|
in: space + `{}` + space,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{V: ObjectClose},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `{` + space + `}` + space,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{V: ObjectClose},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `{` + space,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{E: errEOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `}` + space,
|
|
want: []R{{E: `unexpected token }`}},
|
|
},
|
|
{
|
|
in: `{` + space + `null` + space + `}`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{E: `unexpected token null`},
|
|
},
|
|
},
|
|
{
|
|
in: `{[]}`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{E: `(line 1:2): unexpected token [`},
|
|
},
|
|
},
|
|
{
|
|
in: `{,}`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{E: `unexpected token ,`},
|
|
},
|
|
},
|
|
{
|
|
in: `{"345678"}`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{E: `(line 1:10): unexpected character }, missing ":" after field name`},
|
|
},
|
|
},
|
|
{
|
|
in: `{` + space + `"hello"` + space + `:` + space + `"world"` + space + `}`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{V: Name{"hello"}, P: len(space) + 1, RS: `"hello"`},
|
|
{V: Str{"world"}, RS: `"world"`},
|
|
{V: ObjectClose},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `{"hello" "world"}`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{E: `(line 1:10): unexpected character ", missing ":" after field name`},
|
|
},
|
|
},
|
|
{
|
|
in: `{"hello":`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{V: Name{"hello"}},
|
|
{E: errEOF},
|
|
},
|
|
},
|
|
{
|
|
in: `{"hello":"world"`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{V: Name{"hello"}},
|
|
{V: Str{"world"}},
|
|
{E: errEOF},
|
|
},
|
|
},
|
|
{
|
|
in: `{"hello":"world",`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{V: Name{"hello"}},
|
|
{V: Str{"world"}},
|
|
{E: errEOF},
|
|
},
|
|
},
|
|
{
|
|
in: `{""`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{E: errEOF},
|
|
},
|
|
},
|
|
{
|
|
in: `{"":`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{V: Name{""}},
|
|
{E: errEOF},
|
|
},
|
|
},
|
|
{
|
|
in: `{"34":"89",}`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
{V: Name{"34"}, RS: `"34"`},
|
|
{V: Str{"89"}},
|
|
{E: `syntax error (line 1:12): unexpected token }`},
|
|
},
|
|
},
|
|
{
|
|
in: `{
|
|
"number": 123e2,
|
|
"bool" : false,
|
|
"object": {"string": "world"},
|
|
"null" : null,
|
|
"array" : [1.01, "hello", true],
|
|
"string": "hello"
|
|
}`,
|
|
want: []R{
|
|
{V: ObjectOpen},
|
|
|
|
{V: Name{"number"}},
|
|
{V: I32{12300}},
|
|
|
|
{V: Name{"bool"}},
|
|
{V: Bool{false}},
|
|
|
|
{V: Name{"object"}},
|
|
{V: ObjectOpen},
|
|
{V: Name{"string"}},
|
|
{V: Str{"world"}},
|
|
{V: ObjectClose},
|
|
|
|
{V: Name{"null"}},
|
|
{V: Null},
|
|
|
|
{V: Name{"array"}},
|
|
{V: ArrayOpen},
|
|
{V: F32{1.01}},
|
|
{V: Str{"hello"}},
|
|
{V: Bool{true}},
|
|
{V: ArrayClose},
|
|
|
|
{V: Name{"string"}},
|
|
{V: Str{"hello"}},
|
|
|
|
{V: ObjectClose},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `[
|
|
{"object": {"number": 47}},
|
|
["list"],
|
|
null
|
|
]`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
|
|
{V: ObjectOpen},
|
|
{V: Name{"object"}},
|
|
{V: ObjectOpen},
|
|
{V: Name{"number"}},
|
|
{V: I32{47}},
|
|
{V: ObjectClose},
|
|
{V: ObjectClose},
|
|
|
|
{V: ArrayOpen},
|
|
{V: Str{"list"}},
|
|
{V: ArrayClose},
|
|
|
|
{V: Null},
|
|
|
|
{V: ArrayClose},
|
|
{V: EOF},
|
|
},
|
|
},
|
|
|
|
// Tests for line and column info.
|
|
{
|
|
in: `12345678 x`,
|
|
want: []R{
|
|
{V: I64{12345678}},
|
|
{E: `syntax error (line 1:10): invalid value x`},
|
|
},
|
|
},
|
|
{
|
|
in: "\ntrue\n x",
|
|
want: []R{
|
|
{V: Bool{true}},
|
|
{E: `syntax error (line 3:4): invalid value x`},
|
|
},
|
|
},
|
|
{
|
|
in: `"💩"x`,
|
|
want: []R{
|
|
{V: Str{"💩"}},
|
|
{E: `syntax error (line 1:4): invalid value x`},
|
|
},
|
|
},
|
|
{
|
|
in: "\n\n[\"🔥🔥🔥\"x",
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: Str{"🔥🔥🔥"}},
|
|
{E: `syntax error (line 3:7): invalid value x`},
|
|
},
|
|
},
|
|
{
|
|
// Multi-rune emojis.
|
|
in: `["👍🏻👍🏿"x`,
|
|
want: []R{
|
|
{V: ArrayOpen},
|
|
{V: Str{"👍🏻👍🏿"}},
|
|
{E: `syntax error (line 1:8): invalid value x`},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run("", func(t *testing.T) {
|
|
dec := json.NewDecoder([]byte(tc.in))
|
|
for i, want := range tc.want {
|
|
peekTok, peekErr := dec.Peek()
|
|
tok, err := dec.Read()
|
|
if err != nil {
|
|
if want.E == "" {
|
|
errorf(t, tc.in, "want#%d: Read() got unexpected error: %v", i, err)
|
|
} else if !strings.Contains(err.Error(), want.E) {
|
|
errorf(t, tc.in, "want#%d: Read() got %q, want %q", i, err, want.E)
|
|
}
|
|
return
|
|
}
|
|
if want.E != "" {
|
|
errorf(t, tc.in, "want#%d: Read() got nil error, want %q", i, want.E)
|
|
return
|
|
}
|
|
checkToken(t, tok, i, want, tc.in)
|
|
if !cmp.Equal(tok, peekTok, cmp.Comparer(json.TokenEquals)) {
|
|
errorf(t, tc.in, "want#%d: Peek() %+v != Read() token %+v", i, peekTok, tok)
|
|
}
|
|
if err != peekErr {
|
|
errorf(t, tc.in, "want#%d: Peek() error %v != Read() error %v", i, err, peekErr)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func checkToken(t *testing.T, tok json.Token, idx int, r R, in string) {
|
|
// Validate Token.Pos() if R.P is set.
|
|
if r.P > 0 {
|
|
got := tok.Pos()
|
|
if got != r.P {
|
|
errorf(t, in, "want#%d: Token.Pos() got %v want %v", idx, got, r.P)
|
|
}
|
|
}
|
|
// Validate Token.RawString if R.RS is set.
|
|
if len(r.RS) > 0 {
|
|
got := tok.RawString()
|
|
if got != r.RS {
|
|
errorf(t, in, "want#%d: Token.RawString() got %v want %v", idx, got, r.P)
|
|
}
|
|
}
|
|
|
|
// Skip checking for Token details if r.V is not set.
|
|
if r.V == nil {
|
|
return
|
|
}
|
|
|
|
if err := r.V.check(tok); err != "" {
|
|
errorf(t, in, "want#%d: %s", idx, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
func errorf(t *testing.T, in string, fmtStr string, args ...any) {
|
|
t.Helper()
|
|
vargs := []any{in}
|
|
for _, arg := range args {
|
|
vargs = append(vargs, arg)
|
|
}
|
|
t.Errorf("input:\n%s\n~end~\n"+fmtStr, vargs...)
|
|
}
|
|
|
|
func TestClone(t *testing.T) {
|
|
input := `{"outer":{"str":"hello", "number": 123}}`
|
|
dec := json.NewDecoder([]byte(input))
|
|
|
|
// Clone at the start should produce the same reads as the original.
|
|
clone := dec.Clone()
|
|
compareDecoders(t, dec, clone)
|
|
|
|
// Advance to inner object, clone and compare again.
|
|
dec.Read() // Read ObjectOpen.
|
|
dec.Read() // Read Name.
|
|
clone = dec.Clone()
|
|
compareDecoders(t, dec, clone)
|
|
}
|
|
|
|
func compareDecoders(t *testing.T, d1 *json.Decoder, d2 *json.Decoder) {
|
|
for {
|
|
tok1, err1 := d1.Read()
|
|
tok2, err2 := d2.Read()
|
|
if tok1.Kind() != tok2.Kind() {
|
|
t.Errorf("cloned decoder: got Kind %v, want %v", tok2.Kind(), tok1.Kind())
|
|
}
|
|
if tok1.RawString() != tok2.RawString() {
|
|
t.Errorf("cloned decoder: got RawString %v, want %v", tok2.RawString(), tok1.RawString())
|
|
}
|
|
if err1 != err2 {
|
|
t.Errorf("cloned decoder: got error %v, want %v", err2, err1)
|
|
}
|
|
if tok1.Kind() == json.EOF {
|
|
break
|
|
}
|
|
}
|
|
}
|