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.WriteString(string(p))
28		return count, nil
29	}
30
31	f.buffer.WriteString(string(p[:toWrite]))
32	f.written = f.failAt
33	return f.written, fmt.Errorf("failingWriter failed after writting %d bytes", f.written)
34}
35
36func assertErrorString(t *testing.T, expected string, err error) {
37	expectedErr := errors.New(expected)
38	if 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{int8(1), 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{[]interface{}{int8(1)}, 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 writting 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 writting 2 bytes", err)
189
190	writer = failingWriter{failAt: 13, written: 0}
191	_, err = toml.WriteTo(writer)
192	assertErrorString(t, "failingWriter failed after writting 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 writting 2 bytes", err)
202
203	writer = failingWriter{failAt: 15, written: 0}
204	_, err = toml.WriteTo(writer)
205	assertErrorString(t, "failingWriter failed after writting 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