Added a good string formatter for the AST, to making testing more straight forward.

This commit is contained in:
Maurice Makaay 2019-06-24 07:05:38 +00:00
parent 8838dc9c44
commit 15560b29b0
4 changed files with 60 additions and 51 deletions

22
ast.go
View File

@ -2,6 +2,7 @@ package toml
import (
"fmt"
"sort"
"strings"
"time"
)
@ -74,13 +75,18 @@ func (parseItem item) String() string {
}
return fmt.Sprintf("[%s]", strings.Join(items, ", "))
case pTable:
items := make([]string, len(parseItem.Data))
pairs := parseItem.Data[0].(table)
keys := make([]string, len(pairs))
i := 0
for k, v := range pairs {
items[i] = fmt.Sprintf("%q: %s", k, v.String())
for k := range pairs {
keys[i] = k
i++
}
sort.Strings(keys)
items := make([]string, len(pairs))
for i, k := range keys {
items[i] = fmt.Sprintf("%q: %s", k, pairs[k].String())
}
return fmt.Sprintf("{%s}", strings.Join(items, ", "))
case pComment:
return fmt.Sprintf("comment(%q)", parseItem.Data[0])
@ -154,15 +160,15 @@ func (t *parser) setValue(key item, value item) error {
return nil
}
func (t *parser) newTable(key item) (table, error) {
func (t *parser) openTable(key item) (table, error) {
node := t.Root
// Go over all requested levels of the key. For all levels, except the last
// one, it is okay if a table already exists. For at least the last level,
// no table or value must exist, because that would mean we are overwriting
// an existing key/value pair, which is not allowed.
for i, e := range key.Data {
name := e.(string)
if subItem, ok := node[name]; ok {
for i, value := range key.Data {
keyName := value.(string)
if subItem, ok := node[keyName]; ok {
// You cannot overwrite an already defined key, regardless its value.
if subItem.Type != pTable {
path := formatKeyPath(key, i)
@ -178,7 +184,7 @@ func (t *parser) newTable(key item) (table, error) {
} else {
// Create the subtable.
subTable := make(table)
node[name] = newItem(pTable, subTable)
node[keyName] = newItem(pTable, subTable)
node = subTable
}
}

View File

@ -1,72 +1,69 @@
package toml
import (
"fmt"
"testing"
)
func TestAST_ConstructStructure(t *testing.T) {
testAST(t, func() (error, *parser) {
p := newParser()
p.Root["ding"] = newItem(pInteger, 10)
p.Root["dong"] = newItem(pString, "not a song")
subTable1, _ := p.newTable(newItem(pKey, "key1", "key2 a"))
subTable1["dooh"] = newItem(pBoolean, true)
subTable1["dah"] = newItem(pBoolean, false)
subTable2, _ := p.newTable(newItem(pKey, "key1", "key2 b"))
subTable2["dieh"] = newItem(pFloat, 1.111)
subTable2["duhh"] = newItem(pFloat, 1.18e-12)
p.setValue(newItem(pKey, "ding"), newItem(pInteger, 10))
p.setValue(newItem(pKey, "dong"), newItem(pString, "not a song"))
p.openTable(newItem(pKey, "key1", "key2 a"))
p.setValue(newItem(pKey, "dooh"), newItem(pBoolean, true))
p.setValue(newItem(pKey, "dah"), newItem(pBoolean, false))
p.openTable(newItem(pKey, "key1", "key2 b"))
p.setValue(newItem(pKey, "dieh"), newItem(pFloat, 1.111))
p.setValue(newItem(pKey, "duh"), newItem(pFloat, 1.18e-12))
return nil, p
}, "", `{"ding": 10, "dong": "not a song", "key1": {"key2 a": {"dah": false, "dooh": true}, "key2 b": {"dieh": 1.111, "duh": 1.18e-12}}}`)
}
func TestAST_StoreValueInRootTable(t *testing.T) {
testError(t, func() error {
testAST(t, func() (error, *parser) {
p := newParser()
p.setValue(newItem(pKey, "key1"), newItem(pString, "value1"))
return p.setValue(newItem(pKey, "key2"), newItem(pString, "value2"))
}, "")
return p.setValue(newItem(pKey, "key2"), newItem(pString, "value2")), p
}, "", `{"key1": "value1", "key2": "value2"}`)
}
func TestAST_StoreValueWithMultipartKey_CreatesTableHierarchy(t *testing.T) {
testError(t, func() error {
testAST(t, func() (error, *parser) {
p := newParser()
return p.setValue(newItem(pKey, "key1", "key2", "key3"), newItem(pString, "value"))
}, "")
// TODO an actual test assertion
return p.setValue(newItem(pKey, "key1", "key2", "key3"), newItem(pString, "value")), p
}, "", `{"key1": {"key2": {"key3": "value"}}}`)
}
func TestAST_StoreValueWithMultipartKey_UnderSubtable_CreatesTableHierarchy(t *testing.T) {
testError(t, func() error {
testAST(t, func() (error, *parser) {
p := newParser()
p.newTable(newItem(pKey, "subkey1", "subkey2"))
err := p.setValue(newItem(pKey, "key1", "key2", "key3"), newItem(pString, "value"))
fmt.Printf("%s", p.Root)
return err
}, "")
t.Fail()
// TODO an actual test assertion
p.openTable(newItem(pKey, "tablekey1", "tablekey2"))
return p.setValue(newItem(pKey, "valuekey1", "valuekey2", "valuekey3"), newItem(pString, "value")), p
}, "", `{"tablekey1": {"tablekey2": {"valuekey1": {"valuekey2": {"valuekey3": "value"}}}}}`)
}
func TestAST_StoreDuplicateKeyInRootTable_ReturnsError(t *testing.T) {
testError(t, func() error {
testAST(t, func() (error, *parser) {
p := newParser()
p.setValue(newItem(pKey, "key"), newItem(pString, "value"))
return p.setValue(newItem(pKey, "key"), newItem(pInteger, 321))
}, `Cannot store value: string item already exists at key "key"`)
return p.setValue(newItem(pKey, "key"), newItem(pInteger, 321)), p
}, `Cannot store value: string item already exists at key "key"`, "")
}
func TestAST_GivenExistingTableAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) {
testError(t, func() error {
testAST(t, func() (error, *parser) {
p := newParser()
p.newTable(newItem(pKey, "key1", "key2"))
_, err := p.newTable(newItem(pKey, "key1", "key2"))
return err
}, `Cannot create table: table for key "key1"."key2" already exists`)
p.openTable(newItem(pKey, "key1", "key2"))
_, err := p.openTable(newItem(pKey, "key1", "key2"))
return err, p
}, `Cannot create table: table for key "key1"."key2" already exists`, "")
}
func TestAST_GivenExistingItemAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) {
testError(t, func() error {
testAST(t, func() (error, *parser) {
p := newParser()
p.Root["key"] = newItem(pString, "value")
_, err := p.newTable(newItem(pKey, "key"))
return err
}, `Cannot create table: string item already exists at key "key"`)
_, err := p.openTable(newItem(pKey, "key"))
return err, p
}, `Cannot create table: string item already exists at key "key"`, "")
}

View File

@ -54,17 +54,23 @@ func testParseHandler(t *testing.T, p *parser, handler parse.Handler, test parse
err = parse.New(handler)(test.input)
}
func testError(t *testing.T, code func() error, expected string) {
err := code()
if expected == "" {
func testAST(t *testing.T, code func() (error, *parser), expectedError string, expectedData string) {
err, p := code()
if expectedError == "" {
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
} else {
if err == nil {
t.Fatalf("An error was expected, but no error was returned")
} else if err.Error() != expected {
t.Fatalf("Unexpected error:\nexpected: %s\nactual: %s\n", expected, err.Error())
} else if err.Error() != expectedError {
t.Fatalf("Unexpected error:\nexpected: %s\nactual: %s\n", expectedError, err.Error())
}
}
if expectedData == "" {
return
}
if expectedData != p.Root.String() {
t.Fatalf("Unexpected data after parsing:\nexpected: %s\nactual: %s\n", expectedData, p.Root.String())
}
}

View File

@ -107,5 +107,5 @@ func (t *parser) startPlainTable(p *parse.API) {
}
key := t.Items[0]
t.Items = t.Items[1:]
t.newTable(key)
t.openTable(key)
}