v0.1.1 decode hardening, round trip support, doc and test update
Test / test (push) Canceled after 0s
Test / test (push) Canceled after 0s
This commit is contained in:
15 files changed
+874
-651
No files matched your search
@@ -0,0 +1,141 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeFixedArrayRoundTrip(t *testing.T) {
|
||||
type Item struct {
|
||||
Value uint16 `toml:"value"`
|
||||
}
|
||||
type Config struct {
|
||||
Numbers [3]int64 `toml:"numbers"`
|
||||
Items [2]*Item `toml:"items"`
|
||||
Matrix [2][2]int8 `toml:"matrix"`
|
||||
Empty [0]string `toml:"empty"`
|
||||
}
|
||||
want := Config{
|
||||
Numbers: [3]int64{math.MinInt64, 0, math.MaxInt64},
|
||||
Items: [2]*Item{{1}, {math.MaxUint16}},
|
||||
Matrix: [2][2]int8{{-128, 127}, {0, 1}},
|
||||
}
|
||||
data, err := Marshal(want)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got Config
|
||||
if err := Unmarshal(data, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("got %#v, want %#v", got, want)
|
||||
}
|
||||
for _, input := range []any{[]any{1}, []any{1, 2, 3}, []any{1, 256}} {
|
||||
dst := [2]uint8{7, 8}
|
||||
if err := Decode(input, &dst); err == nil || dst != [2]uint8{7, 8} {
|
||||
t.Fatalf("invalid array changed target: %v (%v)", dst, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodePointerDefaultsAndRollback(t *testing.T) {
|
||||
type Settings struct {
|
||||
Port uint16 `toml:"port"`
|
||||
Name string `toml:"name"`
|
||||
}
|
||||
original := &Settings{Port: 8080, Name: "default"}
|
||||
dst := original
|
||||
if err := Decode(map[string]any{"name": "updated"}, &dst); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dst.Port != 8080 || dst.Name != "updated" || dst == original || original.Name != "default" {
|
||||
t.Fatalf("defaults or original pointer changed: dst=%+v original=%+v", dst, original)
|
||||
}
|
||||
type Config struct {
|
||||
Settings *Settings
|
||||
Map map[string]int
|
||||
Slice []int
|
||||
Limit uint8
|
||||
}
|
||||
cfg := Config{original, map[string]int{"old": 1}, []int{2}, 3}
|
||||
err := Decode(map[string]any{
|
||||
"Settings": map[string]any{"port": 9090},
|
||||
"Map": map[string]any{"new": 4},
|
||||
"Slice": []any{5},
|
||||
"Limit": 256,
|
||||
}, &cfg)
|
||||
if err == nil || cfg.Settings != original || *original != (Settings{8080, "default"}) ||
|
||||
!reflect.DeepEqual(cfg.Map, map[string]int{"old": 1}) || !reflect.DeepEqual(cfg.Slice, []int{2}) || cfg.Limit != 3 {
|
||||
t.Fatalf("partial update: %+v (%v)", cfg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeCyclicInputErrors(t *testing.T) {
|
||||
cycle := map[string]any{}
|
||||
cycle["self"] = cycle
|
||||
var number uint64
|
||||
if err := Decode(cycle, &number); err == nil {
|
||||
t.Fatal("accepted cyclic map as unsigned number")
|
||||
}
|
||||
type Node struct {
|
||||
Next *Node `toml:"self"`
|
||||
}
|
||||
var node Node
|
||||
if err := Decode(cycle, &node); err == nil || node.Next != nil {
|
||||
t.Fatalf("recursive decode must fail without publication: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumericKeyRangeIndependence(t *testing.T) {
|
||||
for _, key := range []string{"0", "-1", "+1", "9223372036854775808", "-9223372036854775809", strings.Repeat("9", 100)} {
|
||||
if _, err := Marshal(map[string]any{key: 1}); err == nil {
|
||||
t.Errorf("encoded numeric key %q", key)
|
||||
}
|
||||
if _, err := NewParser([]byte(strconv.Quote(key) + " = 1")).Parse(); err == nil {
|
||||
t.Errorf("parsed numeric key %q", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeIntegerWidths(t *testing.T) {
|
||||
for _, typ := range []reflect.Type{
|
||||
reflect.TypeFor[int](), reflect.TypeFor[int8](), reflect.TypeFor[int16](), reflect.TypeFor[int32](), reflect.TypeFor[int64](),
|
||||
reflect.TypeFor[uint](), reflect.TypeFor[uint8](), reflect.TypeFor[uint16](), reflect.TypeFor[uint32](), reflect.TypeFor[uint64](),
|
||||
} {
|
||||
t.Run(typ.String(), func(t *testing.T) {
|
||||
bits := typ.Bits()
|
||||
signed := typ.Kind() >= reflect.Int && typ.Kind() <= reflect.Int64
|
||||
max := uint64(math.MaxUint64) >> (64 - bits)
|
||||
if signed {
|
||||
max >>= 1
|
||||
}
|
||||
dst := reflect.New(typ)
|
||||
if err := Decode(max, dst.Interface()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if max < math.MaxUint64 {
|
||||
before := dst.Elem().Interface()
|
||||
if err := Decode(max+1, dst.Interface()); err == nil || dst.Elem().Interface() != before {
|
||||
t.Fatalf("overflow was accepted or mutated destination: %v", err)
|
||||
}
|
||||
}
|
||||
if signed {
|
||||
min := -int64(max) - 1
|
||||
if err := Decode(min, dst.Interface()); err != nil || dst.Elem().Int() != min {
|
||||
t.Fatalf("minimum changed: %v", err)
|
||||
}
|
||||
if min > math.MinInt64 {
|
||||
if err := Decode(min-1, dst.Interface()); err == nil || dst.Elem().Int() != min {
|
||||
t.Fatalf("underflow accepted: %v", err)
|
||||
}
|
||||
}
|
||||
} else if err := Decode(int64(-1), dst.Interface()); err == nil {
|
||||
t.Fatal("negative signed integer accepted")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user