1 package toml
2
3 import (
4 "bytes"
5 "errors"
6 "fmt"
7 "reflect"
8 "strings"
9 "testing"
10 "time"
11 )
12
13 type failingWriter struct {
14 failAt int
15 written int
16 buffer bytes.Buffer
17 }
18
19 func (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
36 func 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
43 func 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
67 func TestTreeWriteToTomlString(t *testing.T) {
68 toml, err := Load(`name = { first = "Tom", last = "Preston-Werner" }
69 points = { 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
90 func 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
106 func 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
140 func 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
146 func 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
157 func 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
163 func 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
169 func 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
175 func 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
182 func TestTreeWriteToFailingWriterInTable(t *testing.T) {
183 toml, _ := Load(`
184 [b]
185 a = 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
195 func TestTreeWriteToFailingWriterInArray(t *testing.T) {
196 toml, _ := Load(`
197 [[b]]
198 a = 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
208 func 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 "score": 4e-08,
240 },
241 }
242 testMaps(t, tree.ToMap(), expected)
243 }
244
245 func TestTreeWriteToMapWithTablesInMultipleChunks(t *testing.T) {
246 tree, _ := Load(`
247 [[menu.main]]
248 a = "menu 1"
249 b = "menu 2"
250 [[menu.main]]
251 c = "menu 3"
252 d = "menu 4"`)
253 expected := map[string]interface{}{
254 "menu": map[string]interface{}{
255 "main": []interface{}{
256 map[string]interface{}{"a": "menu 1", "b": "menu 2"},
257 map[string]interface{}{"c": "menu 3", "d": "menu 4"},
258 },
259 },
260 }
261 treeMap := tree.ToMap()
262
263 testMaps(t, treeMap, expected)
264 }
265
266 func TestTreeWriteToMapWithArrayOfInlineTables(t *testing.T) {
267 tree, _ := Load(`
268 [params]
269 language_tabs = [
270 { key = "shell", name = "Shell" },
271 { key = "ruby", name = "Ruby" },
272 { key = "python", name = "Python" }
273 ]`)
274
275 expected := map[string]interface{}{
276 "params": map[string]interface{}{
277 "language_tabs": []interface{}{
278 map[string]interface{}{
279 "key": "shell",
280 "name": "Shell",
281 },
282 map[string]interface{}{
283 "key": "ruby",
284 "name": "Ruby",
285 },
286 map[string]interface{}{
287 "key": "python",
288 "name": "Python",
289 },
290 },
291 },
292 }
293
294 treeMap := tree.ToMap()
295 testMaps(t, treeMap, expected)
296 }
297
298 func TestTreeWriteToMapWithTableInMixedArray(t *testing.T) {
299 tree, _ := Load(`a = [
300 "foo",
301 [
302 "bar",
303 {baz = "quux"},
304 ],
305 [
306 {a = "b"},
307 {c = "d"},
308 ],
309 ]`)
310 expected := map[string]interface{}{
311 "a": []interface{}{
312 "foo",
313 []interface{}{
314 "bar",
315 map[string]interface{}{
316 "baz": "quux",
317 },
318 },
319 []interface{}{
320 map[string]interface{}{
321 "a": "b",
322 },
323 map[string]interface{}{
324 "c": "d",
325 },
326 },
327 },
328 }
329 treeMap := tree.ToMap()
330
331 testMaps(t, treeMap, expected)
332 }
333
334 func TestTreeWriteToFloat(t *testing.T) {
335 tree, err := Load(`a = 3.0`)
336 if err != nil {
337 t.Fatal(err)
338 }
339 str, err := tree.ToTomlString()
340 if err != nil {
341 t.Fatal(err)
342 }
343 expected := `a = 3.0`
344 if strings.TrimSpace(str) != strings.TrimSpace(expected) {
345 t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str)
346 }
347 }
348
349 func TestTreeWriteToSpecialFloat(t *testing.T) {
350 expected := `a = +inf
351 b = -inf
352 c = nan`
353
354 tree, err := Load(expected)
355 if err != nil {
356 t.Fatal(err)
357 }
358 str, err := tree.ToTomlString()
359 if err != nil {
360 t.Fatal(err)
361 }
362 if strings.TrimSpace(str) != strings.TrimSpace(expected) {
363 t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str)
364 }
365 }
366
367 func TestOrderedEmptyTrees(t *testing.T) {
368 type val struct {
369 Key string `toml:"key"`
370 }
371 type structure struct {
372 First val `toml:"first"`
373 Empty []val `toml:"empty"`
374 }
375 input := structure{First: val{Key: "value"}}
376 buf := new(bytes.Buffer)
377 err := NewEncoder(buf).Order(OrderPreserve).Encode(input)
378 if err != nil {
379 t.Fatal("failed to encode input")
380 }
381 expected := `
382 [first]
383 key = "value"
384 `
385 if expected != buf.String() {
386 t.Fatal("expected and encoded body aren't equal: ", expected, buf.String())
387 }
388 }
389
390 func TestOrderedNonIncreasedLine(t *testing.T) {
391 type NiceMap map[string]string
392 type Manifest struct {
393 NiceMap `toml:"dependencies"`
394 Build struct {
395 BuildCommand string `toml:"build-command"`
396 } `toml:"build"`
397 }
398
399 test := &Manifest{}
400 test.Build.BuildCommand = "test"
401 buf := new(bytes.Buffer)
402 if err := NewEncoder(buf).Order(OrderPreserve).Encode(test); err != nil {
403 panic(err)
404 }
405 expected := `
406 [dependencies]
407
408 [build]
409 build-command = "test"
410 `
411 if expected != buf.String() {
412 t.Fatal("expected and encoded body aren't equal: ", expected, buf.String())
413 }
414 }
415
416 func TestIssue290(t *testing.T) {
417 tomlString :=
418 `[table]
419 "127.0.0.1" = "value"
420 "127.0.0.1:8028" = "value"
421 "character encoding" = "value"
422 "ʎǝʞ" = "value"`
423
424 t1, err := Load(tomlString)
425 if err != nil {
426 t.Fatal("load err:", err)
427 }
428
429 s, err := t1.ToTomlString()
430 if err != nil {
431 t.Fatal("ToTomlString err:", err)
432 }
433
434 _, err = Load(s)
435 if err != nil {
436 t.Fatal("reload err:", err)
437 }
438 }
439
440 func BenchmarkTreeToTomlString(b *testing.B) {
441 toml, err := Load(sampleHard)
442 if err != nil {
443 b.Fatal("Unexpected error:", err)
444 }
445
446 for i := 0; i < b.N; i++ {
447 _, err := toml.ToTomlString()
448 if err != nil {
449 b.Fatal(err)
450 }
451 }
452 }
453
454 var sampleHard = `# Test file for TOML
455 # Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
456 # This part you'll really hate
457
458 [the]
459 test_string = "You'll hate me after this - #" # " Annoying, isn't it?
460
461 [the.hard]
462 test_array = [ "] ", " # "] # ] There you go, parse this!
463 test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
464 # You didn't think it'd as easy as chucking out the last #, did you?
465 another_test_string = " Same thing, but with a string #"
466 harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too"
467 # Things will get harder
468
469 [the.hard."bit#"]
470 "what?" = "You don't think some user won't do that?"
471 multi_line_array = [
472 "]",
473 # ] Oh yes I did
474 ]
475
476 # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test
477
478 #[error] if you didn't catch this, your parser is broken
479 #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
480 #array = [
481 # "This might most likely happen in multiline arrays",
482 # Like here,
483 # "or here,
484 # and here"
485 # ] End of array comment, forgot the #
486 #number = 3.14 pi <--again forgot the # `
487
View as plain text