1
2
3
4 package toml_test
5
6 import (
7 "bytes"
8 "encoding/json"
9 "fmt"
10 "os"
11 "path/filepath"
12 "regexp"
13 "strings"
14 "testing"
15
16 "github.com/BurntSushi/toml"
17 "github.com/BurntSushi/toml/internal/tag"
18 tomltest "github.com/BurntSushi/toml/internal/toml-test"
19 )
20
21
22
23
24
25 var errorTests = map[string][]string{
26 "encoding/bad-utf8*": {"invalid UTF-8 byte"},
27 "encoding/utf16*": {"files cannot contain NULL bytes; probably using UTF-16"},
28 "string/multiline-escape-space": {`invalid escape: '\ '`},
29 }
30
31
32 var metaTests = map[string]string{
33 "implicit-and-explicit-after": `
34 a.b.c: Hash
35 a.b.c.answer: Integer
36 a: Hash
37 a.better: Integer
38 `,
39 "implicit-and-explicit-before": `
40 a: Hash
41 a.better: Integer
42 a.b.c: Hash
43 a.b.c.answer: Integer
44 `,
45 "key/case-sensitive": `
46 sectioN: String
47 section: Hash
48 section.name: String
49 section.NAME: String
50 section.Name: String
51 Section: Hash
52 Section.name: String
53 Section."μ": String
54 Section."Μ": String
55 Section.M: String
56 `,
57 "key/dotted": `
58 name.first: String
59 name.last: String
60 many.dots.here.dot.dot.dot: Integer
61 count.a: Integer
62 count.b: Integer
63 count.c: Integer
64 count.d: Integer
65 count.e: Integer
66 count.f: Integer
67 count.g: Integer
68 count.h: Integer
69 count.i: Integer
70 count.j: Integer
71 count.k: Integer
72 count.l: Integer
73 tbl: Hash
74 tbl.a.b.c: Float
75 a.few.dots: Hash
76 a.few.dots.polka.dot: String
77 a.few.dots.polka.dance-with: String
78 arr: ArrayHash
79 arr.a.b.c: Integer
80 arr.a.b.d: Integer
81 arr: ArrayHash
82 arr.a.b.c: Integer
83 arr.a.b.d: Integer
84 `,
85 "key/empty": `
86 "": String
87 `,
88 "key/quoted-dots": `
89 plain: Integer
90 "with.dot": Integer
91 plain_table: Hash
92 plain_table.plain: Integer
93 plain_table."with.dot": Integer
94 table.withdot: Hash
95 table.withdot.plain: Integer
96 table.withdot."key.with.dots": Integer
97 `,
98 "key/space": `
99 "a b": Integer
100 " c d ": Integer
101 " tbl ": Hash
102 " tbl "."\ttab\ttab\t": String
103 `,
104 "key/special-chars": "\n" +
105 "\"=~!@$^&*()_+-`1234567890[]|/?><.,;:'=\": Integer\n",
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 "table/array-implicit": `
128 albums.songs: ArrayHash
129 albums.songs.name: String
130 `,
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152 "table/array-many": `
153 people: ArrayHash
154 people.first_name: String
155 people.last_name: String
156 people: ArrayHash
157 people.first_name: String
158 people.last_name: String
159 people: ArrayHash
160 people.first_name: String
161 people.last_name: String
162 `,
163 "table/array-nest": `
164 albums: ArrayHash
165 albums.name: String
166 albums.songs: ArrayHash
167 albums.songs.name: String
168 albums.songs: ArrayHash
169 albums.songs.name: String
170 albums: ArrayHash
171 albums.name: String
172 albums.songs: ArrayHash
173 albums.songs.name: String
174 albums.songs: ArrayHash
175 albums.songs.name: String
176 `,
177 "table/array-one": `
178 people: ArrayHash
179 people.first_name: String
180 people.last_name: String
181 `,
182 "table/array-table-array": `
183 a: ArrayHash
184 a.b: ArrayHash
185 a.b.c: Hash
186 a.b.c.d: String
187 a.b: ArrayHash
188 a.b.c: Hash
189 a.b.c.d: String
190 `,
191 "table/empty": `
192 a: Hash
193 `,
194 "table/keyword": `
195 true: Hash
196 false: Hash
197 inf: Hash
198 nan: Hash
199 `,
200 "table/names": `
201 a.b.c: Hash
202 a."b.c": Hash
203 a."d.e": Hash
204 a." x ": Hash
205 d.e.f: Hash
206 g.h.i: Hash
207 j."ʞ".l: Hash
208 x.1.2: Hash
209 `,
210 "table/no-eol": `
211 table: Hash
212 `,
213 "table/sub-empty": `
214 a: Hash
215 a.b: Hash
216 `,
217 "table/whitespace": `
218 "valid key": Hash
219 `,
220 "table/with-literal-string": `
221 a: Hash
222 a."\"b\"": Hash
223 a."\"b\"".c: Hash
224 a."\"b\"".c.answer: Integer
225 `,
226 "table/with-pound": `
227 "key#group": Hash
228 "key#group".answer: Integer
229 `,
230 "table/with-single-quotes": `
231 a: Hash
232 a.b: Hash
233 a.b.c: Hash
234 a.b.c.answer: Integer
235 `,
236 "table/without-super": `
237 x.y.z.w: Hash
238 x: Hash
239 `,
240 }
241
242
243 func TestToml(t *testing.T) {
244 runTomlTest(t, false)
245 }
246
247
248 func TestTomlNext(t *testing.T) {
249 toml.WithTomlNext(func() {
250 runTomlTest(t, true)
251 })
252 }
253
254
255 func TestTomlNextFails(t *testing.T) {
256 runTomlTest(t, true,
257 "valid/string/escape-esc",
258 "valid/datetime/no-seconds",
259 "valid/string/hex-escape",
260 "valid/inline-table/newline",
261 "valid/key/unicode")
262 }
263
264 func runTomlTest(t *testing.T, includeNext bool, wantFail ...string) {
265 for k := range errorTests {
266 _, err := filepath.Match(k, "")
267 if err != nil {
268 t.Fatal(err)
269 }
270 }
271
272
273
274
275
276
277
278 var runTests []string
279 for _, a := range os.Args {
280 if strings.HasPrefix(a, "-test.run=TestToml/") {
281 a = strings.TrimPrefix(a, "-test.run=TestToml/encode/")
282 a = strings.TrimPrefix(a, "-test.run=TestToml/decode/")
283 runTests = []string{a, a + "/*"}
284 break
285 }
286 }
287
288
289
290 var (
291 shouldExistValid = make(map[string]struct{})
292 shouldExistInvalid = make(map[string]struct{})
293 )
294 if len(runTests) == 0 {
295 for k := range metaTests {
296 shouldExistValid["valid/"+k] = struct{}{}
297 }
298 for k := range errorTests {
299 shouldExistInvalid["invalid/"+k] = struct{}{}
300 }
301 }
302
303 run := func(t *testing.T, enc bool) {
304 r := tomltest.Runner{
305 Files: tomltest.EmbeddedTests(),
306 Encoder: enc,
307 Parser: parser{},
308 RunTests: runTests,
309 SkipTests: []string{
310
311
312
313
314
315 "invalid/datetime/time-no-leads",
316
317
318 "valid/comment/noeol",
319 "valid/comment/nonascii",
320
321
322 "invalid/table/append-with-dotted*",
323 "invalid/inline-table/add",
324 "invalid/table/duplicate-key-dotted-table",
325 "invalid/table/duplicate-key-dotted-table2",
326 "invalid/spec/inline-table-2-0",
327 "invalid/spec/table-9-1",
328 "invalid/inline-table/nested_key_conflict",
329 "invalid/table/append-to-array-with-dotted-keys",
330 },
331 }
332 if includeNext {
333 r.Version = "next"
334 }
335
336 tests, err := r.Run()
337 if err != nil {
338 t.Fatal(err)
339 }
340
341 failed := make(map[string]struct{})
342 for _, test := range tests.Tests {
343 t.Run(test.Path, func(t *testing.T) {
344 if test.Failed() {
345 for _, f := range wantFail {
346 if f == test.Path {
347 failed[test.Path] = struct{}{}
348 return
349 }
350 }
351
352 t.Fatalf("\nError:\n%s\n\nInput:\n%s\nOutput:\n%s\nWant:\n%s\n",
353 test.Failure, test.Input, test.Output, test.Want)
354 return
355 }
356
357
358 if test.Type() == tomltest.TypeInvalid {
359 testError(t, test, shouldExistInvalid)
360 }
361
362 if !enc && test.Type() == tomltest.TypeValid {
363 delete(shouldExistValid, test.Path)
364 testMeta(t, test, includeNext)
365 }
366 })
367 }
368 for _, f := range wantFail {
369 if _, ok := failed[f]; !ok {
370 t.Errorf("expected test %q to fail but it didn't", f)
371 }
372 }
373
374 t.Logf("passed: %d; failed: %d; skipped: %d", tests.Passed, tests.Failed, tests.Skipped)
375 }
376
377 t.Run("decode", func(t *testing.T) { run(t, false) })
378 t.Run("encode", func(t *testing.T) { run(t, true) })
379
380 if len(shouldExistValid) > 0 {
381 var s []string
382 for k := range shouldExistValid {
383 s = append(s, k)
384 }
385 t.Errorf("the following meta tests didn't match any files: %s", strings.Join(s, ", "))
386 }
387 if len(shouldExistInvalid) > 0 {
388 var s []string
389 for k := range shouldExistInvalid {
390 s = append(s, k)
391 }
392 t.Errorf("the following meta tests didn't match any files: %s", strings.Join(s, ", "))
393 }
394 }
395
396 var reCollapseSpace = regexp.MustCompile(` +`)
397
398 func testMeta(t *testing.T, test tomltest.Test, includeNext bool) {
399 want, ok := metaTests[strings.TrimPrefix(test.Path, "valid/")]
400 if !ok {
401 return
402 }
403
404
405 if includeNext && (test.Path == "valid/table/names" || test.Path == "valid/key/case-sensitive") {
406 return
407 }
408
409 var s interface{}
410 meta, err := toml.Decode(test.Input, &s)
411 if err != nil {
412 t.Fatal(err)
413 }
414
415 b := new(strings.Builder)
416 for i, k := range meta.Keys() {
417 if i > 0 {
418 b.WriteByte('\n')
419 }
420 fmt.Fprintf(b, "%s: %s", k, meta.Type(k...))
421 }
422 have := b.String()
423
424 want = reCollapseSpace.ReplaceAllString(strings.ReplaceAll(strings.TrimSpace(want), "\t", ""), " ")
425 if have != want {
426 t.Errorf("MetaData wrong\nhave:\n%s\nwant:\n%s", have, want)
427 }
428 }
429
430 func testError(t *testing.T, test tomltest.Test, shouldExist map[string]struct{}) {
431 path := strings.TrimPrefix(test.Path, "invalid/")
432
433 errs, ok := errorTests[path]
434 if ok {
435 delete(shouldExist, "invalid/"+path)
436 }
437 if !ok {
438 for k := range errorTests {
439 ok, _ = filepath.Match(k, path)
440 if ok {
441 delete(shouldExist, "invalid/"+k)
442 errs = errorTests[k]
443 break
444 }
445 }
446 }
447 if !ok {
448 return
449 }
450
451 for _, e := range errs {
452 if !strings.Contains(test.Output, e) {
453 t.Errorf("\nwrong error message\nhave: %s\nwant: %s", test.Output, e)
454 }
455 }
456 }
457
458 type parser struct{}
459
460 func (p parser) Encode(input string) (output string, outputIsError bool, retErr error) {
461 defer func() {
462 if r := recover(); r != nil {
463 switch rr := r.(type) {
464 case error:
465 retErr = rr
466 default:
467 retErr = fmt.Errorf("%s", rr)
468 }
469 }
470 }()
471
472 var tmp interface{}
473 err := json.Unmarshal([]byte(input), &tmp)
474 if err != nil {
475 return "", false, err
476 }
477
478 rm, err := tag.Remove(tmp)
479 if err != nil {
480 return err.Error(), true, retErr
481 }
482
483 buf := new(bytes.Buffer)
484 err = toml.NewEncoder(buf).Encode(rm)
485 if err != nil {
486 return err.Error(), true, retErr
487 }
488
489 return buf.String(), false, retErr
490 }
491
492 func (p parser) Decode(input string) (output string, outputIsError bool, retErr error) {
493 defer func() {
494 if r := recover(); r != nil {
495 switch rr := r.(type) {
496 case error:
497 retErr = rr
498 default:
499 retErr = fmt.Errorf("%s", rr)
500 }
501 }
502 }()
503
504 var d interface{}
505 if _, err := toml.Decode(input, &d); err != nil {
506 return err.Error(), true, retErr
507 }
508
509 j, err := json.MarshalIndent(tag.Add("", d), "", " ")
510 if err != nil {
511 return "", false, err
512 }
513 return string(j), false, retErr
514 }
515
View as plain text