Files
go-toml/unmarshaler_test.go
T
Thomas Pelletier 390927a0cd Reuse AST storage between top-level expressions
```
Comparing:
	old: v2-wip/1da2fc7 (2021-03-25 20:38:05 -0400 -0400)
	run: v2-wip/3f23ab9 (2021-03-25 22:35:06 -0400 -0400)
-----------------------------------------------------------
name                  old time/op    new time/op    delta
UnmarshalSimple/v2-8     700ns ± 3%     705ns ± 2%     ~     (p=0.690 n=5+5)
UnmarshalSimple/v1-8    3.85µs ± 1%    4.02µs ± 4%   +4.19%  (p=0.032 n=5+5)
UnmarshalSimple/bs-8    2.34µs ± 2%    2.38µs ± 3%     ~     (p=0.310 n=5+5)
ReferenceFile/v2-8      32.2µs ±13%    23.9µs ± 1%  -25.79%  (p=0.008 n=5+5)
ReferenceFile/v1-8       270µs ± 2%     264µs ± 2%     ~     (p=0.095 n=5+5)
ReferenceFile/bs-8       291µs ± 0%     294µs ± 0%   +0.88%  (p=0.008 n=5+5)

name                  old alloc/op   new alloc/op   delta
ReferenceFile/v2-8      37.1kB ± 0%     6.7kB ± 0%  -81.91%  (p=0.008 n=5+5)
ReferenceFile/v1-8       131kB ± 0%     131kB ± 0%     ~     (p=0.444 n=5+5)
ReferenceFile/bs-8      80.8kB ± 0%    80.8kB ± 0%     ~     (p=0.571 n=5+5)

name                  old allocs/op  new allocs/op  delta
ReferenceFile/v2-8         152 ± 0%       148 ± 0%   -2.63%  (p=0.008 n=5+5)
ReferenceFile/v1-8       2.65k ± 0%     2.65k ± 0%     ~     (all equal)
ReferenceFile/bs-8       1.73k ± 0%     1.73k ± 0%     ~     (all equal)

~/s/g/p/g/benchmark$ go test -bench=.
goos: linux
goarch: amd64
pkg: github.com/pelletier/go-toml/v2/benchmark
cpu: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz
BenchmarkUnmarshalSimple/v2-8         	 1692444	       710.7 ns/op
BenchmarkUnmarshalSimple/v1-8         	  307609	      3862 ns/op
BenchmarkUnmarshalSimple/bs-8         	  520429	      2285 ns/op
BenchmarkReferenceFile/v2-8           	   50395	     24006 ns/op	    6704 B/op	     148 allocs/op
BenchmarkReferenceFile/v1-8           	    4144	    264655 ns/op	  130567 B/op	    2649 allocs/op
BenchmarkReferenceFile/bs-8           	    3969	    293635 ns/op	   80784 B/op	    1729 allocs/op
PASS
ok  	github.com/pelletier/go-toml/v2/benchmark	8.143s
```
2021-03-25 22:37:16 -04:00

660 lines
12 KiB
Go

