Assisted-by: DeepSeek V4.1 Flash
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package interpres
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// mustEntry returns the entry a table must have, and fails the test when it
|
||||
// does not.
|
||||
func mustEntry(t *testing.T, tbl *Table, key string) *Entry {
|
||||
t.Helper()
|
||||
e, ok := tbl.Get(key)
|
||||
if !ok {
|
||||
t.Fatalf("%q is missing from the table", key)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func TestDocumentKeepsKeyOrder(t *testing.T) {
|
||||
doc, err := Parse([]byte(`
|
||||
b = 1
|
||||
a = 2
|
||||
inline = {y = 1, x = 2}
|
||||
|
||||
[table]
|
||||
z = 3
|
||||
m = 4
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
|
||||
// The root's keys come back in written order, not sorted.
|
||||
if got := doc.Root().Keys(); !slices.Equal(got, []string{"b", "a", "inline", "table"}) {
|
||||
t.Errorf("root keys = %v, want [b a inline table]", got)
|
||||
}
|
||||
|
||||
// So do the keys of an inline table, which the map shape loses.
|
||||
inline, ok := doc.Root().Get("inline")
|
||||
if !ok {
|
||||
t.Fatal("inline is missing from the root")
|
||||
}
|
||||
if !inline.Inline() {
|
||||
t.Error("inline is not marked inline")
|
||||
}
|
||||
if got := inline.Table().Keys(); !slices.Equal(got, []string{"y", "x"}) {
|
||||
t.Errorf("inline keys = %v, want [y x]", got)
|
||||
}
|
||||
|
||||
// And the keys of a table written under a header, which is not inline.
|
||||
tbl, ok := doc.Root().Get("table")
|
||||
if !ok {
|
||||
t.Fatal("table is missing from the root")
|
||||
}
|
||||
if tbl.Inline() {
|
||||
t.Error("table is marked inline")
|
||||
}
|
||||
if got := tbl.Table().Keys(); !slices.Equal(got, []string{"z", "m"}) {
|
||||
t.Errorf("table keys = %v, want [z m]", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocumentValuesAreTheTree(t *testing.T) {
|
||||
doc, err := Parse([]byte("n = 7\ns = \"x\"\n\n[t]\nk = true\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if got := mustEntry(t, doc.Root(), "n").Value(); got != int64(7) {
|
||||
t.Errorf("n = %#v, want int64(7)", got)
|
||||
}
|
||||
tbl := mustEntry(t, doc.Root(), "t").Table()
|
||||
if got := mustEntry(t, tbl, "k").Value(); got != true {
|
||||
t.Errorf("t.k = %#v, want true", got)
|
||||
}
|
||||
// Map gives the tree ParseMap would have returned, the same values.
|
||||
tree := doc.Map()
|
||||
if tree["n"] != int64(7) || tree["s"] != "x" {
|
||||
t.Errorf("Map = %#v", tree)
|
||||
}
|
||||
if tree["t"].(map[string]any)["k"] != true {
|
||||
t.Errorf("Map[t] = %#v", tree["t"])
|
||||
}
|
||||
if tbl.Values()["k"] != true {
|
||||
t.Errorf("t.Values() = %#v", tbl.Values())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocumentComments(t *testing.T) {
|
||||
doc, err := Parse([]byte(`# above b
|
||||
b = 1 # trailing b
|
||||
|
||||
# above the table
|
||||
[table] # trailing table
|
||||
# above m
|
||||
m = 2
|
||||
|
||||
# footer
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
|
||||
b, ok := doc.Root().Get("b")
|
||||
if !ok {
|
||||
t.Fatal("b is missing")
|
||||
}
|
||||
if got := b.Comments(); !slices.Equal(got, []string{"above b"}) {
|
||||
t.Errorf("b comments = %q, want [above b]", got)
|
||||
}
|
||||
if got := b.Trailing(); got != "trailing b" {
|
||||
t.Errorf("b trailing = %q, want \"trailing b\"", got)
|
||||
}
|
||||
|
||||
tbl, ok := doc.Root().Get("table")
|
||||
if !ok {
|
||||
t.Fatal("table is missing")
|
||||
}
|
||||
// A [header] line introduces the table, so the comments around it belong
|
||||
// to the table node; the entry that names it stays bare.
|
||||
if got := tbl.Table().Comments(); !slices.Equal(got, []string{"above the table"}) {
|
||||
t.Errorf("table comments = %q, want [above the table]", got)
|
||||
}
|
||||
if got := tbl.Table().Trailing(); got != "trailing table" {
|
||||
t.Errorf("table trailing = %q, want \"trailing table\"", got)
|
||||
}
|
||||
if got := tbl.Comments(); got != nil {
|
||||
t.Errorf("entry comments = %q, want none", got)
|
||||
}
|
||||
m, ok := tbl.Table().Get("m")
|
||||
if !ok {
|
||||
t.Fatal("table.m is missing")
|
||||
}
|
||||
if got := m.Comments(); !slices.Equal(got, []string{"above m"}) {
|
||||
t.Errorf("m comments = %q, want [above m]", got)
|
||||
}
|
||||
|
||||
if got := doc.Footer(); !slices.Equal(got, []string{"footer"}) {
|
||||
t.Errorf("footer = %q, want [footer]", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocumentCommentsAreWritable(t *testing.T) {
|
||||
doc, err := Parse([]byte("# above\nk = 1\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
entry, ok := doc.Root().Get("k")
|
||||
if !ok {
|
||||
t.Fatal("k is missing")
|
||||
}
|
||||
entry.SetComments([]string{"first", "second"})
|
||||
entry.SetTrailing("beside")
|
||||
if got := entry.Comments(); !slices.Equal(got, []string{"first", "second"}) {
|
||||
t.Errorf("comments = %q", got)
|
||||
}
|
||||
if got := entry.Trailing(); got != "beside" {
|
||||
t.Errorf("trailing = %q", got)
|
||||
}
|
||||
|
||||
tbl := doc.Root()
|
||||
tbl.SetComments([]string{"above the root"})
|
||||
if got := tbl.Comments(); !slices.Equal(got, []string{"above the root"}) {
|
||||
t.Errorf("root comments = %q", got)
|
||||
}
|
||||
doc.SetFooter([]string{"end"})
|
||||
if got := doc.Footer(); !slices.Equal(got, []string{"end"}) {
|
||||
t.Errorf("footer = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocumentArrayOfTables(t *testing.T) {
|
||||
doc, err := Parse([]byte(`# first element
|
||||
[[item]]
|
||||
a = 1
|
||||
|
||||
[[item]]
|
||||
b = 2 # beside b
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
entry, ok := doc.Root().Get("item")
|
||||
if !ok {
|
||||
t.Fatal("item is missing")
|
||||
}
|
||||
elems := entry.Elements()
|
||||
if len(elems) != 2 {
|
||||
t.Fatalf("elements = %d, want 2", len(elems))
|
||||
}
|
||||
if got := elems[0].Keys(); !slices.Equal(got, []string{"a"}) {
|
||||
t.Errorf("first element keys = %v, want [a]", got)
|
||||
}
|
||||
if got := elems[0].Comments(); !slices.Equal(got, []string{"first element"}) {
|
||||
t.Errorf("first element comments = %q", got)
|
||||
}
|
||||
if got := elems[1].Keys(); !slices.Equal(got, []string{"b"}) {
|
||||
t.Errorf("second element keys = %v, want [b]", got)
|
||||
}
|
||||
if got := mustEntry(t, elems[1], "b").Trailing(); got != "beside b" {
|
||||
t.Errorf("b trailing = %q, want \"beside b\"", got)
|
||||
}
|
||||
// The value keeps the map shape the decoder reads.
|
||||
if _, ok := entry.Value().([]map[string]any); !ok {
|
||||
t.Errorf("item value = %#v, want []map[string]any", entry.Value())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocumentDottedKeysAndValueArrays(t *testing.T) {
|
||||
doc, err := Parse([]byte("a.b.c = 1\narr = [1, {x = 1}]\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
|
||||
// A dotted key builds tables, and they are not inline ones.
|
||||
a, ok := doc.Root().Get("a")
|
||||
if !ok {
|
||||
t.Fatal("a is missing")
|
||||
}
|
||||
if a.Inline() {
|
||||
t.Error("a is marked inline")
|
||||
}
|
||||
b, ok := a.Table().Get("b")
|
||||
if !ok {
|
||||
t.Fatal("a.b is missing")
|
||||
}
|
||||
if b.Inline() {
|
||||
t.Error("a.b is marked inline")
|
||||
}
|
||||
if got := b.Table().Keys(); !slices.Equal(got, []string{"c"}) {
|
||||
t.Errorf("a.b keys = %v, want [c]", got)
|
||||
}
|
||||
|
||||
// An inline table inside a value array keeps its node in the elements
|
||||
// slice; the scalar before it has none.
|
||||
arr, ok := doc.Root().Get("arr")
|
||||
if !ok {
|
||||
t.Fatal("arr is missing")
|
||||
}
|
||||
elems := arr.Elements()
|
||||
if len(elems) != 2 {
|
||||
t.Fatalf("elements = %d, want 2", len(elems))
|
||||
}
|
||||
if elems[0] != nil {
|
||||
t.Errorf("elements[0] = %#v, want nil for a scalar", elems[0])
|
||||
}
|
||||
if got := elems[1].Keys(); !slices.Equal(got, []string{"x"}) {
|
||||
t.Errorf("elements[1] keys = %v, want [x]", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocumentWithoutStatements(t *testing.T) {
|
||||
doc, err := Parse([]byte("# only a comment\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if got := doc.Root().Keys(); len(got) != 0 {
|
||||
t.Errorf("keys = %v, want none", got)
|
||||
}
|
||||
if got := doc.Footer(); !slices.Equal(got, []string{"only a comment"}) {
|
||||
t.Errorf("footer = %q, want [only a comment]", got)
|
||||
}
|
||||
|
||||
empty, err := Parse(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("parse of nothing: %v", err)
|
||||
}
|
||||
if len(empty.Root().Keys()) != 0 || len(empty.Footer()) != 0 {
|
||||
t.Errorf("empty document = %v / %q", empty.Root().Keys(), empty.Footer())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMapIsTheValueTree(t *testing.T) {
|
||||
// ParseMap is the path that does not build a document, and it gives the
|
||||
// tree the decoder reads.
|
||||
tree, err := ParseMap([]byte("a = 1\n\n[t]\nb = \"x\"\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if tree["a"] != int64(1) {
|
||||
t.Errorf("a = %#v", tree["a"])
|
||||
}
|
||||
if tree["t"].(map[string]any)["b"] != "x" {
|
||||
t.Errorf("t = %#v", tree["t"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalRejectsDocument(t *testing.T) {
|
||||
// A Document is not a value to marshal: its order and comments would be
|
||||
// dropped, and a struct walk would silently write nothing at all.
|
||||
doc, err := Parse([]byte("a = 1\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if _, err := Marshal(doc); err == nil {
|
||||
t.Fatal("expected an error for a Document")
|
||||
} else if !strings.Contains(err.Error(), "Map()") {
|
||||
t.Errorf("err = %v, want it to point at Map()", err)
|
||||
}
|
||||
if _, err := Marshal(*doc); err == nil {
|
||||
t.Fatal("expected an error for a Document value")
|
||||
}
|
||||
// The tree marshals, which is the way through.
|
||||
out, err := Marshal(doc.Map())
|
||||
if err != nil {
|
||||
t.Fatalf("marshal of the tree: %v", err)
|
||||
}
|
||||
if want := "a = 1\n"; string(out) != want {
|
||||
t.Errorf("output = %q, want %q", out, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user