1 package toml
2
3 import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "math"
8 "net"
9 "os"
10 "strconv"
11 "strings"
12 "testing"
13 "time"
14 )
15
16 func TestEncodeRoundTrip(t *testing.T) {
17 type Config struct {
18 Age int
19 Cats []string
20 Pi float64
21 Perfection []int
22 DOB time.Time
23 Ipaddress net.IP
24 }
25
26 var inputs = Config{
27 Age: 13,
28 Cats: []string{"one", "two", "three"},
29 Pi: 3.145,
30 Perfection: []int{11, 2, 3, 4},
31 DOB: time.Now(),
32 Ipaddress: net.ParseIP("192.168.59.254"),
33 }
34
35 var (
36 firstBuffer bytes.Buffer
37 secondBuffer bytes.Buffer
38 outputs Config
39 )
40 err := NewEncoder(&firstBuffer).Encode(inputs)
41 if err != nil {
42 t.Fatal(err)
43 }
44 _, err = Decode(firstBuffer.String(), &outputs)
45 if err != nil {
46 t.Logf("Could not decode:\n%s\n", firstBuffer.String())
47 t.Fatal(err)
48 }
49 err = NewEncoder(&secondBuffer).Encode(outputs)
50 if err != nil {
51 t.Fatal(err)
52 }
53 if firstBuffer.String() != secondBuffer.String() {
54 t.Errorf("%s\n\nIS NOT IDENTICAL TO\n\n%s", firstBuffer.String(), secondBuffer.String())
55 }
56 }
57
58 func TestEncodeNestedTableArrays(t *testing.T) {
59 type song struct {
60 Name string `toml:"name"`
61 }
62 type album struct {
63 Name string `toml:"name"`
64 Songs []song `toml:"songs"`
65 }
66 type springsteen struct {
67 Albums []album `toml:"albums"`
68 }
69 value := springsteen{
70 []album{
71 {"Born to Run",
72 []song{{"Jungleland"}, {"Meeting Across the River"}}},
73 {"Born in the USA",
74 []song{{"Glory Days"}, {"Dancing in the Dark"}}},
75 },
76 }
77 expected := `[[albums]]
78 name = "Born to Run"
79
80 [[albums.songs]]
81 name = "Jungleland"
82
83 [[albums.songs]]
84 name = "Meeting Across the River"
85
86 [[albums]]
87 name = "Born in the USA"
88
89 [[albums.songs]]
90 name = "Glory Days"
91
92 [[albums.songs]]
93 name = "Dancing in the Dark"
94 `
95 encodeExpected(t, "nested table arrays", value, expected, nil)
96 }
97
98 func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) {
99 type Alpha struct {
100 V int
101 }
102 type Beta struct {
103 V int
104 }
105 type Conf struct {
106 V int
107 A Alpha
108 B []Beta
109 }
110
111 val := Conf{
112 V: 1,
113 A: Alpha{2},
114 B: []Beta{{3}},
115 }
116 expected := "V = 1\n\n[A]\n V = 2\n\n[[B]]\n V = 3\n"
117 encodeExpected(t, "array hash with normal hash order", val, expected, nil)
118 }
119
120 func TestEncodeOmitEmptyStruct(t *testing.T) {
121 type (
122 T struct{ Int int }
123 Tpriv struct {
124 Int int
125 private int
126 }
127 Ttime struct {
128 Time time.Time
129 }
130 )
131
132 tests := []struct {
133 in interface{}
134 want string
135 }{
136 {struct {
137 F T `toml:"f,omitempty"`
138 }{}, ""},
139 {struct {
140 F T `toml:"f,omitempty"`
141 }{T{1}}, "[f]\n Int = 1"},
142
143 {struct {
144 F Tpriv `toml:"f,omitempty"`
145 }{}, ""},
146 {struct {
147 F Tpriv `toml:"f,omitempty"`
148 }{Tpriv{1, 0}}, "[f]\n Int = 1"},
149
150
151 {struct {
152 F Tpriv `toml:"f,omitempty"`
153 }{Tpriv{0, 1}}, "[f]\n Int = 0"},
154
155
156 {struct {
157 F Ttime `toml:"t,omitempty"`
158 }{}, ""},
159 {struct {
160 F Ttime `toml:"t,omitempty"`
161 }{Ttime{time.Time{}.Add(1)}}, "[t]\n Time = 0001-01-01T00:00:00.000000001Z"},
162
163
164
165 }
166
167 for _, tt := range tests {
168 t.Run("", func(t *testing.T) {
169 buf := new(bytes.Buffer)
170
171 err := NewEncoder(buf).Encode(tt.in)
172 if err != nil {
173 t.Fatal(err)
174 }
175
176 have := strings.TrimSpace(buf.String())
177 if have != tt.want {
178 t.Errorf("\nhave:\n%s\nwant:\n%s", have, tt.want)
179 }
180 })
181 }
182 }
183
184 func TestEncodeOmitEmpty(t *testing.T) {
185 type compareable struct {
186 Bool bool `toml:"bool,omitempty"`
187 }
188 type uncomparable struct {
189 Field []string `toml:"field,omitempty"`
190 }
191 type nestedUncomparable struct {
192 Field uncomparable `toml:"uncomparable,omitempty"`
193 Bool bool `toml:"bool,omitempty"`
194 }
195 type simple struct {
196 Bool bool `toml:"bool,omitempty"`
197 String string `toml:"string,omitempty"`
198 Array [0]byte `toml:"array,omitempty"`
199 Slice []int `toml:"slice,omitempty"`
200 Map map[string]string `toml:"map,omitempty"`
201 Time time.Time `toml:"time,omitempty"`
202 Compareable1 compareable `toml:"compareable1,omitempty"`
203 Compareable2 compareable `toml:"compareable2,omitempty"`
204 Uncomparable1 uncomparable `toml:"uncomparable1,omitempty"`
205 Uncomparable2 uncomparable `toml:"uncomparable2,omitempty"`
206 NestedUncomparable1 nestedUncomparable `toml:"nesteduncomparable1,omitempty"`
207 NestedUncomparable2 nestedUncomparable `toml:"nesteduncomparable2,omitempty"`
208 }
209
210 var v simple
211 encodeExpected(t, "fields with omitempty are omitted when empty", v, "", nil)
212 v = simple{
213 Bool: true,
214 String: " ",
215 Slice: []int{2, 3, 4},
216 Map: map[string]string{"foo": "bar"},
217 Time: time.Date(1985, 6, 18, 15, 16, 17, 0, time.UTC),
218 Compareable2: compareable{true},
219 Uncomparable2: uncomparable{[]string{"XXX"}},
220 NestedUncomparable1: nestedUncomparable{uncomparable{[]string{"XXX"}}, false},
221 NestedUncomparable2: nestedUncomparable{uncomparable{}, true},
222 }
223 expected := `bool = true
224 string = " "
225 slice = [2, 3, 4]
226 time = 1985-06-18T15:16:17Z
227
228 [map]
229 foo = "bar"
230
231 [compareable2]
232 bool = true
233
234 [uncomparable2]
235 field = ["XXX"]
236
237 [nesteduncomparable1]
238 [nesteduncomparable1.uncomparable]
239 field = ["XXX"]
240
241 [nesteduncomparable2]
242 bool = true
243 `
244 encodeExpected(t, "fields with omitempty are not omitted when non-empty",
245 v, expected, nil)
246 }
247
248 func TestEncodeOmitEmptyPointer(t *testing.T) {
249 type s struct {
250 String *string `toml:"string,omitempty"`
251 }
252
253 t.Run("nil pointers", func(t *testing.T) {
254 var v struct {
255 String *string `toml:"string,omitempty"`
256 Slice *[]string `toml:"slice,omitempty"`
257 Map *map[string]string `toml:"map,omitempty"`
258 Struct *s `toml:"struct,omitempty"`
259 }
260 encodeExpected(t, "", v, ``, nil)
261 })
262
263 t.Run("zero values", func(t *testing.T) {
264 str := ""
265 sl := []string{}
266 m := map[string]string{}
267
268 v := struct {
269 String *string `toml:"string,omitempty"`
270 Slice *[]string `toml:"slice,omitempty"`
271 Map *map[string]string `toml:"map,omitempty"`
272 Struct *s `toml:"struct,omitempty"`
273 }{&str, &sl, &m, &s{&str}}
274 want := `string = ""
275 slice = []
276
277 [map]
278
279 [struct]
280 string = ""
281 `
282 encodeExpected(t, "", v, want, nil)
283 })
284
285 t.Run("with values", func(t *testing.T) {
286 str := "XXX"
287 sl := []string{"XXX"}
288 m := map[string]string{"XXX": "XXX"}
289
290 v := struct {
291 String *string `toml:"string,omitempty"`
292 Slice *[]string `toml:"slice,omitempty"`
293 Map *map[string]string `toml:"map,omitempty"`
294 Struct *s `toml:"struct,omitempty"`
295 }{&str, &sl, &m, &s{&str}}
296 want := `string = "XXX"
297 slice = ["XXX"]
298
299 [map]
300 XXX = "XXX"
301
302 [struct]
303 string = "XXX"
304 `
305 encodeExpected(t, "", v, want, nil)
306 })
307 }
308
309 func TestEncodeOmitZero(t *testing.T) {
310 type simple struct {
311 Number int `toml:"number,omitzero"`
312 Real float64 `toml:"real,omitzero"`
313 Unsigned uint `toml:"unsigned,omitzero"`
314 }
315
316 value := simple{0, 0.0, uint(0)}
317 expected := ""
318
319 encodeExpected(t, "simple with omitzero, all zero", value, expected, nil)
320
321 value.Number = 10
322 value.Real = 20
323 value.Unsigned = 5
324 expected = `number = 10
325 real = 20.0
326 unsigned = 5
327 `
328 encodeExpected(t, "simple with omitzero, non-zero", value, expected, nil)
329 }
330
331 func TestEncodeOmitemptyEmptyName(t *testing.T) {
332 type simple struct {
333 S []int `toml:",omitempty"`
334 }
335 v := simple{[]int{1, 2, 3}}
336 expected := "S = [1, 2, 3]\n"
337 encodeExpected(t, "simple with omitempty, no name, non-empty field",
338 v, expected, nil)
339 }
340
341 func TestEncodeAnonymousStruct(t *testing.T) {
342 type Inner struct{ N int }
343 type inner struct{ B int }
344 type Embedded struct {
345 Inner1 Inner
346 Inner2 Inner
347 }
348 type Outer0 struct {
349 Inner
350 inner
351 }
352 type Outer1 struct {
353 Inner `toml:"inner"`
354 inner `toml:"innerb"`
355 }
356 type Outer3 struct {
357 Embedded
358 }
359
360 v0 := Outer0{Inner{3}, inner{4}}
361 expected := "N = 3\nB = 4\n"
362 encodeExpected(t, "embedded anonymous untagged struct", v0, expected, nil)
363
364 v1 := Outer1{Inner{3}, inner{4}}
365 expected = "[inner]\n N = 3\n\n[innerb]\n B = 4\n"
366 encodeExpected(t, "embedded anonymous tagged struct", v1, expected, nil)
367
368 v3 := Outer3{Embedded: Embedded{Inner{3}, Inner{4}}}
369 expected = "[Inner1]\n N = 3\n\n[Inner2]\n N = 4\n"
370 encodeExpected(t, "embedded anonymous multiple fields", v3, expected, nil)
371 }
372
373 func TestEncodeAnonymousStructPointerField(t *testing.T) {
374 type Inner struct{ N int }
375 type Outer0 struct{ *Inner }
376 type Outer1 struct {
377 *Inner `toml:"inner"`
378 }
379
380 v0 := Outer0{}
381 expected := ""
382 encodeExpected(t, "nil anonymous untagged struct pointer field", v0, expected, nil)
383
384 v0 = Outer0{&Inner{3}}
385 expected = "N = 3\n"
386 encodeExpected(t, "non-nil anonymous untagged struct pointer field", v0, expected, nil)
387
388 v1 := Outer1{}
389 expected = ""
390 encodeExpected(t, "nil anonymous tagged struct pointer field", v1, expected, nil)
391
392 v1 = Outer1{&Inner{3}}
393 expected = "[inner]\n N = 3\n"
394 encodeExpected(t, "non-nil anonymous tagged struct pointer field", v1, expected, nil)
395 }
396
397 func TestEncodeNestedAnonymousStructs(t *testing.T) {
398 type A struct{ A string }
399 type B struct{ B string }
400 type C struct{ C string }
401 type BC struct {
402 B
403 C
404 }
405 type Outer struct {
406 A
407 BC
408 }
409
410 v := &Outer{
411 A: A{
412 A: "a",
413 },
414 BC: BC{
415 B: B{
416 B: "b",
417 },
418 C: C{
419 C: "c",
420 },
421 },
422 }
423
424 expected := "A = \"a\"\nB = \"b\"\nC = \"c\"\n"
425 encodeExpected(t, "nested anonymous untagged structs", v, expected, nil)
426 }
427
428 type InnerForNextTest struct{ N int }
429
430 func (InnerForNextTest) F() {}
431 func (InnerForNextTest) G() {}
432
433 func TestEncodeAnonymousNoStructField(t *testing.T) {
434 type Inner interface{ F() }
435 type inner interface{ G() }
436 type IntS []int
437 type intS []int
438 type Outer0 struct {
439 Inner
440 inner
441 IntS
442 intS
443 }
444
445 v0 := Outer0{
446 Inner: InnerForNextTest{3},
447 inner: InnerForNextTest{4},
448 IntS: []int{5, 6},
449 intS: []int{7, 8},
450 }
451 expected := "IntS = [5, 6]\n\n[Inner]\n N = 3\n"
452 encodeExpected(t, "non struct anonymous field", v0, expected, nil)
453 }
454
455 func TestEncodeIgnoredFields(t *testing.T) {
456 type simple struct {
457 Number int `toml:"-"`
458 }
459 value := simple{}
460 expected := ""
461 encodeExpected(t, "ignored field", value, expected, nil)
462 }
463
464 func TestEncodeNaN(t *testing.T) {
465 s1 := struct {
466 Nan float64 `toml:"nan"`
467 Inf float64 `toml:"inf"`
468 }{math.NaN(), math.Inf(1)}
469 s2 := struct {
470 Nan float32 `toml:"nan"`
471 Inf float32 `toml:"inf"`
472 }{float32(math.NaN()), float32(math.Inf(-1))}
473 encodeExpected(t, "", s1, "nan = nan\ninf = +inf\n", nil)
474 encodeExpected(t, "", s2, "nan = nan\ninf = -inf\n", nil)
475 }
476
477 func TestEncodePrimitive(t *testing.T) {
478 type MyStruct struct {
479 Data Primitive
480 DataA int
481 DataB string
482 }
483
484 decodeAndEncode := func(toml string) string {
485 var s MyStruct
486 _, err := Decode(toml, &s)
487 if err != nil {
488 t.Fatal(err)
489 }
490
491 var buf bytes.Buffer
492 err = NewEncoder(&buf).Encode(s)
493 if err != nil {
494 t.Fatal(err)
495 }
496 return buf.String()
497 }
498
499 original := `DataA = 1
500 DataB = "bbb"
501 Data = ["Foo", "Bar"]
502 `
503 reEncoded := decodeAndEncode(decodeAndEncode(original))
504
505 if reEncoded != original {
506 t.Errorf(
507 "re-encoded not the same as original\noriginal: %q\nre-encoded: %q",
508 original, reEncoded)
509 }
510 }
511
512 func TestEncodeError(t *testing.T) {
513 tests := []struct {
514 in interface{}
515 wantErr string
516 }{
517 {make(chan int), "unsupported type for key '': chan"},
518 {struct{ C complex128 }{0}, "unsupported type: complex128"},
519 {[]complex128{0}, "unsupported type: complex128"},
520 }
521
522 for _, tt := range tests {
523 t.Run("", func(t *testing.T) {
524 err := NewEncoder(os.Stderr).Encode(tt.in)
525 if err == nil {
526 t.Fatal("err is nil")
527 }
528 if !errorContains(err, tt.wantErr) {
529 t.Errorf("wrong error\nhave: %q\nwant: %q", err, tt.wantErr)
530 }
531 })
532 }
533 }
534
535 type (
536 sound struct{ S string }
537 food struct{ F []string }
538 fun func()
539 cplx complex128
540 ints []int
541
542 sound2 struct{ S string }
543 food2 struct{ F []string }
544 fun2 func()
545 cplx2 complex128
546 ints2 []int
547 )
548
549
550 func (s *sound) MarshalText() ([]byte, error) { return []byte(s.S), nil }
551 func (f food) MarshalText() ([]byte, error) { return []byte(strings.Join(f.F, ", ")), nil }
552 func (f fun) MarshalText() ([]byte, error) { return []byte("why would you do this?"), nil }
553 func (c cplx) MarshalText() ([]byte, error) {
554 cplx := complex128(c)
555 return []byte(fmt.Sprintf("(%f+%fi)", real(cplx), imag(cplx))), nil
556 }
557
558 func intsValue(is []int) []byte {
559 var buf bytes.Buffer
560 buf.WriteByte('<')
561 for i, v := range is {
562 if i > 0 {
563 buf.WriteByte(',')
564 }
565 buf.WriteString(strconv.Itoa(v))
566 }
567 buf.WriteByte('>')
568 return buf.Bytes()
569 }
570
571 func (is *ints) MarshalText() ([]byte, error) {
572 if is == nil {
573 return []byte("[]"), nil
574 }
575 return intsValue(*is), nil
576 }
577
578 func (s *sound2) MarshalTOML() ([]byte, error) { return []byte("\"" + s.S + "\""), nil }
579 func (f food2) MarshalTOML() ([]byte, error) {
580 return []byte("[\"" + strings.Join(f.F, "\", \"") + "\"]"), nil
581 }
582 func (f fun2) MarshalTOML() ([]byte, error) { return []byte("\"why would you do this?\""), nil }
583 func (c cplx2) MarshalTOML() ([]byte, error) {
584 cplx := complex128(c)
585 return []byte(fmt.Sprintf("\"(%f+%fi)\"", real(cplx), imag(cplx))), nil
586 }
587 func (is *ints2) MarshalTOML() ([]byte, error) {
588
589 if is == nil {
590 return []byte(`"[]"`), nil
591 }
592 return []byte(fmt.Sprintf(`"%s"`, intsValue(*is))), nil
593 }
594
595 func TestEncodeTextMarshaler(t *testing.T) {
596 x := struct {
597 Name string
598 Labels map[string]string
599 Sound sound
600 Sound2 *sound
601 Food food
602 Food2 *food
603 Complex cplx
604 Fun fun
605 Ints ints
606 Ints2 *ints2
607 }{
608 Name: "Goblok",
609 Sound: sound{"miauw"},
610 Sound2: &sound{"miauw"},
611 Labels: map[string]string{
612 "type": "cat",
613 "color": "black",
614 },
615 Food: food{[]string{"chicken", "fish"}},
616 Food2: &food{[]string{"chicken", "fish"}},
617 Complex: complex(42, 666),
618 Fun: func() { panic("x") },
619 Ints: ints{1, 2, 3, 4},
620 Ints2: &ints2{1, 2, 3, 4},
621 }
622
623 var buf bytes.Buffer
624 if err := NewEncoder(&buf).Encode(&x); err != nil {
625 t.Fatal(err)
626 }
627
628 want := `Name = "Goblok"
629 Sound = "miauw"
630 Sound2 = "miauw"
631 Food = "chicken, fish"
632 Food2 = "chicken, fish"
633 Complex = "(42.000000+666.000000i)"
634 Fun = "why would you do this?"
635 Ints = "<1,2,3,4>"
636 Ints2 = "<1,2,3,4>"
637
638 [Labels]
639 color = "black"
640 type = "cat"
641 `
642
643 if buf.String() != want {
644 t.Error("\n" + buf.String())
645 }
646 }
647
648 func TestEncodeTOMLMarshaler(t *testing.T) {
649 x := struct {
650 Name string
651 Labels map[string]string
652 Sound sound2
653 Sound2 *sound2
654 Food food2
655 Food2 *food2
656 Complex cplx2
657 Fun fun2
658 }{
659 Name: "Goblok",
660 Sound: sound2{"miauw"},
661 Sound2: &sound2{"miauw"},
662 Labels: map[string]string{
663 "type": "cat",
664 "color": "black",
665 },
666 Food: food2{[]string{"chicken", "fish"}},
667 Food2: &food2{[]string{"chicken", "fish"}},
668 Complex: complex(42, 666),
669 Fun: func() { panic("x") },
670 }
671
672 var buf bytes.Buffer
673 if err := NewEncoder(&buf).Encode(x); err != nil {
674 t.Fatal(err)
675 }
676
677 want := `Name = "Goblok"
678 Sound2 = "miauw"
679 Food = ["chicken", "fish"]
680 Food2 = ["chicken", "fish"]
681 Complex = "(42.000000+666.000000i)"
682 Fun = "why would you do this?"
683
684 [Labels]
685 color = "black"
686 type = "cat"
687
688 [Sound]
689 S = "miauw"
690 `
691
692 if buf.String() != want {
693 t.Error("\n" + buf.String())
694 }
695 }
696
697 type (
698 retNil1 string
699 retNil2 string
700 )
701
702 func (r retNil1) MarshalText() ([]byte, error) { return nil, nil }
703 func (r retNil2) MarshalTOML() ([]byte, error) { return nil, nil }
704
705 func TestEncodeEmpty(t *testing.T) {
706 t.Run("text", func(t *testing.T) {
707 var (
708 s struct{ Text retNil1 }
709 buf bytes.Buffer
710 )
711 err := NewEncoder(&buf).Encode(s)
712 if err == nil {
713 t.Fatalf("no error, but expected an error; output:\n%s", buf.String())
714 }
715 if buf.String() != "" {
716 t.Error("\n" + buf.String())
717 }
718 })
719
720 t.Run("toml", func(t *testing.T) {
721 var (
722 s struct{ Text retNil2 }
723 buf bytes.Buffer
724 )
725 err := NewEncoder(&buf).Encode(s)
726 if err == nil {
727 t.Fatalf("no error, but expected an error; output:\n%s", buf.String())
728 }
729 if buf.String() != "" {
730 t.Error("\n" + buf.String())
731 }
732 })
733 }
734
735
736
737
738
739 func TestEncode32bit(t *testing.T) {
740 type Inner struct {
741 A, B, C string
742 }
743 type Outer struct{ Inner }
744
745 encodeExpected(t, "embedded anonymous untagged struct",
746 Outer{Inner{"a", "b", "c"}},
747 "A = \"a\"\nB = \"b\"\nC = \"c\"\n",
748 nil)
749 }
750
751
752
753
754 func TestEncodeSkipInvalidType(t *testing.T) {
755 buf := new(bytes.Buffer)
756 err := NewEncoder(buf).Encode(struct {
757 Str string `toml:"str"`
758 Arr []func() `toml:"-"`
759 Map map[string]interface{} `toml:"-"`
760 Func func() `toml:"-"`
761 }{
762 Str: "a",
763 Arr: []func(){func() {}},
764 Map: map[string]interface{}{"f": func() {}},
765 Func: func() {},
766 })
767 if err != nil {
768 t.Fatal(err)
769 }
770
771 have := buf.String()
772 want := "str = \"a\"\n"
773 if have != want {
774 t.Errorf("\nwant: %q\nhave: %q\n", want, have)
775 }
776 }
777
778 func TestEncodeDuration(t *testing.T) {
779 tests := []time.Duration{
780 0,
781 time.Second,
782 time.Minute,
783 time.Hour,
784 248*time.Hour + 45*time.Minute + 24*time.Second,
785 12345678 * time.Nanosecond,
786 12345678 * time.Second,
787 4*time.Second + 2*time.Nanosecond,
788 }
789
790 for _, tt := range tests {
791 encodeExpected(t, tt.String(),
792 struct{ Dur time.Duration }{Dur: tt},
793 fmt.Sprintf("Dur = %q", tt), nil)
794 }
795 }
796
797 type jsonT struct {
798 Num json.Number
799 NumP *json.Number
800 Arr []json.Number
801 ArrP []*json.Number
802 Tbl map[string]json.Number
803 TblP map[string]*json.Number
804 }
805
806 var (
807 n2, n4, n6 = json.Number("2"), json.Number("4"), json.Number("6")
808 f2, f4, f6 = json.Number("2.2"), json.Number("4.4"), json.Number("6.6")
809 )
810
811 func TestEncodeJSONNumber(t *testing.T) {
812 tests := []struct {
813 in jsonT
814 want string
815 }{
816 {jsonT{}, "Num = 0"},
817 {jsonT{
818 Num: "1",
819 NumP: &n2,
820 Arr: []json.Number{"3"},
821 ArrP: []*json.Number{&n4},
822 Tbl: map[string]json.Number{"k1": "5"},
823 TblP: map[string]*json.Number{"k2": &n6}}, `
824 Num = 1
825 NumP = 2
826 Arr = [3]
827 ArrP = [4]
828
829 [Tbl]
830 k1 = 5
831
832 [TblP]
833 k2 = 6
834 `},
835 {jsonT{
836 Num: "1.1",
837 NumP: &f2,
838 Arr: []json.Number{"3.3"},
839 ArrP: []*json.Number{&f4},
840 Tbl: map[string]json.Number{"k1": "5.5"},
841 TblP: map[string]*json.Number{"k2": &f6}}, `
842 Num = 1.1
843 NumP = 2.2
844 Arr = [3.3]
845 ArrP = [4.4]
846
847 [Tbl]
848 k1 = 5.5
849
850 [TblP]
851 k2 = 6.6
852 `},
853 }
854
855 for _, tt := range tests {
856 t.Run("", func(t *testing.T) {
857 var buf bytes.Buffer
858 err := NewEncoder(&buf).Encode(tt.in)
859 if err != nil {
860 t.Fatal(err)
861 }
862
863 have := strings.TrimSpace(buf.String())
864 want := strings.ReplaceAll(strings.TrimSpace(tt.want), "\t", "")
865 if have != want {
866 t.Errorf("\nwant:\n%s\nhave:\n%s\n", want, have)
867 }
868 })
869 }
870 }
871
872 func TestEncode(t *testing.T) {
873 type Embedded struct {
874 Int int `toml:"_int"`
875 }
876 type NonStruct int
877
878 date := time.Date(2014, 5, 11, 19, 30, 40, 0, time.UTC)
879 dateStr := "2014-05-11T19:30:40Z"
880
881 tests := map[string]struct {
882 input interface{}
883 wantOutput string
884 wantError error
885 }{
886 "bool field": {
887 input: struct {
888 BoolTrue bool
889 BoolFalse bool
890 }{true, false},
891 wantOutput: "BoolTrue = true\nBoolFalse = false\n",
892 },
893 "int fields": {
894 input: struct {
895 Int int
896 Int8 int8
897 Int16 int16
898 Int32 int32
899 Int64 int64
900 }{1, 2, 3, 4, 5},
901 wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n",
902 },
903 "uint fields": {
904 input: struct {
905 Uint uint
906 Uint8 uint8
907 Uint16 uint16
908 Uint32 uint32
909 Uint64 uint64
910 }{1, 2, 3, 4, 5},
911 wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" +
912 "\nUint64 = 5\n",
913 },
914 "float fields": {
915 input: struct {
916 Float32 float32
917 Float64 float64
918 }{1.5, 2.5},
919 wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n",
920 },
921 "string field": {
922 input: struct{ String string }{"foo"},
923 wantOutput: "String = \"foo\"\n",
924 },
925 "string field with \\n escape": {
926 input: struct{ String string }{"foo\n"},
927 wantOutput: "String = \"foo\\n\"\n",
928 },
929 "string field and unexported field": {
930 input: struct {
931 String string
932 unexported int
933 }{"foo", 0},
934 wantOutput: "String = \"foo\"\n",
935 },
936 "datetime field in UTC": {
937 input: struct{ Date time.Time }{date},
938 wantOutput: fmt.Sprintf("Date = %s\n", dateStr),
939 },
940 "datetime field as primitive": {
941
942
943 input: map[string]interface{}{
944 "Date": date,
945 "Int": 1,
946 },
947 wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr),
948 },
949 "array fields": {
950 input: struct {
951 IntArray0 [0]int
952 IntArray3 [3]int
953 }{[0]int{}, [3]int{1, 2, 3}},
954 wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n",
955 },
956 "slice fields": {
957 input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{
958 nil, []int{}, []int{1, 2, 3},
959 },
960 wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n",
961 },
962 "datetime slices": {
963 input: struct{ DatetimeSlice []time.Time }{
964 []time.Time{date, date},
965 },
966 wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n",
967 dateStr, dateStr),
968 },
969 "nested arrays and slices": {
970 input: struct {
971 SliceOfArrays [][2]int
972 ArrayOfSlices [2][]int
973 SliceOfArraysOfSlices [][2][]int
974 ArrayOfSlicesOfArrays [2][][2]int
975 SliceOfMixedArrays [][2]interface{}
976 ArrayOfMixedSlices [2][]interface{}
977 }{
978 [][2]int{{1, 2}, {3, 4}},
979 [2][]int{{1, 2}, {3, 4}},
980 [][2][]int{
981 {
982 {1, 2}, {3, 4},
983 },
984 {
985 {5, 6}, {7, 8},
986 },
987 },
988 [2][][2]int{
989 {
990 {1, 2}, {3, 4},
991 },
992 {
993 {5, 6}, {7, 8},
994 },
995 },
996 [][2]interface{}{
997 {1, 2}, {"a", "b"},
998 },
999 [2][]interface{}{
1000 {1, 2}, {"a", "b"},
1001 },
1002 },
1003 wantOutput: `SliceOfArrays = [[1, 2], [3, 4]]
1004 ArrayOfSlices = [[1, 2], [3, 4]]
1005 SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
1006 ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
1007 SliceOfMixedArrays = [[1, 2], ["a", "b"]]
1008 ArrayOfMixedSlices = [[1, 2], ["a", "b"]]
1009 `,
1010 },
1011 "empty slice": {
1012 input: struct{ Empty []interface{} }{[]interface{}{}},
1013 wantOutput: "Empty = []\n",
1014 },
1015 "(error) slice with element type mismatch (string and integer)": {
1016 input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}},
1017 wantOutput: "Mixed = [1, \"a\"]\n",
1018 },
1019 "(error) slice with element type mismatch (integer and float)": {
1020 input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}},
1021 wantOutput: "Mixed = [1, 2.5]\n",
1022 },
1023 "slice with elems of differing Go types, same TOML types": {
1024 input: struct {
1025 MixedInts []interface{}
1026 MixedFloats []interface{}
1027 }{
1028 []interface{}{
1029 int(1), int8(2), int16(3), int32(4), int64(5),
1030 uint(1), uint8(2), uint16(3), uint32(4), uint64(5),
1031 },
1032 []interface{}{float32(1.5), float64(2.5)},
1033 },
1034 wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" +
1035 "MixedFloats = [1.5, 2.5]\n",
1036 },
1037 "(error) slice w/ element type mismatch (one is nested array)": {
1038 input: struct{ Mixed []interface{} }{
1039 []interface{}{1, []interface{}{2}},
1040 },
1041 wantOutput: "Mixed = [1, [2]]\n",
1042 },
1043 "(error) slice with 1 nil element": {
1044 input: struct{ NilElement1 []interface{} }{[]interface{}{nil}},
1045 wantError: errArrayNilElement,
1046 },
1047 "(error) slice with 1 nil element (and other non-nil elements)": {
1048 input: struct{ NilElement []interface{} }{
1049 []interface{}{1, nil},
1050 },
1051 wantError: errArrayNilElement,
1052 },
1053 "simple map": {
1054 input: map[string]int{"a": 1, "b": 2},
1055 wantOutput: "a = 1\nb = 2\n",
1056 },
1057 "map with interface{} value type": {
1058 input: map[string]interface{}{"a": 1, "b": "c"},
1059 wantOutput: "a = 1\nb = \"c\"\n",
1060 },
1061 "map with interface{} value type, some of which are structs": {
1062 input: map[string]interface{}{
1063 "a": struct{ Int int }{2},
1064 "b": 1,
1065 },
1066 wantOutput: "b = 1\n\n[a]\n Int = 2\n",
1067 },
1068 "nested map": {
1069 input: map[string]map[string]int{
1070 "a": {"b": 1},
1071 "c": {"d": 2},
1072 },
1073 wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n",
1074 },
1075 "nested struct": {
1076 input: struct{ Struct struct{ Int int } }{
1077 struct{ Int int }{1},
1078 },
1079 wantOutput: "[Struct]\n Int = 1\n",
1080 },
1081 "nested struct and non-struct field": {
1082 input: struct {
1083 Struct struct{ Int int }
1084 Bool bool
1085 }{struct{ Int int }{1}, true},
1086 wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n",
1087 },
1088 "2 nested structs": {
1089 input: struct{ Struct1, Struct2 struct{ Int int } }{
1090 struct{ Int int }{1}, struct{ Int int }{2},
1091 },
1092 wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n",
1093 },
1094 "deeply nested structs": {
1095 input: struct {
1096 Struct1, Struct2 struct{ Struct3 *struct{ Int int } }
1097 }{
1098 struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}},
1099 struct{ Struct3 *struct{ Int int } }{nil},
1100 },
1101 wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" +
1102 "\n\n[Struct2]\n",
1103 },
1104 "nested struct with nil struct elem": {
1105 input: struct {
1106 Struct struct{ Inner *struct{ Int int } }
1107 }{
1108 struct{ Inner *struct{ Int int } }{nil},
1109 },
1110 wantOutput: "[Struct]\n",
1111 },
1112 "nested struct with no fields": {
1113 input: struct {
1114 Struct struct{ Inner struct{} }
1115 }{
1116 struct{ Inner struct{} }{struct{}{}},
1117 },
1118 wantOutput: "[Struct]\n [Struct.Inner]\n",
1119 },
1120 "struct with tags": {
1121 input: struct {
1122 Struct struct {
1123 Int int `toml:"_int"`
1124 } `toml:"_struct"`
1125 Bool bool `toml:"_bool"`
1126 }{
1127 struct {
1128 Int int `toml:"_int"`
1129 }{1}, true,
1130 },
1131 wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n",
1132 },
1133 "embedded struct": {
1134 input: struct{ Embedded }{Embedded{1}},
1135 wantOutput: "_int = 1\n",
1136 },
1137 "embedded *struct": {
1138 input: struct{ *Embedded }{&Embedded{1}},
1139 wantOutput: "_int = 1\n",
1140 },
1141 "nested embedded struct": {
1142 input: struct {
1143 Struct struct{ Embedded } `toml:"_struct"`
1144 }{struct{ Embedded }{Embedded{1}}},
1145 wantOutput: "[_struct]\n _int = 1\n",
1146 },
1147 "nested embedded *struct": {
1148 input: struct {
1149 Struct struct{ *Embedded } `toml:"_struct"`
1150 }{struct{ *Embedded }{&Embedded{1}}},
1151 wantOutput: "[_struct]\n _int = 1\n",
1152 },
1153 "embedded non-struct": {
1154 input: struct{ NonStruct }{5},
1155 wantOutput: "NonStruct = 5\n",
1156 },
1157 "array of tables": {
1158 input: struct {
1159 Structs []*struct{ Int int } `toml:"struct"`
1160 }{
1161 []*struct{ Int int }{{1}, {3}},
1162 },
1163 wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n",
1164 },
1165 "array of tables order": {
1166 input: map[string]interface{}{
1167 "map": map[string]interface{}{
1168 "zero": 5,
1169 "arr": []map[string]int{
1170 {
1171 "friend": 5,
1172 },
1173 },
1174 },
1175 },
1176 wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n",
1177 },
1178 "empty key name": {
1179 input: map[string]int{"": 1},
1180 wantOutput: `"" = 1` + "\n",
1181 },
1182 "key with \\n escape": {
1183 input: map[string]string{"\n": "\n"},
1184 wantOutput: `"\n" = "\n"` + "\n",
1185 },
1186
1187 "empty map name": {
1188 input: map[string]interface{}{
1189 "": map[string]int{"v": 1},
1190 },
1191 wantOutput: "[\"\"]\n v = 1\n",
1192 },
1193 "(error) top-level slice": {
1194 input: []struct{ Int int }{{1}, {2}, {3}},
1195 wantError: errNoKey,
1196 },
1197 "(error) map no string key": {
1198 input: map[int]string{1: ""},
1199 wantError: errNonString,
1200 },
1201
1202 "tbl-in-arr-struct": {
1203 input: struct {
1204 Arr [][]struct{ A, B, C int }
1205 }{[][]struct{ A, B, C int }{{{1, 2, 3}, {4, 5, 6}}}},
1206 wantOutput: "Arr = [[{A = 1, B = 2, C = 3}, {A = 4, B = 5, C = 6}]]",
1207 },
1208
1209 "tbl-in-arr-map": {
1210 input: map[string]interface{}{
1211 "arr": []interface{}{[]interface{}{
1212 map[string]interface{}{
1213 "a": []interface{}{"hello", "world"},
1214 "b": []interface{}{1.12, 4.1},
1215 "c": 1,
1216 "d": map[string]interface{}{"e": "E"},
1217 "f": struct{ A, B int }{1, 2},
1218 "g": []struct{ A, B int }{{3, 4}, {5, 6}},
1219 },
1220 }},
1221 },
1222 wantOutput: `arr = [[{a = ["hello", "world"], b = [1.12, 4.1], c = 1, d = {e = "E"}, f = {A = 1, B = 2}, g = [{A = 3, B = 4}, {A = 5, B = 6}]}]]`,
1223 },
1224
1225 "slice of slice": {
1226 input: struct {
1227 Slices [][]struct{ Int int }
1228 }{
1229 [][]struct{ Int int }{{{1}}, {{2}}, {{3}}},
1230 },
1231 wantOutput: "Slices = [[{Int = 1}], [{Int = 2}], [{Int = 3}]]",
1232 },
1233 }
1234 for label, test := range tests {
1235 encodeExpected(t, label, test.input, test.wantOutput, test.wantError)
1236 }
1237 }
1238
1239 func TestEncodeDoubleTags(t *testing.T) {
1240
1241 s := struct {
1242 A int `toml:"a"`
1243 B int `toml:"a"`
1244 C int `toml:"c"`
1245 }{1, 2, 3}
1246 buf := new(strings.Builder)
1247 err := NewEncoder(buf).Encode(s)
1248 if err != nil {
1249 t.Fatal(err)
1250 }
1251
1252 want := `a = 1
1253 a = 2
1254 c = 3
1255 `
1256 if want != buf.String() {
1257 t.Errorf("\nhave: %s\nwant: %s\n", buf.String(), want)
1258 }
1259 }
1260
1261 type (
1262 Doc1 struct{ N string }
1263 Doc2 struct{ N string }
1264 )
1265
1266 func (d Doc1) MarshalTOML() ([]byte, error) { return []byte(`marshal_toml = "` + d.N + `"`), nil }
1267 func (d Doc2) MarshalText() ([]byte, error) { return []byte(`marshal_text = "` + d.N + `"`), nil }
1268
1269
1270 func TestMarshalDoc(t *testing.T) {
1271 t.Run("toml", func(t *testing.T) {
1272 var buf bytes.Buffer
1273 err := NewEncoder(&buf).Encode(Doc1{"asd"})
1274 if err != nil {
1275 t.Fatal(err)
1276 }
1277
1278 want := `marshal_toml = "asd"`
1279 if want != buf.String() {
1280 t.Errorf("\nhave: %s\nwant: %s\n", buf.String(), want)
1281 }
1282 })
1283
1284 t.Run("text", func(t *testing.T) {
1285 var buf bytes.Buffer
1286 err := NewEncoder(&buf).Encode(Doc2{"asd"})
1287 if err != nil {
1288 t.Fatal(err)
1289 }
1290
1291 want := `"marshal_text = \"asd\""`
1292 if want != buf.String() {
1293 t.Errorf("\nhave: %s\nwant: %s\n", buf.String(), want)
1294 }
1295 })
1296 }
1297
1298 func encodeExpected(t *testing.T, label string, val interface{}, want string, wantErr error) {
1299 t.Helper()
1300 t.Run(label, func(t *testing.T) {
1301 t.Helper()
1302 var buf bytes.Buffer
1303 err := NewEncoder(&buf).Encode(val)
1304 if err != wantErr {
1305 if wantErr != nil {
1306 if wantErr == errAnything && err != nil {
1307 return
1308 }
1309 t.Errorf("want Encode error %v, got %v", wantErr, err)
1310 } else {
1311 t.Errorf("Encode failed: %s", err)
1312 }
1313 }
1314 if err != nil {
1315 return
1316 }
1317
1318 have := strings.TrimSpace(buf.String())
1319 want = strings.TrimSpace(want)
1320 if want != have {
1321 t.Errorf("\nhave:\n%s\nwant:\n%s\n",
1322 "\t"+strings.ReplaceAll(have, "\n", "\n\t"),
1323 "\t"+strings.ReplaceAll(want, "\n", "\n\t"))
1324 }
1325 })
1326 }
1327
View as plain text