mirror of
https://github.com/golang/net.git
synced 2026-03-31 18:37:08 +09:00
This change adds support for parsing date and display string, meaning this package can now fully parse any HTTP SFV that is compliant with RFC 9651. This package is still intended only for internal use at this point. For golang/go#75500 Change-Id: I07626b45f01e0c5cb4e92aa3fea04cc7e2d0c814 Reviewed-on: https://go-review.googlesource.com/c/net/+/708437 Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Carlos Amedee <carlos@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1440 lines
32 KiB
Go
1440 lines
32 KiB
Go
// Copyright 2025 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 httpsfv
|
|
|
|
import (
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestParseList(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
wantMembers []string
|
|
wantParams []string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid list",
|
|
in: `a, b,c`,
|
|
wantMembers: []string{"a", "b", "c"},
|
|
wantParams: []string{"", "", ""},
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid list with params",
|
|
in: `a;foo=bar, b,c; baz=baz`,
|
|
wantMembers: []string{"a", "b", "c"},
|
|
wantParams: []string{";foo=bar", "", "; baz=baz"},
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid list with fake commas",
|
|
in: `a;foo=",", (",")`,
|
|
wantMembers: []string{"a", `(",")`},
|
|
wantParams: []string{`;foo=","`, ""},
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid list with inner list member",
|
|
in: `(a b c); foo, bar;baz`,
|
|
wantMembers: []string{"(a b c)", "bar"},
|
|
wantParams: []string{"; foo", ";baz"},
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid list with trailing comma",
|
|
in: `a;foo=bar, b,c; baz=baz,`,
|
|
wantMembers: []string{"a", "b", "c"},
|
|
wantParams: []string{";foo=bar", "", "; baz=baz"},
|
|
},
|
|
{
|
|
name: "invalid list with unclosed string",
|
|
in: `", b, c,d`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
var gotMembers, gotParams []string
|
|
f := func(member, param string) {
|
|
gotMembers = append(gotMembers, member)
|
|
gotParams = append(gotParams, param)
|
|
}
|
|
ok := ParseList(tc.in, f)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if !slices.Equal(tc.wantMembers, gotMembers) {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotMembers, tc.wantMembers)
|
|
}
|
|
if !slices.Equal(tc.wantParams, gotParams) {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParams, tc.wantParams)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConsumeBareInnerList(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
wantBareItems []string
|
|
wantParams []string
|
|
wantListParam string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid inner list without param",
|
|
in: `(a b c)`,
|
|
wantBareItems: []string{"a", "b", "c"},
|
|
wantParams: []string{"", "", ""},
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid inner list with param",
|
|
in: `(a;d b c;e)`,
|
|
wantBareItems: []string{"a", "b", "c"},
|
|
wantParams: []string{";d", "", ";e"},
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid inner list with fake ending parenthesis",
|
|
in: `(")";foo=")")`,
|
|
wantBareItems: []string{`")"`},
|
|
wantParams: []string{`;foo=")"`},
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid inner list with list parameter",
|
|
in: `(a b;c); d`,
|
|
wantBareItems: []string{"a", "b"},
|
|
wantParams: []string{"", ";c"},
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid inner list with more content after",
|
|
in: `(a b;c); d, a`,
|
|
wantBareItems: []string{"a", "b"},
|
|
wantParams: []string{"", ";c"},
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid inner list",
|
|
in: `(a b;c `,
|
|
wantBareItems: []string{"a", "b"},
|
|
wantParams: []string{"", ";c"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
var gotBareItems, gotParams []string
|
|
f := func(bareItem, param string) {
|
|
gotBareItems = append(gotBareItems, bareItem)
|
|
gotParams = append(gotParams, param)
|
|
}
|
|
gotConsumed, gotRest, ok := consumeBareInnerList(tc.in, f)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if !slices.Equal(tc.wantBareItems, gotBareItems) {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotBareItems, tc.wantBareItems)
|
|
}
|
|
if !slices.Equal(tc.wantParams, gotParams) {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParams, tc.wantParams)
|
|
}
|
|
if gotConsumed+gotRest != tc.in {
|
|
t.Fatalf("test %q: %#v + %#v != %#v", tc.name, gotConsumed, gotRest, tc.in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseBareInnerList(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
wantBareItems []string
|
|
wantParams []string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid inner list",
|
|
in: `(a b;c)`,
|
|
wantBareItems: []string{"a", "b"},
|
|
wantParams: []string{"", ";c"},
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid inner list with list parameter",
|
|
in: `(a b;c); d`,
|
|
wantBareItems: []string{"a", "b"},
|
|
wantParams: []string{"", ";c"},
|
|
},
|
|
{
|
|
name: "invalid inner list",
|
|
in: `(a b;c `,
|
|
wantBareItems: []string{"a", "b"},
|
|
wantParams: []string{"", ";c"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
var gotBareItems, gotParams []string
|
|
f := func(bareItem, param string) {
|
|
gotBareItems = append(gotBareItems, bareItem)
|
|
gotParams = append(gotParams, param)
|
|
}
|
|
ok := ParseBareInnerList(tc.in, f)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if !slices.Equal(tc.wantBareItems, gotBareItems) {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotBareItems, tc.wantBareItems)
|
|
}
|
|
if !slices.Equal(tc.wantParams, gotParams) {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParams, tc.wantParams)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConsumeItem(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
wantBareItem string
|
|
wantParam string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid bare item",
|
|
in: `fookey`,
|
|
wantBareItem: `fookey`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid bare item and param",
|
|
in: `fookey; a="123"`,
|
|
wantBareItem: `fookey`,
|
|
wantParam: `; a="123"`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid item with content after",
|
|
in: `fookey; a="123", otheritem; otherparam=1`,
|
|
wantBareItem: `fookey`,
|
|
wantParam: `; a="123"`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid just param",
|
|
in: `;a="123"`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
var gotBareItem, gotParam string
|
|
f := func(bareItem, param string) {
|
|
gotBareItem = bareItem
|
|
gotParam = param
|
|
}
|
|
gotConsumed, gotRest, ok := consumeItem(tc.in, f)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.wantBareItem != gotBareItem {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotBareItem, tc.wantBareItem)
|
|
}
|
|
if tc.wantParam != gotParam {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParam, tc.wantParam)
|
|
}
|
|
if gotConsumed+gotRest != tc.in {
|
|
t.Fatalf("test %q: %#v + %#v != %#v", tc.name, gotConsumed, gotRest, tc.in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseItem(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
wantBareItem string
|
|
wantParam string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid bare item",
|
|
in: `fookey`,
|
|
wantBareItem: `fookey`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid bare item and param",
|
|
in: `fookey; a="123"`,
|
|
wantBareItem: `fookey`,
|
|
wantParam: `; a="123"`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid item with content after",
|
|
in: `fookey; a="123", otheritem; otherparam=1`,
|
|
wantBareItem: `fookey`,
|
|
wantParam: `; a="123"`,
|
|
},
|
|
{
|
|
name: "invalid just param",
|
|
in: `;a="123"`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
var gotBareItem, gotParam string
|
|
f := func(bareItem, param string) {
|
|
gotBareItem = bareItem
|
|
gotParam = param
|
|
}
|
|
ok := ParseItem(tc.in, f)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.wantBareItem != gotBareItem {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotBareItem, tc.wantBareItem)
|
|
}
|
|
if tc.wantParam != gotParam {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParam, tc.wantParam)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseDictionary(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
wantVal string
|
|
wantParam string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid dictionary with simple value",
|
|
in: `a=b, want=foo, c=d`,
|
|
wantVal: "foo",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid dictionary with implicit value",
|
|
in: `a, want, c=d`,
|
|
wantVal: "?1",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid dictionary with parameter",
|
|
in: `a, want=foo;bar=baz, c=d`,
|
|
wantVal: "foo",
|
|
wantParam: ";bar=baz",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid dictionary with inner list",
|
|
in: `a, want=(a b c d;e;f);g=h, c=d`,
|
|
wantVal: "(a b c d;e;f)",
|
|
wantParam: ";g=h",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid dictionary with fake commas",
|
|
in: `a=(";");b=";",want=foo;bar`,
|
|
wantVal: "foo",
|
|
wantParam: ";bar",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid dictionary with bad key",
|
|
in: `UPPERCASEKEY=BAD, want=foo, c=d`,
|
|
},
|
|
{
|
|
name: "invalid dictionary with trailing comma",
|
|
in: `trailing=comma,`,
|
|
},
|
|
{
|
|
name: "invalid dictionary with unclosed string",
|
|
in: `a=""",want=foo;bar`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
var gotVal, gotParam string
|
|
f := func(key, val, param string) {
|
|
if key == "want" {
|
|
gotVal = val
|
|
gotParam = param
|
|
}
|
|
}
|
|
ok := ParseDictionary(tc.in, f)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.wantVal != gotVal {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotVal, tc.wantVal)
|
|
}
|
|
if tc.wantParam != gotParam {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParam, tc.wantParam)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConsumeParameter(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want any
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid string",
|
|
in: `;parameter;want="wantvalue"`,
|
|
want: "wantvalue",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid integer",
|
|
in: `;parameter;want=123456;something`,
|
|
want: 123456,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid decimal",
|
|
in: `;parameter;want=3.14;something`,
|
|
want: 3.14,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid implicit bool",
|
|
in: `;parameter;want;something`,
|
|
want: true,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid token",
|
|
in: `;want=*atoken;something`,
|
|
want: "*atoken",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid byte sequence",
|
|
in: `;want=:eWF5Cg==:;something`,
|
|
want: "eWF5Cg==",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid repeated key",
|
|
in: `;want=:eWF5Cg==:;now;want=1;is;repeated;want="overwritten!"`,
|
|
want: "overwritten!",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid parameter with content after",
|
|
in: `;want=:eWF5Cg==:;now;want=1;is;repeated;want="overwritten!", some=stuff`,
|
|
want: "overwritten!",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid parameter",
|
|
in: `;UPPERCASEKEY=NOT_ACCEPTED`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests[len(tests)-1:] {
|
|
var got any
|
|
f := func(key, val string) {
|
|
if key != "want" {
|
|
return
|
|
}
|
|
switch {
|
|
case strings.HasPrefix(val, "?"): // Bool
|
|
got = val == "?1"
|
|
case strings.HasPrefix(val, `"`): // String
|
|
got = val[1 : len(val)-1]
|
|
case strings.HasPrefix(val, "*"): // Token
|
|
got = val
|
|
case strings.HasPrefix(val, ":"): // Byte sequence
|
|
got = val[1 : len(val)-1]
|
|
default:
|
|
if valConv, err := strconv.Atoi(val); err == nil { // Integer
|
|
got = valConv
|
|
return
|
|
}
|
|
if valConv, err := strconv.ParseFloat(val, 64); err == nil { // Float
|
|
got = valConv
|
|
return
|
|
}
|
|
}
|
|
}
|
|
consumed, rest, ok := consumeParameter(tc.in, f)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if got != tc.want {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
if consumed+rest != tc.in {
|
|
t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, rest, tc.in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseParameter(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want any
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid parameter",
|
|
in: `;parameter;want="wantvalue"`,
|
|
want: "wantvalue",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid parameter with content after",
|
|
in: `;want=:eWF5Cg==:;now;want=1;is;repeated;want="overwritten!", some=stuff`,
|
|
want: "overwritten!",
|
|
},
|
|
{
|
|
name: "invalid parameter",
|
|
in: `;UPPERCASEKEY=NOT_ACCEPTED`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests[len(tests)-1:] {
|
|
var got any
|
|
f := func(key, val string) {
|
|
if key != "want" {
|
|
return
|
|
}
|
|
switch {
|
|
case strings.HasPrefix(val, "?"): // Bool
|
|
got = val == "?1"
|
|
case strings.HasPrefix(val, `"`): // String
|
|
got = val[1 : len(val)-1]
|
|
case strings.HasPrefix(val, "*"): // Token
|
|
got = val
|
|
case strings.HasPrefix(val, ":"): // Byte sequence
|
|
got = val[1 : len(val)-1]
|
|
default:
|
|
if valConv, err := strconv.Atoi(val); err == nil { // Integer
|
|
got = valConv
|
|
return
|
|
}
|
|
if valConv, err := strconv.ParseFloat(val, 64); err == nil { // Float
|
|
got = valConv
|
|
return
|
|
}
|
|
}
|
|
}
|
|
ok := ParseParameter(tc.in, f)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if got != tc.want {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConsumeKey(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid basic key",
|
|
in: `fookey`,
|
|
want: `fookey`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid basic key with more content after",
|
|
in: `fookey,u=7`,
|
|
want: `fookey`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid key",
|
|
in: `1keycannotstartwithnum`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, gotRest, ok := consumeKey(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
if got+gotRest != tc.in {
|
|
t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConsumeIntegerOrDecimal(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid integer",
|
|
in: "123456",
|
|
want: "123456",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid integer with more content after",
|
|
in: "123456,12345",
|
|
want: "123456",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid max integer",
|
|
in: "999999999999999",
|
|
want: "999999999999999",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid min integer",
|
|
in: "-999999999999999",
|
|
want: "-999999999999999",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid integer too high",
|
|
in: "9999999999999999",
|
|
},
|
|
{
|
|
name: "invalid integer too low",
|
|
in: "-9999999999999999",
|
|
},
|
|
{
|
|
name: "valid decimal",
|
|
in: "-123456789012.123",
|
|
want: "-123456789012.123",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid decimal integer component too long",
|
|
in: "1234567890123.1",
|
|
},
|
|
{
|
|
name: "invalid decimal fraction component too long",
|
|
in: "1.1234",
|
|
},
|
|
{
|
|
name: "invalid decimal trailing dot",
|
|
in: "1.",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, gotRest, ok := consumeIntegerOrDecimal(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
if got+gotRest != tc.in {
|
|
t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseInteger(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want int64
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid integer",
|
|
in: "123456",
|
|
want: 123456,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid integer with more content after",
|
|
in: "123456,12345",
|
|
},
|
|
{
|
|
name: "valid max integer",
|
|
in: "999999999999999",
|
|
want: 999999999999999,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid min integer",
|
|
in: "-999999999999999",
|
|
want: -999999999999999,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid integer too high",
|
|
in: "9999999999999999",
|
|
},
|
|
{
|
|
name: "invalid integer too low",
|
|
in: "-9999999999999999",
|
|
},
|
|
{
|
|
name: "invalid integer with fraction",
|
|
in: "-123456789012.123",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, ok := ParseInteger(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseDecimal(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want float64
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid decimal",
|
|
in: "123456.789",
|
|
want: 123456.789,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid decimal with more content after",
|
|
in: "123456.789, 123",
|
|
},
|
|
{
|
|
name: "invalid decimal with no fraction",
|
|
in: "123456",
|
|
},
|
|
{
|
|
name: "invalid decimal integer component too long",
|
|
in: "1234567890123.1",
|
|
},
|
|
{
|
|
name: "invalid decimal fraction component too long",
|
|
in: "1.1234",
|
|
},
|
|
{
|
|
name: "invalid decimal trailing dot",
|
|
in: "1.",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, ok := ParseDecimal(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConsumeString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid basic string",
|
|
in: `"foo bar"`,
|
|
want: `"foo bar"`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid basic string with more content after",
|
|
in: `"foo bar", a=3`,
|
|
want: `"foo bar"`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid string with escaped dquote",
|
|
in: `"foo bar \""`,
|
|
want: `"foo bar \""`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid string no starting dquote",
|
|
in: `foo bar"`,
|
|
},
|
|
{
|
|
name: "invalid string no closing dquote",
|
|
in: `"foo bar`,
|
|
},
|
|
{
|
|
name: "invalid string invalid character",
|
|
in: string([]byte{'"', 0x00, '"'}),
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, gotRest, ok := consumeString(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
if got+gotRest != tc.in {
|
|
t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid basic string",
|
|
in: `"foo bar"`,
|
|
want: "foo bar",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid basic string with more content after",
|
|
in: `"foo bar", a=3`,
|
|
},
|
|
{
|
|
name: "valid string with escaped dquote",
|
|
in: `"foo bar \""`,
|
|
want: `foo bar \"`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid string no starting dquote",
|
|
in: `foo bar"`,
|
|
},
|
|
{
|
|
name: "invalid string no closing dquote",
|
|
in: `"foo bar`,
|
|
},
|
|
{
|
|
name: "invalid string invalid character",
|
|
in: string([]byte{'"', 0x00, '"'}),
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, ok := ParseString(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConsumeToken(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid token",
|
|
in: "*atoken",
|
|
want: "*atoken",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid token with more content after",
|
|
in: "*atoken something",
|
|
want: "*atoken",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid token",
|
|
in: "0invalid",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, gotRest, ok := consumeToken(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
if got+gotRest != tc.in {
|
|
t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseToken(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid token",
|
|
in: "a_b-c.d3:f%00/*",
|
|
want: "a_b-c.d3:f%00/*",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid token with uppercase",
|
|
in: "FOOBAR",
|
|
want: "FOOBAR",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid token with content after",
|
|
in: "FOOBAR, foobar",
|
|
},
|
|
{
|
|
name: "invalid token",
|
|
in: "0invalid",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, ok := ParseToken(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConsumeByteSequence(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid byte sequence",
|
|
in: ":aGVsbG8gd29ybGQ=:",
|
|
want: ":aGVsbG8gd29ybGQ=:",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid byte sequence with more content after",
|
|
in: ":aGVsbG8gd29ybGQ=::aGVsbG8gd29ybGQ=:",
|
|
want: ":aGVsbG8gd29ybGQ=:",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid byte sequence character",
|
|
in: ":-:",
|
|
},
|
|
{
|
|
name: "invalid byte sequence opening",
|
|
in: "aGVsbG8gd29ybGQ=:",
|
|
},
|
|
{
|
|
name: "invalid byte sequence closing",
|
|
in: ":aGVsbG8gd29ybGQ=",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, gotRest, ok := consumeByteSequence(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
if got+gotRest != tc.in {
|
|
t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseByteSequence(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want []byte
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid byte sequence",
|
|
in: ":aGVsbG8gd29ybGQ=:",
|
|
want: []byte("aGVsbG8gd29ybGQ="),
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid byte sequence with more content after",
|
|
in: ":aGVsbG8gd29ybGQ=::aGVsbG8gd29ybGQ=:",
|
|
},
|
|
{
|
|
name: "invalid byte sequence character",
|
|
in: ":-:",
|
|
},
|
|
{
|
|
name: "invalid byte sequence opening",
|
|
in: "aGVsbG8gd29ybGQ=:",
|
|
},
|
|
{
|
|
name: "invalid byte sequence closing",
|
|
in: ":aGVsbG8gd29ybGQ=",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, ok := ParseByteSequence(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if !slices.Equal(tc.want, got) {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConsumeBoolean(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid boolean",
|
|
in: "?0",
|
|
want: "?0",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid boolean with more content after",
|
|
in: "?1, a=1",
|
|
want: "?1",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid boolean",
|
|
in: "!2",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, gotRest, ok := consumeBoolean(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
if got+gotRest != tc.in {
|
|
t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseBoolean(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want bool
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid boolean false",
|
|
in: "?0",
|
|
want: false,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid boolean true",
|
|
in: "?1",
|
|
want: true,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid boolean with more content after",
|
|
in: "?1, a=1",
|
|
},
|
|
{
|
|
name: "invalid boolean",
|
|
in: "?2",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, ok := ParseBoolean(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConsumeDate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid zero date",
|
|
in: "@0",
|
|
want: "@0",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid positive date",
|
|
in: "@1659578233",
|
|
want: "@1659578233",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid negative date",
|
|
in: "@-1659578233",
|
|
want: "@-1659578233",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid large date",
|
|
in: "@25340221440",
|
|
want: "@25340221440",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid small date",
|
|
in: "@-62135596800",
|
|
want: "@-62135596800",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid decimal date",
|
|
in: "@1.2",
|
|
},
|
|
{
|
|
name: "valid date with more content after",
|
|
in: "@1659578233, foo;bar",
|
|
want: "@1659578233",
|
|
wantOk: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, gotRest, ok := consumeDate(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
if got+gotRest != tc.in {
|
|
t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseDate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want time.Time
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid zero date",
|
|
in: "@0",
|
|
want: time.Unix(0, 0),
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid positive date",
|
|
in: "@1659578233",
|
|
want: time.Date(2022, 8, 4, 1, 57, 13, 0, time.UTC).Local(),
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid negative date",
|
|
in: "@-1659578233",
|
|
want: time.Date(1917, 5, 30, 22, 2, 47, 0, time.UTC).Local(),
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid max date required",
|
|
in: "@253402214400",
|
|
want: time.Date(9999, 12, 31, 0, 0, 0, 0, time.UTC).Local(),
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid min date required",
|
|
in: "@-62135596800",
|
|
want: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Local(),
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid date with fraction",
|
|
in: "@0.123",
|
|
},
|
|
{
|
|
name: "valid date with more content after",
|
|
in: "@0, @0",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, ok := ParseDate(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConsumeDisplayString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid ascii string",
|
|
in: "%\" !%22#$%25&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"",
|
|
want: "%\" !%22#$%25&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid lowercase non-ascii string",
|
|
in: `%"f%c3%bc%c3%bc"`,
|
|
want: `%"f%c3%bc%c3%bc"`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid uppercase non-ascii string",
|
|
in: `%"f%C3%BC%C3%BC"`,
|
|
},
|
|
{
|
|
name: "invalid unqouted string",
|
|
in: "%foo",
|
|
},
|
|
{
|
|
name: "invalid string missing initial quote",
|
|
in: `%foo"`,
|
|
},
|
|
{
|
|
name: "invalid string missing closing quote",
|
|
in: `%"foo`,
|
|
},
|
|
{
|
|
name: "invalid tab in string",
|
|
in: "%\"\t\"",
|
|
},
|
|
{
|
|
name: "invalid newline in string",
|
|
in: "%\"\n\"",
|
|
},
|
|
{
|
|
name: "invalid single quoted string",
|
|
in: `%'foo'`,
|
|
},
|
|
{
|
|
name: "invalid string bad escaping",
|
|
in: `%\"foo %a"`,
|
|
},
|
|
{
|
|
name: "valid string with escaped quotes",
|
|
in: `%"foo %22bar%22 \\ baz"`,
|
|
want: `%"foo %22bar%22 \\ baz"`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid sequence id utf-8 string",
|
|
in: `%"%a0%a1"`,
|
|
},
|
|
{
|
|
name: "invalid 2 bytes sequence utf-8 string",
|
|
in: `%"%c3%28"`,
|
|
},
|
|
{
|
|
name: "invalid 3 bytes sequence utf-8 string",
|
|
in: `%"%e2%28%a1"`,
|
|
},
|
|
{
|
|
name: "invalid 4 bytes sequence utf-8 string",
|
|
in: `%"%f0%28%8c%28"`,
|
|
},
|
|
{
|
|
name: "invalid hex utf-8 string",
|
|
in: `%"%g0%1w"`,
|
|
},
|
|
{
|
|
name: "valid byte order mark in display string",
|
|
in: `%"BOM: %ef%bb%bf"`,
|
|
want: `%"BOM: %ef%bb%bf"`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid string with content after",
|
|
in: `%"foo\nbar", foo;bar`,
|
|
want: `%"foo\nbar"`,
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid unfinished 4 bytes rune",
|
|
in: `%"%f0%9f%98"`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, gotRest, ok := consumeDisplayString(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
if got+gotRest != tc.in {
|
|
t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseDisplayString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "valid ascii string",
|
|
in: "%\" !%22#$%25&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"",
|
|
want: " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid lowercase non-ascii string",
|
|
in: `%"f%c3%bc%c3%bc"`,
|
|
want: "füü",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid uppercase non-ascii string",
|
|
in: `%"f%C3%BC%C3%BC"`,
|
|
},
|
|
{
|
|
name: "invalid unqouted string",
|
|
in: "%foo",
|
|
},
|
|
{
|
|
name: "invalid string missing initial quote",
|
|
in: `%foo"`,
|
|
},
|
|
{
|
|
name: "invalid string missing closing quote",
|
|
in: `%"foo`,
|
|
},
|
|
{
|
|
name: "invalid tab in string",
|
|
in: "%\"\t\"",
|
|
},
|
|
{
|
|
name: "invalid newline in string",
|
|
in: "%\"\n\"",
|
|
},
|
|
{
|
|
name: "invalid single quoted string",
|
|
in: `%'foo'`,
|
|
},
|
|
{
|
|
name: "invalid string bad escaping",
|
|
in: `%\"foo %a"`,
|
|
},
|
|
{
|
|
name: "valid string with escaped quotes",
|
|
in: "%\"foo %22bar%22 \\ baz\"",
|
|
want: "foo \"bar\" \\ baz",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "invalid sequence id utf-8 string",
|
|
in: `%"%a0%a1"`,
|
|
},
|
|
{
|
|
name: "invalid 2 bytes sequence utf-8 string",
|
|
in: `%"%c3%28"`,
|
|
},
|
|
{
|
|
name: "invalid 3 bytes sequence utf-8 string",
|
|
in: `%"%e2%28%a1"`,
|
|
},
|
|
{
|
|
name: "invalid 4 bytes sequence utf-8 string",
|
|
in: `%"%f0%28%8c%28"`,
|
|
},
|
|
{
|
|
name: "invalid hex utf-8 string",
|
|
in: `%"%g0%1w"`,
|
|
},
|
|
{
|
|
name: "valid byte order mark in display string",
|
|
in: `%"BOM: %ef%bb%bf"`,
|
|
want: "BOM: \uFEFF",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "valid string with content after",
|
|
in: `%"foo\nbar", foo;bar`,
|
|
},
|
|
{
|
|
name: "invalid unfinished 4 bytes rune",
|
|
in: `%"%f0%9f%98"`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got, ok := ParseDisplayString(tc.in)
|
|
if ok != tc.wantOk {
|
|
t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok)
|
|
}
|
|
if tc.want != got {
|
|
t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|