Fuzzing setup and fixes (#755)
* encode: fix localdate formatting * encode: fix empty key marshaling * encode: fix invalid quotation of time.Time * encode: ensure control chars are escaped * decode: always use UTC for zero tz * encode: check for invalid characters in keys * encode: always construct map for empty array tables * fuzz: add go 1.18 fuzz test * encode: handle NaNs * encode: allow new lines in quoted keys * encode: never emit table inside array * encode: don't capitalize inf
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
* text=auto
|
* text=auto
|
||||||
|
|
||||||
benchmark/benchmark.toml text eol=lf
|
benchmark/benchmark.toml text eol=lf
|
||||||
|
testdata/** text eol=lf
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ cover() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
pushd "$dir"
|
pushd "$dir"
|
||||||
go test -covermode=atomic -coverprofile=coverage.out ./...
|
go test -covermode=atomic -coverpkg=./... -coverprofile=coverage.out.tmp ./...
|
||||||
|
cat coverage.out.tmp | grep -v testsuite | grep -v tomltestgen | grep -v gotoml-test-decoder > coverage.out
|
||||||
go tool cover -func=coverage.out
|
go tool cover -func=coverage.out
|
||||||
popd
|
popd
|
||||||
|
|
||||||
@@ -103,8 +104,8 @@ coverage() {
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
target_pct="$(cat ${target_out} |sed -E 's/.*total.*\t([0-9.]+)%/\1/;t;d')"
|
target_pct="$(tail -n2 ${target_out} | head -n1 | sed -E 's/.*total.*\t([0-9.]+)%.*/\1/')"
|
||||||
head_pct="$(cat ${head_out} |sed -E 's/.*total.*\t([0-9.]+)%/\1/;t;d')"
|
head_pct="$(tail -n2 ${head_out} | head -n1 | sed -E 's/.*total.*\t([0-9.]+)%/\1/')"
|
||||||
echo "Results: ${target} ${target_pct}% HEAD ${head_pct}%"
|
echo "Results: ${target} ${target_pct}% HEAD ${head_pct}%"
|
||||||
|
|
||||||
delta_pct=$(echo "$head_pct - $target_pct" | bc -l)
|
delta_pct=$(echo "$head_pct - $target_pct" | bc -l)
|
||||||
@@ -112,6 +113,13 @@ coverage() {
|
|||||||
|
|
||||||
if [[ $delta_pct = \-* ]]; then
|
if [[ $delta_pct = \-* ]]; then
|
||||||
echo "Regression!";
|
echo "Regression!";
|
||||||
|
|
||||||
|
target_diff="${output_dir}/target.diff.txt"
|
||||||
|
head_diff="${output_dir}/head.diff.txt"
|
||||||
|
cat "${target_out}" | grep -E '^github.com/pelletier/go-toml' | tr -s "\t " | cut -f 2,3 | sort > "${target_diff}"
|
||||||
|
cat "${head_out}" | grep -E '^github.com/pelletier/go-toml' | tr -s "\t " | cut -f 2,3 | sort > "${head_diff}"
|
||||||
|
|
||||||
|
diff --side-by-side --suppress-common-lines "${target_diff}" "${head_diff}"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -130,7 +130,11 @@ func parseDateTime(b []byte) (time.Time, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
seconds := direction * (hours*3600 + minutes*60)
|
seconds := direction * (hours*3600 + minutes*60)
|
||||||
|
if seconds == 0 {
|
||||||
|
zone = time.UTC
|
||||||
|
} else {
|
||||||
zone = time.FixedZone("", seconds)
|
zone = time.FixedZone("", seconds)
|
||||||
|
}
|
||||||
b = b[dateTimeByteLen:]
|
b = b[dateTimeByteLen:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
//go:build go1.18
|
||||||
|
// +build go1.18
|
||||||
|
|
||||||
|
package toml_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FuzzUnmarshal(f *testing.F) {
|
||||||
|
file, err := ioutil.ReadFile("benchmark/benchmark.toml")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
f.Add(file)
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, b []byte) {
|
||||||
|
if strings.Contains(string(b), "nan") {
|
||||||
|
// Current limitation of testify.
|
||||||
|
// https://github.com/stretchr/testify/issues/624
|
||||||
|
t.Skip("can't compare NaNs")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("INITIAL DOCUMENT ===========================")
|
||||||
|
t.Log(string(b))
|
||||||
|
|
||||||
|
var v interface{}
|
||||||
|
err := toml.Unmarshal(b, &v)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("DECODED VALUE ===========================")
|
||||||
|
t.Logf("%#+v", v)
|
||||||
|
|
||||||
|
encoded, err := toml.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot marshal unmarshaled document: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("ENCODED DOCUMENT ===========================")
|
||||||
|
t.Log(string(encoded))
|
||||||
|
|
||||||
|
var v2 interface{}
|
||||||
|
err = toml.Unmarshal(encoded, &v2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed round trip: %s", err)
|
||||||
|
}
|
||||||
|
require.Equal(t, v, v2)
|
||||||
|
})
|
||||||
|
}
|
||||||
+62
-46
@@ -208,11 +208,20 @@ func (ctx *encoderCtx) isRoot() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
||||||
if !v.IsZero() {
|
i := v.Interface()
|
||||||
i, ok := v.Interface().(time.Time)
|
|
||||||
if ok {
|
switch x := i.(type) {
|
||||||
return i.AppendFormat(b, time.RFC3339), nil
|
case time.Time:
|
||||||
|
if x.Nanosecond() > 0 {
|
||||||
|
return x.AppendFormat(b, time.RFC3339Nano), nil
|
||||||
}
|
}
|
||||||
|
return x.AppendFormat(b, time.RFC3339), nil
|
||||||
|
case LocalTime:
|
||||||
|
return append(b, x.String()...), nil
|
||||||
|
case LocalDate:
|
||||||
|
return append(b, x.String()...), nil
|
||||||
|
case LocalDateTime:
|
||||||
|
return append(b, x.String()...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
hasTextMarshaler := v.Type().Implements(textMarshalerType)
|
hasTextMarshaler := v.Type().Implements(textMarshalerType)
|
||||||
@@ -260,16 +269,31 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e
|
|||||||
case reflect.String:
|
case reflect.String:
|
||||||
b = enc.encodeString(b, v.String(), ctx.options)
|
b = enc.encodeString(b, v.String(), ctx.options)
|
||||||
case reflect.Float32:
|
case reflect.Float32:
|
||||||
if math.Trunc(v.Float()) == v.Float() {
|
f := v.Float()
|
||||||
b = strconv.AppendFloat(b, v.Float(), 'f', 1, 32)
|
|
||||||
|
if math.IsNaN(f) {
|
||||||
|
b = append(b, "nan"...)
|
||||||
|
} else if f > math.MaxFloat32 {
|
||||||
|
b = append(b, "inf"...)
|
||||||
|
} else if f < -math.MaxFloat32 {
|
||||||
|
b = append(b, "-inf"...)
|
||||||
|
} else if math.Trunc(f) == f {
|
||||||
|
b = strconv.AppendFloat(b, f, 'f', 1, 32)
|
||||||
} else {
|
} else {
|
||||||
b = strconv.AppendFloat(b, v.Float(), 'f', -1, 32)
|
b = strconv.AppendFloat(b, f, 'f', -1, 32)
|
||||||
}
|
}
|
||||||
case reflect.Float64:
|
case reflect.Float64:
|
||||||
if math.Trunc(v.Float()) == v.Float() {
|
f := v.Float()
|
||||||
b = strconv.AppendFloat(b, v.Float(), 'f', 1, 64)
|
if math.IsNaN(f) {
|
||||||
|
b = append(b, "nan"...)
|
||||||
|
} else if f > math.MaxFloat64 {
|
||||||
|
b = append(b, "inf"...)
|
||||||
|
} else if f < -math.MaxFloat64 {
|
||||||
|
b = append(b, "-inf"...)
|
||||||
|
} else if math.Trunc(f) == f {
|
||||||
|
b = strconv.AppendFloat(b, f, 'f', 1, 64)
|
||||||
} else {
|
} else {
|
||||||
b = strconv.AppendFloat(b, v.Float(), 'f', -1, 64)
|
b = strconv.AppendFloat(b, f, 'f', -1, 64)
|
||||||
}
|
}
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
if v.Bool() {
|
if v.Bool() {
|
||||||
@@ -300,10 +324,6 @@ func isNil(v reflect.Value) bool {
|
|||||||
func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
|
func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !ctx.hasKey {
|
|
||||||
panic("caller of encodeKv should have set the key in the context")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx.options.omitempty || options.omitempty) && isEmptyValue(v) {
|
if (ctx.options.omitempty || options.omitempty) && isEmptyValue(v) {
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
@@ -313,12 +333,7 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r
|
|||||||
}
|
}
|
||||||
|
|
||||||
b = enc.indent(ctx.indent, b)
|
b = enc.indent(ctx.indent, b)
|
||||||
|
b = enc.encodeKey(b, ctx.key)
|
||||||
b, err = enc.encodeKey(b, ctx.key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b = append(b, " = "...)
|
b = append(b, " = "...)
|
||||||
|
|
||||||
// create a copy of the context because the value of a KV shouldn't
|
// create a copy of the context because the value of a KV shouldn't
|
||||||
@@ -365,7 +380,13 @@ func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byt
|
|||||||
}
|
}
|
||||||
|
|
||||||
func needsQuoting(v string) bool {
|
func needsQuoting(v string) bool {
|
||||||
return strings.ContainsAny(v, "'\b\f\n\r\t")
|
// TODO: vectorize
|
||||||
|
for _, b := range []byte(v) {
|
||||||
|
if b == '\'' || b == '\r' || b == '\n' || invalidAscii(b) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// caller should have checked that the string does not contain new lines or ' .
|
// caller should have checked that the string does not contain new lines or ' .
|
||||||
@@ -437,7 +458,7 @@ func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byt
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// called should have checked that the string is in A-Z / a-z / 0-9 / - / _ .
|
// caller should have checked that the string is in A-Z / a-z / 0-9 / - / _ .
|
||||||
func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte {
|
func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte {
|
||||||
return append(b, v...)
|
return append(b, v...)
|
||||||
}
|
}
|
||||||
@@ -453,20 +474,11 @@ func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error)
|
|||||||
|
|
||||||
b = append(b, '[')
|
b = append(b, '[')
|
||||||
|
|
||||||
var err error
|
b = enc.encodeKey(b, ctx.parentKey[0])
|
||||||
|
|
||||||
b, err = enc.encodeKey(b, ctx.parentKey[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, k := range ctx.parentKey[1:] {
|
for _, k := range ctx.parentKey[1:] {
|
||||||
b = append(b, '.')
|
b = append(b, '.')
|
||||||
|
b = enc.encodeKey(b, k)
|
||||||
b, err = enc.encodeKey(b, k)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b = append(b, "]\n"...)
|
b = append(b, "]\n"...)
|
||||||
@@ -475,19 +487,19 @@ func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
//nolint:cyclop
|
//nolint:cyclop
|
||||||
func (enc *Encoder) encodeKey(b []byte, k string) ([]byte, error) {
|
func (enc *Encoder) encodeKey(b []byte, k string) []byte {
|
||||||
needsQuotation := false
|
needsQuotation := false
|
||||||
cannotUseLiteral := false
|
cannotUseLiteral := false
|
||||||
|
|
||||||
|
if len(k) == 0 {
|
||||||
|
return append(b, "''"...)
|
||||||
|
}
|
||||||
|
|
||||||
for _, c := range k {
|
for _, c := range k {
|
||||||
if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {
|
if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if c == '\n' {
|
|
||||||
return nil, fmt.Errorf("toml: new line characters in keys are not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c == literalQuote {
|
if c == literalQuote {
|
||||||
cannotUseLiteral = true
|
cannotUseLiteral = true
|
||||||
}
|
}
|
||||||
@@ -495,13 +507,17 @@ func (enc *Encoder) encodeKey(b []byte, k string) ([]byte, error) {
|
|||||||
needsQuotation = true
|
needsQuotation = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if needsQuotation && needsQuoting(k) {
|
||||||
|
cannotUseLiteral = true
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case cannotUseLiteral:
|
case cannotUseLiteral:
|
||||||
return enc.encodeQuotedString(false, b, k), nil
|
return enc.encodeQuotedString(false, b, k)
|
||||||
case needsQuotation:
|
case needsQuotation:
|
||||||
return enc.encodeLiteralString(b, k), nil
|
return enc.encodeLiteralString(b, k)
|
||||||
default:
|
default:
|
||||||
return enc.encodeUnquotedKey(b, k), nil
|
return enc.encodeUnquotedKey(b, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -803,6 +819,9 @@ func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool {
|
func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool {
|
||||||
|
if ctx.insideKv {
|
||||||
|
return false
|
||||||
|
}
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
|
|
||||||
if t.Kind() == reflect.Interface {
|
if t.Kind() == reflect.Interface {
|
||||||
@@ -848,7 +867,6 @@ func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]by
|
|||||||
func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
||||||
ctx.shiftKey()
|
ctx.shiftKey()
|
||||||
|
|
||||||
var err error
|
|
||||||
scratch := make([]byte, 0, 64)
|
scratch := make([]byte, 0, 64)
|
||||||
scratch = append(scratch, "[["...)
|
scratch = append(scratch, "[["...)
|
||||||
|
|
||||||
@@ -857,10 +875,7 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.
|
|||||||
scratch = append(scratch, '.')
|
scratch = append(scratch, '.')
|
||||||
}
|
}
|
||||||
|
|
||||||
scratch, err = enc.encodeKey(scratch, k)
|
scratch = enc.encodeKey(scratch, k)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scratch = append(scratch, "]]\n"...)
|
scratch = append(scratch, "]]\n"...)
|
||||||
@@ -869,6 +884,7 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.
|
|||||||
for i := 0; i < v.Len(); i++ {
|
for i := 0; i < v.Len(); i++ {
|
||||||
b = append(b, scratch...)
|
b = append(b, scratch...)
|
||||||
|
|
||||||
|
var err error
|
||||||
b, err = enc.encode(b, ctx, v.Index(i))
|
b, err = enc.encode(b, ctx, v.Index(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
+65
-6
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -45,7 +46,7 @@ func TestMarshal(t *testing.T) {
|
|||||||
v: map[string]string{
|
v: map[string]string{
|
||||||
"hel\nlo": "world",
|
"hel\nlo": "world",
|
||||||
},
|
},
|
||||||
err: true,
|
expected: `"hel\nlo" = 'world'`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: `map with " in key`,
|
desc: `map with " in key`,
|
||||||
@@ -380,7 +381,8 @@ hello = 'world'`,
|
|||||||
v: map[string][]map[string]string{
|
v: map[string][]map[string]string{
|
||||||
"a\n": {{"hello": "world"}},
|
"a\n": {{"hello": "world"}},
|
||||||
},
|
},
|
||||||
err: true,
|
expected: `[["a\n"]]
|
||||||
|
hello = 'world'`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "newline in map in slice",
|
desc: "newline in map in slice",
|
||||||
@@ -440,7 +442,7 @@ hello = 'world'`,
|
|||||||
v: map[string]interface{}{
|
v: map[string]interface{}{
|
||||||
"hello\nworld": 42,
|
"hello\nworld": 42,
|
||||||
},
|
},
|
||||||
err: true,
|
expected: `"hello\nworld" = 42`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "new line in parent of nested table key",
|
desc: "new line in parent of nested table key",
|
||||||
@@ -449,7 +451,8 @@ hello = 'world'`,
|
|||||||
"inner": 42,
|
"inner": 42,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
err: true,
|
expected: `["hello\nworld"]
|
||||||
|
inner = 42`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "new line in nested table key",
|
desc: "new line in nested table key",
|
||||||
@@ -460,7 +463,9 @@ hello = 'world'`,
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
err: true,
|
expected: `[parent]
|
||||||
|
[parent."in\ner"]
|
||||||
|
foo = 42`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "invalid map key",
|
desc: "invalid map key",
|
||||||
@@ -483,7 +488,16 @@ hello = 'world'`,
|
|||||||
}{
|
}{
|
||||||
T: time.Time{},
|
T: time.Time{},
|
||||||
},
|
},
|
||||||
expected: `T = '0001-01-01T00:00:00Z'`,
|
expected: `T = 0001-01-01T00:00:00Z`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "time nano",
|
||||||
|
v: struct {
|
||||||
|
T time.Time
|
||||||
|
}{
|
||||||
|
T: time.Date(1979, time.May, 27, 0, 32, 0, 999999000, time.UTC),
|
||||||
|
},
|
||||||
|
expected: `T = 1979-05-27T00:32:00.999999Z`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "bool",
|
desc: "bool",
|
||||||
@@ -656,6 +670,33 @@ func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) {
|
|||||||
assert.Equal(t, strings.Trim(expected, cutset), strings.Trim(actual, cutset))
|
assert.Equal(t, strings.Trim(expected, cutset), strings.Trim(actual, cutset))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalFloats(t *testing.T) {
|
||||||
|
v := map[string]float32{
|
||||||
|
"nan": float32(math.NaN()),
|
||||||
|
"+inf": float32(math.Inf(1)),
|
||||||
|
"-inf": float32(math.Inf(-1)),
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := `'+inf' = inf
|
||||||
|
-inf = -inf
|
||||||
|
nan = nan
|
||||||
|
`
|
||||||
|
|
||||||
|
actual, err := toml.Marshal(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, string(actual))
|
||||||
|
|
||||||
|
v64 := map[string]float64{
|
||||||
|
"nan": math.NaN(),
|
||||||
|
"+inf": math.Inf(1),
|
||||||
|
"-inf": math.Inf(-1),
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err = toml.Marshal(v64)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, string(actual))
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:funlen
|
//nolint:funlen
|
||||||
func TestMarshalIndentTables(t *testing.T) {
|
func TestMarshalIndentTables(t *testing.T) {
|
||||||
examples := []struct {
|
examples := []struct {
|
||||||
@@ -1027,6 +1068,24 @@ value = ''
|
|||||||
require.Equal(t, expected, string(result))
|
require.Equal(t, expected, string(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLocalTime(t *testing.T) {
|
||||||
|
v := map[string]toml.LocalTime{
|
||||||
|
"a": toml.LocalTime{
|
||||||
|
Hour: 1,
|
||||||
|
Minute: 2,
|
||||||
|
Second: 3,
|
||||||
|
Nanosecond: 4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := `a = 01:02:03.000000004
|
||||||
|
`
|
||||||
|
|
||||||
|
out, err := toml.Marshal(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, string(out))
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleMarshal() {
|
func ExampleMarshal() {
|
||||||
type MyConfig struct {
|
type MyConfig struct {
|
||||||
Version int
|
Version int
|
||||||
|
|||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0=0000-01-01 00:00:00")
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("\"\\n\"=\"\"")
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("''=0")
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0=0000-01-01")
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0=\"\"\"\\U00000000\"\"\"")
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0=[[{}]]")
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("\"\\b\"=\"\"")
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0=inf")
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0=0000-01-01 00:00:00+00:00")
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0=[{}]")
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0=nan")
|
||||||
+4
-2
@@ -322,10 +322,12 @@ func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Val
|
|||||||
return v, nil
|
return v, nil
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
elemType := v.Type().Elem()
|
elemType := v.Type().Elem()
|
||||||
|
var elem reflect.Value
|
||||||
if elemType.Kind() == reflect.Interface {
|
if elemType.Kind() == reflect.Interface {
|
||||||
elemType = mapStringInterfaceType
|
elem = makeMapStringInterface()
|
||||||
|
} else {
|
||||||
|
elem = reflect.New(elemType).Elem()
|
||||||
}
|
}
|
||||||
elem := reflect.New(elemType).Elem()
|
|
||||||
elem2, err := d.handleArrayTable(key, elem)
|
elem2, err := d.handleArrayTable(key, elem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reflect.Value{}, err
|
return reflect.Value{}, err
|
||||||
|
|||||||
+2
-2
@@ -971,7 +971,7 @@ B = "data"`,
|
|||||||
"Name": "Hammer",
|
"Name": "Hammer",
|
||||||
"Sku": int64(738594937),
|
"Sku": int64(738594937),
|
||||||
},
|
},
|
||||||
map[string]interface{}(nil),
|
map[string]interface{}{},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"Name": "Nail",
|
"Name": "Nail",
|
||||||
"Sku": int64(284758393),
|
"Sku": int64(284758393),
|
||||||
@@ -1505,7 +1505,7 @@ B = "data"`,
|
|||||||
target: &map[string]interface{}{},
|
target: &map[string]interface{}{},
|
||||||
expected: &map[string]interface{}{
|
expected: &map[string]interface{}{
|
||||||
"products": []interface{}{
|
"products": []interface{}{
|
||||||
map[string]interface{}(nil),
|
map[string]interface{}{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user