390927a0cd
``` 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 ```
382 lines
6.5 KiB
Go
382 lines
6.5 KiB
Go
package toml
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/pelletier/go-toml/v2/internal/ast"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestParser_AST_Numbers(t *testing.T) {
|
|
examples := []struct {
|
|
desc string
|
|
input string
|
|
kind ast.Kind
|
|
err bool
|
|
}{
|
|
{
|
|
desc: "integer just digits",
|
|
input: `1234`,
|
|
kind: ast.Integer,
|
|
},
|
|
{
|
|
desc: "integer zero",
|
|
input: `0`,
|
|
kind: ast.Integer,
|
|
},
|
|
{
|
|
desc: "integer sign",
|
|
input: `+99`,
|
|
kind: ast.Integer,
|
|
},
|
|
{
|
|
desc: "integer hex uppercase",
|
|
input: `0xDEADBEEF`,
|
|
kind: ast.Integer,
|
|
},
|
|
{
|
|
desc: "integer hex lowercase",
|
|
input: `0xdead_beef`,
|
|
kind: ast.Integer,
|
|
},
|
|
{
|
|
desc: "integer octal",
|
|
input: `0o01234567`,
|
|
kind: ast.Integer,
|
|
},
|
|
{
|
|
desc: "integer binary",
|
|
input: `0b11010110`,
|
|
kind: ast.Integer,
|
|
},
|
|
{
|
|
desc: "float pi",
|
|
input: `3.1415`,
|
|
kind: ast.Float,
|
|
},
|
|
{
|
|
desc: "float negative",
|
|
input: `-0.01`,
|
|
kind: ast.Float,
|
|
},
|
|
{
|
|
desc: "float signed exponent",
|
|
input: `5e+22`,
|
|
kind: ast.Float,
|
|
},
|
|
{
|
|
desc: "float exponent lowercase",
|
|
input: `1e06`,
|
|
kind: ast.Float,
|
|
},
|
|
{
|
|
desc: "float exponent uppercase",
|
|
input: `-2E-2`,
|
|
kind: ast.Float,
|
|
},
|
|
{
|
|
desc: "float fractional with exponent",
|
|
input: `6.626e-34`,
|
|
kind: ast.Float,
|
|
},
|
|
{
|
|
desc: "float underscores",
|
|
input: `224_617.445_991_228`,
|
|
kind: ast.Float,
|
|
},
|
|
{
|
|
desc: "inf",
|
|
input: `inf`,
|
|
kind: ast.Float,
|
|
},
|
|
{
|
|
desc: "inf negative",
|
|
input: `-inf`,
|
|
kind: ast.Float,
|
|
},
|
|
{
|
|
desc: "inf positive",
|
|
input: `+inf`,
|
|
kind: ast.Float,
|
|
},
|
|
{
|
|
desc: "nan",
|
|
input: `nan`,
|
|
kind: ast.Float,
|
|
},
|
|
{
|
|
desc: "nan negative",
|
|
input: `-nan`,
|
|
kind: ast.Float,
|
|
},
|
|
{
|
|
desc: "nan positive",
|
|
input: `+nan`,
|
|
kind: ast.Float,
|
|
},
|
|
}
|
|
|
|
for _, e := range examples {
|
|
t.Run(e.desc, func(t *testing.T) {
|
|
p := parser{}
|
|
p.Reset([]byte(`A = ` + e.input))
|
|
p.NextExpression()
|
|
err := p.Error()
|
|
if e.err {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
|
|
expected := astNode{
|
|
Kind: ast.KeyValue,
|
|
Children: []astNode{
|
|
{Kind: e.kind, Data: []byte(e.input)},
|
|
{Kind: ast.Key, Data: []byte(`A`)},
|
|
},
|
|
}
|
|
compareNode(t, expected, p.Expression())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type astRoot []astNode
|
|
type astNode struct {
|
|
Kind ast.Kind
|
|
Data []byte
|
|
Children []astNode
|
|
}
|
|
|
|
func compareAST(t *testing.T, expected astRoot, actual *ast.Root) {
|
|
it := actual.Iterator()
|
|
compareIterator(t, expected, it)
|
|
}
|
|
|
|
func compareNode(t *testing.T, e astNode, n ast.Node) {
|
|
require.Equal(t, e.Kind, n.Kind)
|
|
require.Equal(t, e.Data, n.Data)
|
|
|
|
compareIterator(t, e.Children, n.Children())
|
|
}
|
|
|
|
func compareIterator(t *testing.T, expected []astNode, actual ast.Iterator) {
|
|
idx := 0
|
|
|
|
for actual.Next() {
|
|
n := actual.Node()
|
|
|
|
if idx >= len(expected) {
|
|
t.Fatal("extra child in actual tree")
|
|
}
|
|
e := expected[idx]
|
|
|
|
compareNode(t, e, n)
|
|
|
|
idx++
|
|
}
|
|
|
|
if idx < len(expected) {
|
|
t.Fatal("missing children in actual", "idx =", idx, "expected =", len(expected))
|
|
}
|
|
}
|
|
|
|
func (r astRoot) toOrig() *ast.Root {
|
|
builder := &ast.Builder{}
|
|
|
|
var last ast.Reference
|
|
|
|
for i, n := range r {
|
|
ref := builder.Push(ast.Node{
|
|
Kind: n.Kind,
|
|
Data: n.Data,
|
|
})
|
|
|
|
if i > 0 {
|
|
builder.Chain(last, ref)
|
|
}
|
|
last = ref
|
|
|
|
if len(n.Children) > 0 {
|
|
c := childrenToOrig(builder, n.Children)
|
|
builder.AttachChild(ref, c)
|
|
}
|
|
}
|
|
|
|
return builder.Tree()
|
|
}
|
|
|
|
func childrenToOrig(b *ast.Builder, nodes []astNode) ast.Reference {
|
|
var first ast.Reference
|
|
var last ast.Reference
|
|
for i, n := range nodes {
|
|
ref := b.Push(ast.Node{
|
|
Kind: n.Kind,
|
|
Data: n.Data,
|
|
})
|
|
if i == 0 {
|
|
first = ref
|
|
} else {
|
|
b.Chain(last, ref)
|
|
}
|
|
last = ref
|
|
|
|
if len(n.Children) > 0 {
|
|
c := childrenToOrig(b, n.Children)
|
|
b.AttachChild(ref, c)
|
|
}
|
|
}
|
|
return first
|
|
}
|
|
|
|
func TestParser_AST(t *testing.T) {
|
|
examples := []struct {
|
|
desc string
|
|
input string
|
|
ast astNode
|
|
err bool
|
|
}{
|
|
{
|
|
desc: "simple string assignment",
|
|
input: `A = "hello"`,
|
|
ast: astNode{
|
|
Kind: ast.KeyValue,
|
|
Children: []astNode{
|
|
{
|
|
Kind: ast.String,
|
|
Data: []byte(`hello`),
|
|
},
|
|
{
|
|
Kind: ast.Key,
|
|
Data: []byte(`A`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "simple bool assignment",
|
|
input: `A = true`,
|
|
ast: astNode{
|
|
Kind: ast.KeyValue,
|
|
Children: []astNode{
|
|
{
|
|
Kind: ast.Bool,
|
|
Data: []byte(`true`),
|
|
},
|
|
{
|
|
Kind: ast.Key,
|
|
Data: []byte(`A`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "array of strings",
|
|
input: `A = ["hello", ["world", "again"]]`,
|
|
ast: astNode{
|
|
Kind: ast.KeyValue,
|
|
Children: []astNode{
|
|
{
|
|
Kind: ast.Array,
|
|
Children: []astNode{
|
|
{
|
|
Kind: ast.String,
|
|
Data: []byte(`hello`),
|
|
},
|
|
{
|
|
Kind: ast.Array,
|
|
Children: []astNode{
|
|
{
|
|
Kind: ast.String,
|
|
Data: []byte(`world`),
|
|
},
|
|
{
|
|
Kind: ast.String,
|
|
Data: []byte(`again`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Kind: ast.Key,
|
|
Data: []byte(`A`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "array of arrays of strings",
|
|
input: `A = ["hello", "world"]`,
|
|
ast: astNode{
|
|
Kind: ast.KeyValue,
|
|
Children: []astNode{
|
|
{
|
|
Kind: ast.Array,
|
|
Children: []astNode{
|
|
{
|
|
Kind: ast.String,
|
|
Data: []byte(`hello`),
|
|
},
|
|
{
|
|
Kind: ast.String,
|
|
Data: []byte(`world`),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Kind: ast.Key,
|
|
Data: []byte(`A`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "inline table",
|
|
input: `name = { first = "Tom", last = "Preston-Werner" }`,
|
|
ast: astNode{
|
|
Kind: ast.KeyValue,
|
|
Children: []astNode{
|
|
{
|
|
Kind: ast.InlineTable,
|
|
Children: []astNode{
|
|
{
|
|
Kind: ast.KeyValue,
|
|
Children: []astNode{
|
|
{Kind: ast.String, Data: []byte(`Tom`)},
|
|
{Kind: ast.Key, Data: []byte(`first`)},
|
|
},
|
|
},
|
|
{
|
|
Kind: ast.KeyValue,
|
|
Children: []astNode{
|
|
{Kind: ast.String, Data: []byte(`Preston-Werner`)},
|
|
{Kind: ast.Key, Data: []byte(`last`)},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Kind: ast.Key,
|
|
Data: []byte(`name`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, e := range examples {
|
|
t.Run(e.desc, func(t *testing.T) {
|
|
p := parser{}
|
|
p.Reset([]byte(e.input))
|
|
p.NextExpression()
|
|
err := p.Error()
|
|
if e.err {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
compareNode(t, e.ast, p.Expression())
|
|
}
|
|
})
|
|
}
|
|
}
|