1package toml 2 3import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "reflect" 8 "strings" 9 "testing" 10 "time" 11) 12 13type failingWriter struct { 14 failAt int 15 written int 16 buffer bytes.Buffer 17} 18 19func (f *failingWriter) Write(p []byte) (n int, err error) { 20 count := len(p) 21 toWrite := f.failAt - (count + f.written) 22 if toWrite < 0 { 23 toWrite = 0 24 } 25 if toWrite > count { 26 f.written += count 27 f.buffer.Write(p) 28 return count, nil 29 } 30 31 f.buffer.Write(p[:toWrite]) 32 f.written = f.failAt 33 return toWrite, fmt.Errorf("failingWriter failed after writing %d bytes", f.written) 34} 35 36func assertErrorString(t *testing.T, expected string, err error) { 37 expectedErr := errors.New(expected) 38 if err == nil || err.Error() != expectedErr.Error() { 39 t.Errorf("expecting error %s, but got %s instead", expected, err) 40 } 41} 42 43func TestTreeWriteToEmptyTable(t *testing.T) { 44 doc := `[[empty-tables]] 45[[empty-tables]]` 46 47 toml, err := Load(doc) 48 if err != nil { 49 t.Fatal("Unexpected Load error:", err) 50 } 51 tomlString, err := toml.ToTomlString() 52 if err != nil { 53 t.Fatal("Unexpected ToTomlString error:", err) 54 } 55 56 expected := ` 57[[empty-tables]] 58 59[[empty-tables]] 60` 61 62 if tomlString != expected { 63 t.Fatalf("Expected:\n%s\nGot:\n%s", expected, tomlString) 64 } 65} 66 67func TestTreeWriteToTomlString(t *testing.T) { 68 toml, err := Load(`name = { first = "Tom", last = "Preston-Werner" } 69points = { x = 1, y = 2 }`) 70 71 if err != nil { 72 t.Fatal("Unexpected error:", err) 73 } 74 75 tomlString, _ := toml.ToTomlString() 76 reparsedTree, err := Load(tomlString) 77 78 assertTree(t, reparsedTree, err, map[string]interface{}{ 79 "name": map[string]interface{}{ 80 "first": "Tom", 81 "last": "Preston-Werner", 82 }, 83 "points": map[string]interface{}{ 84 "x": int64(1), 85 "y": int64(2), 86 }, 87 }) 88} 89 90func TestTreeWriteToTomlStringSimple(t *testing.T) { 91 tree, err := Load("[foo]\n\n[[foo.bar]]\na = 42\n\n[[foo.bar]]\na = 69\n") 92 if err != nil { 93 t.Errorf("Test failed to parse: %v", err) 94 return 95 } 96 result, err := tree.ToTomlString() 97 if err != nil { 98 t.Errorf("Unexpected error: %s", err) 99 } 100 expected := "\n[foo]\n\n [[foo.bar]]\n a = 42\n\n [[foo.bar]]\n a = 69\n" 101 if result != expected { 102 t.Errorf("Expected got '%s', expected '%s'", result, expected) 103 } 104} 105 106func TestTreeWriteToTomlStringKeysOrders(t *testing.T) { 107 for i := 0; i < 100; i++ { 108 tree, _ := Load(` 109 foobar = true 110 bar = "baz" 111 foo = 1 112 [qux] 113 foo = 1 114 bar = "baz2"`) 115 116 stringRepr, _ := tree.ToTomlString() 117 118 t.Log("Intermediate string representation:") 119 t.Log(stringRepr) 120 121 r := strings.NewReader(stringRepr) 122 toml, err := LoadReader(r) 123 124 if err != nil { 125 t.Fatal("Unexpected error:", err) 126 } 127 128 assertTree(t, toml, err, map[string]interface{}{ 129 "foobar": true, 130 "bar": "baz", 131 "foo": 1, 132 "qux": map[string]interface{}{ 133 "foo": 1, 134 "bar": "baz2", 135 }, 136 }) 137 } 138} 139 140func testMaps(t *testing.T, actual, expected map[string]interface{}) { 141 if !reflect.DeepEqual(actual, expected) { 142 t.Fatal("trees aren't equal.\n", "Expected:\n", expected, "\nActual:\n", actual) 143 } 144} 145 146func TestTreeWriteToMapSimple(t *testing.T) { 147 tree, _ := Load("a = 42\nb = 17") 148 149 expected := map[string]interface{}{ 150 "a": int64(42), 151 "b": int64(17), 152 } 153 154 testMaps(t, tree.ToMap(), expected) 155} 156 157func TestTreeWriteToInvalidTreeSimpleValue(t *testing.T) { 158 tree := Tree{values: map[string]interface{}{"foo": int8(1)}} 159 _, err := tree.ToTomlString() 160 assertErrorString(t, "invalid value type at foo: int8", err) 161} 162 163func TestTreeWriteToInvalidTreeTomlValue(t *testing.T) { 164 tree := Tree{values: map[string]interface{}{"foo": &tomlValue{value: int8(1), comment: "", position: Position{}}}} 165 _, err := tree.ToTomlString() 166 assertErrorString(t, "unsupported value type int8: 1", err) 167} 168 169func TestTreeWriteToInvalidTreeTomlValueArray(t *testing.T) { 170 tree := Tree{values: map[string]interface{}{"foo": &tomlValue{value: int8(1), comment: "", position: Position{}}}} 171 _, err := tree.ToTomlString() 172 assertErrorString(t, "unsupported value type int8: 1", err) 173} 174 175func TestTreeWriteToFailingWriterInSimpleValue(t *testing.T) { 176 toml, _ := Load(`a = 2`) 177 writer := failingWriter{failAt: 0, written: 0} 178 _, err := toml.WriteTo(&writer) 179 assertErrorString(t, "failingWriter failed after writing 0 bytes", err) 180} 181 182func TestTreeWriteToFailingWriterInTable(t *testing.T) { 183 toml, _ := Load(` 184[b] 185a = 2`) 186 writer := failingWriter{failAt: 2, written: 0} 187 _, err := toml.WriteTo(&writer) 188 assertErrorString(t, "failingWriter failed after writing 2 bytes", err) 189 190 writer = failingWriter{failAt: 13, written: 0} 191 _, err = toml.WriteTo(&writer) 192 assertErrorString(t, "failingWriter failed after writing 13 bytes", err) 193} 194 195func TestTreeWriteToFailingWriterInArray(t *testing.T) { 196 toml, _ := Load(` 197[[b]] 198a = 2`) 199 writer := failingWriter{failAt: 2, written: 0} 200 _, err := toml.WriteTo(&writer) 201 assertErrorString(t, "failingWriter failed after writing 2 bytes", err) 202 203 writer = failingWriter{failAt: 15, written: 0} 204 _, err = toml.WriteTo(&writer) 205 assertErrorString(t, "failingWriter failed after writing 15 bytes", err) 206} 207 208func TestTreeWriteToMapExampleFile(t *testing.T) { 209 tree, _ := LoadFile("example.toml") 210 expected := map[string]interface{}{ 211 "title": "TOML Example", 212 "owner": map[string]interface{}{ 213 "name": "Tom Preston-Werner", 214 "organization": "GitHub", 215 "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", 216 "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), 217 }, 218 "database": map[string]interface{}{ 219 "server": "192.168.1.1", 220 "ports": []interface{}{int64(8001), int64(8001), int64(8002)}, 221 "connection_max": int64(5000), 222 "enabled": true, 223 }, 224 "servers": map[string]interface{}{ 225 "alpha": map[string]interface{}{ 226 "ip": "10.0.0.1", 227 "dc": "eqdc10", 228 }, 229 "beta": map[string]interface{}{ 230 "ip": "10.0.0.2", 231 "dc": "eqdc10", 232 }, 233 }, 234 "clients": map[string]interface{}{ 235 "data": []interface{}{ 236 []interface{}{"gamma", "delta"}, 237 []interface{}{int64(1), int64(2)}, 238 }, 239 }, 240 } 241 testMaps(t, tree.ToMap(), expected) 242} 243 244func TestTreeWriteToMapWithTablesInMultipleChunks(t *testing.T) { 245 tree, _ := Load(` 246 [[menu.main]] 247 a = "menu 1" 248 b = "menu 2" 249 [[menu.main]] 250 c = "menu 3" 251 d = "menu 4"`) 252 expected := map[string]interface{}{ 253 "menu": map[string]interface{}{ 254 "main": []interface{}{ 255 map[string]interface{}{"a": "menu 1", "b": "menu 2"}, 256 map[string]interface{}{"c": "menu 3", "d": "menu 4"}, 257 }, 258 }, 259 } 260 treeMap := tree.ToMap() 261 262 testMaps(t, treeMap, expected) 263} 264 265func TestTreeWriteToMapWithArrayOfInlineTables(t *testing.T) { 266 tree, _ := Load(` 267 [params] 268 language_tabs = [ 269 { key = "shell", name = "Shell" }, 270 { key = "ruby", name = "Ruby" }, 271 { key = "python", name = "Python" } 272 ]`) 273 274 expected := map[string]interface{}{ 275 "params": map[string]interface{}{ 276 "language_tabs": []interface{}{ 277 map[string]interface{}{ 278 "key": "shell", 279 "name": "Shell", 280 }, 281 map[string]interface{}{ 282 "key": "ruby", 283 "name": "Ruby", 284 }, 285 map[string]interface{}{ 286 "key": "python", 287 "name": "Python", 288 }, 289 }, 290 }, 291 } 292 293 treeMap := tree.ToMap() 294 testMaps(t, treeMap, expected) 295} 296 297func TestTreeWriteToFloat(t *testing.T) { 298 tree, err := Load(`a = 3.0`) 299 if err != nil { 300 t.Fatal(err) 301 } 302 str, err := tree.ToTomlString() 303 if err != nil { 304 t.Fatal(err) 305 } 306 expected := `a = 3.0` 307 if strings.TrimSpace(str) != strings.TrimSpace(expected) { 308 t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str) 309 } 310} 311 312func TestTreeWriteToSpecialFloat(t *testing.T) { 313 expected := `a = +inf 314b = -inf 315c = nan` 316 317 tree, err := Load(expected) 318 if err != nil { 319 t.Fatal(err) 320 } 321 str, err := tree.ToTomlString() 322 if err != nil { 323 t.Fatal(err) 324 } 325 if strings.TrimSpace(str) != strings.TrimSpace(expected) { 326 t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str) 327 } 328} 329 330func BenchmarkTreeToTomlString(b *testing.B) { 331 toml, err := Load(sampleHard) 332 if err != nil { 333 b.Fatal("Unexpected error:", err) 334 } 335 336 for i := 0; i < b.N; i++ { 337 _, err := toml.ToTomlString() 338 if err != nil { 339 b.Fatal(err) 340 } 341 } 342} 343 344var sampleHard = `# Test file for TOML 345# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate 346# This part you'll really hate 347 348[the] 349test_string = "You'll hate me after this - #" # " Annoying, isn't it? 350 351 [the.hard] 352 test_array = [ "] ", " # "] # ] There you go, parse this! 353 test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] 354 # You didn't think it'd as easy as chucking out the last #, did you? 355 another_test_string = " Same thing, but with a string #" 356 harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" 357 # Things will get harder 358 359 [the.hard."bit#"] 360 "what?" = "You don't think some user won't do that?" 361 multi_line_array = [ 362 "]", 363 # ] Oh yes I did 364 ] 365 366# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test 367 368#[error] if you didn't catch this, your parser is broken 369#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 370#array = [ 371# "This might most likely happen in multiline arrays", 372# Like here, 373# "or here, 374# and here" 375# ] End of array comment, forgot the # 376#number = 3.14 pi <--again forgot the # ` 377