mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-04-09 21:44:37 +00:00
Avoid very long errors returned by limiting the length of what errId returns to 32 bytes (the value is chosen so that the error will not be too long yet useful). Append ellipsis to the returned value to denote that it was truncated. Change-Id: I232d5192a2d9ad675daa0be0fe0c8518489c2953 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/406694 Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Lasse Folger <lassefolger@google.com>
1946 lines
46 KiB
Go
1946 lines
46 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 text_test
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"testing"
|
|
"unicode/utf8"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
"google.golang.org/protobuf/internal/encoding/text"
|
|
"google.golang.org/protobuf/internal/flags"
|
|
)
|
|
|
|
var eofErr = text.ErrUnexpectedEOF.Error()
|
|
|
|
type R struct {
|
|
// K is expected Kind of the returned Token object from calling Decoder.Read.
|
|
K text.Kind
|
|
// E is expected error substring from calling Decoder.Read if set.
|
|
E string
|
|
// T contains NT (if K is Name) or ST (if K is Scalar) or nil (others)
|
|
T interface{}
|
|
// P is expected Token.Pos if set > 0.
|
|
P int
|
|
// RS is expected result from Token.RawString() if not empty.
|
|
RS string
|
|
}
|
|
|
|
// NT contains data for checking against a name token.
|
|
type NT struct {
|
|
K text.NameKind
|
|
// Sep is true if name token should have separator character, else false.
|
|
Sep bool
|
|
// If K is IdentName or TypeName, invoke corresponding getter and compare against this field.
|
|
S string
|
|
// If K is FieldNumber, invoke getter and compare against this field.
|
|
N int32
|
|
}
|
|
|
|
// ST contains data for checking against a scalar token.
|
|
type ST struct {
|
|
// checker that is expected to return OK.
|
|
ok checker
|
|
// checker that is expected to return not OK.
|
|
nok checker
|
|
}
|
|
|
|
// checker provides API for the token wrapper API call types Str, Enum, Bool,
|
|
// Uint64, Uint32, Int64, Int32, Float64, Float32.
|
|
type checker interface {
|
|
// checkOk checks and expects for token API call to return ok and compare
|
|
// against implementation-stored value. Returns empty string if success,
|
|
// else returns error message describing the error.
|
|
checkOk(text.Token) string
|
|
// checkNok checks and expects for token API call to return not ok. Returns
|
|
// empty string if success, else returns error message describing the error.
|
|
checkNok(text.Token) string
|
|
}
|
|
|
|
type Str struct {
|
|
val string
|
|
}
|
|
|
|
func (s Str) checkOk(tok text.Token) string {
|
|
got, ok := tok.String()
|
|
if !ok {
|
|
return fmt.Sprintf("Token.String() returned not OK for token: %v", tok.RawString())
|
|
}
|
|
if got != s.val {
|
|
return fmt.Sprintf("Token.String() got %q want %q for token: %v", got, s.val, tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (s Str) checkNok(tok text.Token) string {
|
|
if _, ok := tok.String(); ok {
|
|
return fmt.Sprintf("Token.String() returned OK for token: %v", tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Enum struct {
|
|
val string
|
|
}
|
|
|
|
func (e Enum) checkOk(tok text.Token) string {
|
|
got, ok := tok.Enum()
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Enum() returned not OK for token: %v", tok.RawString())
|
|
}
|
|
if got != e.val {
|
|
return fmt.Sprintf("Token.Enum() got %q want %q for token: %v", got, e.val, tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (e Enum) checkNok(tok text.Token) string {
|
|
if _, ok := tok.Enum(); ok {
|
|
return fmt.Sprintf("Token.Enum() returned OK for token: %v", tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Bool struct {
|
|
val bool
|
|
}
|
|
|
|
func (b Bool) checkOk(tok text.Token) string {
|
|
got, ok := tok.Bool()
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Bool() returned not OK for token: %v", tok.RawString())
|
|
}
|
|
if got != b.val {
|
|
return fmt.Sprintf("Token.Bool() got %v want %v for token: %v", got, b.val, tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (b Bool) checkNok(tok text.Token) string {
|
|
if _, ok := tok.Bool(); ok {
|
|
return fmt.Sprintf("Token.Bool() returned OK for token: %v", tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Uint64 struct {
|
|
val uint64
|
|
}
|
|
|
|
func (n Uint64) checkOk(tok text.Token) string {
|
|
got, ok := tok.Uint64()
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Uint64() returned not OK for token: %v", tok.RawString())
|
|
}
|
|
if got != n.val {
|
|
return fmt.Sprintf("Token.Uint64() got %v want %v for token: %v", got, n.val, tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (n Uint64) checkNok(tok text.Token) string {
|
|
if _, ok := tok.Uint64(); ok {
|
|
return fmt.Sprintf("Token.Uint64() returned OK for token: %v", tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Uint32 struct {
|
|
val uint32
|
|
}
|
|
|
|
func (n Uint32) checkOk(tok text.Token) string {
|
|
got, ok := tok.Uint32()
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Uint32() returned not OK for token: %v", tok.RawString())
|
|
}
|
|
if got != n.val {
|
|
return fmt.Sprintf("Token.Uint32() got %v want %v for token: %v", got, n.val, tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (n Uint32) checkNok(tok text.Token) string {
|
|
if _, ok := tok.Uint32(); ok {
|
|
return fmt.Sprintf("Token.Uint32() returned OK for token: %v", tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Int64 struct {
|
|
val int64
|
|
}
|
|
|
|
func (n Int64) checkOk(tok text.Token) string {
|
|
got, ok := tok.Int64()
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Int64() returned not OK for token: %v", tok.RawString())
|
|
}
|
|
if got != n.val {
|
|
return fmt.Sprintf("Token.Int64() got %v want %v for token: %v", got, n.val, tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (n Int64) checkNok(tok text.Token) string {
|
|
if _, ok := tok.Int64(); ok {
|
|
return fmt.Sprintf("Token.Int64() returned OK for token: %v", tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Int32 struct {
|
|
val int32
|
|
}
|
|
|
|
func (n Int32) checkOk(tok text.Token) string {
|
|
got, ok := tok.Int32()
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Int32() returned not OK for token: %v", tok.RawString())
|
|
}
|
|
if got != n.val {
|
|
return fmt.Sprintf("Token.Int32() got %v want %v for token: %v", got, n.val, tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (n Int32) checkNok(tok text.Token) string {
|
|
if _, ok := tok.Int32(); ok {
|
|
return fmt.Sprintf("Token.Int32() returned OK for token: %v", tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Float64 struct {
|
|
val float64
|
|
}
|
|
|
|
func (n Float64) checkOk(tok text.Token) string {
|
|
got, ok := tok.Float64()
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Float64() returned not OK for token: %v", tok.RawString())
|
|
}
|
|
if math.IsNaN(got) && math.IsNaN(n.val) {
|
|
return ""
|
|
}
|
|
if got != n.val {
|
|
return fmt.Sprintf("Token.Float64() got %v want %v for token: %v", got, n.val, tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (n Float64) checkNok(tok text.Token) string {
|
|
if _, ok := tok.Float64(); ok {
|
|
return fmt.Sprintf("Token.Float64() returned OK for token: %v", tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Float32 struct {
|
|
val float32
|
|
}
|
|
|
|
func (n Float32) checkOk(tok text.Token) string {
|
|
got, ok := tok.Float32()
|
|
if !ok {
|
|
return fmt.Sprintf("Token.Float32() returned not OK for token: %v", tok.RawString())
|
|
}
|
|
if math.IsNaN(float64(got)) && math.IsNaN(float64(n.val)) {
|
|
return ""
|
|
}
|
|
if got != n.val {
|
|
return fmt.Sprintf("Token.Float32() got %v want %v for token: %v", got, n.val, tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (n Float32) checkNok(tok text.Token) string {
|
|
if _, ok := tok.Float32(); ok {
|
|
return fmt.Sprintf("Token.Float32() returned OK for token: %v", tok.RawString())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func TestDecoder(t *testing.T) {
|
|
const space = " \n\r\t"
|
|
tests := []struct {
|
|
in string
|
|
// want is a list of expected Tokens returned from calling Decoder.Read.
|
|
// An item makes the test code invoke Decoder.Read and compare against
|
|
// R.K and R.E. If R.K is Name, it compares
|
|
want []R
|
|
}{
|
|
{
|
|
in: "",
|
|
want: []R{{K: text.EOF}},
|
|
},
|
|
{
|
|
in: "# comment",
|
|
want: []R{{K: text.EOF}},
|
|
},
|
|
{
|
|
in: space + "# comment" + space,
|
|
want: []R{{K: text.EOF}},
|
|
},
|
|
{
|
|
in: space,
|
|
want: []R{{K: text.EOF, P: len(space)}},
|
|
},
|
|
{
|
|
// Calling Read after EOF will keep returning EOF for
|
|
// succeeding Read calls.
|
|
in: space,
|
|
want: []R{
|
|
{K: text.EOF},
|
|
{K: text.EOF},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
// NUL is an invalid whitespace since C++ uses C-strings.
|
|
in: "\x00",
|
|
want: []R{{E: "invalid field name: \x00"}},
|
|
},
|
|
|
|
// Field names.
|
|
{
|
|
in: "name",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, S: "name"}, RS: "name"},
|
|
{E: eofErr},
|
|
},
|
|
},
|
|
{
|
|
in: space + "name:" + space,
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "name"}},
|
|
{E: eofErr},
|
|
},
|
|
},
|
|
{
|
|
in: space + "name" + space + ":" + space,
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "name"}},
|
|
{E: eofErr},
|
|
},
|
|
},
|
|
{
|
|
in: "name # comment",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, S: "name"}},
|
|
{E: eofErr},
|
|
},
|
|
},
|
|
{
|
|
// Comments only extend until the newline.
|
|
in: "# comment \nname",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, S: "name"}, P: 11},
|
|
},
|
|
},
|
|
{
|
|
in: "name # comment \n:",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "name"}},
|
|
},
|
|
},
|
|
{
|
|
in: "name123",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, S: "name123"}},
|
|
},
|
|
},
|
|
{
|
|
in: "name_123",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, S: "name_123"}},
|
|
},
|
|
},
|
|
{
|
|
in: "_123",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, S: "_123"}},
|
|
},
|
|
},
|
|
{
|
|
in: ":",
|
|
want: []R{{E: "syntax error (line 1:1): invalid field name: :"}},
|
|
},
|
|
{
|
|
in: "\n\n\n {",
|
|
want: []R{{E: "syntax error (line 4:2): invalid field name: {"}},
|
|
},
|
|
{
|
|
in: "123name",
|
|
want: []R{{E: "invalid field name: 123name"}},
|
|
},
|
|
{
|
|
in: `/`,
|
|
want: []R{{E: `invalid field name: /`}},
|
|
},
|
|
{
|
|
in: `世界`,
|
|
want: []R{{E: `invalid field name: 世`}},
|
|
},
|
|
{
|
|
in: `1a/b`,
|
|
want: []R{{E: `invalid field name: 1a`}},
|
|
},
|
|
{
|
|
in: `1c\d`,
|
|
want: []R{{E: `invalid field name: 1c`}},
|
|
},
|
|
{
|
|
in: "\x84f",
|
|
want: []R{{E: "invalid field name: \x84"}},
|
|
},
|
|
{
|
|
in: "\uFFFDxxx",
|
|
want: []R{{E: "invalid field name: \uFFFD"}},
|
|
},
|
|
{
|
|
in: "-a234567890123456789012345678901234567890abc",
|
|
want: []R{{E: "invalid field name: -a2345678901234567890123456789012…"}},
|
|
},
|
|
{
|
|
in: "[type]",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.TypeName, S: "type"}, RS: "[type]"},
|
|
},
|
|
},
|
|
{
|
|
// V1 allows this syntax. C++ does not, however, C++ also fails if
|
|
// field is Any and does not contain '/'.
|
|
in: "[/type]",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.TypeName, S: "/type"}},
|
|
},
|
|
},
|
|
{
|
|
in: "[.type]",
|
|
want: []R{{E: "invalid type URL/extension field name: [.type]"}},
|
|
},
|
|
{
|
|
in: "[pkg.Foo.extension_field]",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.TypeName, S: "pkg.Foo.extension_field"}},
|
|
},
|
|
},
|
|
{
|
|
in: "[domain.com/type]",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.TypeName, S: "domain.com/type"}},
|
|
},
|
|
},
|
|
{
|
|
in: "[domain.com/pkg.type]",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.TypeName, S: "domain.com/pkg.type"}},
|
|
},
|
|
},
|
|
{
|
|
in: "[sub.domain.com\x2fpath\x2fto\x2fproto.package.name]",
|
|
want: []R{
|
|
{
|
|
K: text.Name,
|
|
T: NT{
|
|
K: text.TypeName,
|
|
S: "sub.domain.com/path/to/proto.package.name",
|
|
},
|
|
RS: "[sub.domain.com\x2fpath\x2fto\x2fproto.package.name]",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// V2 no longer allows a quoted string for the Any type URL.
|
|
in: `["domain.com/pkg.type"]`,
|
|
want: []R{{E: `invalid type URL/extension field name: ["`}},
|
|
},
|
|
{
|
|
// V2 no longer allows a quoted string for the Any type URL.
|
|
in: `['domain.com/pkg.type']`,
|
|
want: []R{{E: `invalid type URL/extension field name: ['`}},
|
|
},
|
|
{
|
|
in: "[pkg.Foo.extension_field:",
|
|
want: []R{{E: "invalid type URL/extension field name: [pkg.Foo.extension_field:"}},
|
|
},
|
|
{
|
|
// V2 no longer allows whitespace within identifier "word".
|
|
in: "[proto.packa ge.field]",
|
|
want: []R{{E: "invalid type URL/extension field name: [proto.packa g"}},
|
|
},
|
|
{
|
|
// V2 no longer allows comments within identifier "word".
|
|
in: "[proto.packa # comment\n ge.field]",
|
|
want: []R{{E: "invalid type URL/extension field name: [proto.packa # comment\n g"}},
|
|
},
|
|
{
|
|
in: "[proto.package.]",
|
|
want: []R{{E: "invalid type URL/extension field name: [proto.package."}},
|
|
},
|
|
{
|
|
in: "[proto.package/]",
|
|
want: []R{{E: "invalid type URL/extension field name: [proto.package/"}},
|
|
},
|
|
{
|
|
in: `message_field{[bad@]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.MessageOpen},
|
|
{E: `invalid type URL/extension field name: [bad@`},
|
|
},
|
|
},
|
|
{
|
|
in: `message_field{[invalid//type]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.MessageOpen},
|
|
{E: `invalid type URL/extension field name: [invalid//`},
|
|
},
|
|
},
|
|
{
|
|
in: `message_field{[proto.package.]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.MessageOpen},
|
|
{E: `invalid type URL/extension field name: [proto.package.`},
|
|
},
|
|
},
|
|
{
|
|
in: "[proto.package",
|
|
want: []R{{E: eofErr}},
|
|
},
|
|
{
|
|
in: "[" + space + "type" + space + "]" + space + ":",
|
|
want: []R{
|
|
{
|
|
K: text.Name,
|
|
T: NT{
|
|
K: text.TypeName,
|
|
Sep: true,
|
|
S: "type",
|
|
},
|
|
RS: "[" + space + "type" + space + "]",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Whitespaces/comments are only allowed betweeb
|
|
in: "[" + space + "domain" + space + "." + space + "com # comment\n" +
|
|
"/" + "pkg" + space + "." + space + "type" + space + "]",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.TypeName, S: "domain.com/pkg.type"}},
|
|
},
|
|
},
|
|
{
|
|
in: "42",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.FieldNumber, N: 42}},
|
|
},
|
|
},
|
|
{
|
|
in: "0x42:",
|
|
want: []R{{E: "invalid field number: 0x42"}},
|
|
},
|
|
{
|
|
in: "042:",
|
|
want: []R{{E: "invalid field number: 042"}},
|
|
},
|
|
{
|
|
in: "123.456:",
|
|
want: []R{{E: "invalid field number: 123.456"}},
|
|
},
|
|
{
|
|
in: "-123",
|
|
want: []R{{E: "invalid field number: -123"}},
|
|
},
|
|
{
|
|
// Field number > math.MaxInt32.
|
|
in: "2147483648:",
|
|
want: []R{{E: "invalid field number: 2147483648"}},
|
|
},
|
|
|
|
// String field value. More string parsing specific testing in
|
|
// TestUnmarshalString.
|
|
{
|
|
in: `name: "hello world"`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{
|
|
K: text.Scalar,
|
|
T: ST{ok: Str{"hello world"}, nok: Enum{}},
|
|
RS: `"hello world"`,
|
|
},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name: 'hello'`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
},
|
|
},
|
|
{
|
|
in: `name: "hello'`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{E: eofErr},
|
|
},
|
|
},
|
|
{
|
|
in: `name: 'hello`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{E: eofErr},
|
|
},
|
|
},
|
|
{
|
|
// Field name without separator is ok. prototext package will need
|
|
// to determine that this is not valid for scalar values.
|
|
in: space + `name` + space + `"hello"` + space,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
},
|
|
},
|
|
{
|
|
in: `name'hello'`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
},
|
|
},
|
|
{
|
|
in: `name: ` + space + `"hello"` + space + `,`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name` + space + `:` + `"hello"` + space + `;` + space,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name:"hello" , ,`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar},
|
|
{E: "(line 1:16): invalid field name: ,"},
|
|
},
|
|
},
|
|
{
|
|
in: `name:"hello" , ;`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar},
|
|
{E: "(line 1:16): invalid field name: ;"},
|
|
},
|
|
},
|
|
{
|
|
in: `name:"hello" name:'world'`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Str{"world"}}},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name:"hello", name:"world"`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Str{"world"}}},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name:"hello"; name:"world",`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Str{"world"}}},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `foo:"hello"bar:"world"`,
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "foo"}},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
{K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "bar"}},
|
|
{K: text.Scalar, T: ST{ok: Str{"world"}}},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `foo:"hello"[bar]:"world"`,
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "foo"}},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
{K: text.Name, T: NT{K: text.TypeName, Sep: true, S: "bar"}},
|
|
{K: text.Scalar, T: ST{ok: Str{"world"}}},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name:"foo"` + space + `"bar"` + space + `'qux'`,
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "name"}},
|
|
{K: text.Scalar, T: ST{ok: Str{"foobarqux"}}},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name:"foo"'bar'"qux"`,
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "name"}},
|
|
{K: text.Scalar, T: ST{ok: Str{"foobarqux"}}},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name:"foo"` + space + `"bar" # comment` + "\n'qux' # comment",
|
|
want: []R{
|
|
{K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "name"}},
|
|
{K: text.Scalar, T: ST{ok: Str{"foobarqux"}}},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
|
|
// Lists.
|
|
{
|
|
in: `name: [`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{E: eofErr},
|
|
},
|
|
},
|
|
{
|
|
in: `name: []`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.ListClose},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name []`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.ListClose},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name: [,`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{E: `(line 1:8): invalid scalar value: ,`},
|
|
},
|
|
},
|
|
{
|
|
in: `name: [0`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar},
|
|
{E: eofErr},
|
|
},
|
|
},
|
|
{
|
|
in: `name: [` + space + `"hello"` + space + `]` + space,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}, P: len(space) + 7},
|
|
{K: text.ListClose},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name: ["hello",]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
{E: `invalid scalar value: ]`},
|
|
},
|
|
},
|
|
{
|
|
in: `name: ["foo"` + space + `'bar' "qux"]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Str{"foobarqux"}}},
|
|
{K: text.ListClose},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name:` + space + `["foo",` + space + "'bar', # comment\n\n" + `"qux"]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Str{"foo"}}},
|
|
{K: text.Scalar, T: ST{ok: Str{"bar"}}},
|
|
{K: text.Scalar, T: ST{ok: Str{"qux"}}},
|
|
{K: text.ListClose},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
|
|
{
|
|
// List within list is not allowed.
|
|
in: `name: [[]]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{E: `syntax error (line 1:8): invalid scalar value: [`},
|
|
},
|
|
},
|
|
{
|
|
// List items need to be separated by ,.
|
|
in: `name: ["foo" true]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Str{"foo"}}},
|
|
{E: `syntax error (line 1:14): unexpected character 't'`},
|
|
},
|
|
},
|
|
{
|
|
in: `name: ["foo"; "bar"]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Str{"foo"}}},
|
|
{E: `syntax error (line 1:13): unexpected character ';'`},
|
|
},
|
|
},
|
|
{
|
|
in: `name: ["foo", true, ENUM, 1.0]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Str{"foo"}}},
|
|
{K: text.Scalar, T: ST{ok: Enum{"true"}}},
|
|
{K: text.Scalar, T: ST{ok: Enum{"ENUM"}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{1.0}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
|
|
// Boolean literal values.
|
|
{
|
|
in: `name: True`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{
|
|
K: text.Scalar,
|
|
T: ST{ok: Bool{true}},
|
|
},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name false`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{
|
|
K: text.Scalar,
|
|
T: ST{ok: Bool{false}},
|
|
},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `name: [t, f, True, False, true, false, 1, 0, 0x01, 0x00, 01, 00]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Bool{true}}},
|
|
{K: text.Scalar, T: ST{ok: Bool{false}}},
|
|
{K: text.Scalar, T: ST{ok: Bool{true}}},
|
|
{K: text.Scalar, T: ST{ok: Bool{false}}},
|
|
{K: text.Scalar, T: ST{ok: Bool{true}}},
|
|
{K: text.Scalar, T: ST{ok: Bool{false}}},
|
|
{K: text.Scalar, T: ST{ok: Bool{true}}},
|
|
{K: text.Scalar, T: ST{ok: Bool{false}}},
|
|
{K: text.Scalar, T: ST{ok: Bool{true}}},
|
|
{K: text.Scalar, T: ST{ok: Bool{false}}},
|
|
{K: text.Scalar, T: ST{ok: Bool{true}}},
|
|
{K: text.Scalar, T: ST{ok: Bool{false}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
// Looks like boolean but not.
|
|
in: `name: [tRUe, falSE, -1, -0, -0x01, -0x00, -01, -00, 0.0]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{nok: Bool{}}},
|
|
{K: text.Scalar, T: ST{nok: Bool{}}},
|
|
{K: text.Scalar, T: ST{nok: Bool{}}},
|
|
{K: text.Scalar, T: ST{nok: Bool{}}},
|
|
{K: text.Scalar, T: ST{nok: Bool{}}},
|
|
{K: text.Scalar, T: ST{nok: Bool{}}},
|
|
{K: text.Scalar, T: ST{nok: Bool{}}},
|
|
{K: text.Scalar, T: ST{nok: Bool{}}},
|
|
{K: text.Scalar, T: ST{nok: Bool{}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `foo: true[bar] false`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Bool{true}}},
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Bool{false}}},
|
|
},
|
|
},
|
|
|
|
// Enum field values.
|
|
{
|
|
in: space + `name: ENUM`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Enum{"ENUM"}}},
|
|
},
|
|
},
|
|
{
|
|
in: space + `name:[TRUE, FALSE, T, F, t, f]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Enum{"TRUE"}}},
|
|
{K: text.Scalar, T: ST{ok: Enum{"FALSE"}}},
|
|
{K: text.Scalar, T: ST{ok: Enum{"T"}}},
|
|
{K: text.Scalar, T: ST{ok: Enum{"F"}}},
|
|
{K: text.Scalar, T: ST{ok: Enum{"t"}}},
|
|
{K: text.Scalar, T: ST{ok: Enum{"f"}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `foo: Enum1[bar]:Enum2`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Enum{"Enum1"}}},
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Enum{"Enum2"}}},
|
|
},
|
|
},
|
|
{
|
|
// Invalid enum values.
|
|
in: `name: [-inf, -foo, "string", 42, 1.0, 0x47]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{nok: Enum{}}},
|
|
{K: text.Scalar, T: ST{nok: Enum{}}},
|
|
{K: text.Scalar, T: ST{nok: Enum{}}},
|
|
{K: text.Scalar, T: ST{nok: Enum{}}},
|
|
{K: text.Scalar, T: ST{nok: Enum{}}},
|
|
{K: text.Scalar, T: ST{nok: Enum{}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `name: true.`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{E: `invalid scalar value: true.`},
|
|
},
|
|
},
|
|
|
|
// Numeric values.
|
|
{
|
|
in: `nums:42 nums:0x2A nums:052`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Uint64{42}}},
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Uint64{42}}},
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Uint64{42}}},
|
|
},
|
|
},
|
|
{
|
|
in: `nums:[-42, -0x2a, -052]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{nok: Uint64{}}},
|
|
{K: text.Scalar, T: ST{nok: Uint64{}}},
|
|
{K: text.Scalar, T: ST{nok: Uint64{}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums:[-42, -0x2a, -052]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Int64{-42}}},
|
|
{K: text.Scalar, T: ST{ok: Int64{-42}}},
|
|
{K: text.Scalar, T: ST{ok: Int64{-42}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums: [0,0x0,00,-9876543210,9876543210,0x0123456789abcdef,-0x0123456789abcdef,01234567,-01234567]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Uint64{0}}},
|
|
{K: text.Scalar, T: ST{ok: Int64{0}}},
|
|
{K: text.Scalar, T: ST{ok: Uint64{0}}},
|
|
{K: text.Scalar, T: ST{ok: Int64{-9876543210}}},
|
|
{K: text.Scalar, T: ST{ok: Uint64{9876543210}}},
|
|
{K: text.Scalar, T: ST{ok: Uint64{0x0123456789abcdef}}},
|
|
{K: text.Scalar, T: ST{ok: Int64{-0x0123456789abcdef}}},
|
|
{K: text.Scalar, T: ST{ok: Uint64{01234567}}},
|
|
{K: text.Scalar, T: ST{ok: Int64{-01234567}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums: [0,0x0,00,-876543210,876543210,0x01234,-0x01234,01234567,-01234567]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Uint32{0}}},
|
|
{K: text.Scalar, T: ST{ok: Int32{0}}},
|
|
{K: text.Scalar, T: ST{ok: Uint32{0}}},
|
|
{K: text.Scalar, T: ST{ok: Int32{-876543210}}},
|
|
{K: text.Scalar, T: ST{ok: Uint32{876543210}}},
|
|
{K: text.Scalar, T: ST{ok: Uint32{0x01234}}},
|
|
{K: text.Scalar, T: ST{ok: Int32{-0x01234}}},
|
|
{K: text.Scalar, T: ST{ok: Uint32{01234567}}},
|
|
{K: text.Scalar, T: ST{ok: Int32{-01234567}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums: [` +
|
|
fmt.Sprintf("%d", uint64(math.MaxUint64)) + `,` +
|
|
fmt.Sprintf("%d", uint32(math.MaxUint32)) + `,` +
|
|
fmt.Sprintf("%d", int64(math.MaxInt64)) + `,` +
|
|
fmt.Sprintf("%d", int64(math.MinInt64)) + `,` +
|
|
fmt.Sprintf("%d", int32(math.MaxInt32)) + `,` +
|
|
fmt.Sprintf("%d", int32(math.MinInt32)) +
|
|
`]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Uint64{math.MaxUint64}}},
|
|
{K: text.Scalar, T: ST{ok: Uint32{math.MaxUint32}}},
|
|
{K: text.Scalar, T: ST{ok: Int64{math.MaxInt64}}},
|
|
{K: text.Scalar, T: ST{ok: Int64{math.MinInt64}}},
|
|
{K: text.Scalar, T: ST{ok: Int32{math.MaxInt32}}},
|
|
{K: text.Scalar, T: ST{ok: Int32{math.MinInt32}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
// Integer exceeds range.
|
|
in: `nums: [` +
|
|
`18446744073709551616,` + // max uint64 + 1
|
|
fmt.Sprintf("%d", uint64(math.MaxUint32+1)) + `,` +
|
|
fmt.Sprintf("%d", uint64(math.MaxInt64+1)) + `,` +
|
|
`-9223372036854775809,` + // min int64 - 1
|
|
fmt.Sprintf("%d", uint64(math.MaxInt32+1)) + `,` +
|
|
fmt.Sprintf("%d", int64(math.MinInt32-1)) + `` +
|
|
`]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{nok: Uint64{}}},
|
|
{K: text.Scalar, T: ST{nok: Uint32{}}},
|
|
{K: text.Scalar, T: ST{nok: Int64{}}},
|
|
{K: text.Scalar, T: ST{nok: Int64{}}},
|
|
{K: text.Scalar, T: ST{nok: Int32{}}},
|
|
{K: text.Scalar, T: ST{nok: Int32{}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums: [0xbeefbeef, 0xbeefbeefbeefbeef]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{
|
|
K: text.Scalar,
|
|
T: func() ST {
|
|
if flags.ProtoLegacy {
|
|
return ST{ok: Int32{-1091584273}}
|
|
}
|
|
return ST{nok: Int32{}}
|
|
}(),
|
|
},
|
|
{
|
|
K: text.Scalar,
|
|
T: func() ST {
|
|
if flags.ProtoLegacy {
|
|
return ST{ok: Int64{-4688318750159552785}}
|
|
}
|
|
return ST{nok: Int64{}}
|
|
}(),
|
|
},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums: [0.,0f,1f,10f,-0f,-1f,-10f,1.0,0.1e-3,1.5e+5,1e10,.0]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Float64{0.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{0.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{1.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{10.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{-0.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{-1.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{-10.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{1.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{0.1e-3}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{1.5e+5}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{1.0e+10}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{0.0}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums: [0.,0f,1f,10f,-0f,-1f,-10f,1.0,0.1e-3,1.5e+5,1e10,.0]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Float32{0.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{0.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{1.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{10.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{-0.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{-1.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{-10.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{1.0}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{0.1e-3}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{1.5e+5}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{1.0e+10}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{0.0}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums: [0.,1f,10F,1e1,1.10]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{nok: Int64{}}},
|
|
{K: text.Scalar, T: ST{nok: Int64{}}},
|
|
{K: text.Scalar, T: ST{nok: Int64{}}},
|
|
{K: text.Scalar, T: ST{nok: Int64{}}},
|
|
{K: text.Scalar, T: ST{nok: Int64{}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums: [0.,1f,10F,1e1,1.10]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{nok: Int32{}}},
|
|
{K: text.Scalar, T: ST{nok: Int32{}}},
|
|
{K: text.Scalar, T: ST{nok: Int32{}}},
|
|
{K: text.Scalar, T: ST{nok: Int32{}}},
|
|
{K: text.Scalar, T: ST{nok: Int32{}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums: [0.,1f,10F,1e1,1.10]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{nok: Uint64{}}},
|
|
{K: text.Scalar, T: ST{nok: Uint64{}}},
|
|
{K: text.Scalar, T: ST{nok: Uint64{}}},
|
|
{K: text.Scalar, T: ST{nok: Uint64{}}},
|
|
{K: text.Scalar, T: ST{nok: Uint64{}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums: [0.,1f,10F,1e1,1.10]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{nok: Uint32{}}},
|
|
{K: text.Scalar, T: ST{nok: Uint32{}}},
|
|
{K: text.Scalar, T: ST{nok: Uint32{}}},
|
|
{K: text.Scalar, T: ST{nok: Uint32{}}},
|
|
{K: text.Scalar, T: ST{nok: Uint32{}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums: [` +
|
|
fmt.Sprintf("%g", math.MaxFloat32) + `,` +
|
|
fmt.Sprintf("%g", -math.MaxFloat32) + `,` +
|
|
fmt.Sprintf("%g", math.MaxFloat32*2) + `,` +
|
|
fmt.Sprintf("%g", -math.MaxFloat32*2) + `,` +
|
|
`3.59539e+308,` + // math.MaxFloat64 * 2
|
|
`-3.59539e+308,` + // -math.MaxFloat64 * 2
|
|
fmt.Sprintf("%d000", uint64(math.MaxUint64)) +
|
|
`]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.MaxFloat32)}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(-math.MaxFloat32)}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.MaxUint64) * 1000}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `nums: [` +
|
|
fmt.Sprintf("%g", math.MaxFloat64) + `,` +
|
|
fmt.Sprintf("%g", -math.MaxFloat64) + `,` +
|
|
`3.59539e+308,` + // math.MaxFloat64 * 2
|
|
`-3.59539e+308,` + // -math.MaxFloat64 * 2
|
|
fmt.Sprintf("%d000", uint64(math.MaxUint64)) +
|
|
`]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.MaxFloat64}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{-math.MaxFloat64}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Inf(1)}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Inf(-1)}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{float64(math.MaxUint64) * 1000}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
// -0 is only valid for signed types. It is not valid for unsigned types.
|
|
in: `num: [-0, -0]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{nok: Uint32{}}},
|
|
{K: text.Scalar, T: ST{nok: Uint64{}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
// -0 is only valid for signed types. It is not valid for unsigned types.
|
|
in: `num: [-0, -0]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Int32{0}}},
|
|
{K: text.Scalar, T: ST{ok: Int64{0}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
// Negative zeros on float64 should preserve sign bit.
|
|
in: `num: [-0, -.0]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Copysign(0, -1)}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Copysign(0, -1)}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
// Negative zeros on float32 should preserve sign bit.
|
|
in: `num: [-0, -.0]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Copysign(0, -1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Copysign(0, -1))}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `num: +0`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{E: `invalid scalar value: +`},
|
|
},
|
|
},
|
|
{
|
|
in: `num: 01.1234`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{E: `invalid scalar value: 01.1234`},
|
|
},
|
|
},
|
|
{
|
|
in: `num: 0x`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{E: `invalid scalar value: 0x`},
|
|
},
|
|
},
|
|
{
|
|
in: `num: 0xX`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{E: `invalid scalar value: 0xX`},
|
|
},
|
|
},
|
|
{
|
|
in: `num: 0800`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{E: `invalid scalar value: 0800`},
|
|
},
|
|
},
|
|
{
|
|
in: `num: 1.`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Float32{1.0}}},
|
|
},
|
|
},
|
|
{
|
|
in: `num: -.`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{E: `invalid scalar value: -.`},
|
|
},
|
|
},
|
|
|
|
// Float special literal values, case-insensitive match.
|
|
{
|
|
in: `name:[nan, NaN, Nan, NAN]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.NaN()}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.NaN()}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.NaN()}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.NaN()}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `name:[inf, INF, infinity, Infinity, INFinity]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Inf(1)}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Inf(1)}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Inf(1)}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Inf(1)}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Inf(1)}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `name:[-inf, -INF, -infinity, -Infinity, -INFinity]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Inf(-1)}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Inf(-1)}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Inf(-1)}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Inf(-1)}}},
|
|
{K: text.Scalar, T: ST{ok: Float64{math.Inf(-1)}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `name:[nan, NaN, Nan, NAN]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.NaN())}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.NaN())}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.NaN())}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.NaN())}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `name:[inf, INF, infinity, Infinity, INFinity]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
in: `name:[-inf, -INF, -infinity, -Infinity, -INFinity]`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}},
|
|
{K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}},
|
|
{K: text.ListClose},
|
|
},
|
|
},
|
|
{
|
|
// C++ permits this, but we currently reject this. It is easy to add
|
|
// if needed.
|
|
in: `name: -nan`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{nok: Float64{}}},
|
|
},
|
|
},
|
|
// Messages.
|
|
{
|
|
in: `m: {}`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.MessageOpen},
|
|
{K: text.MessageClose},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `m: <>`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.MessageOpen},
|
|
{K: text.MessageClose},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: space + `m {` + space + "\n# comment\n" + `}` + space,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.MessageOpen},
|
|
{K: text.MessageClose},
|
|
},
|
|
},
|
|
{
|
|
in: `m { foo: < bar: "hello" > }`,
|
|
want: []R{
|
|
{K: text.Name, RS: "m"},
|
|
{K: text.MessageOpen},
|
|
|
|
{K: text.Name, RS: "foo"},
|
|
{K: text.MessageOpen},
|
|
|
|
{K: text.Name, RS: "bar"},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
|
|
{K: text.MessageClose},
|
|
|
|
{K: text.MessageClose},
|
|
},
|
|
},
|
|
{
|
|
in: `list [ <s:"hello">, {s:"world"} ]`,
|
|
want: []R{
|
|
{K: text.Name, RS: "list"},
|
|
{K: text.ListOpen},
|
|
|
|
{K: text.MessageOpen},
|
|
{K: text.Name, RS: "s"},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
{K: text.MessageClose},
|
|
|
|
{K: text.MessageOpen},
|
|
{K: text.Name, RS: "s"},
|
|
{K: text.Scalar, T: ST{ok: Str{"world"}}},
|
|
{K: text.MessageClose},
|
|
|
|
{K: text.ListClose},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
{
|
|
in: `m: { >`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.MessageOpen},
|
|
{E: `mismatched close character '>'`},
|
|
},
|
|
},
|
|
{
|
|
in: `m: <s: "hello"}`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.MessageOpen},
|
|
|
|
{K: text.Name},
|
|
{K: text.Scalar, T: ST{ok: Str{"hello"}}},
|
|
|
|
{E: `mismatched close character '}'`},
|
|
},
|
|
},
|
|
{
|
|
in: `{}`,
|
|
want: []R{{E: `invalid field name: {`}},
|
|
},
|
|
{
|
|
in: `
|
|
m: {
|
|
foo: true;
|
|
bar: {
|
|
enum: ENUM
|
|
list: [ < >, { } ] ;
|
|
}
|
|
[qux]: "end"
|
|
}
|
|
`,
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.MessageOpen},
|
|
|
|
{K: text.Name, RS: "foo"},
|
|
{K: text.Scalar, T: ST{ok: Bool{true}}},
|
|
|
|
{K: text.Name, RS: "bar"},
|
|
{K: text.MessageOpen},
|
|
|
|
{K: text.Name, RS: "enum"},
|
|
{K: text.Scalar, T: ST{ok: Enum{"ENUM"}}},
|
|
|
|
{K: text.Name, RS: "list"},
|
|
{K: text.ListOpen},
|
|
{K: text.MessageOpen},
|
|
{K: text.MessageClose},
|
|
{K: text.MessageOpen},
|
|
{K: text.MessageClose},
|
|
{K: text.ListClose},
|
|
|
|
{K: text.MessageClose},
|
|
|
|
{K: text.Name, RS: "[qux]"},
|
|
{K: text.Scalar, T: ST{ok: Str{"end"}}},
|
|
|
|
{K: text.MessageClose},
|
|
{K: text.EOF},
|
|
},
|
|
},
|
|
|
|
// Other syntax errors.
|
|
{
|
|
in: "x: -",
|
|
want: []R{
|
|
{K: text.Name},
|
|
{E: `syntax error (line 1:4): invalid scalar value: -`},
|
|
},
|
|
},
|
|
{
|
|
in: "x:[\"💩\"x",
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Str{"💩"}}, P: 3},
|
|
{E: `syntax error (line 1:7)`},
|
|
},
|
|
},
|
|
{
|
|
in: "x:\n\n[\"🔥🔥🔥\"x",
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Str{"🔥🔥🔥"}}, P: 5},
|
|
{E: `syntax error (line 3:7)`},
|
|
},
|
|
},
|
|
{
|
|
// multi-rune emojis; could be column:8
|
|
in: "x:[\"👍🏻👍🏿\"x",
|
|
want: []R{
|
|
{K: text.Name},
|
|
{K: text.ListOpen},
|
|
{K: text.Scalar, T: ST{ok: Str{"👍🏻👍🏿"}}, P: 3},
|
|
{E: `syntax error (line 1:10)`},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run("", func(t *testing.T) {
|
|
tc := tc
|
|
in := []byte(tc.in)
|
|
dec := text.NewDecoder(in[:len(in):len(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, "Read() got unexpected error: %v", err)
|
|
} else if !strings.Contains(err.Error(), want.E) {
|
|
errorf(t, tc.in, "Read() got %q, want %q", err, want.E)
|
|
}
|
|
return
|
|
}
|
|
if want.E != "" {
|
|
errorf(t, tc.in, "Read() got nil error, want %q", want.E)
|
|
return
|
|
}
|
|
gotK := tok.Kind()
|
|
if gotK != want.K {
|
|
errorf(t, tc.in, "Read() got %v, want %v", gotK, want.K)
|
|
return
|
|
}
|
|
checkToken(t, tok, i, want, tc.in)
|
|
if !cmp.Equal(tok, peekTok, cmp.Comparer(text.TokenEquals)) {
|
|
errorf(t, tc.in, "Peek() %+v != Read() token %+v", peekTok, tok)
|
|
}
|
|
if err != peekErr {
|
|
errorf(t, tc.in, "Peek() error %v != Read() error %v", err, peekErr)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func checkToken(t *testing.T, tok text.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.T is not set.
|
|
if r.T == nil {
|
|
return
|
|
}
|
|
|
|
switch tok.Kind() {
|
|
case text.Name:
|
|
want := r.T.(NT)
|
|
kind := tok.NameKind()
|
|
if kind != want.K {
|
|
errorf(t, in, "want#%d: Token.NameKind() got %v want %v", idx, kind, want.K)
|
|
return
|
|
}
|
|
switch kind {
|
|
case text.IdentName:
|
|
got := tok.IdentName()
|
|
if got != want.S {
|
|
errorf(t, in, "want#%d: Token.IdentName() got %v want %v", idx, got, want.S)
|
|
}
|
|
case text.TypeName:
|
|
got := tok.TypeName()
|
|
if got != want.S {
|
|
errorf(t, in, "want#%d: Token.TypeName() got %v want %v", idx, got, want.S)
|
|
}
|
|
case text.FieldNumber:
|
|
got := tok.FieldNumber()
|
|
if got != want.N {
|
|
errorf(t, in, "want#%d: Token.FieldNumber() got %v want %v", idx, got, want.N)
|
|
}
|
|
}
|
|
|
|
case text.Scalar:
|
|
want := r.T.(ST)
|
|
if ok := want.ok; ok != nil {
|
|
if err := ok.checkOk(tok); err != "" {
|
|
errorf(t, in, "want#%d: %s", idx, err)
|
|
}
|
|
}
|
|
if nok := want.nok; nok != nil {
|
|
if err := nok.checkNok(tok); err != "" {
|
|
errorf(t, in, "want#%d: %s", idx, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func errorf(t *testing.T, in string, fmtStr string, args ...interface{}) {
|
|
t.Helper()
|
|
vargs := []interface{}{in}
|
|
for _, arg := range args {
|
|
vargs = append(vargs, arg)
|
|
}
|
|
t.Errorf("input:\n%s\n~end~\n"+fmtStr, vargs...)
|
|
}
|
|
|
|
func TestUnmarshalString(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
// want is expected string result.
|
|
want string
|
|
// err is expected error substring from calling DecodeString if set.
|
|
err string
|
|
}{
|
|
{
|
|
in: func() string {
|
|
var b []byte
|
|
for i := 0; i < utf8.RuneSelf; i++ {
|
|
switch i {
|
|
case 0, '\\', '\n', '\'': // these must be escaped, so ignore them
|
|
default:
|
|
b = append(b, byte(i))
|
|
}
|
|
}
|
|
return "'" + string(b) + "'"
|
|
}(),
|
|
want: "\x01\x02\x03\x04\x05\x06\a\b\t\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f",
|
|
},
|
|
{
|
|
in: "'\xde\xad\xbe\xef'",
|
|
err: `invalid UTF-8 detected`,
|
|
},
|
|
{
|
|
// Valid UTF-8 wire encoding, but sub-optimal encoding.
|
|
in: "'\xc0\x80'",
|
|
err: "invalid UTF-8 detected",
|
|
},
|
|
{
|
|
// Valid UTF-8 wire encoding, but invalid rune (surrogate pair).
|
|
in: "'\xed\xa0\x80'",
|
|
err: "invalid UTF-8 detected",
|
|
},
|
|
{
|
|
// Valid UTF-8 wire encoding, but invalid rune (above max rune).
|
|
in: "'\xf7\xbf\xbf\xbf'",
|
|
err: "invalid UTF-8 detected",
|
|
},
|
|
{
|
|
// Valid UTF-8 wire encoding of the RuneError rune.
|
|
in: "'\xef\xbf\xbd'",
|
|
want: string(utf8.RuneError),
|
|
},
|
|
{
|
|
in: "'hello\u1234world'",
|
|
want: "hello\u1234world",
|
|
},
|
|
{
|
|
in: `'\"\'\\\?\a\b\n\r\t\v\f\1\12\123\xA\xaB\x12\uAb8f\U0010FFFF'`,
|
|
want: "\"'\\?\a\b\n\r\t\v\f\x01\nS\n\xab\x12\uab8f\U0010ffff",
|
|
},
|
|
{
|
|
in: `str: '\8'`,
|
|
err: `invalid escape code "\\8" in string`,
|
|
},
|
|
{
|
|
in: `'\1x'`,
|
|
want: "\001x",
|
|
},
|
|
{
|
|
in: `'\12x'`,
|
|
want: "\012x",
|
|
},
|
|
{
|
|
in: `'\123x'`,
|
|
want: "\123x",
|
|
},
|
|
{
|
|
in: `'\1234x'`,
|
|
want: "\1234x",
|
|
},
|
|
{
|
|
in: `'\1'`,
|
|
want: "\001",
|
|
},
|
|
{
|
|
in: `'\12'`,
|
|
want: "\012",
|
|
},
|
|
{
|
|
in: `'\123'`,
|
|
want: "\123",
|
|
},
|
|
{
|
|
in: `'\1234'`,
|
|
want: "\1234",
|
|
},
|
|
{
|
|
in: `'\377'`,
|
|
want: "\377",
|
|
},
|
|
{
|
|
// Overflow octal escape.
|
|
in: `'\400'`,
|
|
err: `invalid octal escape code "\\400" in string`,
|
|
},
|
|
{
|
|
in: `'\xfx'`,
|
|
want: "\x0fx",
|
|
},
|
|
{
|
|
in: `'\xffx'`,
|
|
want: "\xffx",
|
|
},
|
|
{
|
|
in: `'\xfffx'`,
|
|
want: "\xfffx",
|
|
},
|
|
{
|
|
in: `'\xf'`,
|
|
want: "\x0f",
|
|
},
|
|
{
|
|
in: `'\xff'`,
|
|
want: "\xff",
|
|
},
|
|
{
|
|
in: `'\xfff'`,
|
|
want: "\xfff",
|
|
},
|
|
{
|
|
in: `'\xz'`,
|
|
err: `invalid hex escape code "\\x" in string`,
|
|
},
|
|
{
|
|
in: `'\uPo'`,
|
|
err: eofErr,
|
|
},
|
|
{
|
|
in: `'\uPoo'`,
|
|
err: `invalid Unicode escape code "\\uPoo'" in string`,
|
|
},
|
|
{
|
|
in: `str: '\uPoop'`,
|
|
err: `invalid Unicode escape code "\\uPoop" in string`,
|
|
},
|
|
{
|
|
// Unmatched surrogate pair.
|
|
in: `str: '\uDEAD'`,
|
|
err: `unexpected EOF`, // trying to reader other half
|
|
},
|
|
{
|
|
// Surrogate pair with invalid other half.
|
|
in: `str: '\uDEAD\u0000'`,
|
|
err: `invalid Unicode escape code "\\u0000" in string`,
|
|
},
|
|
{
|
|
// Properly matched surrogate pair.
|
|
in: `'\uD800\uDEAD'`,
|
|
want: "𐊭",
|
|
},
|
|
{
|
|
// Overflow on Unicode rune.
|
|
in: `'\U00110000'`,
|
|
err: `invalid Unicode escape code "\\U00110000" in string`,
|
|
},
|
|
{
|
|
in: `'\z'`,
|
|
err: `invalid escape code "\\z" in string`,
|
|
},
|
|
{
|
|
// Strings cannot have NUL literal since C-style strings forbid them.
|
|
in: "'\x00'",
|
|
err: `invalid character '\x00' in string`,
|
|
},
|
|
{
|
|
// Strings cannot have newline literal. The C++ permits them if an
|
|
// option is specified to allow them. In Go, we always forbid them.
|
|
in: "'\n'",
|
|
err: `invalid character '\n' in string`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run("", func(t *testing.T) {
|
|
got, err := text.UnmarshalString(tc.in)
|
|
if err != nil {
|
|
if tc.err == "" {
|
|
errorf(t, tc.in, "UnmarshalString() got unexpected error: %q", err)
|
|
} else if !strings.Contains(err.Error(), tc.err) {
|
|
errorf(t, tc.in, "UnmarshalString() error got %q, want %q", err, tc.err)
|
|
}
|
|
return
|
|
}
|
|
if tc.err != "" {
|
|
errorf(t, tc.in, "UnmarshalString() got nil error, want %q", tc.err)
|
|
return
|
|
}
|
|
if got != tc.want {
|
|
errorf(t, tc.in, "UnmarshalString()\n[got]\n%s\n[want]\n%s", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Tests line and column number produced by Decoder.Position.
|
|
func TestPosition(t *testing.T) {
|
|
dec := text.NewDecoder([]byte("0123456789\n12345\n789"))
|
|
|
|
tests := []struct {
|
|
pos int
|
|
row int
|
|
col int
|
|
}{
|
|
{
|
|
pos: 0,
|
|
row: 1,
|
|
col: 1,
|
|
},
|
|
{
|
|
pos: 10,
|
|
row: 1,
|
|
col: 11,
|
|
},
|
|
{
|
|
pos: 11,
|
|
row: 2,
|
|
col: 1,
|
|
},
|
|
{
|
|
pos: 18,
|
|
row: 3,
|
|
col: 2,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run("", func(t *testing.T) {
|
|
row, col := dec.Position(tc.pos)
|
|
if row != tc.row || col != tc.col {
|
|
t.Errorf("Position(%d) got (%d,%d) want (%d,%d)", tc.pos, row, col, tc.row, tc.col)
|
|
}
|
|
})
|
|
}
|
|
}
|