201 lines
5.3 KiB
Go
201 lines
5.3 KiB
Go
package toml
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestDecode_NumericOverflow(t *testing.T) {
|
|
type T struct {
|
|
I8 int8 `toml:"i8"`
|
|
U8 uint8 `toml:"u8"`
|
|
F32 float32 `toml:"f32"`
|
|
}
|
|
var tgt T
|
|
if err := Decode(map[string]any{"i8": 300}, &tgt); err == nil {
|
|
t.Errorf("300 into int8 must error, got %d", tgt.I8)
|
|
}
|
|
if err := Decode(map[string]any{"u8": 256}, &tgt); err == nil {
|
|
t.Errorf("256 into uint8 must error, got %d", tgt.U8)
|
|
}
|
|
if err := Decode(map[string]any{"f32": 1e300}, &tgt); err == nil {
|
|
t.Errorf("1e300 into float32 must error, got %g", tgt.F32)
|
|
}
|
|
// Boundaries remain valid
|
|
if err := Decode(map[string]any{"i8": 127, "u8": 255}, &tgt); err != nil {
|
|
t.Fatalf("boundary decode failed: %v", err)
|
|
}
|
|
if tgt.I8 != 127 || tgt.U8 != 255 {
|
|
t.Errorf("boundary values: %d %d", tgt.I8, tgt.U8)
|
|
}
|
|
}
|
|
|
|
func TestDecode_UnsupportedKinds(t *testing.T) {
|
|
type T struct {
|
|
C complex128 `toml:"v"`
|
|
}
|
|
var c T
|
|
if err := Decode(map[string]any{"v": 1}, &c); err == nil {
|
|
t.Error("complex128 target must error, not zero-fill")
|
|
}
|
|
type T2 struct {
|
|
Ch chan int `toml:"v"`
|
|
}
|
|
var c2 T2
|
|
if err := Decode(map[string]any{"v": 1}, &c2); err == nil {
|
|
t.Error("chan target must error")
|
|
}
|
|
}
|
|
|
|
func TestDecode_NonEmptyInterface(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("input-triggered panic: %v", r)
|
|
}
|
|
}()
|
|
type T struct {
|
|
R interface{ Read([]byte) (int, error) } `toml:"r"`
|
|
}
|
|
var tgt T
|
|
if err := Decode(map[string]any{"r": map[string]any{}}, &tgt); err == nil {
|
|
t.Error("map into non-empty interface must error")
|
|
}
|
|
}
|
|
|
|
func TestDecode_Uint64Wrap(t *testing.T) {
|
|
type T struct {
|
|
V int64 `toml:"v"`
|
|
}
|
|
var tgt T
|
|
if err := Decode(map[string]any{"v": uint64(math.MaxUint64)}, &tgt); err == nil {
|
|
t.Errorf("MaxUint64 into int64 must error, got %d", tgt.V)
|
|
}
|
|
if err := Decode(map[string]any{"v": uint(math.MaxUint64)}, &tgt); err == nil {
|
|
t.Errorf("uint wrap into int64 must error, got %d", tgt.V)
|
|
}
|
|
if err := Decode(map[string]any{"v": uint64(42)}, &tgt); err != nil || tgt.V != 42 {
|
|
t.Errorf("in-range uint64 failed: %v %d", err, tgt.V)
|
|
}
|
|
}
|
|
|
|
func TestMarshal_FloatFormatting(t *testing.T) {
|
|
b, err := Marshal(map[string]any{"f": float32(3.14)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := strings.TrimSpace(string(b)); got != "f = 3.14" {
|
|
t.Errorf("float32 noise digits: %s", got)
|
|
}
|
|
|
|
b, err = Marshal(map[string]any{"f": 1e100})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := strings.TrimSpace(string(b)); got != "f = 1e+100" {
|
|
t.Errorf("digit expansion instead of exponent: %s", got)
|
|
}
|
|
var m map[string]any
|
|
if err := Unmarshal(b, &m); err != nil {
|
|
t.Fatalf("re-parse of exponent form failed: %v", err)
|
|
}
|
|
if m["f"] != 1e100 {
|
|
t.Errorf("round trip mismatch: %v", m["f"])
|
|
}
|
|
|
|
// Integral floats keep the .0 fixup
|
|
b, _ = Marshal(map[string]any{"f": 100.0})
|
|
if got := strings.TrimSpace(string(b)); got != "f = 100.0" {
|
|
t.Errorf(".0 fixup lost: %s", got)
|
|
}
|
|
}
|
|
|
|
func TestMarshal_NaNInf(t *testing.T) {
|
|
for _, v := range []float64{math.NaN(), math.Inf(1), math.Inf(-1)} {
|
|
if _, err := Marshal(map[string]any{"f": v}); err == nil {
|
|
t.Errorf("%v must fail to encode", v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMarshal_SliceHomogeneity(t *testing.T) {
|
|
if _, err := Marshal(map[string]any{"x": []any{map[string]any{"a": 1}, 2}}); err == nil {
|
|
t.Error("mixed table/scalar slice must error")
|
|
}
|
|
if _, err := Marshal(map[string]any{"x": []any{1, nil, 2}}); err == nil {
|
|
t.Error("nil interface element must error")
|
|
}
|
|
|
|
type Item struct {
|
|
N int `toml:"n"`
|
|
}
|
|
b, err := Marshal(map[string]any{"items": []*Item{nil, {N: 1}}})
|
|
if err != nil {
|
|
t.Fatalf("nil pointer in table slice must be skipped, not error: %v", err)
|
|
}
|
|
if strings.Count(string(b), "[[items]]") != 1 {
|
|
t.Errorf("expected exactly one [[items]]:\n%s", b)
|
|
}
|
|
}
|
|
|
|
func TestMarshal_TagSortedKeys(t *testing.T) {
|
|
type T struct {
|
|
B int `toml:"z"`
|
|
Z int `toml:"a"`
|
|
}
|
|
b, err := Marshal(T{B: 1, Z: 2})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := strings.TrimSpace(string(b)); got != "a = 2\nz = 1" {
|
|
t.Errorf("keys not sorted by emitted name:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestLexer_UnicodeEscapes(t *testing.T) {
|
|
var m map[string]any
|
|
if err := Unmarshal([]byte(`s = "caf\u00E9 \U0001F600"`), &m); err != nil {
|
|
t.Fatalf("unicode escape parse failed: %v", err)
|
|
}
|
|
if m["s"] != "café 😀" {
|
|
t.Errorf("got %q", m["s"])
|
|
}
|
|
|
|
for _, in := range []string{
|
|
`s = "\uD800"`, // surrogate
|
|
`s = "\u12"`, // too few digits
|
|
`s = "\U00110000"`, // > MaxRune
|
|
`s = "\uZZZZ"`, // non-hex
|
|
} {
|
|
if err := Unmarshal([]byte(in), &m); err == nil {
|
|
t.Errorf("%s should fail", in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParser_InlineTableImmutable(t *testing.T) {
|
|
for _, in := range []string{
|
|
"t = { a = 1 }\nt.b = 2",
|
|
"t = { a = 1 }\n[t]\nb = 2",
|
|
"t = { a = 1 }\n[t.sub]\nb = 2",
|
|
} {
|
|
p := NewParser([]byte(in))
|
|
if _, err := p.Parse(); err == nil {
|
|
t.Errorf("inline table extension should fail:\n%s", in)
|
|
}
|
|
}
|
|
// Intra-table dotted keys remain valid
|
|
var m map[string]any
|
|
if err := Unmarshal([]byte(`t = { a.b = 1, a.c = 2 }`), &m); err != nil {
|
|
t.Errorf("dotted keys within inline table must parse: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParser_ValueDepthLimit(t *testing.T) {
|
|
input := "x = " + strings.Repeat("[", 5000) + strings.Repeat("]", 5000)
|
|
p := NewParser([]byte(input))
|
|
if _, err := p.Parse(); err == nil {
|
|
t.Error("expected nesting depth error")
|
|
}
|
|
}
|