package toml_test
import (
"math"
"testing"
"github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUnmarshal_Integers(t *testing.T) {
examples := []struct {
desc string
input string
expected int64
err bool
}{
{
desc: "integer just digits",
input: `1234`,
expected: 1234,
},
{
desc: "integer zero",
input: `0`,
expected: 0,
},
{
desc: "integer sign",
input: `+99`,
expected: 99,
},
{
desc: "integer hex uppercase",
input: `0xDEADBEEF`,
expected: 0xDEADBEEF,
},
{
desc: "integer hex lowercase",
input: `0xdead_beef`,
expected: 0xDEADBEEF,
},
{
desc: "integer octal",
input: `0o01234567`,
expected: 0o01234567,
},
{
desc: "integer binary",
input: `0b11010110`,
expected: 0b11010110,
},
}
type doc struct {
A int64
}
for _, e := range examples {
t.Run(e.desc, func(t *testing.T) {
doc := doc{}
err := toml.Unmarshal([]byte(`A = `+e.input), &doc)
require.NoError(t, err)
assert.Equal(t, e.expected, doc.A)
})
}
}
func TestUnmarshal_Floats(t *testing.T) {
examples := []struct {
desc string
input string
expected float64
testFn func(t *testing.T, v float64)
err bool
}{
{
desc: "float pi",
input: `3.1415`,
expected: 3.1415,
},
{
desc: "float negative",
input: `-0.01`,
expected: -0.01,
},
{
desc: "float signed exponent",
input: `5e+22`,
expected: 5e+22,
},
{
desc: "float exponent lowercase",
input: `1e06`,
expected: 1e06,
},
{
desc: "float exponent uppercase",
input: `-2E-2`,
expected: -2e-2,
},
{
desc: "float fractional with exponent",
input: `6.626e-34`,
expected: 6.626e-34,
},
{
desc: "float underscores",
input: `224_617.445_991_228`,
expected: 224_617.445_991_228,
},
{
desc: "inf",
input: `inf`,
expected: math.Inf(+1),
},
{
desc: "inf negative",
input: `-inf`,
expected: math.Inf(-1),
},
{
desc: "inf positive",
input: `+inf`,
expected: math.Inf(+1),
},
{
desc: "nan",
input: `nan`,
testFn: func(t *testing.T, v float64) {
assert.True(t, math.IsNaN(v))
},
},
{
desc: "nan negative",
input: `-nan`,
testFn: func(t *testing.T, v float64) {
assert.True(t, math.IsNaN(v))
},
},
{
desc: "nan positive",
input: `+nan`,
testFn: func(t *testing.T, v float64) {
assert.True(t, math.IsNaN(v))
},
},
}
type doc struct {
A float64
}
for _, e := range examples {
t.Run(e.desc, func(t *testing.T) {
doc := doc{}
err := toml.Unmarshal([]byte(`A = `+e.input), &doc)
require.NoError(t, err)
if e.testFn != nil {
e.testFn(t, doc.A)
} else {
assert.Equal(t, e.expected, doc.A)
}
})
}
}
func TestUnmarshal(t *testing.T) {
type test struct {
target interface{}
expected interface{}
err bool
}
examples := []struct {
skip bool
desc string
input string
gen func() test
}{
{
desc: "kv string",
input: `A = "foo"`,
gen: func() test {
type doc struct {
A string
}
return test{
target: &doc{},
expected: &doc{A: "foo"},
}
},
},
{
desc: "kv bool true",
input: `A = true`,
gen: func() test {
type doc struct {
A bool
}
return test{
target: &doc{},
expected: &doc{A: true},
}
},
},
{
desc: "kv bool false",
input: `A = false`,
gen: func() test {
type doc struct {
A bool
}
return test{
target: &doc{A: true},
expected: &doc{A: false},
}
},
},
{
desc: "string array",
input: `A = ["foo", "bar"]`,
gen: func() test {
type doc struct {
A []string
}
return test{
target: &doc{},
expected: &doc{A: []string{"foo", "bar"}},
}
},
},
{
desc: "standard table",
input: `[A]
B = "data"`,
gen: func() test {
type A struct {
B string
}
type doc struct {
A A
}
return test{
target: &doc{},
expected: &doc{A: A{B: "data"}},
}
},
},
{
desc: "inline table",
input: `Name = {First = "hello", Last = "world"}`,
gen: func() test {
type name struct {
First string
Last string
}
type doc struct {
Name name
}
return test{
target: &doc{},
expected: &doc{Name: name{
First: "hello",
Last: "world",
}},
}
},
},
{
desc: "inline table inside array",
input: `Names = [{First = "hello", Last = "world"}, {First = "ab", Last = "cd"}]`,
gen: func() test {
type name struct {
First string
Last string
}
type doc struct {
Names []name
}
return test{
target: &doc{},
expected: &doc{
Names: []name{
{
First: "hello",
Last: "world",
},
{
First: "ab",
Last: "cd",
},
},
},
}
},
},
{
desc: "into map[string]interface{}",
input: `A = "foo"`,
gen: func() test {
doc := map[string]interface{}{}
return test{
target: &doc,
expected: &map[string]interface{}{
"A": "foo",
},
}
},
},
{
desc: "multi keys of different types into map[string]interface{}",
input: `A = "foo"
B = 42`,
gen: func() test {
doc := map[string]interface{}{}
return test{
target: &doc,
expected: &map[string]interface{}{
"A": "foo",
"B": int64(42),
},
}
},
},
{
desc: "slice in a map[string]interface{}",
input: `A = ["foo", "bar"]`,
gen: func() test {
doc := map[string]interface{}{}
return test{
target: &doc,
expected: &map[string]interface{}{
"A": []interface{}{"foo", "bar"},
},
}
},
},
{
desc: "string into map[string]string",
input: `A = "foo"`,
gen: func() test {
doc := map[string]string{}
return test{
target: &doc,
expected: &map[string]string{
"A": "foo",
},
}
},
},
{
desc: "float64 into map[string]string",
input: `A = 42.0`,
gen: func() test {
doc := map[string]string{}
return test{
target: &doc,
err: true,
}
},
},
{
desc: "one-level one-element array table",
input: `[[First]]
Second = "hello"`,
gen: func() test {
type First struct {
Second string
}
type Doc struct {
First []First
}
return test{
target: &Doc{},
expected: &Doc{
First: []First{
{
Second: "hello",
},
},
},
}
},
},
{
desc: "one-level multi-element array table",
input: `[[Products]]
Name = "Hammer"
Sku = 738594937
[[Products]] # empty table within the array
[[Products]]
Name = "Nail"
Sku = 284758393
Color = "gray"`,
gen: func() test {
type Product struct {
Name string
Sku int64
Color string
}
type Doc struct {
Products []Product
}
return test{
target: &Doc{},
expected: &Doc{
Products: []Product{
{Name: "Hammer", Sku: 738594937},
{},
{Name: "Nail", Sku: 284758393, Color: "gray"},
},
},
}
},
},
{
desc: "one-level multi-element array table to map",
input: `[[Products]]
Name = "Hammer"
Sku = 738594937
[[Products]] # empty table within the array
[[Products]]
Name = "Nail"
Sku = 284758393
Color = "gray"`,
gen: func() test {
return test{
target: &map[string]interface{}{},
expected: &map[string]interface{}{
"Products": []interface{}{
map[string]interface{}{
"Name": "Hammer",
"Sku": int64(738594937),
},
nil,
map[string]interface{}{
"Name": "Nail",
"Sku": int64(284758393),
"Color": "gray",
},
},
},
}
},
},
{
desc: "sub-table in array table",
input: `[[Fruits]]
Name = "apple"
[Fruits.Physical] # subtable
Color = "red"
Shape = "round"`,
gen: func() test {
return test{
target: &map[string]interface{}{},
expected: &map[string]interface{}{
"Fruits": []interface{}{
map[string]interface{}{
"Name": "apple",
"Physical": map[string]interface{}{
"Color": "red",
"Shape": "round",
},
},
},
},
}
},
},
{
desc: "multiple sub-table in array tables",
input: `[[Fruits]]
Name = "apple"
[[Fruits.Varieties]] # nested array of tables
Name = "red delicious"
[[Fruits.Varieties]]
Name = "granny smith"
[[Fruits]]
Name = "banana"
[[Fruits.Varieties]]
Name = "plantain"`,
gen: func() test {
return test{
target: &map[string]interface{}{},
expected: &map[string]interface{}{
"Fruits": []interface{}{
map[string]interface{}{
"Name": "apple",
"Varieties": []interface{}{
map[string]interface{}{
"Name": "red delicious",
},
map[string]interface{}{
"Name": "granny smith",
},
},
},
map[string]interface{}{
"Name": "banana",
"Varieties": []interface{}{
map[string]interface{}{
"Name": "plantain",
},
},
},
},
},
}
},
},
{
desc: "multiple sub-table in array tables into structs",
input: `[[Fruits]]
Name = "apple"
[[Fruits.Varieties]] # nested array of tables
Name = "red delicious"
[[Fruits.Varieties]]
Name = "granny smith"
[[Fruits]]
Name = "banana"
[[Fruits.Varieties]]
Name = "plantain"`,
gen: func() test {
type Variety struct {
Name string
}
type Fruit struct {
Name string
Varieties []Variety
}
type doc struct {
Fruits []Fruit
}
return test{
target: &doc{},
expected: &doc{
Fruits: []Fruit{
{
Name: "apple",
Varieties: []Variety{
{Name: "red delicious"},
{Name: "granny smith"},
},
},
{
Name: "banana",
Varieties: []Variety{
{Name: "plantain"},
},
},
},
},
}
},
},
{
desc: "slice pointer in slice pointer",
input: `A = ["Hello"]`,
gen: func() test {
type doc struct {
A *[]*string
}
hello := "Hello"
return test{
target: &doc{},
expected: &doc{
A: &[]*string{&hello},
},
}
},
},
{
desc: "interface holding a struct",
input: `[A]
B = "After"`,
gen: func() test {
type inner struct {
B interface{}
}
type doc struct {
A interface{}
}
return test{
target: &doc{
A: inner{
B: "Before",
},
},
expected: &doc{
A: map[string]interface{}{
"B": "After",
},
},
}
},
},
{
desc: "array of structs with table arrays",
input: `[[A]]
B = "one"
[[A]]
B = "two"`,
gen: func() test {
type inner struct {
B string
}
type doc struct {
A [4]inner
}
return test{
target: &doc{},
expected: &doc{
A: [4]inner{
{B: "one"},
{B: "two"},
},
},
}
},
},
}
for _, e := range examples {
t.Run(e.desc, func(t *testing.T) {
if e.skip {
t.Skip()
}
test := e.gen()
if test.err && test.expected != nil {
panic("invalid test: cannot expect both an error and a value")
}
err := toml.Unmarshal([]byte(e.input), test.target)
if test.err {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, test.expected, test.target)
}
})
}
}