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 TestTreeWriteToFloat(t *testing.T) {
299	tree, err := Load(`a = 3.0`)
300	if err != nil {
301		t.Fatal(err)
302	}
303	str, err := tree.ToTomlString()
304	if err != nil {
305		t.Fatal(err)
306	}
307	expected := `a = 3.0`
308	if strings.TrimSpace(str) != strings.TrimSpace(expected) {
309		t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str)
310	}
311}
312
313func TestTreeWriteToSpecialFloat(t *testing.T) {
314	expected := `a = +inf
315b = -inf
316c = nan`
317
318	tree, err := Load(expected)
319	if err != nil {
320		t.Fatal(err)
321	}
322	str, err := tree.ToTomlString()
323	if err != nil {
324		t.Fatal(err)
325	}
326	if strings.TrimSpace(str) != strings.TrimSpace(expected) {
327		t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str)
328	}
329}
330
331func TestIssue290(t *testing.T) {
332	tomlString :=
333		`[table]
334"127.0.0.1" = "value"
335"127.0.0.1:8028" = "value"
336"character encoding" = "value"
337"ʎǝʞ" = "value"`
338
339	t1, err := Load(tomlString)
340	if err != nil {
341		t.Fatal("load err:", err)
342	}
343
344	s, err := t1.ToTomlString()
345	if err != nil {
346		t.Fatal("ToTomlString err:", err)
347	}
348
349	_, err = Load(s)
350	if err != nil {
351		t.Fatal("reload err:", err)
352	}
353}
354
355func BenchmarkTreeToTomlString(b *testing.B) {
356	toml, err := Load(sampleHard)
357	if err != nil {
358		b.Fatal("Unexpected error:", err)
359	}
360
361	for i := 0; i < b.N; i++ {
362		_, err := toml.ToTomlString()
363		if err != nil {
364			b.Fatal(err)
365		}
366	}
367}
368
369var sampleHard = `# Test file for TOML
370# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
371# This part you'll really hate
372
373[the]
374test_string = "You'll hate me after this - #"          # " Annoying, isn't it?
375
376    [the.hard]
377    test_array = [ "] ", " # "]      # ] There you go, parse this!
378    test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
379    # You didn't think it'd as easy as chucking out the last #, did you?
380    another_test_string = " Same thing, but with a string #"
381    harder_test_string = " And when \"'s are in the string, along with # \""   # "and comments are there too"
382    # Things will get harder
383
384        [the.hard."bit#"]
385        "what?" = "You don't think some user won't do that?"
386        multi_line_array = [
387            "]",
388            # ] Oh yes I did
389            ]
390
391# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test
392
393#[error]   if you didn't catch this, your parser is broken
394#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
395#array = [
396#         "This might most likely happen in multiline arrays",
397#         Like here,
398#         "or here,
399#         and here"
400#         ]     End of array comment, forgot the #
401#number = 3.14  pi <--again forgot the #         `
402