v0.1.0 initial commit
This commit is contained in:
+517
@@ -0,0 +1,517 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecode_UnexportedFieldPanic(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Recovered from panic: %v. Logic should skip unexported fields.", r)
|
||||
}
|
||||
}()
|
||||
|
||||
data := map[string]any{"secret": "hacker"}
|
||||
type Security struct {
|
||||
secret string
|
||||
Public string `toml:"secret"`
|
||||
}
|
||||
|
||||
var s Security
|
||||
_ = Decode(data, &s)
|
||||
}
|
||||
|
||||
func TestLexer_InvalidNumbers(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
wantErr bool
|
||||
}{
|
||||
{"1.0a", true}, // Not valid TOML document structure
|
||||
{"1.-0", true}, // Not valid TOML document structure
|
||||
{"0xG1", true}, // Invalid hex digit
|
||||
{"+", true}, // Lone + is invalid TOML
|
||||
{"[1.2.3]", true}, // Multi-dot in numeric context
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
p := NewParser([]byte(tc.input))
|
||||
_, err := p.Parse()
|
||||
if tc.wantErr && err == nil {
|
||||
t.Errorf("Input %q should have failed parsing", tc.input)
|
||||
}
|
||||
if !tc.wantErr && err != nil {
|
||||
t.Errorf("Input %q unexpected error: %v", tc.input, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_DeepPointers(t *testing.T) {
|
||||
data := map[string]any{"val": 42}
|
||||
type T struct {
|
||||
Val ******int `toml:"val"`
|
||||
}
|
||||
var tgt T
|
||||
if err := Decode(data, &tgt); err != nil {
|
||||
t.Fatalf("Deep pointer decode failed: %v", err)
|
||||
}
|
||||
if ******tgt.Val != 42 {
|
||||
t.Errorf("Expected 42, got %d", ******tgt.Val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_LargeIntPrecision(t *testing.T) {
|
||||
largeVal := int64(4611686018427387905)
|
||||
data := map[string]any{"id": int(largeVal)}
|
||||
|
||||
type T struct {
|
||||
ID int64 `toml:"id"`
|
||||
}
|
||||
var tgt T
|
||||
_ = Decode(data, &tgt)
|
||||
|
||||
if tgt.ID != largeVal {
|
||||
t.Errorf("Precision loss detected: got %d, want %d", tgt.ID, largeVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_NumericKeyRejection(t *testing.T) {
|
||||
inputs := [][]byte{
|
||||
[]byte(`123 = "value"`),
|
||||
[]byte(`[123]`),
|
||||
[]byte(`[a.123.b]`),
|
||||
}
|
||||
|
||||
for _, in := range inputs {
|
||||
p := NewParser(in)
|
||||
if _, err := p.Parse(); err == nil {
|
||||
t.Errorf("Parser should have rejected numeric key in: %s", string(in))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPanic_LexerInfinity(t *testing.T) {
|
||||
input := []byte("key = \"\x00\xff\"\n[table\x00]")
|
||||
l := NewLexer(input)
|
||||
for i := 0; i < 100; i++ {
|
||||
tok := l.NextToken()
|
||||
if tok.Type == TokenEOF {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Error("Lexer likely stuck in infinite loop on invalid input")
|
||||
}
|
||||
|
||||
func TestPanic_DeepNesting(t *testing.T) {
|
||||
depth := 1000
|
||||
input := strings.Repeat("a.", depth) + "b = 1"
|
||||
p := NewParser([]byte(input))
|
||||
_, err := p.Parse()
|
||||
if err != nil && !strings.Contains(err.Error(), "key path conflict") {
|
||||
t.Logf("Caught expected deep nesting error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreak_TableRedefinition(t *testing.T) {
|
||||
input := []byte(`
|
||||
anchor = 1
|
||||
[anchor]
|
||||
sub = 2
|
||||
`)
|
||||
p := NewParser(input)
|
||||
_, err := p.Parse()
|
||||
if err == nil {
|
||||
t.Error("Parser failed to catch redefinition of a value as a table")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreak_MalformedScientificNotation(t *testing.T) {
|
||||
tests := []string{
|
||||
"val = 1e",
|
||||
"val = 1e+",
|
||||
"val = .5",
|
||||
"val = 1.e2",
|
||||
}
|
||||
for _, tc := range tests {
|
||||
p := NewParser([]byte(tc))
|
||||
_, err := p.Parse()
|
||||
if err == nil {
|
||||
t.Errorf("Should have failed to parse malformed float: %s", tc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreak_SliceTypeMismatch(t *testing.T) {
|
||||
data := map[string]any{
|
||||
"list": []any{1, "string", 3},
|
||||
}
|
||||
type Target struct {
|
||||
List []int `toml:"list"`
|
||||
}
|
||||
var tgt Target
|
||||
err := Decode(data, &tgt)
|
||||
if err == nil {
|
||||
t.Error("Decoder should have failed converting string to int inside slice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreak_InvalidDottedKeyInInlineTable(t *testing.T) {
|
||||
input := []byte(`config = { valid.123 = "fail" }`)
|
||||
p := NewParser(input)
|
||||
_, err := p.Parse()
|
||||
if err == nil {
|
||||
t.Error("Parser allowed numeric segment in dotted inline table key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPanic_NilInterfaceAssignment(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Panic during nil interface decoding: %v", r)
|
||||
}
|
||||
}()
|
||||
var target any
|
||||
data := map[string]any{"a": 1}
|
||||
_ = Decode(data, &target)
|
||||
}
|
||||
|
||||
func TestStructural_NestedReentry(t *testing.T) {
|
||||
input := []byte(`
|
||||
[a.b.c]
|
||||
depth = 3
|
||||
[a]
|
||||
root_val = 1
|
||||
[a.b]
|
||||
mid_val = 2
|
||||
`)
|
||||
p := NewParser(input)
|
||||
res, err := p.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Valid nested reentry failed: %v", err)
|
||||
}
|
||||
|
||||
a := res["a"].(map[string]any)
|
||||
if a["root_val"] != 1 {
|
||||
t.Errorf("Missing root_val: %v", a["root_val"])
|
||||
}
|
||||
b := a["b"].(map[string]any)
|
||||
if b["mid_val"] != 2 {
|
||||
t.Errorf("Missing mid_val: %v", b["mid_val"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreak_KeyCollisionDotted(t *testing.T) {
|
||||
input := []byte(`
|
||||
a.b = 1
|
||||
[a.b]
|
||||
c = 2
|
||||
`)
|
||||
p := NewParser(input)
|
||||
_, err := p.Parse()
|
||||
if err == nil {
|
||||
t.Error("Should have failed: redefining scalar a.b as a table")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreak_IntegerOverflow(t *testing.T) {
|
||||
input := []byte(`val = 9223372036854775808`)
|
||||
p := NewParser(input)
|
||||
_, err := p.Parse()
|
||||
if err == nil {
|
||||
t.Error("Parser should have errored on int64 overflow")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreak_ArrayTableShadowing(t *testing.T) {
|
||||
input := []byte(`
|
||||
[conflict]
|
||||
sub = 1
|
||||
[[conflict]]
|
||||
sub = 2
|
||||
`)
|
||||
p := NewParser(input)
|
||||
_, err := p.Parse()
|
||||
if err == nil {
|
||||
t.Error("Should have failed: [conflict] followed by [[conflict]]")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreak_RecursiveDecoder(t *testing.T) {
|
||||
type Recursive struct {
|
||||
Next *Recursive `toml:"next"`
|
||||
}
|
||||
data := map[string]any{
|
||||
"next": map[string]any{
|
||||
"next": map[string]any{
|
||||
"next": map[string]any{},
|
||||
},
|
||||
},
|
||||
}
|
||||
var target Recursive
|
||||
err := Decode(data, &target)
|
||||
if err != nil {
|
||||
t.Fatalf("Recursive decode failed: %v", err)
|
||||
}
|
||||
if target.Next.Next.Next == nil {
|
||||
t.Error("Recursive decoding depth mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreak_DottedKeyConflictWithTable(t *testing.T) {
|
||||
input := []byte(`
|
||||
[a]
|
||||
b.c = 1
|
||||
[a.b]
|
||||
c = 2
|
||||
`)
|
||||
p := NewParser(input)
|
||||
_, err := p.Parse()
|
||||
if err == nil {
|
||||
t.Error("Should have failed: duplicate definition of a.b.c")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexer_CommentEdgeCases(t *testing.T) {
|
||||
input := []byte(`
|
||||
key = "value # not a comment" # this is a comment
|
||||
# Empty line with comment
|
||||
# indented comment
|
||||
[table] # comment after table
|
||||
`)
|
||||
p := NewParser(input)
|
||||
res, err := p.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Lexer failed on valid comments: %v", err)
|
||||
}
|
||||
if res["key"] != "value # not a comment" {
|
||||
t.Errorf("Comment in string was incorrectly truncated: %v", res["key"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexer_StrictNumericValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected []TokenType
|
||||
}{
|
||||
{
|
||||
"Multiple dots error",
|
||||
"1.1.1",
|
||||
[]TokenType{TokenError},
|
||||
},
|
||||
{
|
||||
"Octal is valid",
|
||||
"val = 0123",
|
||||
[]TokenType{TokenIdent, TokenEqual, TokenInteger, TokenEOF},
|
||||
},
|
||||
{
|
||||
"Negative with leading zero valid",
|
||||
"val = -01",
|
||||
[]TokenType{TokenIdent, TokenEqual, TokenInteger, TokenEOF},
|
||||
},
|
||||
{
|
||||
"Float then dot then int",
|
||||
"1e1.5",
|
||||
[]TokenType{TokenFloat, TokenDot, TokenInteger, TokenEOF},
|
||||
},
|
||||
{
|
||||
"Float then ident",
|
||||
"1e1e1",
|
||||
[]TokenType{TokenFloat, TokenIdent, TokenEOF},
|
||||
},
|
||||
{
|
||||
"Float then ident with dots",
|
||||
"1.00a00",
|
||||
[]TokenType{TokenFloat, TokenIdent, TokenEOF},
|
||||
},
|
||||
{
|
||||
"Incomplete exponent error",
|
||||
"val = 1e+",
|
||||
[]TokenType{TokenIdent, TokenEqual, TokenInteger, TokenIdent, TokenError},
|
||||
},
|
||||
{
|
||||
"Zero valid",
|
||||
"val = 0",
|
||||
[]TokenType{TokenIdent, TokenEqual, TokenInteger, TokenEOF},
|
||||
},
|
||||
{
|
||||
"Negative zero valid",
|
||||
"val = -0",
|
||||
[]TokenType{TokenIdent, TokenEqual, TokenInteger, TokenEOF},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
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("[%s] token count: got %d %v, want %d %v", tc.input, len(got), got, len(tc.expected), tc.expected)
|
||||
return
|
||||
}
|
||||
for i, exp := range tc.expected {
|
||||
if got[i] != exp {
|
||||
t.Errorf("[%s] token[%d]: got %v, want %v", tc.input, i, got[i], exp)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_KeyPathDeepExhaustion(t *testing.T) {
|
||||
input := []byte(`
|
||||
a.b.c.d.e = 1
|
||||
a.b.c.f = 2
|
||||
[a.b.c]
|
||||
g = 3
|
||||
[a.b.c.d]
|
||||
h = 4
|
||||
[a.b]
|
||||
i = 5
|
||||
`)
|
||||
p := NewParser(input)
|
||||
res, err := p.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed on complex but valid nested reentry: %v", err)
|
||||
}
|
||||
|
||||
a := res["a"].(map[string]any)
|
||||
b := a["b"].(map[string]any)
|
||||
if b["i"] != 5 {
|
||||
t.Errorf("Value 'i' lost in table reentry. Got %v", b["i"])
|
||||
}
|
||||
if _, ok := b["c"].(map[string]any); !ok {
|
||||
t.Errorf("Sub-map 'c' lost during parent reentry")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_FloatParsingErrors(t *testing.T) {
|
||||
tests := []string{
|
||||
"f = .5",
|
||||
"f = 1.",
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
p := NewParser([]byte(tc))
|
||||
_, err := p.Parse()
|
||||
if err == nil {
|
||||
t.Errorf("Should have failed to parse: %s", tc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexer_HexWithE(t *testing.T) {
|
||||
// 0xDEAD must be Integer, not misclassified as Float due to 'E'
|
||||
input := "val = 0xDEAD"
|
||||
l := NewLexer([]byte(input))
|
||||
|
||||
tok := l.NextToken() // val
|
||||
if tok.Type != TokenIdent {
|
||||
t.Errorf("Expected Ident, got %v", tok.Type)
|
||||
}
|
||||
tok = l.NextToken() // =
|
||||
if tok.Type != TokenEqual {
|
||||
t.Errorf("Expected Equal, got %v", tok.Type)
|
||||
}
|
||||
tok = l.NextToken() // 0xDEAD
|
||||
if tok.Type != TokenInteger {
|
||||
t.Errorf("Expected Integer for hex, got %v (%s)", tok.Type, tok.Literal)
|
||||
}
|
||||
if tok.Literal != "0xDEAD" {
|
||||
t.Errorf("Literal mismatch: %q", tok.Literal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexer_IPAddressAndVersion(t *testing.T) {
|
||||
// IP-like or semver must error on multi-dot
|
||||
input := "version = 1.2.3"
|
||||
l := NewLexer([]byte(input))
|
||||
|
||||
tok := l.NextToken() // version
|
||||
if tok.Type != TokenIdent {
|
||||
t.Errorf("Expected Ident, got %v", tok.Type)
|
||||
}
|
||||
tok = l.NextToken() // =
|
||||
if tok.Type != TokenEqual {
|
||||
t.Errorf("Expected Equal, got %v", tok.Type)
|
||||
}
|
||||
tok = l.NextToken() // 1.2.3 should error
|
||||
if tok.Type != TokenError {
|
||||
t.Errorf("Expected Error for multi-dot, got %v (%s)", tok.Type, tok.Literal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_StrictNoNumericKeys(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
name string
|
||||
}{
|
||||
{`123 = "val"`, "Bare integer key"},
|
||||
{`[123]`, "Integer table header"},
|
||||
{`["456"]`, "Quoted integer key"},
|
||||
{`a.1.b = "val"`, "Numeric segment in dotted key"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
p := NewParser([]byte(tc.input))
|
||||
_, err := p.Parse()
|
||||
if err == nil {
|
||||
t.Errorf("Failed %s: should have rejected numeric key", tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexer_AmbiguousNumericDotted(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected []TokenType
|
||||
}{
|
||||
{"1.a", []TokenType{TokenInteger, TokenDot, TokenIdent, TokenEOF}},
|
||||
{"1.0.0", []TokenType{TokenError}}, // Multi-dot
|
||||
{"1e1.5", []TokenType{TokenFloat, TokenDot, TokenInteger, 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("%s: got %v, want %v", tc.input, got, tc.expected)
|
||||
continue
|
||||
}
|
||||
for i, exp := range tc.expected {
|
||||
if got[i] != exp {
|
||||
t.Errorf("%s[%d]: got %v, want %v", tc.input, i, got[i], exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_KeyValueContext(t *testing.T) {
|
||||
input := `key = 1.1`
|
||||
p := NewParser([]byte(input))
|
||||
_, err := p.Parse()
|
||||
if err != nil {
|
||||
t.Errorf("Valid float value failed: %v", err)
|
||||
}
|
||||
|
||||
input2 := `1.1 = "value"`
|
||||
p2 := NewParser([]byte(input2))
|
||||
_, err = p2.Parse()
|
||||
if err == nil {
|
||||
t.Error("Float key should have been rejected")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user