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			"score": 4e-08,
240		},
241	}
242	testMaps(t, tree.ToMap(), expected)
243}
244
245func 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
266func 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
298func 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
334func 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
349func TestTreeWriteToSpecialFloat(t *testing.T) {
350	expected := `a = +inf
351b = -inf
352c = 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
367func 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
390func 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
416func 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
440func 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
454var 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]
459test_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