package toml import ( "bytes" "errors" "fmt" "reflect" "strings" "testing" "time" ) type failingWriter struct { failAt int written int buffer bytes.Buffer } func (f *failingWriter) Write(p []byte) (n int, err error) { count := len(p) toWrite := f.failAt - (count + f.written) if toWrite < 0 { toWrite = 0 } if toWrite > count { f.written += count f.buffer.Write(p) return count, nil } f.buffer.Write(p[:toWrite]) f.written = f.failAt return toWrite, fmt.Errorf("failingWriter failed after writing %d bytes", f.written) } func assertErrorString(t *testing.T, expected string, err error) { expectedErr := errors.New(expected) if err == nil || err.Error() != expectedErr.Error() { t.Errorf("expecting error %s, but got %s instead", expected, err) } } func TestTreeWriteToEmptyTable(t *testing.T) { doc := `[[empty-tables]] [[empty-tables]]` toml, err := Load(doc) if err != nil { t.Fatal("Unexpected Load error:", err) } tomlString, err := toml.ToTomlString() if err != nil { t.Fatal("Unexpected ToTomlString error:", err) } expected := ` [[empty-tables]] [[empty-tables]] ` if tomlString != expected { t.Fatalf("Expected:\n%s\nGot:\n%s", expected, tomlString) } } func TestTreeWriteToTomlString(t *testing.T) { toml, err := Load(`name = { first = "Tom", last = "Preston-Werner" } points = { x = 1, y = 2 }`) if err != nil { t.Fatal("Unexpected error:", err) } tomlString, _ := toml.ToTomlString() reparsedTree, err := Load(tomlString) assertTree(t, reparsedTree, err, map[string]interface{}{ "name": map[string]interface{}{ "first": "Tom", "last": "Preston-Werner", }, "points": map[string]interface{}{ "x": int64(1), "y": int64(2), }, }) } func TestTreeWriteToTomlStringSimple(t *testing.T) { tree, err := Load("[foo]\n\n[[foo.bar]]\na = 42\n\n[[foo.bar]]\na = 69\n") if err != nil { t.Errorf("Test failed to parse: %v", err) return } result, err := tree.ToTomlString() if err != nil { t.Errorf("Unexpected error: %s", err) } expected := "\n[foo]\n\n [[foo.bar]]\n a = 42\n\n [[foo.bar]]\n a = 69\n" if result != expected { t.Errorf("Expected got '%s', expected '%s'", result, expected) } } func TestTreeWriteToTomlStringKeysOrders(t *testing.T) { for i := 0; i < 100; i++ { tree, _ := Load(` foobar = true bar = "baz" foo = 1 [qux] foo = 1 bar = "baz2"`) stringRepr, _ := tree.ToTomlString() t.Log("Intermediate string representation:") t.Log(stringRepr) r := strings.NewReader(stringRepr) toml, err := LoadReader(r) if err != nil { t.Fatal("Unexpected error:", err) } assertTree(t, toml, err, map[string]interface{}{ "foobar": true, "bar": "baz", "foo": 1, "qux": map[string]interface{}{ "foo": 1, "bar": "baz2", }, }) } } func testMaps(t *testing.T, actual, expected map[string]interface{}) { if !reflect.DeepEqual(actual, expected) { t.Fatal("trees aren't equal.\n", "Expected:\n", expected, "\nActual:\n", actual) } } func TestTreeWriteToMapSimple(t *testing.T) { tree, _ := Load("a = 42\nb = 17") expected := map[string]interface{}{ "a": int64(42), "b": int64(17), } testMaps(t, tree.ToMap(), expected) } func TestTreeWriteToInvalidTreeSimpleValue(t *testing.T) { tree := Tree{values: map[string]interface{}{"foo": int8(1)}} _, err := tree.ToTomlString() assertErrorString(t, "invalid value type at foo: int8", err) } func TestTreeWriteToInvalidTreeTomlValue(t *testing.T) { tree := Tree{values: map[string]interface{}{"foo": &tomlValue{value: int8(1), comment: "", position: Position{}}}} _, err := tree.ToTomlString() assertErrorString(t, "unsupported value type int8: 1", err) } func TestTreeWriteToInvalidTreeTomlValueArray(t *testing.T) { tree := Tree{values: map[string]interface{}{"foo": &tomlValue{value: int8(1), comment: "", position: Position{}}}} _, err := tree.ToTomlString() assertErrorString(t, "unsupported value type int8: 1", err) } func TestTreeWriteToFailingWriterInSimpleValue(t *testing.T) { toml, _ := Load(`a = 2`) writer := failingWriter{failAt: 0, written: 0} _, err := toml.WriteTo(&writer) assertErrorString(t, "failingWriter failed after writing 0 bytes", err) } func TestTreeWriteToFailingWriterInTable(t *testing.T) { toml, _ := Load(` [b] a = 2`) writer := failingWriter{failAt: 2, written: 0} _, err := toml.WriteTo(&writer) assertErrorString(t, "failingWriter failed after writing 2 bytes", err) writer = failingWriter{failAt: 13, written: 0} _, err = toml.WriteTo(&writer) assertErrorString(t, "failingWriter failed after writing 13 bytes", err) } func TestTreeWriteToFailingWriterInArray(t *testing.T) { toml, _ := Load(` [[b]] a = 2`) writer := failingWriter{failAt: 2, written: 0} _, err := toml.WriteTo(&writer) assertErrorString(t, "failingWriter failed after writing 2 bytes", err) writer = failingWriter{failAt: 15, written: 0} _, err = toml.WriteTo(&writer) assertErrorString(t, "failingWriter failed after writing 15 bytes", err) } func TestTreeWriteToMapExampleFile(t *testing.T) { tree, _ := LoadFile("example.toml") expected := map[string]interface{}{ "title": "TOML Example", "owner": map[string]interface{}{ "name": "Tom Preston-Werner", "organization": "GitHub", "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), }, "database": map[string]interface{}{ "server": "192.168.1.1", "ports": []interface{}{int64(8001), int64(8001), int64(8002)}, "connection_max": int64(5000), "enabled": true, }, "servers": map[string]interface{}{ "alpha": map[string]interface{}{ "ip": "10.0.0.1", "dc": "eqdc10", }, "beta": map[string]interface{}{ "ip": "10.0.0.2", "dc": "eqdc10", }, }, "clients": map[string]interface{}{ "data": []interface{}{ []interface{}{"gamma", "delta"}, []interface{}{int64(1), int64(2)}, }, }, } testMaps(t, tree.ToMap(), expected) } func TestTreeWriteToMapWithTablesInMultipleChunks(t *testing.T) { tree, _ := Load(` [[menu.main]] a = "menu 1" b = "menu 2" [[menu.main]] c = "menu 3" d = "menu 4"`) expected := map[string]interface{}{ "menu": map[string]interface{}{ "main": []interface{}{ map[string]interface{}{"a": "menu 1", "b": "menu 2"}, map[string]interface{}{"c": "menu 3", "d": "menu 4"}, }, }, } treeMap := tree.ToMap() testMaps(t, treeMap, expected) } func TestTreeWriteToMapWithArrayOfInlineTables(t *testing.T) { tree, _ := Load(` [params] language_tabs = [ { key = "shell", name = "Shell" }, { key = "ruby", name = "Ruby" }, { key = "python", name = "Python" } ]`) expected := map[string]interface{}{ "params": map[string]interface{}{ "language_tabs": []interface{}{ map[string]interface{}{ "key": "shell", "name": "Shell", }, map[string]interface{}{ "key": "ruby", "name": "Ruby", }, map[string]interface{}{ "key": "python", "name": "Python", }, }, }, } treeMap := tree.ToMap() testMaps(t, treeMap, expected) } func TestTreeWriteToFloat(t *testing.T) { tree, err := Load(`a = 3.0`) if err != nil { t.Fatal(err) } str, err := tree.ToTomlString() if err != nil { t.Fatal(err) } expected := `a = 3.0` if strings.TrimSpace(str) != strings.TrimSpace(expected) { t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str) } } func BenchmarkTreeToTomlString(b *testing.B) { toml, err := Load(sampleHard) if err != nil { b.Fatal("Unexpected error:", err) } for i := 0; i < b.N; i++ { _, err := toml.ToTomlString() if err != nil { b.Fatal(err) } } } var sampleHard = `# Test file for TOML # Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate # This part you'll really hate [the] test_string = "You'll hate me after this - #" # " Annoying, isn't it? [the.hard] test_array = [ "] ", " # "] # ] There you go, parse this! test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] # You didn't think it'd as easy as chucking out the last #, did you? another_test_string = " Same thing, but with a string #" harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" # Things will get harder [the.hard."bit#"] "what?" = "You don't think some user won't do that?" multi_line_array = [ "]", # ] Oh yes I did ] # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test #[error] if you didn't catch this, your parser is broken #string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this #array = [ # "This might most likely happen in multiline arrays", # Like here, # "or here, # and here" # ] End of array comment, forgot the # #number = 3.14 pi <--again forgot the # `