484 lines
11 KiB
Go
484 lines
11 KiB
Go
package toml
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
// TestUnmarshal_Complex verifies the full pipeline from TOML string to struct
|
|
// utilizing the latest generic decoding logic.
|
|
func TestUnmarshal_Complex(t *testing.T) {
|
|
input := []byte(`
|
|
title = "Vi-Fighter Config"
|
|
|
|
[settings]
|
|
debug = true
|
|
max_fps = 144
|
|
scale = 1.5
|
|
|
|
[owner]
|
|
name = "Admin"
|
|
id = 55
|
|
|
|
[network]
|
|
hosts = ["10.0.0.1", "10.0.0.2"]
|
|
ports = [8080, 8081]
|
|
|
|
[[servers]]
|
|
name = "alpha"
|
|
active = true
|
|
|
|
[[servers]]
|
|
name = "beta"
|
|
active = false
|
|
`)
|
|
|
|
type Settings struct {
|
|
Debug bool `toml:"debug"`
|
|
MaxFPS int `toml:"max_fps"`
|
|
Scale float64 `toml:"scale"`
|
|
}
|
|
|
|
type Server struct {
|
|
Name string `toml:"name"`
|
|
Active bool `toml:"active"`
|
|
}
|
|
|
|
type Config struct {
|
|
Title string `toml:"title"`
|
|
Settings Settings `toml:"settings"`
|
|
Owner map[string]any `toml:"owner"` // Test dynamic map
|
|
Network struct {
|
|
Hosts []string `toml:"hosts"`
|
|
Ports []int `toml:"ports"`
|
|
} `toml:"network"`
|
|
Servers []Server `toml:"servers"` // Test Array of Tables
|
|
}
|
|
|
|
var cfg Config
|
|
if err := Unmarshal(input, &cfg); err != nil {
|
|
t.Fatalf("Unmarshal failed: %v", err)
|
|
}
|
|
|
|
// 1. Basic Fields
|
|
if cfg.Title != "Vi-Fighter Config" {
|
|
t.Errorf("Title mismatch: got %q", cfg.Title)
|
|
}
|
|
|
|
// 2. Nested Struct & Types
|
|
if !cfg.Settings.Debug {
|
|
t.Error("Settings.Debug should be true")
|
|
}
|
|
if cfg.Settings.MaxFPS != 144 {
|
|
t.Errorf("Settings.MaxFPS mismatch: got %d", cfg.Settings.MaxFPS)
|
|
}
|
|
if cfg.Settings.Scale != 1.5 {
|
|
t.Errorf("Settings.Scale mismatch: got %f", cfg.Settings.Scale)
|
|
}
|
|
|
|
// 3. Dynamic Map (owner)
|
|
if name, ok := cfg.Owner["name"].(string); !ok || name != "Admin" {
|
|
t.Errorf("Owner.Name mismatch: got %v", cfg.Owner["name"])
|
|
}
|
|
// Check int conversion in dynamic map (parser returns int/float, decode handles struct fields, but map keeps raw parser types)
|
|
// Parser likely returns int for 55.
|
|
if id, ok := cfg.Owner["id"].(int); !ok || id != 55 {
|
|
// Fallback check if parser returned generic float for number
|
|
if fId, okf := cfg.Owner["id"].(float64); !okf || fId != 55 {
|
|
t.Errorf("Owner.ID mismatch: got %T %v", cfg.Owner["id"], cfg.Owner["id"])
|
|
}
|
|
}
|
|
|
|
// 4. Slices
|
|
if len(cfg.Network.Hosts) != 2 || cfg.Network.Hosts[0] != "10.0.0.1" {
|
|
t.Errorf("Network.Hosts mismatch: %v", cfg.Network.Hosts)
|
|
}
|
|
if len(cfg.Network.Ports) != 2 || cfg.Network.Ports[1] != 8081 {
|
|
t.Errorf("Network.Ports mismatch: %v", cfg.Network.Ports)
|
|
}
|
|
|
|
// 5. Array of Tables
|
|
if len(cfg.Servers) != 2 {
|
|
t.Fatalf("Expected 2 servers, got %d", len(cfg.Servers))
|
|
}
|
|
if cfg.Servers[0].Name != "alpha" || !cfg.Servers[0].Active {
|
|
t.Errorf("Server[0] mismatch: %+v", cfg.Servers[0])
|
|
}
|
|
if cfg.Servers[1].Name != "beta" || cfg.Servers[1].Active {
|
|
t.Errorf("Server[1] mismatch: %+v", cfg.Servers[1])
|
|
}
|
|
}
|
|
|
|
// TestDecode_RawPrimitives validates the reflection logic in decode.go
|
|
// specifically for type coercion (int -> float, int -> int64, etc.)
|
|
func TestDecode_RawPrimitives(t *testing.T) {
|
|
// Simulate map[string]any output from Parser
|
|
data := map[string]any{
|
|
"int_val": 100, // int
|
|
"float_val": 123.45, // float64
|
|
"bool_val": true, // bool
|
|
"str_val": "hello", // string
|
|
"any_val": "dynamic", // string -> any
|
|
}
|
|
|
|
type Target struct {
|
|
Int int64 `toml:"int_val"` // Test int -> int64
|
|
Float float32 `toml:"float_val"` // Test float64 -> float32
|
|
Bool bool `toml:"bool_val"`
|
|
Str string `toml:"str_val"`
|
|
Any any `toml:"any_val"`
|
|
}
|
|
|
|
var tgt Target
|
|
if err := Decode(data, &tgt); err != nil {
|
|
t.Fatalf("Decode failed: %v", err)
|
|
}
|
|
|
|
if tgt.Int != 100 {
|
|
t.Errorf("Int64 coercion failed: got %d", tgt.Int)
|
|
}
|
|
// Approximate float comparison
|
|
if tgt.Float < 123.44 || tgt.Float > 123.46 {
|
|
t.Errorf("Float32 coercion failed: got %f", tgt.Float)
|
|
}
|
|
if !tgt.Bool {
|
|
t.Error("Bool failed")
|
|
}
|
|
if tgt.Str != "hello" {
|
|
t.Error("String failed")
|
|
}
|
|
if tgt.Any != "dynamic" {
|
|
t.Error("Any interface assignment failed")
|
|
}
|
|
}
|
|
|
|
// TestDecode_NestedStructs tests direct Decode usage without Parser
|
|
func TestDecode_NestedStructs(t *testing.T) {
|
|
// Nested map structure simulating [parent.child]
|
|
data := map[string]any{
|
|
"parent": map[string]any{
|
|
"child": map[string]any{
|
|
"val": 99,
|
|
},
|
|
},
|
|
}
|
|
|
|
type Child struct {
|
|
Val int `toml:"val"`
|
|
}
|
|
type Parent struct {
|
|
Child Child `toml:"child"`
|
|
}
|
|
type Top struct {
|
|
Parent Parent `toml:"parent"`
|
|
}
|
|
|
|
var tgt Top
|
|
if err := Decode(data, &tgt); err != nil {
|
|
t.Fatalf("Decode nested failed: %v", err)
|
|
}
|
|
|
|
if tgt.Parent.Child.Val != 99 {
|
|
t.Errorf("Nested decoding failed: got %d", tgt.Parent.Child.Val)
|
|
}
|
|
}
|
|
|
|
// TestDecode_SliceCoercion tests converting []any (from parser) to specific slices
|
|
func TestDecode_SliceCoercion(t *testing.T) {
|
|
data := map[string]any{
|
|
"nums": []any{1, 2, 3},
|
|
}
|
|
|
|
type T struct {
|
|
Nums []int `toml:"nums"`
|
|
}
|
|
|
|
var tgt T
|
|
if err := Decode(data, &tgt); err != nil {
|
|
t.Fatalf("Decode slice failed: %v", err)
|
|
}
|
|
|
|
if len(tgt.Nums) != 3 || tgt.Nums[2] != 3 {
|
|
t.Errorf("Slice decoding failed: %v", tgt.Nums)
|
|
}
|
|
}
|
|
|
|
// TestDecode_MapMap tests map[string]map[string]T
|
|
func TestDecode_MapMap(t *testing.T) {
|
|
data := map[string]any{
|
|
"config": map[string]any{
|
|
"env": map[string]any{
|
|
"production": true,
|
|
},
|
|
},
|
|
}
|
|
|
|
type T struct {
|
|
Config map[string]map[string]bool `toml:"config"`
|
|
}
|
|
|
|
var tgt T
|
|
if err := Decode(data, &tgt); err != nil {
|
|
t.Fatalf("Decode map-map failed: %v", err)
|
|
}
|
|
|
|
if !tgt.Config["env"]["production"] {
|
|
t.Error("Deep map decoding failed")
|
|
}
|
|
}
|
|
|
|
// TestDecode_TargetValidation ensures non-pointer targets fail
|
|
func TestDecode_TargetValidation(t *testing.T) {
|
|
var tgt struct{}
|
|
err := Decode(map[string]any{}, tgt) // Pass by value (error)
|
|
if err == nil {
|
|
t.Error("Expected error when passing non-pointer to Decode")
|
|
}
|
|
|
|
var ptr *struct{} = nil
|
|
err = Decode(map[string]any{}, ptr) // Pass nil pointer (error)
|
|
if err == nil {
|
|
t.Error("Expected error when passing nil pointer to Decode")
|
|
}
|
|
}
|
|
|
|
// TestDecode_PrivateHelperAccess verifies toFloat functionality indirectly
|
|
// via Decode since we are in package toml
|
|
func TestDecode_TypeMismatch(t *testing.T) {
|
|
data := map[string]any{
|
|
"val": "not a number",
|
|
}
|
|
type T struct {
|
|
Val int `toml:"val"`
|
|
}
|
|
var tgt T
|
|
err := Decode(data, &tgt)
|
|
if err == nil {
|
|
t.Error("Expected error decoding string to int")
|
|
}
|
|
}
|
|
|
|
func TestLexer_DottedKeyVsFloat(t *testing.T) {
|
|
// Verify lexer correctly distinguishes dotted keys from floats
|
|
tests := []struct {
|
|
input string
|
|
expected []TokenType
|
|
}{
|
|
{"a.b", []TokenType{TokenIdent, TokenDot, TokenIdent, TokenEOF}},
|
|
{"1.5", []TokenType{TokenFloat, TokenEOF}},
|
|
{"-3.14", []TokenType{TokenFloat, TokenEOF}},
|
|
{"+2.0", []TokenType{TokenFloat, TokenEOF}},
|
|
{"a.b.c", []TokenType{TokenIdent, TokenDot, TokenIdent, TokenDot, TokenIdent, TokenEOF}},
|
|
{"1e10", []TokenType{TokenFloat, TokenEOF}},
|
|
{"1.5e-3", []TokenType{TokenFloat, TokenEOF}},
|
|
{"key_name", []TokenType{TokenIdent, TokenEOF}},
|
|
{"key-name", []TokenType{TokenIdent, TokenEOF}},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
l := NewLexer([]byte(tc.input))
|
|
var got []TokenType
|
|
for {
|
|
tok := l.NextToken()
|
|
got = append(got, tok.Type)
|
|
if tok.Type == TokenEOF || tok.Type == TokenError {
|
|
break
|
|
}
|
|
}
|
|
if len(got) != len(tc.expected) {
|
|
t.Errorf("input %q: token count mismatch, got %d, want %d", tc.input, len(got), len(tc.expected))
|
|
continue
|
|
}
|
|
for i, tt := range tc.expected {
|
|
if got[i] != tt {
|
|
t.Errorf("input %q: token[%d] = %v, want %v", tc.input, i, got[i], tt)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUnmarshal_FloatInNestedTable(t *testing.T) {
|
|
input := []byte(`
|
|
[physics.gravity]
|
|
x = 0.0
|
|
y = -9.81
|
|
z = 0.0
|
|
`)
|
|
type Vec3 struct {
|
|
X float64 `toml:"x"`
|
|
Y float64 `toml:"y"`
|
|
Z float64 `toml:"z"`
|
|
}
|
|
type Config struct {
|
|
Physics struct {
|
|
Gravity Vec3 `toml:"gravity"`
|
|
} `toml:"physics"`
|
|
}
|
|
|
|
var cfg Config
|
|
if err := Unmarshal(input, &cfg); err != nil {
|
|
t.Fatalf("Unmarshal failed: %v", err)
|
|
}
|
|
if cfg.Physics.Gravity.Y != -9.81 {
|
|
t.Errorf("Gravity.Y = %f, want -9.81", cfg.Physics.Gravity.Y)
|
|
}
|
|
}
|
|
|
|
func TestUnmarshal_DeepDottedKeys(t *testing.T) {
|
|
input := []byte(`
|
|
[a.b.c.d]
|
|
value = 42
|
|
`)
|
|
type Config struct {
|
|
A struct {
|
|
B struct {
|
|
C struct {
|
|
D struct {
|
|
Value int `toml:"value"`
|
|
} `toml:"d"`
|
|
} `toml:"c"`
|
|
} `toml:"b"`
|
|
} `toml:"a"`
|
|
}
|
|
|
|
var cfg Config
|
|
if err := Unmarshal(input, &cfg); err != nil {
|
|
t.Fatalf("Unmarshal failed: %v", err)
|
|
}
|
|
if cfg.A.B.C.D.Value != 42 {
|
|
t.Errorf("Value = %d, want 42", cfg.A.B.C.D.Value)
|
|
}
|
|
}
|
|
|
|
func TestUnmarshal_MixedDottedAndInline(t *testing.T) {
|
|
input := []byte(`
|
|
[server.http]
|
|
port = 8080
|
|
tls = { enabled = true, cert = "server.crt" }
|
|
`)
|
|
type TLS struct {
|
|
Enabled bool `toml:"enabled"`
|
|
Cert string `toml:"cert"`
|
|
}
|
|
type Config struct {
|
|
Server struct {
|
|
HTTP struct {
|
|
Port int `toml:"port"`
|
|
TLS TLS `toml:"tls"`
|
|
} `toml:"http"`
|
|
} `toml:"server"`
|
|
}
|
|
|
|
var cfg Config
|
|
if err := Unmarshal(input, &cfg); err != nil {
|
|
t.Fatalf("Unmarshal failed: %v", err)
|
|
}
|
|
if cfg.Server.HTTP.Port != 8080 {
|
|
t.Errorf("Port = %d, want 8080", cfg.Server.HTTP.Port)
|
|
}
|
|
if !cfg.Server.HTTP.TLS.Enabled {
|
|
t.Error("TLS.Enabled should be true")
|
|
}
|
|
}
|
|
|
|
func TestUnmarshal_ScientificNotation(t *testing.T) {
|
|
input := []byte(`
|
|
planck = 6.626e-34
|
|
avogadro = 6.022e+23
|
|
speed_of_light = 3e8
|
|
`)
|
|
type Config struct {
|
|
Planck float64 `toml:"planck"`
|
|
Avogadro float64 `toml:"avogadro"`
|
|
SpeedOfLight float64 `toml:"speed_of_light"`
|
|
}
|
|
|
|
var cfg Config
|
|
if err := Unmarshal(input, &cfg); err != nil {
|
|
t.Fatalf("Unmarshal failed: %v", err)
|
|
}
|
|
if cfg.SpeedOfLight != 3e8 {
|
|
t.Errorf("SpeedOfLight = %e, want 3e8", cfg.SpeedOfLight)
|
|
}
|
|
}
|
|
|
|
func TestUnmarshal_HyphenatedKeys(t *testing.T) {
|
|
input := []byte(`
|
|
[my-section]
|
|
my-key = "value"
|
|
another_key = 123
|
|
`)
|
|
type Config struct {
|
|
MySection struct {
|
|
MyKey string `toml:"my-key"`
|
|
AnotherKey int `toml:"another_key"`
|
|
} `toml:"my-section"`
|
|
}
|
|
|
|
var cfg Config
|
|
if err := Unmarshal(input, &cfg); err != nil {
|
|
t.Fatalf("Unmarshal failed: %v", err)
|
|
}
|
|
if cfg.MySection.MyKey != "value" {
|
|
t.Errorf("MyKey = %q, want \"value\"", cfg.MySection.MyKey)
|
|
}
|
|
}
|
|
|
|
func TestUnmarshal_ArrayOfTablesWithPointers(t *testing.T) {
|
|
input := []byte(`
|
|
[[items]]
|
|
name = "first"
|
|
value = 1.5
|
|
|
|
[[items]]
|
|
name = "second"
|
|
value = 2.5
|
|
`)
|
|
type Item struct {
|
|
Name string `toml:"name"`
|
|
Value float64 `toml:"value"`
|
|
}
|
|
type Config struct {
|
|
Items []*Item `toml:"items"`
|
|
}
|
|
|
|
var cfg Config
|
|
if err := Unmarshal(input, &cfg); err != nil {
|
|
t.Fatalf("Unmarshal failed: %v", err)
|
|
}
|
|
if len(cfg.Items) != 2 {
|
|
t.Fatalf("len(Items) = %d, want 2", len(cfg.Items))
|
|
}
|
|
if cfg.Items[0] == nil || cfg.Items[0].Value != 1.5 {
|
|
t.Errorf("Items[0] mismatch: %+v", cfg.Items[0])
|
|
}
|
|
}
|
|
|
|
func TestUnmarshal_NestedMapPointers(t *testing.T) {
|
|
input := []byte(`
|
|
[entities.player]
|
|
health = 100
|
|
speed = 5.5
|
|
|
|
[entities.enemy]
|
|
health = 50
|
|
speed = 3.0
|
|
`)
|
|
type Entity struct {
|
|
Health int `toml:"health"`
|
|
Speed float64 `toml:"speed"`
|
|
}
|
|
type Config struct {
|
|
Entities map[string]*Entity `toml:"entities"`
|
|
}
|
|
|
|
var cfg Config
|
|
if err := Unmarshal(input, &cfg); err != nil {
|
|
t.Fatalf("Unmarshal failed: %v", err)
|
|
}
|
|
if cfg.Entities["player"] == nil || cfg.Entities["player"].Speed != 5.5 {
|
|
t.Errorf("player mismatch: %+v", cfg.Entities["player"])
|
|
}
|
|
if cfg.Entities["enemy"] == nil || cfg.Entities["enemy"].Health != 50 {
|
|
t.Errorf("enemy mismatch: %+v", cfg.Entities["enemy"])
|
|
}
|
|
} |