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

View File

@ -1,72 +1,69 @@
package toml package toml
import ( import (
"fmt"
"testing" "testing"
) )
func TestAST_ConstructStructure(t *testing.T) { func TestAST_ConstructStructure(t *testing.T) {
p := newParser() testAST(t, func() (error, *parser) {
p.Root["ding"] = newItem(pInteger, 10) p := newParser()
p.Root["dong"] = newItem(pString, "not a song") p.setValue(newItem(pKey, "ding"), newItem(pInteger, 10))
subTable1, _ := p.newTable(newItem(pKey, "key1", "key2 a")) p.setValue(newItem(pKey, "dong"), newItem(pString, "not a song"))
subTable1["dooh"] = newItem(pBoolean, true) p.openTable(newItem(pKey, "key1", "key2 a"))
subTable1["dah"] = newItem(pBoolean, false) p.setValue(newItem(pKey, "dooh"), newItem(pBoolean, true))
subTable2, _ := p.newTable(newItem(pKey, "key1", "key2 b")) p.setValue(newItem(pKey, "dah"), newItem(pBoolean, false))
subTable2["dieh"] = newItem(pFloat, 1.111) p.openTable(newItem(pKey, "key1", "key2 b"))
subTable2["duhh"] = newItem(pFloat, 1.18e-12) 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) { func TestAST_StoreValueInRootTable(t *testing.T) {
testError(t, func() error { testAST(t, func() (error, *parser) {
p := newParser() p := newParser()
p.setValue(newItem(pKey, "key1"), newItem(pString, "value1")) 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) { func TestAST_StoreValueWithMultipartKey_CreatesTableHierarchy(t *testing.T) {
testError(t, func() error { testAST(t, func() (error, *parser) {
p := newParser() p := newParser()
return p.setValue(newItem(pKey, "key1", "key2", "key3"), newItem(pString, "value")) return p.setValue(newItem(pKey, "key1", "key2", "key3"), newItem(pString, "value")), p
}, "") }, "", `{"key1": {"key2": {"key3": "value"}}}`)
// TODO an actual test assertion
} }
func TestAST_StoreValueWithMultipartKey_UnderSubtable_CreatesTableHierarchy(t *testing.T) { func TestAST_StoreValueWithMultipartKey_UnderSubtable_CreatesTableHierarchy(t *testing.T) {
testError(t, func() error { testAST(t, func() (error, *parser) {
p := newParser() p := newParser()
p.newTable(newItem(pKey, "subkey1", "subkey2")) p.openTable(newItem(pKey, "tablekey1", "tablekey2"))
err := p.setValue(newItem(pKey, "key1", "key2", "key3"), newItem(pString, "value")) return p.setValue(newItem(pKey, "valuekey1", "valuekey2", "valuekey3"), newItem(pString, "value")), p
fmt.Printf("%s", p.Root) }, "", `{"tablekey1": {"tablekey2": {"valuekey1": {"valuekey2": {"valuekey3": "value"}}}}}`)
return err
}, "")
t.Fail()
// TODO an actual test assertion
} }
func TestAST_StoreDuplicateKeyInRootTable_ReturnsError(t *testing.T) { func TestAST_StoreDuplicateKeyInRootTable_ReturnsError(t *testing.T) {
testError(t, func() error { testAST(t, func() (error, *parser) {
p := newParser() p := newParser()
p.setValue(newItem(pKey, "key"), newItem(pString, "value")) p.setValue(newItem(pKey, "key"), newItem(pString, "value"))
return p.setValue(newItem(pKey, "key"), newItem(pInteger, 321)) return p.setValue(newItem(pKey, "key"), newItem(pInteger, 321)), p
}, `Cannot store value: string item already exists at key "key"`) }, `Cannot store value: string item already exists at key "key"`, "")
} }
func TestAST_GivenExistingTableAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) { func TestAST_GivenExistingTableAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) {
testError(t, func() error { testAST(t, func() (error, *parser) {
p := newParser() p := newParser()
p.newTable(newItem(pKey, "key1", "key2")) p.openTable(newItem(pKey, "key1", "key2"))
_, err := p.newTable(newItem(pKey, "key1", "key2")) _, err := p.openTable(newItem(pKey, "key1", "key2"))
return err return err, p
}, `Cannot create table: table for key "key1"."key2" already exists`) }, `Cannot create table: table for key "key1"."key2" already exists`, "")
} }
func TestAST_GivenExistingItemAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) { func TestAST_GivenExistingItemAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) {
testError(t, func() error { testAST(t, func() (error, *parser) {
p := newParser() p := newParser()
p.Root["key"] = newItem(pString, "value") p.Root["key"] = newItem(pString, "value")
_, err := p.newTable(newItem(pKey, "key")) _, err := p.openTable(newItem(pKey, "key"))
return err return err, p
}, `Cannot create table: string item already exists at key "key"`) }, `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) err = parse.New(handler)(test.input)
} }
func testError(t *testing.T, code func() error, expected string) { func testAST(t *testing.T, code func() (error, *parser), expectedError string, expectedData string) {
err := code() err, p := code()
if expected == "" { if expectedError == "" {
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %s", err) t.Fatalf("Unexpected error: %s", err)
} }
} else { } else {
if err == nil { if err == nil {
t.Fatalf("An error was expected, but no error was returned") t.Fatalf("An error was expected, but no error was returned")
} else if err.Error() != expected { } else if err.Error() != expectedError {
t.Fatalf("Unexpected error:\nexpected: %s\nactual: %s\n", expected, err.Error()) 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] key := t.Items[0]
t.Items = t.Items[1:] t.Items = t.Items[1:]
t.newTable(key) t.openTable(key)
} }