Stack-based unmarshaler (#546)

* Benchmark script

* Rewrite unmarshaler using the stack

Instead of tracking the build chain using `target`s, use the stack
instead.

Working and most benchmarks look good, but regression on structs unmarshalling.

~60% slower on ReferenceFile/struct.

* Shortcut to check if last node of iterator

* Remove unecessary pointer allocation

* Skip over unused keys without marking them as seen

* Add some tests

* Fix mktemp on macos
This commit is contained in:
Thomas Pelletier
2021-05-31 12:14:13 -04:00
committed by GitHub
parent 11f022ab09
commit 250e073408
20 changed files with 1340 additions and 1296 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ enable = [
# "nlreturn", # "nlreturn",
"noctx", "noctx",
"nolintlint", "nolintlint",
"paralleltest", #"paralleltest",
"prealloc", "prealloc",
"predeclared", "predeclared",
"revive", "revive",
+16 -17
View File
@@ -31,13 +31,14 @@ var bench_inputs = []struct {
func TestUnmarshalDatasetCode(t *testing.T) { func TestUnmarshalDatasetCode(t *testing.T) {
for _, tc := range bench_inputs { for _, tc := range bench_inputs {
buf := fixture(t, tc.name)
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
buf := fixture(t, tc.name)
var v interface{} var v interface{}
check(t, toml.Unmarshal(buf, &v)) require.NoError(t, toml.Unmarshal(buf, &v))
b, err := json.Marshal(v) b, err := json.Marshal(v)
check(t, err) require.NoError(t, err)
require.Equal(t, len(b), tc.jsonLen) require.Equal(t, len(b), tc.jsonLen)
}) })
} }
@@ -45,14 +46,14 @@ func TestUnmarshalDatasetCode(t *testing.T) {
func BenchmarkUnmarshalDataset(b *testing.B) { func BenchmarkUnmarshalDataset(b *testing.B) {
for _, tc := range bench_inputs { for _, tc := range bench_inputs {
buf := fixture(b, tc.name)
b.Run(tc.name, func(b *testing.B) { b.Run(tc.name, func(b *testing.B) {
buf := fixture(b, tc.name)
b.SetBytes(int64(len(buf))) b.SetBytes(int64(len(buf)))
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var v interface{} var v interface{}
check(b, toml.Unmarshal(buf, &v)) require.NoError(b, toml.Unmarshal(buf, &v))
} }
}) })
} }
@@ -60,22 +61,20 @@ func BenchmarkUnmarshalDataset(b *testing.B) {
// fixture returns the uncompressed contents of path. // fixture returns the uncompressed contents of path.
func fixture(tb testing.TB, path string) []byte { func fixture(tb testing.TB, path string) []byte {
f, err := os.Open(filepath.Join("testdata", path+".toml.gz")) tb.Helper()
check(tb, err)
file := path + ".toml.gz"
f, err := os.Open(filepath.Join("testdata", file))
if os.IsNotExist(err) {
tb.Skip("benchmark fixture not found:", file)
}
require.NoError(tb, err)
defer f.Close() defer f.Close()
gz, err := gzip.NewReader(f) gz, err := gzip.NewReader(f)
check(tb, err) require.NoError(tb, err)
buf, err := ioutil.ReadAll(gz) buf, err := ioutil.ReadAll(gz)
check(tb, err) require.NoError(tb, err)
return buf return buf
} }
func check(tb testing.TB, err error) {
if err != nil {
tb.Helper()
tb.Fatal(err)
}
}
+12
View File
@@ -9,6 +9,18 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestUnmarshalSimple(t *testing.T) {
doc := []byte(`A = "hello"`)
d := struct {
A string
}{}
err := toml.Unmarshal(doc, &d)
if err != nil {
panic(err)
}
}
func BenchmarkUnmarshalSimple(b *testing.B) { func BenchmarkUnmarshalSimple(b *testing.B) {
doc := []byte(`A = "hello"`) doc := []byte(`A = "hello"`)
+35 -2
View File
@@ -39,6 +39,9 @@ benchmark [OPTIONS...] [BRANCH]
-d Compare benchmarks of HEAD with BRANCH using benchstats. In -d Compare benchmarks of HEAD with BRANCH using benchstats. In
this form the BRANCH argument is required. this form the BRANCH argument is required.
-a Compare benchmarks of HEAD against go-toml v1 and
BurntSushi/toml.
coverage [OPTIONS...] [BRANCH] coverage [OPTIONS...] [BRANCH]
Generates code coverage. Generates code coverage.
@@ -118,6 +121,7 @@ coverage() {
bench() { bench() {
branch="${1}" branch="${1}"
out="${2}" out="${2}"
replace="${3}"
dir="$(mktemp -d)" dir="$(mktemp -d)"
stderr "Executing benchmark for ${branch} at ${dir}" stderr "Executing benchmark for ${branch} at ${dir}"
@@ -129,6 +133,15 @@ bench() {
fi fi
pushd "$dir" pushd "$dir"
if [ "${replace}" != "" ]; then
find ./benchmark/ -iname '*.go' -exec sed -i -E "s|github.com/pelletier/go-toml/v2|${replace}|g" {} \;
go get "${replace}"
# hack: remove canada.toml.gz because it is not supported by
# burntsushi, and replace is only used for benchmark -a
rm -f benchmark/testdata/canada.toml.gz
fi
go test -bench=. -count=10 ./... | tee "${out}" go test -bench=. -count=10 ./... | tee "${out}"
popd popd
@@ -142,14 +155,34 @@ benchmark() {
-d) -d)
shift shift
target="${1?Need to provide a target branch argument}" target="${1?Need to provide a target branch argument}"
old=`mktemp`
old=`mktemp --suffix=-${target}`
bench "${target}" "${old}" bench "${target}" "${old}"
new=`mktemp` new=`mktemp --suffix=-HEAD`
bench HEAD "${new}" bench HEAD "${new}"
benchstat "${old}" "${new}" benchstat "${old}" "${new}"
return 0 return 0
;; ;;
-a)
shift
v2stats=`mktemp -t go-toml-v2`
bench HEAD "${v2stats}" "github.com/pelletier/go-toml/v2"
v1stats=`mktemp -t go-toml-v1`
bench HEAD "${v1stats}" "github.com/pelletier/go-toml"
bsstats=`mktemp -t bs-toml`
bench HEAD "${bsstats}" "github.com/BurntSushi/toml"
cp "${v2stats}" go-toml-v2.txt
cp "${v1stats}" go-toml-v1.txt
cp "${bsstats}" bs-toml.txt
benchstat -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt
rm -f go-toml-v2.txt go-toml-v1.txt bs-toml.txt
return $?
esac esac
bench "${1-HEAD}" `mktemp` bench "${1-HEAD}" `mktemp`
+1 -3
View File
@@ -12,7 +12,6 @@ import (
//nolint:funlen //nolint:funlen
func TestDecodeError(t *testing.T) { func TestDecodeError(t *testing.T) {
t.Parallel()
examples := []struct { examples := []struct {
desc string desc string
@@ -154,7 +153,7 @@ line 5`,
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
t.Parallel()
b := bytes.Buffer{} b := bytes.Buffer{}
b.Write([]byte(e.doc[0])) b.Write([]byte(e.doc[0]))
start := b.Len() start := b.Len()
@@ -182,7 +181,6 @@ line 5`,
} }
func TestDecodeError_Accessors(t *testing.T) { func TestDecodeError_Accessors(t *testing.T) {
t.Parallel()
e := DecodeError{ e := DecodeError{
message: "foo", message: "foo",
+100
View File
@@ -0,0 +1,100 @@
package toml_test
import (
"testing"
"github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/require"
)
func TestFastSimple(t *testing.T) {
m := map[string]int64{}
err := toml.Unmarshal([]byte(`a = 42`), &m)
require.NoError(t, err)
require.Equal(t, map[string]int64{"a": 42}, m)
}
func TestFastSimpleString(t *testing.T) {
m := map[string]string{}
err := toml.Unmarshal([]byte(`a = "hello"`), &m)
require.NoError(t, err)
require.Equal(t, map[string]string{"a": "hello"}, m)
}
func TestFastSimpleInterface(t *testing.T) {
m := map[string]interface{}{}
err := toml.Unmarshal([]byte(`
a = "hello"
b = 42`), &m)
require.NoError(t, err)
require.Equal(t, map[string]interface{}{
"a": "hello",
"b": int64(42),
}, m)
}
func TestFastMultipartKeyInterface(t *testing.T) {
m := map[string]interface{}{}
err := toml.Unmarshal([]byte(`
a.interim = "test"
a.b.c = "hello"
b = 42`), &m)
require.NoError(t, err)
require.Equal(t, map[string]interface{}{
"a": map[string]interface{}{
"interim": "test",
"b": map[string]interface{}{
"c": "hello",
},
},
"b": int64(42),
}, m)
}
func TestFastExistingMap(t *testing.T) {
m := map[string]interface{}{
"ints": map[string]int{},
}
err := toml.Unmarshal([]byte(`
ints.one = 1
ints.two = 2
strings.yo = "hello"`), &m)
require.NoError(t, err)
require.Equal(t, map[string]interface{}{
"ints": map[string]interface{}{
"one": int64(1),
"two": int64(2),
},
"strings": map[string]interface{}{
"yo": "hello",
},
}, m)
}
func TestFastArrayTable(t *testing.T) {
b := []byte(`
[root]
[[root.nested]]
name = 'Bob'
[[root.nested]]
name = 'Alice'
`)
m := map[string]interface{}{}
err := toml.Unmarshal(b, &m)
require.NoError(t, err)
require.Equal(t, map[string]interface{}{
"root": map[string]interface{}{
"nested": []interface{}{
map[string]interface{}{
"name": "Bob",
},
map[string]interface{}{
"name": "Alice",
},
},
},
}, m)
}
+6
View File
@@ -28,6 +28,12 @@ func (c *Iterator) Next() bool {
return c.node.Valid() return c.node.Valid()
} }
// IsLast returns true if the current node of the iterator is the last one.
// Subsequent call to Next() will return false.
func (c *Iterator) IsLast() bool {
return c.node.next <= 0
}
// Node returns a copy of the node pointed at by the iterator. // Node returns a copy of the node pointed at by the iterator.
func (c *Iterator) Node() Node { func (c *Iterator) Node() Node {
return c.node return c.node
@@ -223,11 +223,13 @@ type testSubDoc struct {
unexported int `toml:"shouldntBeHere"` unexported int `toml:"shouldntBeHere"`
} }
var biteMe = "Bite me" var (
var float1 float32 = 12.3 biteMe = "Bite me"
var float2 float32 = 45.6 float1 float32 = 12.3
var float3 float32 = 78.9 float2 float32 = 45.6
var subdoc = testSubDoc{"Second", 0} float3 float32 = 78.9
subdoc = testSubDoc{"Second", 0}
)
var docData = testDoc{ var docData = testDoc{
Title: "TOML Marshal Testing", Title: "TOML Marshal Testing",
@@ -382,7 +384,7 @@ var intErrTomls = []string{
} }
func TestErrUnmarshal(t *testing.T) { func TestErrUnmarshal(t *testing.T) {
var errTomls = []string{ errTomls := []string{
"bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", "bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", "bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"",
"bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"", "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"",
@@ -468,7 +470,7 @@ func TestEmptyUnmarshalOmit(t *testing.T) {
Map map[string]string `toml:"map,omitempty"` Map map[string]string `toml:"map,omitempty"`
} }
var emptyTestData2 = emptyMarshalTestStruct2{ emptyTestData2 := emptyMarshalTestStruct2{
Title: "Placeholder", Title: "Placeholder",
Bool: false, Bool: false,
Int: 0, Int: 0,
@@ -496,21 +498,23 @@ type pointerMarshalTestStruct struct {
DblPtr *[]*[]*string DblPtr *[]*[]*string
} }
var pointerStr = "Hello" var (
var pointerList = []string{"Hello back"} pointerStr = "Hello"
var pointerListPtr = []*string{&pointerStr} pointerList = []string{"Hello back"}
var pointerMap = map[string]string{"response": "Goodbye"} pointerListPtr = []*string{&pointerStr}
var pointerMapPtr = map[string]*string{"alternate": &pointerStr} pointerMap = map[string]string{"response": "Goodbye"}
var pointerTestData = pointerMarshalTestStruct{ pointerMapPtr = map[string]*string{"alternate": &pointerStr}
Str: &pointerStr, pointerTestData = pointerMarshalTestStruct{
List: &pointerList, Str: &pointerStr,
ListPtr: &pointerListPtr, List: &pointerList,
Map: &pointerMap, ListPtr: &pointerListPtr,
MapPtr: &pointerMapPtr, Map: &pointerMap,
EmptyStr: nil, MapPtr: &pointerMapPtr,
EmptyList: nil, EmptyStr: nil,
EmptyMap: nil, EmptyList: nil,
} EmptyMap: nil,
}
)
var pointerTestToml = []byte(`List = ["Hello back"] var pointerTestToml = []byte(`List = ["Hello back"]
ListPtr = ["Hello"] ListPtr = ["Hello"]
@@ -538,15 +542,17 @@ func TestUnmarshalTypeMismatch(t *testing.T) {
type nestedMarshalTestStruct struct { type nestedMarshalTestStruct struct {
String [][]string String [][]string
//Struct [][]basicMarshalTestSubStruct // Struct [][]basicMarshalTestSubStruct
StringPtr *[]*[]*string StringPtr *[]*[]*string
// StructPtr *[]*[]*basicMarshalTestSubStruct // StructPtr *[]*[]*basicMarshalTestSubStruct
} }
var str1 = "Three" var (
var str2 = "Four" str1 = "Three"
var strPtr = []*string{&str1, &str2} str2 = "Four"
var strPtr2 = []*[]*string{&strPtr} strPtr = []*string{&str1, &str2}
strPtr2 = []*[]*string{&strPtr}
)
var nestedTestData = nestedMarshalTestStruct{ var nestedTestData = nestedMarshalTestStruct{
String: [][]string{{"Five", "Six"}, {"One", "Two"}}, String: [][]string{{"Five", "Six"}, {"One", "Two"}},
@@ -597,6 +603,7 @@ var nestedCustomMarshalerData = customMarshalerParent{
var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"] var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"]
me = "Maiku Suteda" me = "Maiku Suteda"
`) `)
var nestedCustomMarshalerTomlForUnmarshal = []byte(`[friends] var nestedCustomMarshalerTomlForUnmarshal = []byte(`[friends]
FirstName = "Sally" FirstName = "Sally"
LastName = "Fields"`) LastName = "Fields"`)
@@ -613,11 +620,11 @@ func (x *IntOrString) MarshalTOML() ([]byte, error) {
} }
func TestUnmarshalTextMarshaler(t *testing.T) { func TestUnmarshalTextMarshaler(t *testing.T) {
var nested = struct { nested := struct {
Friends textMarshaler `toml:"friends"` Friends textMarshaler `toml:"friends"`
}{} }{}
var expected = struct { expected := struct {
Friends textMarshaler `toml:"friends"` Friends textMarshaler `toml:"friends"`
}{ }{
Friends: textMarshaler{FirstName: "Sally", LastName: "Fields"}, Friends: textMarshaler{FirstName: "Sally", LastName: "Fields"},
@@ -1360,7 +1367,6 @@ func TestUnmarshalPreservesUnexportedFields(t *testing.T) {
t.Run("unexported field should not be set from toml", func(t *testing.T) { t.Run("unexported field should not be set from toml", func(t *testing.T) {
var actual unexportedFieldPreservationTest var actual unexportedFieldPreservationTest
err := toml.Unmarshal([]byte(doc), &actual) err := toml.Unmarshal([]byte(doc), &actual)
if err != nil { if err != nil {
t.Fatal("did not expect an error") t.Fatal("did not expect an error")
} }
@@ -1394,7 +1400,6 @@ func TestUnmarshalPreservesUnexportedFields(t *testing.T) {
Nested3: &unexportedFieldPreservationTestNested{"baz", "bax"}, Nested3: &unexportedFieldPreservationTestNested{"baz", "bax"},
} }
err := toml.Unmarshal([]byte(doc), &actual) err := toml.Unmarshal([]byte(doc), &actual)
if err != nil { if err != nil {
t.Fatal("did not expect an error") t.Fatal("did not expect an error")
} }
@@ -1431,7 +1436,6 @@ func TestUnmarshalLocalDate(t *testing.T) {
var obj dateStruct var obj dateStruct
err := toml.Unmarshal([]byte(doc), &obj) err := toml.Unmarshal([]byte(doc), &obj)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1457,7 +1461,6 @@ func TestUnmarshalLocalDate(t *testing.T) {
var obj dateStruct var obj dateStruct
err := toml.Unmarshal([]byte(doc), &obj) err := toml.Unmarshal([]byte(doc), &obj)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1495,7 +1498,8 @@ func TestUnmarshalLocalDateTime(t *testing.T) {
Second: 0, Second: 0,
Nanosecond: 0, Nanosecond: 0,
}, },
}}, },
},
{ {
name: "with nanoseconds", name: "with nanoseconds",
in: "1979-05-27T00:32:00.999999", in: "1979-05-27T00:32:00.999999",
@@ -1526,7 +1530,6 @@ func TestUnmarshalLocalDateTime(t *testing.T) {
var obj dateStruct var obj dateStruct
err := toml.Unmarshal([]byte(doc), &obj) err := toml.Unmarshal([]byte(doc), &obj)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1544,7 +1547,6 @@ func TestUnmarshalLocalDateTime(t *testing.T) {
var obj dateStruct var obj dateStruct
err := toml.Unmarshal([]byte(doc), &obj) err := toml.Unmarshal([]byte(doc), &obj)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1613,7 +1615,6 @@ func TestUnmarshalLocalTime(t *testing.T) {
var obj dateStruct var obj dateStruct
err := toml.Unmarshal([]byte(doc), &obj) err := toml.Unmarshal([]byte(doc), &obj)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -2283,8 +2284,7 @@ func (d *durationString) UnmarshalTOML(v interface{}) error {
return nil return nil
} }
type config437Error struct { type config437Error struct{}
}
func (e *config437Error) UnmarshalTOML(v interface{}) error { func (e *config437Error) UnmarshalTOML(v interface{}) error {
return errors.New("expected") return errors.New("expected")
+3 -3
View File
@@ -106,7 +106,7 @@ func (s *SeenTracker) create(parentIdx int, name []byte, kind keyKind, explicit
// that have been seen in previous calls, and validates that types are consistent. // that have been seen in previous calls, and validates that types are consistent.
func (s *SeenTracker) CheckExpression(node ast.Node) error { func (s *SeenTracker) CheckExpression(node ast.Node) error {
if s.entries == nil { if s.entries == nil {
//s.entries = make([]entry, 0, 8) // s.entries = make([]entry, 0, 8)
// Skip ID = 0 to remove the confusion between nodes whose parent has // Skip ID = 0 to remove the confusion between nodes whose parent has
// id 0 and root nodes (parent id is 0 because it's the zero value). // id 0 and root nodes (parent id is 0 because it's the zero value).
s.nextID = 1 s.nextID = 1
@@ -134,7 +134,7 @@ func (s *SeenTracker) checkTable(node ast.Node) error {
// it in a function requires to copy the iterator, or allocate it to the // it in a function requires to copy the iterator, or allocate it to the
// heap, which is not cheap. // heap, which is not cheap.
for it.Next() { for it.Next() {
if !it.Node().Next().Valid() { if it.IsLast() {
break break
} }
@@ -175,7 +175,7 @@ func (s *SeenTracker) checkArrayTable(node ast.Node) error {
parentIdx := -1 parentIdx := -1
for it.Next() { for it.Next() {
if !it.Node().Next().Valid() { if it.IsLast() {
break break
} }
-18
View File
@@ -26,7 +26,6 @@ func cmpEqual(x, y interface{}) bool {
} }
func TestDates(t *testing.T) { func TestDates(t *testing.T) {
t.Parallel()
for _, test := range []struct { for _, test := range []struct {
date LocalDate date LocalDate
@@ -64,7 +63,6 @@ func TestDates(t *testing.T) {
} }
func TestDateIsValid(t *testing.T) { func TestDateIsValid(t *testing.T) {
t.Parallel()
for _, test := range []struct { for _, test := range []struct {
date LocalDate date LocalDate
@@ -91,7 +89,6 @@ func TestDateIsValid(t *testing.T) {
} }
func TestParseDate(t *testing.T) { func TestParseDate(t *testing.T) {
t.Parallel()
var emptyDate LocalDate var emptyDate LocalDate
@@ -118,7 +115,6 @@ func TestParseDate(t *testing.T) {
} }
func TestDateArithmetic(t *testing.T) { func TestDateArithmetic(t *testing.T) {
t.Parallel()
for _, test := range []struct { for _, test := range []struct {
desc string desc string
@@ -180,7 +176,6 @@ func TestDateArithmetic(t *testing.T) {
} }
func TestDateBefore(t *testing.T) { func TestDateBefore(t *testing.T) {
t.Parallel()
for _, test := range []struct { for _, test := range []struct {
d1, d2 LocalDate d1, d2 LocalDate
@@ -198,7 +193,6 @@ func TestDateBefore(t *testing.T) {
} }
func TestDateAfter(t *testing.T) { func TestDateAfter(t *testing.T) {
t.Parallel()
for _, test := range []struct { for _, test := range []struct {
d1, d2 LocalDate d1, d2 LocalDate
@@ -215,7 +209,6 @@ func TestDateAfter(t *testing.T) {
} }
func TestTimeToString(t *testing.T) { func TestTimeToString(t *testing.T) {
t.Parallel()
for _, test := range []struct { for _, test := range []struct {
str string str string
@@ -249,7 +242,6 @@ func TestTimeToString(t *testing.T) {
} }
func TestTimeOf(t *testing.T) { func TestTimeOf(t *testing.T) {
t.Parallel()
for _, test := range []struct { for _, test := range []struct {
time time.Time time time.Time
@@ -265,7 +257,6 @@ func TestTimeOf(t *testing.T) {
} }
func TestTimeIsValid(t *testing.T) { func TestTimeIsValid(t *testing.T) {
t.Parallel()
for _, test := range []struct { for _, test := range []struct {
time LocalTime time LocalTime
@@ -291,7 +282,6 @@ func TestTimeIsValid(t *testing.T) {
} }
func TestDateTimeToString(t *testing.T) { func TestDateTimeToString(t *testing.T) {
t.Parallel()
for _, test := range []struct { for _, test := range []struct {
str string str string
@@ -323,7 +313,6 @@ func TestDateTimeToString(t *testing.T) {
} }
func TestParseDateTimeErrors(t *testing.T) { func TestParseDateTimeErrors(t *testing.T) {
t.Parallel()
for _, str := range []string{ for _, str := range []string{
"", "",
@@ -339,7 +328,6 @@ func TestParseDateTimeErrors(t *testing.T) {
} }
func TestDateTimeOf(t *testing.T) { func TestDateTimeOf(t *testing.T) {
t.Parallel()
for _, test := range []struct { for _, test := range []struct {
time time.Time time time.Time
@@ -361,7 +349,6 @@ func TestDateTimeOf(t *testing.T) {
} }
func TestDateTimeIsValid(t *testing.T) { func TestDateTimeIsValid(t *testing.T) {
t.Parallel()
// No need to be exhaustive here; it's just LocalDate.IsValid && LocalTime.IsValid. // No need to be exhaustive here; it's just LocalDate.IsValid && LocalTime.IsValid.
for _, test := range []struct { for _, test := range []struct {
@@ -380,7 +367,6 @@ func TestDateTimeIsValid(t *testing.T) {
} }
func TestDateTimeIn(t *testing.T) { func TestDateTimeIn(t *testing.T) {
t.Parallel()
dt := LocalDateTime{LocalDate{2016, 1, 2}, LocalTime{3, 4, 5, 6}} dt := LocalDateTime{LocalDate{2016, 1, 2}, LocalTime{3, 4, 5, 6}}
@@ -391,7 +377,6 @@ func TestDateTimeIn(t *testing.T) {
} }
func TestDateTimeBefore(t *testing.T) { func TestDateTimeBefore(t *testing.T) {
t.Parallel()
d1 := LocalDate{2016, 12, 31} d1 := LocalDate{2016, 12, 31}
d2 := LocalDate{2017, 1, 1} d2 := LocalDate{2017, 1, 1}
@@ -414,7 +399,6 @@ func TestDateTimeBefore(t *testing.T) {
} }
func TestDateTimeAfter(t *testing.T) { func TestDateTimeAfter(t *testing.T) {
t.Parallel()
d1 := LocalDate{2016, 12, 31} d1 := LocalDate{2016, 12, 31}
d2 := LocalDate{2017, 1, 1} d2 := LocalDate{2017, 1, 1}
@@ -437,7 +421,6 @@ func TestDateTimeAfter(t *testing.T) {
} }
func TestMarshalJSON(t *testing.T) { func TestMarshalJSON(t *testing.T) {
t.Parallel()
for _, test := range []struct { for _, test := range []struct {
value interface{} value interface{}
@@ -459,7 +442,6 @@ func TestMarshalJSON(t *testing.T) {
} }
func TestUnmarshalJSON(t *testing.T) { func TestUnmarshalJSON(t *testing.T) {
t.Parallel()
var ( var (
d LocalDate d LocalDate
-2
View File
@@ -640,8 +640,6 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte
return b, nil return b, nil
} }
var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
func willConvertToTable(ctx encoderCtx, v reflect.Value) bool { func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
if v.Type() == timeType || v.Type().Implements(textMarshalerType) { if v.Type() == timeType || v.Type().Implements(textMarshalerType) {
return false return false
-22
View File
@@ -14,8 +14,6 @@ import (
//nolint:funlen //nolint:funlen
func TestMarshal(t *testing.T) { func TestMarshal(t *testing.T) {
t.Parallel()
someInt := 42 someInt := 42
type structInline struct { type structInline struct {
@@ -516,8 +514,6 @@ K = 42`,
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
t.Parallel()
b, err := toml.Marshal(e.v) b, err := toml.Marshal(e.v)
if e.err { if e.err {
require.Error(t, err) require.Error(t, err)
@@ -609,8 +605,6 @@ func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) {
//nolint:funlen //nolint:funlen
func TestMarshalIndentTables(t *testing.T) { func TestMarshalIndentTables(t *testing.T) {
t.Parallel()
examples := []struct { examples := []struct {
desc string desc string
v interface{} v interface{}
@@ -661,8 +655,6 @@ root = 'value0'
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
t.Parallel()
var buf strings.Builder var buf strings.Builder
enc := toml.NewEncoder(&buf) enc := toml.NewEncoder(&buf)
enc.SetIndentTables(true) enc.SetIndentTables(true)
@@ -685,24 +677,18 @@ func (c *customTextMarshaler) MarshalText() ([]byte, error) {
} }
func TestMarshalTextMarshaler_NoRoot(t *testing.T) { func TestMarshalTextMarshaler_NoRoot(t *testing.T) {
t.Parallel()
c := customTextMarshaler{} c := customTextMarshaler{}
_, err := toml.Marshal(&c) _, err := toml.Marshal(&c)
require.Error(t, err) require.Error(t, err)
} }
func TestMarshalTextMarshaler_Error(t *testing.T) { func TestMarshalTextMarshaler_Error(t *testing.T) {
t.Parallel()
m := map[string]interface{}{"a": &customTextMarshaler{value: 1}} m := map[string]interface{}{"a": &customTextMarshaler{value: 1}}
_, err := toml.Marshal(m) _, err := toml.Marshal(m)
require.Error(t, err) require.Error(t, err)
} }
func TestMarshalTextMarshaler_ErrorInline(t *testing.T) { func TestMarshalTextMarshaler_ErrorInline(t *testing.T) {
t.Parallel()
type s struct { type s struct {
A map[string]interface{} `inline:"true"` A map[string]interface{} `inline:"true"`
} }
@@ -716,8 +702,6 @@ func TestMarshalTextMarshaler_ErrorInline(t *testing.T) {
} }
func TestMarshalTextMarshaler(t *testing.T) { func TestMarshalTextMarshaler(t *testing.T) {
t.Parallel()
m := map[string]interface{}{"a": &customTextMarshaler{value: 2}} m := map[string]interface{}{"a": &customTextMarshaler{value: 2}}
r, err := toml.Marshal(m) r, err := toml.Marshal(m)
require.NoError(t, err) require.NoError(t, err)
@@ -731,7 +715,6 @@ func (b *brokenWriter) Write([]byte) (int, error) {
} }
func TestEncodeToBrokenWriter(t *testing.T) { func TestEncodeToBrokenWriter(t *testing.T) {
t.Parallel()
w := brokenWriter{} w := brokenWriter{}
enc := toml.NewEncoder(&w) enc := toml.NewEncoder(&w)
err := enc.Encode(map[string]string{"hello": "world"}) err := enc.Encode(map[string]string{"hello": "world"})
@@ -739,7 +722,6 @@ func TestEncodeToBrokenWriter(t *testing.T) {
} }
func TestEncoderSetIndentSymbol(t *testing.T) { func TestEncoderSetIndentSymbol(t *testing.T) {
t.Parallel()
var w strings.Builder var w strings.Builder
enc := toml.NewEncoder(&w) enc := toml.NewEncoder(&w)
enc.SetIndentTables(true) enc.SetIndentTables(true)
@@ -753,8 +735,6 @@ func TestEncoderSetIndentSymbol(t *testing.T) {
} }
func TestIssue436(t *testing.T) { func TestIssue436(t *testing.T) {
t.Parallel()
data := []byte(`{"a": [ { "b": { "c": "d" } } ]}`) data := []byte(`{"a": [ { "b": { "c": "d" } } ]}`)
var v interface{} var v interface{}
@@ -774,8 +754,6 @@ c = 'd'
} }
func TestIssue424(t *testing.T) { func TestIssue424(t *testing.T) {
t.Parallel()
type Message1 struct { type Message1 struct {
Text string Text string
} }
+2 -4
View File
@@ -9,7 +9,6 @@ import (
//nolint:funlen //nolint:funlen
func TestParser_AST_Numbers(t *testing.T) { func TestParser_AST_Numbers(t *testing.T) {
t.Parallel()
examples := []struct { examples := []struct {
desc string desc string
@@ -137,7 +136,7 @@ func TestParser_AST_Numbers(t *testing.T) {
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
t.Parallel()
p := parser{} p := parser{}
p.Reset([]byte(`A = ` + e.input)) p.Reset([]byte(`A = ` + e.input))
p.NextExpression() p.NextExpression()
@@ -200,7 +199,6 @@ func compareIterator(t *testing.T, expected []astNode, actual ast.Iterator) {
//nolint:funlen //nolint:funlen
func TestParser_AST(t *testing.T) { func TestParser_AST(t *testing.T) {
t.Parallel()
examples := []struct { examples := []struct {
desc string desc string
@@ -340,7 +338,7 @@ func TestParser_AST(t *testing.T) {
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
t.Parallel()
p := parser{} p := parser{}
p.Reset([]byte(e.input)) p.Reset([]byte(e.input))
p.NextExpression() p.NextExpression()
+19
View File
@@ -3,6 +3,7 @@ package toml
import ( import (
"github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/ast"
"github.com/pelletier/go-toml/v2/internal/tracker" "github.com/pelletier/go-toml/v2/internal/tracker"
"github.com/pelletier/go-toml/v2/internal/unsafe"
) )
type strict struct { type strict struct {
@@ -86,3 +87,21 @@ func (s *strict) Error(doc []byte) error {
return err return err
} }
func keyLocation(node ast.Node) []byte {
k := node.Key()
hasOne := k.Next()
if !hasOne {
panic("should not be called with empty key")
}
start := k.Node().Data
end := k.Node().Data
for k.Next() {
end = k.Node().Data
}
return unsafe.BytesRange(start, end)
}
-536
View File
@@ -1,536 +0,0 @@
package toml
import (
"fmt"
"math"
"reflect"
"strings"
"sync"
)
type target interface {
// Dereferences the target.
get() reflect.Value
// Store a string at the target.
setString(v string)
// Store a boolean at the target
setBool(v bool)
// Store an int64 at the target
setInt64(v int64)
// Store a float64 at the target
setFloat64(v float64)
// Stores any value at the target
set(v reflect.Value)
}
// valueTarget just contains a reflect.Value that can be set.
// It is used for struct fields.
type valueTarget reflect.Value
func (t valueTarget) get() reflect.Value {
return reflect.Value(t)
}
func (t valueTarget) set(v reflect.Value) {
reflect.Value(t).Set(v)
}
func (t valueTarget) setString(v string) {
t.get().SetString(v)
}
func (t valueTarget) setBool(v bool) {
t.get().SetBool(v)
}
func (t valueTarget) setInt64(v int64) {
t.get().SetInt(v)
}
func (t valueTarget) setFloat64(v float64) {
t.get().SetFloat(v)
}
// interfaceTarget wraps an other target to dereference on get.
type interfaceTarget struct {
x target
}
func (t interfaceTarget) get() reflect.Value {
return t.x.get().Elem()
}
func (t interfaceTarget) set(v reflect.Value) {
t.x.set(v)
}
func (t interfaceTarget) setString(v string) {
panic("interface targets should always go through set")
}
func (t interfaceTarget) setBool(v bool) {
panic("interface targets should always go through set")
}
func (t interfaceTarget) setInt64(v int64) {
panic("interface targets should always go through set")
}
func (t interfaceTarget) setFloat64(v float64) {
panic("interface targets should always go through set")
}
// mapTarget targets a specific key of a map.
type mapTarget struct {
v reflect.Value
k reflect.Value
}
func (t mapTarget) get() reflect.Value {
return t.v.MapIndex(t.k)
}
func (t mapTarget) set(v reflect.Value) {
t.v.SetMapIndex(t.k, v)
}
func (t mapTarget) setString(v string) {
t.set(reflect.ValueOf(v))
}
func (t mapTarget) setBool(v bool) {
t.set(reflect.ValueOf(v))
}
func (t mapTarget) setInt64(v int64) {
t.set(reflect.ValueOf(v))
}
func (t mapTarget) setFloat64(v float64) {
t.set(reflect.ValueOf(v))
}
// makes sure that the value pointed at by t is indexable (Slice, Array), or
// dereferences to an indexable (Ptr, Interface).
func ensureValueIndexable(t target) error {
f := t.get()
switch f.Type().Kind() {
case reflect.Slice:
if f.IsNil() {
t.set(reflect.MakeSlice(f.Type(), 0, 0))
return nil
}
case reflect.Interface:
if f.IsNil() || f.Elem().Type() != sliceInterfaceType {
t.set(reflect.MakeSlice(sliceInterfaceType, 0, 0))
return nil
}
case reflect.Ptr:
panic("pointer should have already been dereferenced")
case reflect.Array:
// arrays are always initialized.
default:
return fmt.Errorf("toml: cannot store array in a %s", f.Kind())
}
return nil
}
var (
sliceInterfaceType = reflect.TypeOf([]interface{}{})
mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{})
)
func ensureMapIfInterface(x target) {
v := x.get()
if v.Kind() == reflect.Interface && v.IsNil() {
newElement := reflect.MakeMap(mapStringInterfaceType)
x.set(newElement)
}
}
func setString(t target, v string) error {
f := t.get()
switch f.Kind() {
case reflect.String:
t.setString(v)
case reflect.Interface:
t.set(reflect.ValueOf(v))
default:
return fmt.Errorf("toml: cannot assign string to a %s", f.Kind())
}
return nil
}
func setBool(t target, v bool) error {
f := t.get()
switch f.Kind() {
case reflect.Bool:
t.setBool(v)
case reflect.Interface:
t.set(reflect.ValueOf(v))
default:
return fmt.Errorf("toml: cannot assign boolean to a %s", f.Kind())
}
return nil
}
const (
maxInt = int64(^uint(0) >> 1)
minInt = -maxInt - 1
)
//nolint:funlen,gocognit,cyclop
func setInt64(t target, v int64) error {
f := t.get()
switch f.Kind() {
case reflect.Int64:
t.setInt64(v)
case reflect.Int32:
if v < math.MinInt32 || v > math.MaxInt32 {
return fmt.Errorf("toml: number %d does not fit in an int32", v)
}
t.set(reflect.ValueOf(int32(v)))
return nil
case reflect.Int16:
if v < math.MinInt16 || v > math.MaxInt16 {
return fmt.Errorf("toml: number %d does not fit in an int16", v)
}
t.set(reflect.ValueOf(int16(v)))
case reflect.Int8:
if v < math.MinInt8 || v > math.MaxInt8 {
return fmt.Errorf("toml: number %d does not fit in an int8", v)
}
t.set(reflect.ValueOf(int8(v)))
case reflect.Int:
if v < minInt || v > maxInt {
return fmt.Errorf("toml: number %d does not fit in an int", v)
}
t.set(reflect.ValueOf(int(v)))
case reflect.Uint64:
if v < 0 {
return fmt.Errorf("toml: negative number %d does not fit in an uint64", v)
}
t.set(reflect.ValueOf(uint64(v)))
case reflect.Uint32:
if v < 0 || v > math.MaxUint32 {
return fmt.Errorf("toml: negative number %d does not fit in an uint32", v)
}
t.set(reflect.ValueOf(uint32(v)))
case reflect.Uint16:
if v < 0 || v > math.MaxUint16 {
return fmt.Errorf("toml: negative number %d does not fit in an uint16", v)
}
t.set(reflect.ValueOf(uint16(v)))
case reflect.Uint8:
if v < 0 || v > math.MaxUint8 {
return fmt.Errorf("toml: negative number %d does not fit in an uint8", v)
}
t.set(reflect.ValueOf(uint8(v)))
case reflect.Uint:
if v < 0 {
return fmt.Errorf("toml: negative number %d does not fit in an uint", v)
}
t.set(reflect.ValueOf(uint(v)))
case reflect.Interface:
t.set(reflect.ValueOf(v))
default:
return fmt.Errorf("toml: integer cannot be assigned to %s", f.Kind())
}
return nil
}
func setFloat64(t target, v float64) error {
f := t.get()
switch f.Kind() {
case reflect.Float64:
t.setFloat64(v)
case reflect.Float32:
if v > math.MaxFloat32 {
return fmt.Errorf("toml: number %f does not fit in a float32", v)
}
t.set(reflect.ValueOf(float32(v)))
case reflect.Interface:
t.set(reflect.ValueOf(v))
default:
return fmt.Errorf("toml: float cannot be assigned to %s", f.Kind())
}
return nil
}
// Returns the element at idx of the value pointed at by target, or an error if
// t does not point to an indexable.
// If the target points to an Array and idx is out of bounds, it returns
// (nil, nil) as this is not a fatal error (the unmarshaler will skip).
func elementAt(t target, idx int) target {
f := t.get()
switch f.Kind() {
case reflect.Slice:
//nolint:godox
// TODO: use the idx function argument and avoid alloc if possible.
idx := f.Len()
t.set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem()))
return valueTarget(t.get().Index(idx))
case reflect.Array:
if idx >= f.Len() {
return nil
}
return valueTarget(f.Index(idx))
case reflect.Interface:
// This function is called after ensureValueIndexable, so it's
// guaranteed that f contains an initialized slice.
ifaceElem := f.Elem()
idx := ifaceElem.Len()
newElem := reflect.New(ifaceElem.Type().Elem()).Elem()
newSlice := reflect.Append(ifaceElem, newElem)
t.set(newSlice)
return valueTarget(t.get().Elem().Index(idx))
default:
// Why ensureValueIndexable let it go through?
panic(fmt.Errorf("elementAt received unhandled value type: %s", f.Kind()))
}
}
func (d *decoder) scopeTableTarget(shouldAppend bool, t target, name string) (target, bool, error) {
x := t.get()
switch x.Kind() {
// Kinds that need to recurse
case reflect.Interface:
t := scopeInterface(shouldAppend, t)
return d.scopeTableTarget(shouldAppend, t, name)
case reflect.Ptr:
t := scopePtr(t)
return d.scopeTableTarget(shouldAppend, t, name)
case reflect.Slice:
t := scopeSlice(shouldAppend, t)
shouldAppend = false
return d.scopeTableTarget(shouldAppend, t, name)
case reflect.Array:
t, err := d.scopeArray(shouldAppend, t)
if err != nil {
return t, false, err
}
shouldAppend = false
return d.scopeTableTarget(shouldAppend, t, name)
// Terminal kinds
case reflect.Struct:
return scopeStruct(x, name)
case reflect.Map:
if x.IsNil() {
t.set(reflect.MakeMap(x.Type()))
x = t.get()
}
return scopeMap(x, name)
default:
panic(fmt.Sprintf("can't scope on a %s", x.Kind()))
}
}
func scopeInterface(shouldAppend bool, t target) target {
initInterface(shouldAppend, t)
return interfaceTarget{t}
}
func scopePtr(t target) target {
initPtr(t)
return valueTarget(t.get().Elem())
}
func initPtr(t target) {
x := t.get()
if !x.IsNil() {
return
}
t.set(reflect.New(x.Type().Elem()))
}
// initInterface makes sure that the interface pointed at by the target is not
// nil.
// Returns the target to the initialized value of the target.
func initInterface(shouldAppend bool, t target) {
x := t.get()
if x.Kind() != reflect.Interface {
panic("this should only be called on interfaces")
}
if !x.IsNil() && (x.Elem().Type() == sliceInterfaceType || x.Elem().Type() == mapStringInterfaceType) {
return
}
var newElement reflect.Value
if shouldAppend {
newElement = reflect.MakeSlice(sliceInterfaceType, 0, 0)
} else {
newElement = reflect.MakeMap(mapStringInterfaceType)
}
t.set(newElement)
}
func scopeSlice(shouldAppend bool, t target) target {
v := t.get()
if shouldAppend {
newElem := reflect.New(v.Type().Elem())
newSlice := reflect.Append(v, newElem.Elem())
t.set(newSlice)
v = t.get()
}
return valueTarget(v.Index(v.Len() - 1))
}
func (d *decoder) scopeArray(shouldAppend bool, t target) (target, error) {
v := t.get()
idx := d.arrayIndex(shouldAppend, v)
if idx >= v.Len() {
return nil, fmt.Errorf("toml: impossible to insert element beyond array's size: %d", v.Len())
}
return valueTarget(v.Index(idx)), nil
}
func scopeMap(v reflect.Value, name string) (target, bool, error) {
k := reflect.ValueOf(name)
keyType := v.Type().Key()
if !k.Type().AssignableTo(keyType) {
if !k.Type().ConvertibleTo(keyType) {
return nil, false, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", k.Type(), keyType)
}
k = k.Convert(keyType)
}
if !v.MapIndex(k).IsValid() {
newElem := reflect.New(v.Type().Elem())
v.SetMapIndex(k, newElem.Elem())
}
return mapTarget{
v: v,
k: k,
}, true, nil
}
type fieldPathsMap = map[string][]int
type fieldPathsCache struct {
m map[reflect.Type]fieldPathsMap
l sync.RWMutex
}
func (c *fieldPathsCache) get(t reflect.Type) (fieldPathsMap, bool) {
c.l.RLock()
paths, ok := c.m[t]
c.l.RUnlock()
return paths, ok
}
func (c *fieldPathsCache) set(t reflect.Type, m fieldPathsMap) {
c.l.Lock()
c.m[t] = m
c.l.Unlock()
}
var globalFieldPathsCache = fieldPathsCache{
m: map[reflect.Type]fieldPathsMap{},
l: sync.RWMutex{},
}
func scopeStruct(v reflect.Value, name string) (target, bool, error) {
//nolint:godox
// TODO: cache this, and reduce allocations
fieldPaths, ok := globalFieldPathsCache.get(v.Type())
if !ok {
fieldPaths = map[string][]int{}
path := make([]int, 0, 16)
var walk func(reflect.Value)
walk = func(v reflect.Value) {
t := v.Type()
for i := 0; i < t.NumField(); i++ {
l := len(path)
path = append(path, i)
f := t.Field(i)
if f.Anonymous {
walk(v.Field(i))
} else if f.PkgPath == "" {
// only consider exported fields
fieldName, ok := f.Tag.Lookup("toml")
if !ok {
fieldName = f.Name
}
pathCopy := make([]int, len(path))
copy(pathCopy, path)
fieldPaths[fieldName] = pathCopy
// extra copy for the case-insensitive match
fieldPaths[strings.ToLower(fieldName)] = pathCopy
}
path = path[:l]
}
}
walk(v)
globalFieldPathsCache.set(v.Type(), fieldPaths)
}
path, ok := fieldPaths[name]
if !ok {
path, ok = fieldPaths[strings.ToLower(name)]
}
if !ok {
return nil, false, nil
}
return valueTarget(v.FieldByIndex(path)), true, nil
}
-207
View File
@@ -1,207 +0,0 @@
package toml
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestStructTarget_Ensure(t *testing.T) {
t.Parallel()
examples := []struct {
desc string
input reflect.Value
name string
test func(v reflect.Value, err error)
}{
{
desc: "handle a nil slice of string",
input: reflect.ValueOf(&struct{ A []string }{}).Elem(),
name: "A",
test: func(v reflect.Value, err error) {
assert.NoError(t, err)
assert.False(t, v.IsNil())
},
},
{
desc: "handle an existing slice of string",
input: reflect.ValueOf(&struct{ A []string }{A: []string{"foo"}}).Elem(),
name: "A",
test: func(v reflect.Value, err error) {
assert.NoError(t, err)
require.False(t, v.IsNil())
s, ok := v.Interface().([]string)
if !ok {
t.Errorf("interface %v should be castable into []string", s)
return
}
assert.Equal(t, []string{"foo"}, s)
},
},
}
for _, e := range examples {
e := e
t.Run(e.desc, func(t *testing.T) {
t.Parallel()
d := decoder{}
target, _, err := d.scopeTableTarget(false, valueTarget(e.input), e.name)
require.NoError(t, err)
err = ensureValueIndexable(target)
v := target.get()
e.test(v, err)
})
}
}
func TestStructTarget_SetString(t *testing.T) {
t.Parallel()
str := "value"
examples := []struct {
desc string
input reflect.Value
name string
test func(v reflect.Value, err error)
}{
{
desc: "sets a string",
input: reflect.ValueOf(&struct{ A string }{}).Elem(),
name: "A",
test: func(v reflect.Value, err error) {
assert.NoError(t, err)
assert.Equal(t, str, v.String())
},
},
{
desc: "fails on a float",
input: reflect.ValueOf(&struct{ A float64 }{}).Elem(),
name: "A",
test: func(v reflect.Value, err error) {
assert.Error(t, err)
},
},
{
desc: "fails on a slice",
input: reflect.ValueOf(&struct{ A []string }{}).Elem(),
name: "A",
test: func(v reflect.Value, err error) {
assert.Error(t, err)
},
},
}
for _, e := range examples {
e := e
t.Run(e.desc, func(t *testing.T) {
t.Parallel()
d := decoder{}
target, _, err := d.scopeTableTarget(false, valueTarget(e.input), e.name)
require.NoError(t, err)
err = setString(target, str)
v := target.get()
e.test(v, err)
})
}
}
func TestPushNew(t *testing.T) {
t.Parallel()
t.Run("slice of strings", func(t *testing.T) {
t.Parallel()
type Doc struct {
A []string
}
d := Doc{}
dec := decoder{}
x, _, err := dec.scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A")
require.NoError(t, err)
n := elementAt(x, 0)
n.setString("hello")
require.Equal(t, []string{"hello"}, d.A)
n = elementAt(x, 1)
n.setString("world")
require.Equal(t, []string{"hello", "world"}, d.A)
})
t.Run("slice of interfaces", func(t *testing.T) {
t.Parallel()
type Doc struct {
A []interface{}
}
d := Doc{}
dec := decoder{}
x, _, err := dec.scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A")
require.NoError(t, err)
n := elementAt(x, 0)
require.NoError(t, setString(n, "hello"))
require.Equal(t, []interface{}{"hello"}, d.A)
n = elementAt(x, 1)
require.NoError(t, setString(n, "world"))
require.Equal(t, []interface{}{"hello", "world"}, d.A)
})
}
func TestScope_Struct(t *testing.T) {
t.Parallel()
examples := []struct {
desc string
input reflect.Value
name string
err bool
found bool
idx []int
}{
{
desc: "simple field",
input: reflect.ValueOf(&struct{ A string }{}).Elem(),
name: "A",
idx: []int{0},
found: true,
},
{
desc: "fails not-exported field",
input: reflect.ValueOf(&struct{ a string }{}).Elem(),
name: "a",
err: false,
found: false,
},
}
for _, e := range examples {
e := e
t.Run(e.desc, func(t *testing.T) {
t.Parallel()
dec := decoder{}
x, found, err := dec.scopeTableTarget(false, valueTarget(e.input), e.name)
assert.Equal(t, e.found, found)
if e.err {
assert.Error(t, err)
}
if found {
x2, ok := x.(valueTarget)
require.True(t, ok)
x2.get()
}
})
}
}
-74
View File
@@ -6,35 +6,30 @@ import (
) )
func TestInvalidDatetimeMalformedNoLeads(t *testing.T) { func TestInvalidDatetimeMalformedNoLeads(t *testing.T) {
t.Parallel()
input := `no-leads = 1987-7-05T17:45:00Z` input := `no-leads = 1987-7-05T17:45:00Z`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidDatetimeMalformedNoSecs(t *testing.T) { func TestInvalidDatetimeMalformedNoSecs(t *testing.T) {
t.Parallel()
input := `no-secs = 1987-07-05T17:45Z` input := `no-secs = 1987-07-05T17:45Z`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidDatetimeMalformedNoT(t *testing.T) { func TestInvalidDatetimeMalformedNoT(t *testing.T) {
t.Parallel()
input := `no-t = 1987-07-0517:45:00Z` input := `no-t = 1987-07-0517:45:00Z`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidDatetimeMalformedWithMilli(t *testing.T) { func TestInvalidDatetimeMalformedWithMilli(t *testing.T) {
t.Parallel()
input := `with-milli = 1987-07-5T17:45:00.12Z` input := `with-milli = 1987-07-5T17:45:00.12Z`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidDuplicateKeyTable(t *testing.T) { func TestInvalidDuplicateKeyTable(t *testing.T) {
t.Parallel()
input := `[fruit] input := `[fruit]
type = "apple" type = "apple"
@@ -45,7 +40,6 @@ apple = "yes"`
} }
func TestInvalidDuplicateKeys(t *testing.T) { func TestInvalidDuplicateKeys(t *testing.T) {
t.Parallel()
input := `dupe = false input := `dupe = false
dupe = true` dupe = true`
@@ -53,7 +47,6 @@ dupe = true`
} }
func TestInvalidDuplicateTables(t *testing.T) { func TestInvalidDuplicateTables(t *testing.T) {
t.Parallel()
input := `[a] input := `[a]
[a]` [a]`
@@ -61,21 +54,18 @@ func TestInvalidDuplicateTables(t *testing.T) {
} }
func TestInvalidEmptyImplicitTable(t *testing.T) { func TestInvalidEmptyImplicitTable(t *testing.T) {
t.Parallel()
input := `[naughty..naughty]` input := `[naughty..naughty]`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidEmptyTable(t *testing.T) { func TestInvalidEmptyTable(t *testing.T) {
t.Parallel()
input := `[]` input := `[]`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidFloatNoLeadingZero(t *testing.T) { func TestInvalidFloatNoLeadingZero(t *testing.T) {
t.Parallel()
input := `answer = .12345 input := `answer = .12345
neganswer = -.12345` neganswer = -.12345`
@@ -83,7 +73,6 @@ neganswer = -.12345`
} }
func TestInvalidFloatNoTrailingDigits(t *testing.T) { func TestInvalidFloatNoTrailingDigits(t *testing.T) {
t.Parallel()
input := `answer = 1. input := `answer = 1.
neganswer = -1.` neganswer = -1.`
@@ -91,21 +80,18 @@ neganswer = -1.`
} }
func TestInvalidKeyEmpty(t *testing.T) { func TestInvalidKeyEmpty(t *testing.T) {
t.Parallel()
input := ` = 1` input := ` = 1`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidKeyHash(t *testing.T) { func TestInvalidKeyHash(t *testing.T) {
t.Parallel()
input := `a# = 1` input := `a# = 1`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidKeyNewline(t *testing.T) { func TestInvalidKeyNewline(t *testing.T) {
t.Parallel()
input := `a input := `a
= 1` = 1`
@@ -113,28 +99,24 @@ func TestInvalidKeyNewline(t *testing.T) {
} }
func TestInvalidKeyOpenBracket(t *testing.T) { func TestInvalidKeyOpenBracket(t *testing.T) {
t.Parallel()
input := `[abc = 1` input := `[abc = 1`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidKeySingleOpenBracket(t *testing.T) { func TestInvalidKeySingleOpenBracket(t *testing.T) {
t.Parallel()
input := `[` input := `[`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidKeySpace(t *testing.T) { func TestInvalidKeySpace(t *testing.T) {
t.Parallel()
input := `a b = 1` input := `a b = 1`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidKeyStartBracket(t *testing.T) { func TestInvalidKeyStartBracket(t *testing.T) {
t.Parallel()
input := `[a] input := `[a]
[xyz = 5 [xyz = 5
@@ -143,42 +125,36 @@ func TestInvalidKeyStartBracket(t *testing.T) {
} }
func TestInvalidKeyTwoEquals(t *testing.T) { func TestInvalidKeyTwoEquals(t *testing.T) {
t.Parallel()
input := `key= = 1` input := `key= = 1`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidStringBadByteEscape(t *testing.T) { func TestInvalidStringBadByteEscape(t *testing.T) {
t.Parallel()
input := `naughty = "\xAg"` input := `naughty = "\xAg"`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidStringBadEscape(t *testing.T) { func TestInvalidStringBadEscape(t *testing.T) {
t.Parallel()
input := `invalid-escape = "This string has a bad \a escape character."` input := `invalid-escape = "This string has a bad \a escape character."`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidStringByteEscapes(t *testing.T) { func TestInvalidStringByteEscapes(t *testing.T) {
t.Parallel()
input := `answer = "\x33"` input := `answer = "\x33"`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidStringNoClose(t *testing.T) { func TestInvalidStringNoClose(t *testing.T) {
t.Parallel()
input := `no-ending-quote = "One time, at band camp` input := `no-ending-quote = "One time, at band camp`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidTableArrayImplicit(t *testing.T) { func TestInvalidTableArrayImplicit(t *testing.T) {
t.Parallel()
input := "# This test is a bit tricky. It should fail because the first use of\n" + input := "# This test is a bit tricky. It should fail because the first use of\n" +
"# `[[albums.songs]]` without first declaring `albums` implies that `albums`\n" + "# `[[albums.songs]]` without first declaring `albums` implies that `albums`\n" +
@@ -198,7 +174,6 @@ func TestInvalidTableArrayImplicit(t *testing.T) {
} }
func TestInvalidTableArrayMalformedBracket(t *testing.T) { func TestInvalidTableArrayMalformedBracket(t *testing.T) {
t.Parallel()
input := `[[albums] input := `[[albums]
name = "Born to Run"` name = "Born to Run"`
@@ -206,7 +181,6 @@ name = "Born to Run"`
} }
func TestInvalidTableArrayMalformedEmpty(t *testing.T) { func TestInvalidTableArrayMalformedEmpty(t *testing.T) {
t.Parallel()
input := `[[]] input := `[[]]
name = "Born to Run"` name = "Born to Run"`
@@ -214,14 +188,12 @@ name = "Born to Run"`
} }
func TestInvalidTableEmpty(t *testing.T) { func TestInvalidTableEmpty(t *testing.T) {
t.Parallel()
input := `[]` input := `[]`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidTableNestedBracketsClose(t *testing.T) { func TestInvalidTableNestedBracketsClose(t *testing.T) {
t.Parallel()
input := `[a]b] input := `[a]b]
zyx = 42` zyx = 42`
@@ -229,7 +201,6 @@ zyx = 42`
} }
func TestInvalidTableNestedBracketsOpen(t *testing.T) { func TestInvalidTableNestedBracketsOpen(t *testing.T) {
t.Parallel()
input := `[a[b] input := `[a[b]
zyx = 42` zyx = 42`
@@ -237,14 +208,12 @@ zyx = 42`
} }
func TestInvalidTableWhitespace(t *testing.T) { func TestInvalidTableWhitespace(t *testing.T) {
t.Parallel()
input := `[invalid key]` input := `[invalid key]`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidTableWithPound(t *testing.T) { func TestInvalidTableWithPound(t *testing.T) {
t.Parallel()
input := `[key#group] input := `[key#group]
answer = 42` answer = 42`
@@ -252,7 +221,6 @@ answer = 42`
} }
func TestInvalidTextAfterArrayEntries(t *testing.T) { func TestInvalidTextAfterArrayEntries(t *testing.T) {
t.Parallel()
input := `array = [ input := `array = [
"Is there life after an array separator?", No "Is there life after an array separator?", No
@@ -262,28 +230,24 @@ func TestInvalidTextAfterArrayEntries(t *testing.T) {
} }
func TestInvalidTextAfterInteger(t *testing.T) { func TestInvalidTextAfterInteger(t *testing.T) {
t.Parallel()
input := `answer = 42 the ultimate answer?` input := `answer = 42 the ultimate answer?`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidTextAfterString(t *testing.T) { func TestInvalidTextAfterString(t *testing.T) {
t.Parallel()
input := `string = "Is there life after strings?" No.` input := `string = "Is there life after strings?" No.`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidTextAfterTable(t *testing.T) { func TestInvalidTextAfterTable(t *testing.T) {
t.Parallel()
input := `[error] this shouldn't be here` input := `[error] this shouldn't be here`
testgenInvalid(t, input) testgenInvalid(t, input)
} }
func TestInvalidTextBeforeArraySeparator(t *testing.T) { func TestInvalidTextBeforeArraySeparator(t *testing.T) {
t.Parallel()
input := `array = [ input := `array = [
"Is there life before an array separator?" No, "Is there life before an array separator?" No,
@@ -293,7 +257,6 @@ func TestInvalidTextBeforeArraySeparator(t *testing.T) {
} }
func TestInvalidTextInArray(t *testing.T) { func TestInvalidTextInArray(t *testing.T) {
t.Parallel()
input := `array = [ input := `array = [
"Entry 1", "Entry 1",
@@ -304,7 +267,6 @@ func TestInvalidTextInArray(t *testing.T) {
} }
func TestValidArrayEmpty(t *testing.T) { func TestValidArrayEmpty(t *testing.T) {
t.Parallel()
input := `thevoid = [[[[[]]]]]` input := `thevoid = [[[[[]]]]]`
jsonRef := `{ jsonRef := `{
@@ -322,7 +284,6 @@ func TestValidArrayEmpty(t *testing.T) {
} }
func TestValidArrayNospaces(t *testing.T) { func TestValidArrayNospaces(t *testing.T) {
t.Parallel()
input := `ints = [1,2,3]` input := `ints = [1,2,3]`
jsonRef := `{ jsonRef := `{
@@ -339,7 +300,6 @@ func TestValidArrayNospaces(t *testing.T) {
} }
func TestValidArraysHetergeneous(t *testing.T) { func TestValidArraysHetergeneous(t *testing.T) {
t.Parallel()
input := `mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]` input := `mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]`
jsonRef := `{ jsonRef := `{
@@ -365,7 +325,6 @@ func TestValidArraysHetergeneous(t *testing.T) {
} }
func TestValidArraysNested(t *testing.T) { func TestValidArraysNested(t *testing.T) {
t.Parallel()
input := `nest = [["a"], ["b"]]` input := `nest = [["a"], ["b"]]`
jsonRef := `{ jsonRef := `{
@@ -385,7 +344,6 @@ func TestValidArraysNested(t *testing.T) {
} }
func TestValidArrays(t *testing.T) { func TestValidArrays(t *testing.T) {
t.Parallel()
input := `ints = [1, 2, 3] input := `ints = [1, 2, 3]
floats = [1.1, 2.1, 3.1] floats = [1.1, 2.1, 3.1]
@@ -433,7 +391,6 @@ dates = [
} }
func TestValidBool(t *testing.T) { func TestValidBool(t *testing.T) {
t.Parallel()
input := `t = true input := `t = true
f = false` f = false`
@@ -445,7 +402,6 @@ f = false`
} }
func TestValidCommentsEverywhere(t *testing.T) { func TestValidCommentsEverywhere(t *testing.T) {
t.Parallel()
input := `# Top comment. input := `# Top comment.
# Top comment. # Top comment.
@@ -487,7 +443,6 @@ more = [ # Comment
} }
func TestValidDatetime(t *testing.T) { func TestValidDatetime(t *testing.T) {
t.Parallel()
input := `bestdayever = 1987-07-05T17:45:00Z` input := `bestdayever = 1987-07-05T17:45:00Z`
jsonRef := `{ jsonRef := `{
@@ -497,7 +452,6 @@ func TestValidDatetime(t *testing.T) {
} }
func TestValidEmpty(t *testing.T) { func TestValidEmpty(t *testing.T) {
t.Parallel()
input := `` input := ``
jsonRef := `{}` jsonRef := `{}`
@@ -505,7 +459,6 @@ func TestValidEmpty(t *testing.T) {
} }
func TestValidExample(t *testing.T) { func TestValidExample(t *testing.T) {
t.Parallel()
input := `best-day-ever = 1987-07-05T17:45:00Z input := `best-day-ever = 1987-07-05T17:45:00Z
@@ -530,7 +483,6 @@ perfection = [6, 28, 496]`
} }
func TestValidFloat(t *testing.T) { func TestValidFloat(t *testing.T) {
t.Parallel()
input := `pi = 3.14 input := `pi = 3.14
negpi = -3.14` negpi = -3.14`
@@ -542,7 +494,6 @@ negpi = -3.14`
} }
func TestValidImplicitAndExplicitAfter(t *testing.T) { func TestValidImplicitAndExplicitAfter(t *testing.T) {
t.Parallel()
input := `[a.b.c] input := `[a.b.c]
answer = 42 answer = 42
@@ -563,7 +514,6 @@ better = 43`
} }
func TestValidImplicitAndExplicitBefore(t *testing.T) { func TestValidImplicitAndExplicitBefore(t *testing.T) {
t.Parallel()
input := `[a] input := `[a]
better = 43 better = 43
@@ -584,7 +534,6 @@ answer = 42`
} }
func TestValidImplicitGroups(t *testing.T) { func TestValidImplicitGroups(t *testing.T) {
t.Parallel()
input := `[a.b.c] input := `[a.b.c]
answer = 42` answer = 42`
@@ -601,7 +550,6 @@ answer = 42`
} }
func TestValidInteger(t *testing.T) { func TestValidInteger(t *testing.T) {
t.Parallel()
input := `answer = 42 input := `answer = 42
neganswer = -42` neganswer = -42`
@@ -613,7 +561,6 @@ neganswer = -42`
} }
func TestValidKeyEqualsNospace(t *testing.T) { func TestValidKeyEqualsNospace(t *testing.T) {
t.Parallel()
input := `answer=42` input := `answer=42`
jsonRef := `{ jsonRef := `{
@@ -623,7 +570,6 @@ func TestValidKeyEqualsNospace(t *testing.T) {
} }
func TestValidKeySpace(t *testing.T) { func TestValidKeySpace(t *testing.T) {
t.Parallel()
input := `"a b" = 1` input := `"a b" = 1`
jsonRef := `{ jsonRef := `{
@@ -633,7 +579,6 @@ func TestValidKeySpace(t *testing.T) {
} }
func TestValidKeySpecialChars(t *testing.T) { func TestValidKeySpecialChars(t *testing.T) {
t.Parallel()
input := "\"~!@$^&*()_+-`1234567890[]|/?><.,;:'\" = 1\n" input := "\"~!@$^&*()_+-`1234567890[]|/?><.,;:'\" = 1\n"
jsonRef := "{\n" + jsonRef := "{\n" +
@@ -645,7 +590,6 @@ func TestValidKeySpecialChars(t *testing.T) {
} }
func TestValidLongFloat(t *testing.T) { func TestValidLongFloat(t *testing.T) {
t.Parallel()
input := `longpi = 3.141592653589793 input := `longpi = 3.141592653589793
neglongpi = -3.141592653589793` neglongpi = -3.141592653589793`
@@ -657,7 +601,6 @@ neglongpi = -3.141592653589793`
} }
func TestValidLongInteger(t *testing.T) { func TestValidLongInteger(t *testing.T) {
t.Parallel()
input := `answer = 9223372036854775807 input := `answer = 9223372036854775807
neganswer = -9223372036854775808` neganswer = -9223372036854775808`
@@ -669,7 +612,6 @@ neganswer = -9223372036854775808`
} }
func TestValidMultilineString(t *testing.T) { func TestValidMultilineString(t *testing.T) {
t.Parallel()
input := `multiline_empty_one = """""" input := `multiline_empty_one = """"""
multiline_empty_two = """ multiline_empty_two = """
@@ -728,7 +670,6 @@ equivalent_three = """\
} }
func TestValidRawMultilineString(t *testing.T) { func TestValidRawMultilineString(t *testing.T) {
t.Parallel()
input := `oneline = '''This string has a ' quote character.''' input := `oneline = '''This string has a ' quote character.'''
firstnl = ''' firstnl = '''
@@ -757,7 +698,6 @@ in it.'''`
} }
func TestValidRawString(t *testing.T) { func TestValidRawString(t *testing.T) {
t.Parallel()
input := `backspace = 'This string has a \b backspace character.' input := `backspace = 'This string has a \b backspace character.'
tab = 'This string has a \t tab character.' tab = 'This string has a \t tab character.'
@@ -800,7 +740,6 @@ backslash = 'This string has a \\ backslash character.'`
} }
func TestValidStringEmpty(t *testing.T) { func TestValidStringEmpty(t *testing.T) {
t.Parallel()
input := `answer = ""` input := `answer = ""`
jsonRef := `{ jsonRef := `{
@@ -813,7 +752,6 @@ func TestValidStringEmpty(t *testing.T) {
} }
func TestValidStringEscapes(t *testing.T) { func TestValidStringEscapes(t *testing.T) {
t.Parallel()
input := `backspace = "This string has a \b backspace character." input := `backspace = "This string has a \b backspace character."
tab = "This string has a \t tab character." tab = "This string has a \t tab character."
@@ -876,7 +814,6 @@ notunicode4 = "This string does not have a unicode \\\u0075 escape."`
} }
func TestValidStringSimple(t *testing.T) { func TestValidStringSimple(t *testing.T) {
t.Parallel()
input := `answer = "You are not drinking enough whisky."` input := `answer = "You are not drinking enough whisky."`
jsonRef := `{ jsonRef := `{
@@ -889,7 +826,6 @@ func TestValidStringSimple(t *testing.T) {
} }
func TestValidStringWithPound(t *testing.T) { func TestValidStringWithPound(t *testing.T) {
t.Parallel()
input := `pound = "We see no # comments here." input := `pound = "We see no # comments here."
poundcomment = "But there are # some comments here." # Did I # mess you up?` poundcomment = "But there are # some comments here." # Did I # mess you up?`
@@ -904,7 +840,6 @@ poundcomment = "But there are # some comments here." # Did I # mess you up?`
} }
func TestValidTableArrayImplicit(t *testing.T) { func TestValidTableArrayImplicit(t *testing.T) {
t.Parallel()
input := `[[albums.songs]] input := `[[albums.songs]]
name = "Glory Days"` name = "Glory Days"`
@@ -919,7 +854,6 @@ name = "Glory Days"`
} }
func TestValidTableArrayMany(t *testing.T) { func TestValidTableArrayMany(t *testing.T) {
t.Parallel()
input := `[[people]] input := `[[people]]
first_name = "Bruce" first_name = "Bruce"
@@ -952,7 +886,6 @@ last_name = "Seger"`
} }
func TestValidTableArrayNest(t *testing.T) { func TestValidTableArrayNest(t *testing.T) {
t.Parallel()
input := `[[albums]] input := `[[albums]]
name = "Born to Run" name = "Born to Run"
@@ -993,7 +926,6 @@ name = "Born in the USA"
} }
func TestValidTableArrayOne(t *testing.T) { func TestValidTableArrayOne(t *testing.T) {
t.Parallel()
input := `[[people]] input := `[[people]]
first_name = "Bruce" first_name = "Bruce"
@@ -1010,7 +942,6 @@ last_name = "Springsteen"`
} }
func TestValidTableEmpty(t *testing.T) { func TestValidTableEmpty(t *testing.T) {
t.Parallel()
input := `[a]` input := `[a]`
jsonRef := `{ jsonRef := `{
@@ -1020,7 +951,6 @@ func TestValidTableEmpty(t *testing.T) {
} }
func TestValidTableSubEmpty(t *testing.T) { func TestValidTableSubEmpty(t *testing.T) {
t.Parallel()
input := `[a] input := `[a]
[a.b]` [a.b]`
@@ -1031,7 +961,6 @@ func TestValidTableSubEmpty(t *testing.T) {
} }
func TestValidTableWhitespace(t *testing.T) { func TestValidTableWhitespace(t *testing.T) {
t.Parallel()
input := `["valid key"]` input := `["valid key"]`
jsonRef := `{ jsonRef := `{
@@ -1041,7 +970,6 @@ func TestValidTableWhitespace(t *testing.T) {
} }
func TestValidTableWithPound(t *testing.T) { func TestValidTableWithPound(t *testing.T) {
t.Parallel()
input := `["key#group"] input := `["key#group"]
answer = 42` answer = 42`
@@ -1054,7 +982,6 @@ answer = 42`
} }
func TestValidUnicodeEscape(t *testing.T) { func TestValidUnicodeEscape(t *testing.T) {
t.Parallel()
input := `answer4 = "\u03B4" input := `answer4 = "\u03B4"
answer8 = "\U000003B4"` answer8 = "\U000003B4"`
@@ -1066,7 +993,6 @@ answer8 = "\U000003B4"`
} }
func TestValidUnicodeLiteral(t *testing.T) { func TestValidUnicodeLiteral(t *testing.T) {
t.Parallel()
input := `answer = "δ"` input := `answer = "δ"`
jsonRef := `{ jsonRef := `{
+13
View File
@@ -0,0 +1,13 @@
package toml
import (
"encoding"
"reflect"
"time"
)
var timeType = reflect.TypeOf(time.Time{})
var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{})
var sliceInterfaceType = reflect.TypeOf([]interface{}{})
+834 -331
View File
File diff suppressed because it is too large Load Diff
+259 -37
View File
@@ -14,10 +14,23 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
type badReader struct{}
func (r *badReader) Read([]byte) (int, error) {
return 0, fmt.Errorf("testing error")
}
func TestDecodeReaderError(t *testing.T) {
r := &badReader{}
dec := toml.NewDecoder(r)
m := map[string]interface{}{}
err := dec.Decode(&m)
require.Error(t, err)
}
// nolint:funlen // nolint:funlen
func TestUnmarshal_Integers(t *testing.T) { func TestUnmarshal_Integers(t *testing.T) {
t.Parallel()
examples := []struct { examples := []struct {
desc string desc string
input string input string
@@ -88,8 +101,6 @@ func TestUnmarshal_Integers(t *testing.T) {
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
t.Parallel()
doc := doc{} doc := doc{}
err := toml.Unmarshal([]byte(`A = `+e.input), &doc) err := toml.Unmarshal([]byte(`A = `+e.input), &doc)
if e.err { if e.err {
@@ -104,8 +115,6 @@ func TestUnmarshal_Integers(t *testing.T) {
//nolint:funlen //nolint:funlen
func TestUnmarshal_Floats(t *testing.T) { func TestUnmarshal_Floats(t *testing.T) {
t.Parallel()
examples := []struct { examples := []struct {
desc string desc string
input string input string
@@ -197,8 +206,6 @@ func TestUnmarshal_Floats(t *testing.T) {
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
t.Parallel()
doc := doc{} doc := doc{}
err := toml.Unmarshal([]byte(`A = `+e.input), &doc) err := toml.Unmarshal([]byte(`A = `+e.input), &doc)
require.NoError(t, err) require.NoError(t, err)
@@ -213,8 +220,6 @@ func TestUnmarshal_Floats(t *testing.T) {
//nolint:funlen //nolint:funlen
func TestUnmarshal(t *testing.T) { func TestUnmarshal(t *testing.T) {
t.Parallel()
type test struct { type test struct {
target interface{} target interface{}
expected interface{} expected interface{}
@@ -814,6 +819,240 @@ B = "data"`,
} }
}, },
}, },
{
desc: "array table into interface in struct",
input: `[[foo]]
bar = "hello"`,
gen: func() test {
type doc struct {
Foo interface{}
}
return test{
target: &doc{},
expected: &doc{
Foo: []interface{}{
map[string]interface{}{
"bar": "hello",
},
},
},
}
},
},
{
desc: "array table into interface in struct already initialized with right type",
input: `[[foo]]
bar = "hello"`,
gen: func() test {
type doc struct {
Foo interface{}
}
return test{
target: &doc{
Foo: []interface{}{},
},
expected: &doc{
Foo: []interface{}{
map[string]interface{}{
"bar": "hello",
},
},
},
}
},
},
{
desc: "array table into interface in struct already initialized with wrong type",
input: `[[foo]]
bar = "hello"`,
gen: func() test {
type doc struct {
Foo interface{}
}
return test{
target: &doc{
Foo: []string{},
},
expected: &doc{
Foo: []interface{}{
map[string]interface{}{
"bar": "hello",
},
},
},
}
},
},
{
desc: "array table into nil ptr",
input: `[[foo]]
bar = "hello"`,
gen: func() test {
type doc struct {
Foo *[]interface{}
}
return test{
target: &doc{},
expected: &doc{
Foo: &[]interface{}{
map[string]interface{}{
"bar": "hello",
},
},
},
}
},
},
{
desc: "array table into nil ptr of invalid type",
input: `[[foo]]
bar = "hello"`,
gen: func() test {
type doc struct {
Foo *string
}
return test{
target: &doc{},
err: true,
}
},
},
{
desc: "array table with intermediate ptr",
input: `[[foo.bar]]
bar = "hello"`,
gen: func() test {
type doc struct {
Foo *map[string]interface{}
}
return test{
target: &doc{},
expected: &doc{
Foo: &map[string]interface{}{
"bar": []interface{}{
map[string]interface{}{
"bar": "hello",
},
},
},
},
}
},
},
{
desc: "unmarshal array into interface that contains a slice",
input: `a = [1,2,3]`,
gen: func() test {
type doc struct {
A interface{}
}
return test{
target: &doc{
A: []string{},
},
expected: &doc{
A: []interface{}{
int64(1),
int64(2),
int64(3),
},
},
}
},
},
{
desc: "unmarshal array into interface that contains a []interface{}",
input: `a = [1,2,3]`,
gen: func() test {
type doc struct {
A interface{}
}
return test{
target: &doc{
A: []interface{}{},
},
expected: &doc{
A: []interface{}{
int64(1),
int64(2),
int64(3),
},
},
}
},
},
{
desc: "unmarshal key into map with existing value",
input: `a = "new"`,
gen: func() test {
return test{
target: &map[string]interface{}{"a": "old"},
expected: &map[string]interface{}{"a": "new"},
}
},
},
{
desc: "unmarshal key into map with existing value",
input: `a.b = "new"`,
gen: func() test {
type doc struct {
A interface{}
}
return test{
target: &doc{},
expected: &doc{
A: map[string]interface{}{
"b": "new",
},
},
}
},
},
{
desc: "unmarshal array into struct field with existing array",
input: `a = [1,2]`,
gen: func() test {
type doc struct {
A []int
}
return test{
target: &doc{},
expected: &doc{
A: []int{1, 2},
},
}
},
},
{
desc: "unmarshal inline table into map",
input: `a = {b="hello"}`,
gen: func() test {
type doc struct {
A map[string]interface{}
}
return test{
target: &doc{},
expected: &doc{
A: map[string]interface{}{
"b": "hello",
},
},
}
},
},
{
desc: "unmarshal inline table into map of incorrect type",
input: `a = {b="hello"}`,
gen: func() test {
type doc struct {
A map[string]int
}
return test{
target: &doc{},
err: true,
}
},
},
{ {
desc: "slice pointer in slice pointer", desc: "slice pointer in slice pointer",
input: `A = ["Hello"]`, input: `A = ["Hello"]`,
@@ -1155,8 +1394,6 @@ B = "data"`,
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
t.Parallel()
if e.skip { if e.skip {
t.Skip() t.Skip()
} }
@@ -1241,6 +1478,16 @@ func TestUnmarshalOverflows(t *testing.T) {
} }
} }
func TestUnmarshalInvalidTarget(t *testing.T) {
x := "foo"
err := toml.Unmarshal([]byte{}, x)
require.Error(t, err)
var m *map[string]interface{}
err = toml.Unmarshal([]byte{}, m)
require.Error(t, err)
}
func TestUnmarshalFloat32(t *testing.T) { func TestUnmarshalFloat32(t *testing.T) {
t.Run("fits", func(t *testing.T) { t.Run("fits", func(t *testing.T) {
doc := "A = 1.2" doc := "A = 1.2"
@@ -1277,8 +1524,6 @@ type Config484 struct {
} }
func TestIssue484(t *testing.T) { func TestIssue484(t *testing.T) {
t.Parallel()
raw := []byte(`integers = ["1","2","3","100"]`) raw := []byte(`integers = ["1","2","3","100"]`)
var cfg Config484 var cfg Config484
@@ -1299,8 +1544,6 @@ func (m Map458) A(s string) Slice458 {
} }
func TestIssue458(t *testing.T) { func TestIssue458(t *testing.T) {
t.Parallel()
s := []byte(`[[package]] s := []byte(`[[package]]
dependencies = ["regex"] dependencies = ["regex"]
name = "decode" name = "decode"
@@ -1320,8 +1563,6 @@ version = "0.1.0"`)
} }
func TestIssue252(t *testing.T) { func TestIssue252(t *testing.T) {
t.Parallel()
type config struct { type config struct {
Val1 string `toml:"val1"` Val1 string `toml:"val1"`
Val2 string `toml:"val2"` Val2 string `toml:"val2"`
@@ -1342,8 +1583,6 @@ val1 = "test1"
} }
func TestIssue494(t *testing.T) { func TestIssue494(t *testing.T) {
t.Parallel()
data := ` data := `
foo = 2021-04-08 foo = 2021-04-08
bar = 2021-04-08 bar = 2021-04-08
@@ -1359,8 +1598,6 @@ bar = 2021-04-08
} }
func TestIssue507(t *testing.T) { func TestIssue507(t *testing.T) {
t.Parallel()
data := []byte{'0', '=', '\n', '0', 'a', 'm', 'e'} data := []byte{'0', '=', '\n', '0', 'a', 'm', 'e'}
m := map[string]interface{}{} m := map[string]interface{}{}
err := toml.Unmarshal(data, &m) err := toml.Unmarshal(data, &m)
@@ -1369,8 +1606,6 @@ func TestIssue507(t *testing.T) {
//nolint:funlen //nolint:funlen
func TestUnmarshalDecodeErrors(t *testing.T) { func TestUnmarshalDecodeErrors(t *testing.T) {
t.Parallel()
examples := []struct { examples := []struct {
desc string desc string
data string data string
@@ -1603,8 +1838,6 @@ world'`,
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
t.Parallel()
m := map[string]interface{}{} m := map[string]interface{}{}
err := toml.Unmarshal([]byte(e.data), &m) err := toml.Unmarshal([]byte(e.data), &m)
require.Error(t, err) require.Error(t, err)
@@ -1624,8 +1857,6 @@ world'`,
//nolint:funlen //nolint:funlen
func TestLocalDateTime(t *testing.T) { func TestLocalDateTime(t *testing.T) {
t.Parallel()
examples := []struct { examples := []struct {
desc string desc string
input string input string
@@ -1675,7 +1906,6 @@ func TestLocalDateTime(t *testing.T) {
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
t.Parallel()
t.Log("input:", e.input) t.Log("input:", e.input)
doc := `a = ` + e.input doc := `a = ` + e.input
m := map[string]toml.LocalDateTime{} m := map[string]toml.LocalDateTime{}
@@ -1691,8 +1921,6 @@ func TestLocalDateTime(t *testing.T) {
} }
func TestIssue287(t *testing.T) { func TestIssue287(t *testing.T) {
t.Parallel()
b := `y=[[{}]]` b := `y=[[{}]]`
v := map[string]interface{}{} v := map[string]interface{}{}
err := toml.Unmarshal([]byte(b), &v) err := toml.Unmarshal([]byte(b), &v)
@@ -1709,8 +1937,6 @@ func TestIssue287(t *testing.T) {
} }
func TestIssue508(t *testing.T) { func TestIssue508(t *testing.T) {
t.Parallel()
type head struct { type head struct {
Title string `toml:"title"` Title string `toml:"title"`
} }
@@ -1729,8 +1955,6 @@ func TestIssue508(t *testing.T) {
//nolint:funlen //nolint:funlen
func TestDecoderStrict(t *testing.T) { func TestDecoderStrict(t *testing.T) {
t.Parallel()
examples := []struct { examples := []struct {
desc string desc string
input string input string
@@ -1801,8 +2025,6 @@ bar = 42
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
t.Parallel()
t.Run("strict", func(t *testing.T) { t.Run("strict", func(t *testing.T) {
r := strings.NewReader(e.input) r := strings.NewReader(e.input)
d := toml.NewDecoder(r) d := toml.NewDecoder(r)