mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-25 05:44:06 -06:00
760 lines
16 KiB
Go
760 lines
16 KiB
Go
package toml
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/BurntSushi/toml/internal"
|
|
)
|
|
|
|
func TestDecodeReader(t *testing.T) {
|
|
var i struct{ A int }
|
|
meta, err := DecodeReader(strings.NewReader("a = 42"), &i)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
have := fmt.Sprintf("%v %v %v", i, meta.Keys(), meta.Type("a"))
|
|
want := "{42} [a] Integer"
|
|
if have != want {
|
|
t.Errorf("\nhave: %s\nwant: %s", have, want)
|
|
}
|
|
}
|
|
|
|
func TestDecodeFile(t *testing.T) {
|
|
tmp, err := ioutil.TempFile("", "toml-")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Remove(tmp.Name())
|
|
if _, err := tmp.WriteString("a = 42"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tmp.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var i struct{ A int }
|
|
meta, err := DecodeFile(tmp.Name(), &i)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
have := fmt.Sprintf("%v %v %v", i, meta.Keys(), meta.Type("a"))
|
|
want := "{42} [a] Integer"
|
|
if have != want {
|
|
t.Errorf("\nhave: %s\nwant: %s", have, want)
|
|
}
|
|
}
|
|
|
|
func TestDecodeBOM(t *testing.T) {
|
|
for _, tt := range [][]byte{
|
|
[]byte("\xff\xfea = \"b\""),
|
|
[]byte("\xfe\xffa = \"b\""),
|
|
} {
|
|
t.Run("", func(t *testing.T) {
|
|
var s struct{ A string }
|
|
_, err := Decode(string(tt), &s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s.A != "b" {
|
|
t.Errorf(`s.A is not "b" but %q`, s.A)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDecodeEmbedded(t *testing.T) {
|
|
type Dog struct{ Name string }
|
|
type Age int
|
|
type cat struct{ Name string }
|
|
|
|
for _, test := range []struct {
|
|
label string
|
|
input string
|
|
decodeInto interface{}
|
|
wantDecoded interface{}
|
|
}{
|
|
{
|
|
label: "embedded struct",
|
|
input: `Name = "milton"`,
|
|
decodeInto: &struct{ Dog }{},
|
|
wantDecoded: &struct{ Dog }{Dog{"milton"}},
|
|
},
|
|
{
|
|
label: "embedded non-nil pointer to struct",
|
|
input: `Name = "milton"`,
|
|
decodeInto: &struct{ *Dog }{},
|
|
wantDecoded: &struct{ *Dog }{&Dog{"milton"}},
|
|
},
|
|
{
|
|
label: "embedded nil pointer to struct",
|
|
input: ``,
|
|
decodeInto: &struct{ *Dog }{},
|
|
wantDecoded: &struct{ *Dog }{nil},
|
|
},
|
|
{
|
|
label: "unexported embedded struct",
|
|
input: `Name = "socks"`,
|
|
decodeInto: &struct{ cat }{},
|
|
wantDecoded: &struct{ cat }{cat{"socks"}},
|
|
},
|
|
{
|
|
label: "embedded int",
|
|
input: `Age = -5`,
|
|
decodeInto: &struct{ Age }{},
|
|
wantDecoded: &struct{ Age }{-5},
|
|
},
|
|
} {
|
|
_, err := Decode(test.input, test.decodeInto)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(test.wantDecoded, test.decodeInto) {
|
|
t.Errorf("%s: want decoded == %+v, got %+v",
|
|
test.label, test.wantDecoded, test.decodeInto)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecodeIgnoreFields(t *testing.T) {
|
|
const input = `
|
|
Number = 123
|
|
- = 234
|
|
`
|
|
var s struct {
|
|
Number int `toml:"-"`
|
|
}
|
|
if _, err := Decode(input, &s); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s.Number != 0 {
|
|
t.Errorf("got: %d; want 0", s.Number)
|
|
}
|
|
}
|
|
|
|
func TestDecodeTableArrays(t *testing.T) {
|
|
var tomlTableArrays = `
|
|
[[albums]]
|
|
name = "Born to Run"
|
|
|
|
[[albums.songs]]
|
|
name = "Jungleland"
|
|
|
|
[[albums.songs]]
|
|
name = "Meeting Across the River"
|
|
|
|
[[albums]]
|
|
name = "Born in the USA"
|
|
|
|
[[albums.songs]]
|
|
name = "Glory Days"
|
|
|
|
[[albums.songs]]
|
|
name = "Dancing in the Dark"
|
|
`
|
|
|
|
type Song struct {
|
|
Name string
|
|
}
|
|
|
|
type Album struct {
|
|
Name string
|
|
Songs []Song
|
|
}
|
|
|
|
type Music struct {
|
|
Albums []Album
|
|
}
|
|
|
|
expected := Music{[]Album{
|
|
{"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}},
|
|
{"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}},
|
|
}}
|
|
var got Music
|
|
if _, err := Decode(tomlTableArrays, &got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(expected, got) {
|
|
t.Fatalf("\n%#v\n!=\n%#v\n", expected, got)
|
|
}
|
|
}
|
|
|
|
func TestDecodePointers(t *testing.T) {
|
|
type Object struct {
|
|
Type string
|
|
Description string
|
|
}
|
|
|
|
type Dict struct {
|
|
NamedObject map[string]*Object
|
|
BaseObject *Object
|
|
Strptr *string
|
|
Strptrs []*string
|
|
}
|
|
s1, s2, s3 := "blah", "abc", "def"
|
|
expected := &Dict{
|
|
Strptr: &s1,
|
|
Strptrs: []*string{&s2, &s3},
|
|
NamedObject: map[string]*Object{
|
|
"foo": {"FOO", "fooooo!!!"},
|
|
"bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"},
|
|
},
|
|
BaseObject: &Object{"BASE", "da base"},
|
|
}
|
|
|
|
ex1 := `
|
|
Strptr = "blah"
|
|
Strptrs = ["abc", "def"]
|
|
|
|
[NamedObject.foo]
|
|
Type = "FOO"
|
|
Description = "fooooo!!!"
|
|
|
|
[NamedObject.bar]
|
|
Type = "BAR"
|
|
Description = "ba-ba-ba-ba-barrrr!!!"
|
|
|
|
[BaseObject]
|
|
Type = "BASE"
|
|
Description = "da base"
|
|
`
|
|
dict := new(Dict)
|
|
_, err := Decode(ex1, dict)
|
|
if err != nil {
|
|
t.Errorf("Decode error: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(expected, dict) {
|
|
t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict)
|
|
}
|
|
}
|
|
|
|
func TestDecodeBadDatetime(t *testing.T) {
|
|
var x struct{ T time.Time }
|
|
for _, s := range []string{"123", "1230"} {
|
|
input := "T = " + s
|
|
if _, err := Decode(input, &x); err == nil {
|
|
t.Errorf("Expected invalid DateTime error for %q", s)
|
|
}
|
|
}
|
|
}
|
|
|
|
type sphere struct {
|
|
Center [3]float64
|
|
Radius float64
|
|
}
|
|
|
|
func TestDecodeArrayWrongSize(t *testing.T) {
|
|
var s1 sphere
|
|
if _, err := Decode(`center = [0.1, 2.3]`, &s1); err == nil {
|
|
t.Fatal("Expected array type mismatch error")
|
|
}
|
|
}
|
|
|
|
func TestDecodeIntOverflow(t *testing.T) {
|
|
type table struct {
|
|
Value int8
|
|
}
|
|
var tab table
|
|
if _, err := Decode(`value = 500`, &tab); err == nil {
|
|
t.Fatal("Expected integer out-of-bounds error.")
|
|
}
|
|
}
|
|
|
|
func TestDecodeSizedInts(t *testing.T) {
|
|
type table struct {
|
|
U8 uint8
|
|
U16 uint16
|
|
U32 uint32
|
|
U64 uint64
|
|
U uint
|
|
I8 int8
|
|
I16 int16
|
|
I32 int32
|
|
I64 int64
|
|
I int
|
|
}
|
|
answer := table{1, 1, 1, 1, 1, -1, -1, -1, -1, -1}
|
|
toml := `
|
|
u8 = 1
|
|
u16 = 1
|
|
u32 = 1
|
|
u64 = 1
|
|
u = 1
|
|
i8 = -1
|
|
i16 = -1
|
|
i32 = -1
|
|
i64 = -1
|
|
i = -1
|
|
`
|
|
var tab table
|
|
if _, err := Decode(toml, &tab); err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
if answer != tab {
|
|
t.Fatalf("Expected %#v but got %#v", answer, tab)
|
|
}
|
|
}
|
|
|
|
func TestDecodeTypes(t *testing.T) {
|
|
type mystr string
|
|
|
|
for _, tt := range []struct {
|
|
v interface{}
|
|
want string
|
|
}{
|
|
{new(map[string]int64), ""},
|
|
{new(map[mystr]int64), ""},
|
|
|
|
{3, "non-pointer int"},
|
|
{(*int)(nil), "nil"},
|
|
{new(map[int]string), "cannot decode to a map with non-string key type"},
|
|
{new(map[interface{}]string), "cannot decode to a map with non-string key type"},
|
|
} {
|
|
t.Run(fmt.Sprintf("%T", tt.v), func(t *testing.T) {
|
|
_, err := Decode(`x = 3`, tt.v)
|
|
if !errorContains(err, tt.want) {
|
|
t.Errorf("wrong error\nhave: %q\nwant: %q", err, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUnmarshaler(t *testing.T) {
|
|
var tomlBlob = `
|
|
[dishes.hamboogie]
|
|
name = "Hamboogie with fries"
|
|
price = 10.99
|
|
|
|
[[dishes.hamboogie.ingredients]]
|
|
name = "Bread Bun"
|
|
|
|
[[dishes.hamboogie.ingredients]]
|
|
name = "Lettuce"
|
|
|
|
[[dishes.hamboogie.ingredients]]
|
|
name = "Real Beef Patty"
|
|
|
|
[[dishes.hamboogie.ingredients]]
|
|
name = "Tomato"
|
|
|
|
[dishes.eggsalad]
|
|
name = "Egg Salad with rice"
|
|
price = 3.99
|
|
|
|
[[dishes.eggsalad.ingredients]]
|
|
name = "Egg"
|
|
|
|
[[dishes.eggsalad.ingredients]]
|
|
name = "Mayo"
|
|
|
|
[[dishes.eggsalad.ingredients]]
|
|
name = "Rice"
|
|
`
|
|
m := &menu{}
|
|
if _, err := Decode(tomlBlob, m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(m.Dishes) != 2 {
|
|
t.Log("two dishes should be loaded with UnmarshalTOML()")
|
|
t.Errorf("expected %d but got %d", 2, len(m.Dishes))
|
|
}
|
|
|
|
eggSalad := m.Dishes["eggsalad"]
|
|
if _, ok := interface{}(eggSalad).(dish); !ok {
|
|
t.Errorf("expected a dish")
|
|
}
|
|
|
|
if eggSalad.Name != "Egg Salad with rice" {
|
|
t.Errorf("expected the dish to be named 'Egg Salad with rice'")
|
|
}
|
|
|
|
if len(eggSalad.Ingredients) != 3 {
|
|
t.Log("dish should be loaded with UnmarshalTOML()")
|
|
t.Errorf("expected %d but got %d", 3, len(eggSalad.Ingredients))
|
|
}
|
|
|
|
found := false
|
|
for _, i := range eggSalad.Ingredients {
|
|
if i.Name == "Rice" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Rice was not loaded in UnmarshalTOML()")
|
|
}
|
|
|
|
// test on a value - must be passed as *
|
|
o := menu{}
|
|
if _, err := Decode(tomlBlob, &o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
}
|
|
|
|
func TestDecodeInlineTable(t *testing.T) {
|
|
input := `
|
|
[CookieJar]
|
|
Types = {Chocolate = "yummy", Oatmeal = "best ever"}
|
|
|
|
[Seasons]
|
|
Locations = {NY = {Temp = "not cold", Rating = 4}, MI = {Temp = "freezing", Rating = 9}}
|
|
`
|
|
type cookieJar struct {
|
|
Types map[string]string
|
|
}
|
|
type properties struct {
|
|
Temp string
|
|
Rating int
|
|
}
|
|
type seasons struct {
|
|
Locations map[string]properties
|
|
}
|
|
type wrapper struct {
|
|
CookieJar cookieJar
|
|
Seasons seasons
|
|
}
|
|
var got wrapper
|
|
|
|
meta, err := Decode(input, &got)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := wrapper{
|
|
CookieJar: cookieJar{
|
|
Types: map[string]string{
|
|
"Chocolate": "yummy",
|
|
"Oatmeal": "best ever",
|
|
},
|
|
},
|
|
Seasons: seasons{
|
|
Locations: map[string]properties{
|
|
"NY": {
|
|
Temp: "not cold",
|
|
Rating: 4,
|
|
},
|
|
"MI": {
|
|
Temp: "freezing",
|
|
Rating: 9,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("after decode, got:\n\n%#v\n\nwant:\n\n%#v", got, want)
|
|
}
|
|
if len(meta.keys) != 12 {
|
|
t.Errorf("after decode, got %d meta keys; want 12", len(meta.keys))
|
|
}
|
|
if len(meta.types) != 12 {
|
|
t.Errorf("after decode, got %d meta types; want 12", len(meta.types))
|
|
}
|
|
}
|
|
|
|
func TestDecodeInlineTableArray(t *testing.T) {
|
|
type point struct {
|
|
X, Y, Z int
|
|
}
|
|
var got struct {
|
|
Points []point
|
|
}
|
|
// Example inline table array from the spec.
|
|
const in = `
|
|
points = [ { x = 1, y = 2, z = 3 },
|
|
{ x = 7, y = 8, z = 9 },
|
|
{ x = 2, y = 4, z = 8 } ]
|
|
|
|
`
|
|
if _, err := Decode(in, &got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []point{
|
|
{X: 1, Y: 2, Z: 3},
|
|
{X: 7, Y: 8, Z: 9},
|
|
{X: 2, Y: 4, Z: 8},
|
|
}
|
|
if !reflect.DeepEqual(got.Points, want) {
|
|
t.Errorf("got %#v; want %#v", got.Points, want)
|
|
}
|
|
}
|
|
|
|
type menu struct {
|
|
Dishes map[string]dish
|
|
}
|
|
|
|
func (m *menu) UnmarshalTOML(p interface{}) error {
|
|
m.Dishes = make(map[string]dish)
|
|
data, _ := p.(map[string]interface{})
|
|
dishes := data["dishes"].(map[string]interface{})
|
|
for n, v := range dishes {
|
|
if d, ok := v.(map[string]interface{}); ok {
|
|
nd := dish{}
|
|
nd.UnmarshalTOML(d)
|
|
m.Dishes[n] = nd
|
|
} else {
|
|
return fmt.Errorf("not a dish")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type dish struct {
|
|
Name string
|
|
Price float32
|
|
Ingredients []ingredient
|
|
}
|
|
|
|
func (d *dish) UnmarshalTOML(p interface{}) error {
|
|
data, _ := p.(map[string]interface{})
|
|
d.Name, _ = data["name"].(string)
|
|
d.Price, _ = data["price"].(float32)
|
|
ingredients, _ := data["ingredients"].([]map[string]interface{})
|
|
for _, e := range ingredients {
|
|
n, _ := interface{}(e).(map[string]interface{})
|
|
name, _ := n["name"].(string)
|
|
i := ingredient{name}
|
|
d.Ingredients = append(d.Ingredients, i)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type ingredient struct {
|
|
Name string
|
|
}
|
|
|
|
func TestDecodeSlices(t *testing.T) {
|
|
type T struct {
|
|
S []string
|
|
}
|
|
for i, tt := range []struct {
|
|
v T
|
|
input string
|
|
want T
|
|
}{
|
|
{T{}, "", T{}},
|
|
{T{[]string{}}, "", T{[]string{}}},
|
|
{T{[]string{"a", "b"}}, "", T{[]string{"a", "b"}}},
|
|
{T{}, "S = []", T{[]string{}}},
|
|
{T{[]string{}}, "S = []", T{[]string{}}},
|
|
{T{[]string{"a", "b"}}, "S = []", T{[]string{}}},
|
|
{T{}, `S = ["x"]`, T{[]string{"x"}}},
|
|
{T{[]string{}}, `S = ["x"]`, T{[]string{"x"}}},
|
|
{T{[]string{"a", "b"}}, `S = ["x"]`, T{[]string{"x"}}},
|
|
} {
|
|
if _, err := Decode(tt.input, &tt.v); err != nil {
|
|
t.Errorf("[%d] %s", i, err)
|
|
continue
|
|
}
|
|
if !reflect.DeepEqual(tt.v, tt.want) {
|
|
t.Errorf("[%d] got %#v; want %#v", i, tt.v, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecodePrimitive(t *testing.T) {
|
|
type S struct {
|
|
P Primitive
|
|
}
|
|
type T struct {
|
|
S []int
|
|
}
|
|
slicep := func(s []int) *[]int { return &s }
|
|
arrayp := func(a [2]int) *[2]int { return &a }
|
|
mapp := func(m map[string]int) *map[string]int { return &m }
|
|
for i, tt := range []struct {
|
|
v interface{}
|
|
input string
|
|
want interface{}
|
|
}{
|
|
// slices
|
|
{slicep(nil), "", slicep(nil)},
|
|
{slicep([]int{}), "", slicep([]int{})},
|
|
{slicep([]int{1, 2, 3}), "", slicep([]int{1, 2, 3})},
|
|
{slicep(nil), "P = [1,2]", slicep([]int{1, 2})},
|
|
{slicep([]int{}), "P = [1,2]", slicep([]int{1, 2})},
|
|
{slicep([]int{1, 2, 3}), "P = [1,2]", slicep([]int{1, 2})},
|
|
|
|
// arrays
|
|
{arrayp([2]int{2, 3}), "", arrayp([2]int{2, 3})},
|
|
{arrayp([2]int{2, 3}), "P = [3,4]", arrayp([2]int{3, 4})},
|
|
|
|
// maps
|
|
{mapp(nil), "", mapp(nil)},
|
|
{mapp(map[string]int{}), "", mapp(map[string]int{})},
|
|
{mapp(map[string]int{"a": 1}), "", mapp(map[string]int{"a": 1})},
|
|
{mapp(nil), "[P]\na = 2", mapp(map[string]int{"a": 2})},
|
|
{mapp(map[string]int{}), "[P]\na = 2", mapp(map[string]int{"a": 2})},
|
|
{mapp(map[string]int{"a": 1, "b": 3}), "[P]\na = 2", mapp(map[string]int{"a": 2, "b": 3})},
|
|
|
|
// structs
|
|
{&T{nil}, "[P]", &T{nil}},
|
|
{&T{[]int{}}, "[P]", &T{[]int{}}},
|
|
{&T{[]int{1, 2, 3}}, "[P]", &T{[]int{1, 2, 3}}},
|
|
{&T{nil}, "[P]\nS = [1,2]", &T{[]int{1, 2}}},
|
|
{&T{[]int{}}, "[P]\nS = [1,2]", &T{[]int{1, 2}}},
|
|
{&T{[]int{1, 2, 3}}, "[P]\nS = [1,2]", &T{[]int{1, 2}}},
|
|
} {
|
|
var s S
|
|
md, err := Decode(tt.input, &s)
|
|
if err != nil {
|
|
t.Errorf("[%d] Decode error: %s", i, err)
|
|
continue
|
|
}
|
|
if err := md.PrimitiveDecode(s.P, tt.v); err != nil {
|
|
t.Errorf("[%d] PrimitiveDecode error: %s", i, err)
|
|
continue
|
|
}
|
|
if !reflect.DeepEqual(tt.v, tt.want) {
|
|
t.Errorf("[%d] got %#v; want %#v", i, tt.v, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkDecode(b *testing.B) {
|
|
var testSimple = `
|
|
age = 250
|
|
andrew = "gallant"
|
|
kait = "brady"
|
|
now = 1987-07-05T05:45:00Z
|
|
nowEast = 2017-06-22T16:15:21+08:00
|
|
nowWest = 2017-06-22T02:14:36-06:00
|
|
yesOrNo = true
|
|
pi = 3.14
|
|
colors = [
|
|
["red", "green", "blue"],
|
|
["cyan", "magenta", "yellow", "black"],
|
|
]
|
|
|
|
[My.Cats]
|
|
plato = "cat 1"
|
|
cauchy = """ cat 2
|
|
"""
|
|
`
|
|
|
|
type cats struct {
|
|
Plato string
|
|
Cauchy string
|
|
}
|
|
type simple struct {
|
|
Age int
|
|
Colors [][]string
|
|
Pi float64
|
|
YesOrNo bool
|
|
Now time.Time
|
|
NowEast time.Time
|
|
NowWest time.Time
|
|
Andrew string
|
|
Kait string
|
|
My map[string]cats
|
|
}
|
|
|
|
var val simple
|
|
_, err := Decode(testSimple, &val)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
Decode(testSimple, &val)
|
|
}
|
|
}
|
|
|
|
func TestDecodeDatetime(t *testing.T) {
|
|
// Test here in addition to toml-test to ensure the TZs are correct.
|
|
tz7 := time.FixedZone("", -3600*7)
|
|
|
|
for _, tt := range []struct {
|
|
in string
|
|
want time.Time
|
|
}{
|
|
// Offset datetime
|
|
{"1979-05-27T07:32:00Z", time.Date(1979, 05, 27, 07, 32, 0, 0, time.UTC)},
|
|
{"1979-05-27T07:32:00.999999Z", time.Date(1979, 05, 27, 07, 32, 0, 999999000, time.UTC)},
|
|
{"1979-05-27T00:32:00-07:00", time.Date(1979, 05, 27, 00, 32, 0, 0, tz7)},
|
|
{"1979-05-27T00:32:00.999999-07:00", time.Date(1979, 05, 27, 00, 32, 0, 999999000, tz7)},
|
|
{"1979-05-27T00:32:00.24-07:00", time.Date(1979, 05, 27, 00, 32, 0, 240000000, tz7)},
|
|
{"1979-05-27 07:32:00Z", time.Date(1979, 05, 27, 07, 32, 0, 0, time.UTC)},
|
|
{"1979-05-27t07:32:00z", time.Date(1979, 05, 27, 07, 32, 0, 0, time.UTC)},
|
|
|
|
// Make sure the space between the datetime and "#" isn't lexed.
|
|
{"1979-05-27T07:32:12-07:00 # c", time.Date(1979, 05, 27, 07, 32, 12, 0, tz7)},
|
|
|
|
// Local times.
|
|
{"1979-05-27T07:32:00", time.Date(1979, 05, 27, 07, 32, 0, 0, internal.LocalDatetime)},
|
|
{"1979-05-27T07:32:00.999999", time.Date(1979, 05, 27, 07, 32, 0, 999999000, internal.LocalDatetime)},
|
|
{"1979-05-27T07:32:00.25", time.Date(1979, 05, 27, 07, 32, 0, 250000000, internal.LocalDatetime)},
|
|
{"1979-05-27", time.Date(1979, 05, 27, 0, 0, 0, 0, internal.LocalDate)},
|
|
{"07:32:00", time.Date(0, 1, 1, 07, 32, 0, 0, internal.LocalTime)},
|
|
{"07:32:00.999999", time.Date(0, 1, 1, 07, 32, 0, 999999000, internal.LocalTime)},
|
|
} {
|
|
t.Run(tt.in, func(t *testing.T) {
|
|
var x struct{ D time.Time }
|
|
input := "d = " + tt.in
|
|
if _, err := Decode(input, &x); err != nil {
|
|
t.Fatalf("got error: %s", err)
|
|
}
|
|
|
|
if h, w := x.D.Format(time.RFC3339Nano), tt.want.Format(time.RFC3339Nano); h != w {
|
|
t.Errorf("\nhave: %s\nwant: %s", h, w)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseError(t *testing.T) {
|
|
file :=
|
|
`a = "a"
|
|
b = "b"
|
|
c = 001 # invalid
|
|
`
|
|
|
|
var s struct {
|
|
A, B string
|
|
C int
|
|
}
|
|
_, err := Decode(file, &s)
|
|
if err == nil {
|
|
t.Fatal("err is nil")
|
|
}
|
|
|
|
var pErr ParseError
|
|
if !errors.As(err, &pErr) {
|
|
t.Fatalf("err is not a ParseError: %T %[1]v", err)
|
|
}
|
|
|
|
want := ParseError{
|
|
Line: 3,
|
|
LastKey: "c",
|
|
Message: `Invalid integer "001": cannot have leading zeroes`,
|
|
}
|
|
if !strings.Contains(pErr.Message, want.Message) ||
|
|
pErr.Line != want.Line ||
|
|
pErr.LastKey != want.LastKey {
|
|
t.Errorf("unexpected data\nhave: %#v\nwant: %#v", pErr, want)
|
|
}
|
|
}
|
|
|
|
// errorContains checks if the error message in have contains the text in
|
|
// want.
|
|
//
|
|
// This is safe when have is nil. Use an empty string for want if you want to
|
|
// test that err is nil.
|
|
func errorContains(have error, want string) bool {
|
|
if have == nil {
|
|
return want == ""
|
|
}
|
|
if want == "" {
|
|
return false
|
|
}
|
|
return strings.Contains(have.Error(), want)
|
|
}
|