git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/toml/encode_test.go (about) 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 // Private field being set also counts as "not empty". 151 {struct { 152 F Tpriv `toml:"f,omitempty"` 153 }{Tpriv{0, 1}}, "[f]\n Int = 0"}, 154 155 // time.Time is common use case, so test that explicitly. 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 // TODO: also test with MarshalText, MarshalTOML returning non-zero 164 // value. 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 TestEncodeWithOmitEmpty(t *testing.T) { 185 type uncomparable struct { 186 Field []string `toml:"Field,omitempty"` 187 } 188 type simple struct { 189 Bool bool `toml:"bool,omitempty"` 190 String string `toml:"string,omitempty"` 191 Array [0]byte `toml:"array,omitempty"` 192 Slice []int `toml:"slice,omitempty"` 193 Map map[string]string `toml:"map,omitempty"` 194 Time time.Time `toml:"time,omitempty"` 195 Uncomparable1 uncomparable `toml:"uncomparable1,omitempty"` 196 Uncomparable2 uncomparable `toml:"uncomparable2,omitempty"` 197 } 198 199 var v simple 200 encodeExpected(t, "fields with omitempty are omitted when empty", v, ` 201 [uncomparable1] 202 203 [uncomparable2] 204 `, nil) 205 v = simple{ 206 Bool: true, 207 String: " ", 208 Slice: []int{2, 3, 4}, 209 Map: map[string]string{"foo": "bar"}, 210 Time: time.Date(1985, 6, 18, 15, 16, 17, 0, time.UTC), 211 Uncomparable2: uncomparable{[]string{"XXX"}}, 212 } 213 expected := `bool = true 214 string = " " 215 slice = [2, 3, 4] 216 time = 1985-06-18T15:16:17Z 217 218 [map] 219 foo = "bar" 220 221 [uncomparable1] 222 223 [uncomparable2] 224 Field = ["XXX"] 225 ` 226 encodeExpected(t, "fields with omitempty are not omitted when non-empty", 227 v, expected, nil) 228 } 229 230 func TestEncodeWithOmitZero(t *testing.T) { 231 type simple struct { 232 Number int `toml:"number,omitzero"` 233 Real float64 `toml:"real,omitzero"` 234 Unsigned uint `toml:"unsigned,omitzero"` 235 } 236 237 value := simple{0, 0.0, uint(0)} 238 expected := "" 239 240 encodeExpected(t, "simple with omitzero, all zero", value, expected, nil) 241 242 value.Number = 10 243 value.Real = 20 244 value.Unsigned = 5 245 expected = `number = 10 246 real = 20.0 247 unsigned = 5 248 ` 249 encodeExpected(t, "simple with omitzero, non-zero", value, expected, nil) 250 } 251 252 func TestEncodeOmitemptyWithEmptyName(t *testing.T) { 253 type simple struct { 254 S []int `toml:",omitempty"` 255 } 256 v := simple{[]int{1, 2, 3}} 257 expected := "S = [1, 2, 3]\n" 258 encodeExpected(t, "simple with omitempty, no name, non-empty field", 259 v, expected, nil) 260 } 261 262 func TestEncodeAnonymousStruct(t *testing.T) { 263 type Inner struct{ N int } 264 type inner struct{ B int } 265 type Outer0 struct { 266 Inner 267 inner 268 } 269 type Outer1 struct { 270 Inner `toml:"inner"` 271 inner `toml:"innerb"` 272 } 273 274 v0 := Outer0{Inner{3}, inner{4}} 275 expected := "N = 3\nB = 4\n" 276 encodeExpected(t, "embedded anonymous untagged struct", v0, expected, nil) 277 278 v1 := Outer1{Inner{3}, inner{4}} 279 expected = "[inner]\n N = 3\n\n[innerb]\n B = 4\n" 280 encodeExpected(t, "embedded anonymous tagged struct", v1, expected, nil) 281 } 282 283 func TestEncodeAnonymousStructPointerField(t *testing.T) { 284 type Inner struct{ N int } 285 type Outer0 struct{ *Inner } 286 type Outer1 struct { 287 *Inner `toml:"inner"` 288 } 289 290 v0 := Outer0{} 291 expected := "" 292 encodeExpected(t, "nil anonymous untagged struct pointer field", v0, expected, nil) 293 294 v0 = Outer0{&Inner{3}} 295 expected = "N = 3\n" 296 encodeExpected(t, "non-nil anonymous untagged struct pointer field", v0, expected, nil) 297 298 v1 := Outer1{} 299 expected = "" 300 encodeExpected(t, "nil anonymous tagged struct pointer field", v1, expected, nil) 301 302 v1 = Outer1{&Inner{3}} 303 expected = "[inner]\n N = 3\n" 304 encodeExpected(t, "non-nil anonymous tagged struct pointer field", v1, expected, nil) 305 } 306 307 func TestEncodeNestedAnonymousStructs(t *testing.T) { 308 type A struct{ A string } 309 type B struct{ B string } 310 type C struct{ C string } 311 type BC struct { 312 B 313 C 314 } 315 type Outer struct { 316 A 317 BC 318 } 319 320 v := &Outer{ 321 A: A{ 322 A: "a", 323 }, 324 BC: BC{ 325 B: B{ 326 B: "b", 327 }, 328 C: C{ 329 C: "c", 330 }, 331 }, 332 } 333 334 expected := "A = \"a\"\nB = \"b\"\nC = \"c\"\n" 335 encodeExpected(t, "nested anonymous untagged structs", v, expected, nil) 336 } 337 338 type InnerForNextTest struct{ N int } 339 340 func (InnerForNextTest) F() {} 341 func (InnerForNextTest) G() {} 342 343 func TestEncodeAnonymousNoStructField(t *testing.T) { 344 type Inner interface{ F() } 345 type inner interface{ G() } 346 type IntS []int 347 type intS []int 348 type Outer0 struct { 349 Inner 350 inner 351 IntS 352 intS 353 } 354 355 v0 := Outer0{ 356 Inner: InnerForNextTest{3}, 357 inner: InnerForNextTest{4}, 358 IntS: []int{5, 6}, 359 intS: []int{7, 8}, 360 } 361 expected := "IntS = [5, 6]\n\n[Inner]\n N = 3\n" 362 encodeExpected(t, "non struct anonymous field", v0, expected, nil) 363 } 364 365 func TestEncodeIgnoredFields(t *testing.T) { 366 type simple struct { 367 Number int `toml:"-"` 368 } 369 value := simple{} 370 expected := "" 371 encodeExpected(t, "ignored field", value, expected, nil) 372 } 373 374 func TestEncodeNaN(t *testing.T) { 375 s1 := struct { 376 Nan float64 `toml:"nan"` 377 Inf float64 `toml:"inf"` 378 }{math.NaN(), math.Inf(1)} 379 s2 := struct { 380 Nan float32 `toml:"nan"` 381 Inf float32 `toml:"inf"` 382 }{float32(math.NaN()), float32(math.Inf(-1))} 383 encodeExpected(t, "", s1, "nan = nan\ninf = +inf\n", nil) 384 encodeExpected(t, "", s2, "nan = nan\ninf = -inf\n", nil) 385 } 386 387 func TestEncodePrimitive(t *testing.T) { 388 type MyStruct struct { 389 Data Primitive 390 DataA int 391 DataB string 392 } 393 394 decodeAndEncode := func(toml string) string { 395 var s MyStruct 396 _, err := Decode(toml, &s) 397 if err != nil { 398 t.Fatal(err) 399 } 400 401 var buf bytes.Buffer 402 err = NewEncoder(&buf).Encode(s) 403 if err != nil { 404 t.Fatal(err) 405 } 406 return buf.String() 407 } 408 409 original := `DataA = 1 410 DataB = "bbb" 411 Data = ["Foo", "Bar"] 412 ` 413 reEncoded := decodeAndEncode(decodeAndEncode(original)) 414 415 if reEncoded != original { 416 t.Errorf( 417 "re-encoded not the same as original\noriginal: %q\nre-encoded: %q", 418 original, reEncoded) 419 } 420 } 421 422 func TestEncodeError(t *testing.T) { 423 tests := []struct { 424 in interface{} 425 wantErr string 426 }{ 427 {make(chan int), "unsupported type for key '': chan"}, 428 {struct{ C complex128 }{0}, "unsupported type: complex128"}, 429 {[]complex128{0}, "unsupported type: complex128"}, 430 } 431 432 for _, tt := range tests { 433 t.Run("", func(t *testing.T) { 434 err := NewEncoder(os.Stderr).Encode(tt.in) 435 if err == nil { 436 t.Fatal("err is nil") 437 } 438 if !errorContains(err, tt.wantErr) { 439 t.Errorf("wrong error\nhave: %q\nwant: %q", err, tt.wantErr) 440 } 441 }) 442 } 443 } 444 445 type ( 446 sound struct{ S string } 447 food struct{ F []string } 448 fun func() 449 cplx complex128 450 ints []int 451 452 sound2 struct{ S string } 453 food2 struct{ F []string } 454 fun2 func() 455 cplx2 complex128 456 ints2 []int 457 ) 458 459 // This is intentionally wrong (pointer receiver) 460 func (s *sound) MarshalText() ([]byte, error) { return []byte(s.S), nil } 461 func (f food) MarshalText() ([]byte, error) { return []byte(strings.Join(f.F, ", ")), nil } 462 func (f fun) MarshalText() ([]byte, error) { return []byte("why would you do this?"), nil } 463 func (c cplx) MarshalText() ([]byte, error) { 464 cplx := complex128(c) 465 return []byte(fmt.Sprintf("(%f+%fi)", real(cplx), imag(cplx))), nil 466 } 467 468 func intsValue(is []int) []byte { 469 var buf bytes.Buffer 470 buf.WriteByte('<') 471 for i, v := range is { 472 if i > 0 { 473 buf.WriteByte(',') 474 } 475 buf.WriteString(strconv.Itoa(v)) 476 } 477 buf.WriteByte('>') 478 return buf.Bytes() 479 } 480 481 func (is *ints) MarshalText() ([]byte, error) { 482 if is == nil { 483 return []byte("[]"), nil 484 } 485 return intsValue(*is), nil 486 } 487 488 func (s *sound2) MarshalTOML() ([]byte, error) { return []byte("\"" + s.S + "\""), nil } 489 func (f food2) MarshalTOML() ([]byte, error) { 490 return []byte("[\"" + strings.Join(f.F, "\", \"") + "\"]"), nil 491 } 492 func (f fun2) MarshalTOML() ([]byte, error) { return []byte("\"why would you do this?\""), nil } 493 func (c cplx2) MarshalTOML() ([]byte, error) { 494 cplx := complex128(c) 495 return []byte(fmt.Sprintf("\"(%f+%fi)\"", real(cplx), imag(cplx))), nil 496 } 497 func (is *ints2) MarshalTOML() ([]byte, error) { 498 // MarshalTOML must quote by self 499 if is == nil { 500 return []byte(`"[]"`), nil 501 } 502 return []byte(fmt.Sprintf(`"%s"`, intsValue(*is))), nil 503 } 504 505 func TestEncodeTextMarshaler(t *testing.T) { 506 x := struct { 507 Name string 508 Labels map[string]string 509 Sound sound 510 Sound2 *sound 511 Food food 512 Food2 *food 513 Complex cplx 514 Fun fun 515 Ints ints 516 Ints2 *ints2 517 }{ 518 Name: "Goblok", 519 Sound: sound{"miauw"}, 520 Sound2: &sound{"miauw"}, 521 Labels: map[string]string{ 522 "type": "cat", 523 "color": "black", 524 }, 525 Food: food{[]string{"chicken", "fish"}}, 526 Food2: &food{[]string{"chicken", "fish"}}, 527 Complex: complex(42, 666), 528 Fun: func() { panic("x") }, 529 Ints: ints{1, 2, 3, 4}, 530 Ints2: &ints2{1, 2, 3, 4}, 531 } 532 533 var buf bytes.Buffer 534 if err := NewEncoder(&buf).Encode(&x); err != nil { 535 t.Fatal(err) 536 } 537 538 want := `Name = "Goblok" 539 Sound = "miauw" 540 Sound2 = "miauw" 541 Food = "chicken, fish" 542 Food2 = "chicken, fish" 543 Complex = "(42.000000+666.000000i)" 544 Fun = "why would you do this?" 545 Ints = "<1,2,3,4>" 546 Ints2 = "<1,2,3,4>" 547 548 [Labels] 549 color = "black" 550 type = "cat" 551 ` 552 553 if buf.String() != want { 554 t.Error("\n" + buf.String()) 555 } 556 } 557 558 func TestEncodeTOMLMarshaler(t *testing.T) { 559 x := struct { 560 Name string 561 Labels map[string]string 562 Sound sound2 563 Sound2 *sound2 564 Food food2 565 Food2 *food2 566 Complex cplx2 567 Fun fun2 568 }{ 569 Name: "Goblok", 570 Sound: sound2{"miauw"}, 571 Sound2: &sound2{"miauw"}, 572 Labels: map[string]string{ 573 "type": "cat", 574 "color": "black", 575 }, 576 Food: food2{[]string{"chicken", "fish"}}, 577 Food2: &food2{[]string{"chicken", "fish"}}, 578 Complex: complex(42, 666), 579 Fun: func() { panic("x") }, 580 } 581 582 var buf bytes.Buffer 583 if err := NewEncoder(&buf).Encode(x); err != nil { 584 t.Fatal(err) 585 } 586 587 want := `Name = "Goblok" 588 Sound2 = "miauw" 589 Food = ["chicken", "fish"] 590 Food2 = ["chicken", "fish"] 591 Complex = "(42.000000+666.000000i)" 592 Fun = "why would you do this?" 593 594 [Labels] 595 color = "black" 596 type = "cat" 597 598 [Sound] 599 S = "miauw" 600 ` 601 602 if buf.String() != want { 603 t.Error("\n" + buf.String()) 604 } 605 } 606 607 type ( 608 retNil1 string 609 retNil2 string 610 ) 611 612 func (r retNil1) MarshalText() ([]byte, error) { return nil, nil } 613 func (r retNil2) MarshalTOML() ([]byte, error) { return nil, nil } 614 615 func TestEncodeEmpty(t *testing.T) { 616 t.Run("text", func(t *testing.T) { 617 var ( 618 s struct{ Text retNil1 } 619 buf bytes.Buffer 620 ) 621 err := NewEncoder(&buf).Encode(s) 622 if err == nil { 623 t.Fatalf("no error, but expected an error; output:\n%s", buf.String()) 624 } 625 if buf.String() != "" { 626 t.Error("\n" + buf.String()) 627 } 628 }) 629 630 t.Run("toml", func(t *testing.T) { 631 var ( 632 s struct{ Text retNil2 } 633 buf bytes.Buffer 634 ) 635 err := NewEncoder(&buf).Encode(s) 636 if err == nil { 637 t.Fatalf("no error, but expected an error; output:\n%s", buf.String()) 638 } 639 if buf.String() != "" { 640 t.Error("\n" + buf.String()) 641 } 642 }) 643 } 644 645 // Would previously fail on 32bit architectures; can test with: 646 // 647 // GOARCH=386 go test -c && ./toml.test 648 // GOARCH=arm GOARM=7 go test -c && qemu-arm ./toml.test 649 func TestEncode32bit(t *testing.T) { 650 type Inner struct { 651 A, B, C string 652 } 653 type Outer struct{ Inner } 654 655 encodeExpected(t, "embedded anonymous untagged struct", 656 Outer{Inner{"a", "b", "c"}}, 657 "A = \"a\"\nB = \"b\"\nC = \"c\"\n", 658 nil) 659 } 660 661 // Skip invalid types if it has toml:"-" 662 // 663 // https://git.sr.ht/~pingoo/stdx/toml/issues/345 664 func TestEncodeSkipInvalidType(t *testing.T) { 665 buf := new(bytes.Buffer) 666 err := NewEncoder(buf).Encode(struct { 667 Str string `toml:"str"` 668 Arr []func() `toml:"-"` 669 Map map[string]interface{} `toml:"-"` 670 Func func() `toml:"-"` 671 }{ 672 Str: "a", 673 Arr: []func(){func() {}}, 674 Map: map[string]interface{}{"f": func() {}}, 675 Func: func() {}, 676 }) 677 if err != nil { 678 t.Fatal(err) 679 } 680 681 have := buf.String() 682 want := "str = \"a\"\n" 683 if have != want { 684 t.Errorf("\nwant: %q\nhave: %q\n", want, have) 685 } 686 } 687 688 func TestEncodeDuration(t *testing.T) { 689 tests := []time.Duration{ 690 0, 691 time.Second, 692 time.Minute, 693 time.Hour, 694 248*time.Hour + 45*time.Minute + 24*time.Second, 695 12345678 * time.Nanosecond, 696 12345678 * time.Second, 697 4*time.Second + 2*time.Nanosecond, 698 } 699 700 for _, tt := range tests { 701 encodeExpected(t, tt.String(), 702 struct{ Dur time.Duration }{Dur: tt}, 703 fmt.Sprintf("Dur = %q", tt), nil) 704 } 705 } 706 707 type jsonT struct { 708 Num json.Number 709 NumP *json.Number 710 Arr []json.Number 711 ArrP []*json.Number 712 Tbl map[string]json.Number 713 TblP map[string]*json.Number 714 } 715 716 var ( 717 n2, n4, n6 = json.Number("2"), json.Number("4"), json.Number("6") 718 f2, f4, f6 = json.Number("2.2"), json.Number("4.4"), json.Number("6.6") 719 ) 720 721 func TestEncodeJSONNumber(t *testing.T) { 722 tests := []struct { 723 in jsonT 724 want string 725 }{ 726 {jsonT{}, "Num = 0"}, 727 {jsonT{ 728 Num: "1", 729 NumP: &n2, 730 Arr: []json.Number{"3"}, 731 ArrP: []*json.Number{&n4}, 732 Tbl: map[string]json.Number{"k1": "5"}, 733 TblP: map[string]*json.Number{"k2": &n6}}, ` 734 Num = 1 735 NumP = 2 736 Arr = [3] 737 ArrP = [4] 738 739 [Tbl] 740 k1 = 5 741 742 [TblP] 743 k2 = 6 744 `}, 745 {jsonT{ 746 Num: "1.1", 747 NumP: &f2, 748 Arr: []json.Number{"3.3"}, 749 ArrP: []*json.Number{&f4}, 750 Tbl: map[string]json.Number{"k1": "5.5"}, 751 TblP: map[string]*json.Number{"k2": &f6}}, ` 752 Num = 1.1 753 NumP = 2.2 754 Arr = [3.3] 755 ArrP = [4.4] 756 757 [Tbl] 758 k1 = 5.5 759 760 [TblP] 761 k2 = 6.6 762 `}, 763 } 764 765 for _, tt := range tests { 766 t.Run("", func(t *testing.T) { 767 var buf bytes.Buffer 768 err := NewEncoder(&buf).Encode(tt.in) 769 if err != nil { 770 t.Fatal(err) 771 } 772 773 have := strings.TrimSpace(buf.String()) 774 want := strings.ReplaceAll(strings.TrimSpace(tt.want), "\t", "") 775 if have != want { 776 t.Errorf("\nwant:\n%s\nhave:\n%s\n", want, have) 777 } 778 }) 779 } 780 } 781 782 func TestEncode(t *testing.T) { 783 type Embedded struct { 784 Int int `toml:"_int"` 785 } 786 type NonStruct int 787 788 date := time.Date(2014, 5, 11, 19, 30, 40, 0, time.UTC) 789 dateStr := "2014-05-11T19:30:40Z" 790 791 tests := map[string]struct { 792 input interface{} 793 wantOutput string 794 wantError error 795 }{ 796 "bool field": { 797 input: struct { 798 BoolTrue bool 799 BoolFalse bool 800 }{true, false}, 801 wantOutput: "BoolTrue = true\nBoolFalse = false\n", 802 }, 803 "int fields": { 804 input: struct { 805 Int int 806 Int8 int8 807 Int16 int16 808 Int32 int32 809 Int64 int64 810 }{1, 2, 3, 4, 5}, 811 wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n", 812 }, 813 "uint fields": { 814 input: struct { 815 Uint uint 816 Uint8 uint8 817 Uint16 uint16 818 Uint32 uint32 819 Uint64 uint64 820 }{1, 2, 3, 4, 5}, 821 wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" + 822 "\nUint64 = 5\n", 823 }, 824 "float fields": { 825 input: struct { 826 Float32 float32 827 Float64 float64 828 }{1.5, 2.5}, 829 wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n", 830 }, 831 "string field": { 832 input: struct{ String string }{"foo"}, 833 wantOutput: "String = \"foo\"\n", 834 }, 835 "string field with \\n escape": { 836 input: struct{ String string }{"foo\n"}, 837 wantOutput: "String = \"foo\\n\"\n", 838 }, 839 "string field and unexported field": { 840 input: struct { 841 String string 842 unexported int 843 }{"foo", 0}, 844 wantOutput: "String = \"foo\"\n", 845 }, 846 "datetime field in UTC": { 847 input: struct{ Date time.Time }{date}, 848 wantOutput: fmt.Sprintf("Date = %s\n", dateStr), 849 }, 850 "datetime field as primitive": { 851 // Using a map here to fail if isStructOrMap() returns true for 852 // time.Time. 853 input: map[string]interface{}{ 854 "Date": date, 855 "Int": 1, 856 }, 857 wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr), 858 }, 859 "array fields": { 860 input: struct { 861 IntArray0 [0]int 862 IntArray3 [3]int 863 }{[0]int{}, [3]int{1, 2, 3}}, 864 wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n", 865 }, 866 "slice fields": { 867 input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{ 868 nil, []int{}, []int{1, 2, 3}, 869 }, 870 wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n", 871 }, 872 "datetime slices": { 873 input: struct{ DatetimeSlice []time.Time }{ 874 []time.Time{date, date}, 875 }, 876 wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n", 877 dateStr, dateStr), 878 }, 879 "nested arrays and slices": { 880 input: struct { 881 SliceOfArrays [][2]int 882 ArrayOfSlices [2][]int 883 SliceOfArraysOfSlices [][2][]int 884 ArrayOfSlicesOfArrays [2][][2]int 885 SliceOfMixedArrays [][2]interface{} 886 ArrayOfMixedSlices [2][]interface{} 887 }{ 888 [][2]int{{1, 2}, {3, 4}}, 889 [2][]int{{1, 2}, {3, 4}}, 890 [][2][]int{ 891 { 892 {1, 2}, {3, 4}, 893 }, 894 { 895 {5, 6}, {7, 8}, 896 }, 897 }, 898 [2][][2]int{ 899 { 900 {1, 2}, {3, 4}, 901 }, 902 { 903 {5, 6}, {7, 8}, 904 }, 905 }, 906 [][2]interface{}{ 907 {1, 2}, {"a", "b"}, 908 }, 909 [2][]interface{}{ 910 {1, 2}, {"a", "b"}, 911 }, 912 }, 913 wantOutput: `SliceOfArrays = [[1, 2], [3, 4]] 914 ArrayOfSlices = [[1, 2], [3, 4]] 915 SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] 916 ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] 917 SliceOfMixedArrays = [[1, 2], ["a", "b"]] 918 ArrayOfMixedSlices = [[1, 2], ["a", "b"]] 919 `, 920 }, 921 "empty slice": { 922 input: struct{ Empty []interface{} }{[]interface{}{}}, 923 wantOutput: "Empty = []\n", 924 }, 925 "(error) slice with element type mismatch (string and integer)": { 926 input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}}, 927 wantOutput: "Mixed = [1, \"a\"]\n", 928 }, 929 "(error) slice with element type mismatch (integer and float)": { 930 input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}}, 931 wantOutput: "Mixed = [1, 2.5]\n", 932 }, 933 "slice with elems of differing Go types, same TOML types": { 934 input: struct { 935 MixedInts []interface{} 936 MixedFloats []interface{} 937 }{ 938 []interface{}{ 939 int(1), int8(2), int16(3), int32(4), int64(5), 940 uint(1), uint8(2), uint16(3), uint32(4), uint64(5), 941 }, 942 []interface{}{float32(1.5), float64(2.5)}, 943 }, 944 wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" + 945 "MixedFloats = [1.5, 2.5]\n", 946 }, 947 "(error) slice w/ element type mismatch (one is nested array)": { 948 input: struct{ Mixed []interface{} }{ 949 []interface{}{1, []interface{}{2}}, 950 }, 951 wantOutput: "Mixed = [1, [2]]\n", 952 }, 953 "(error) slice with 1 nil element": { 954 input: struct{ NilElement1 []interface{} }{[]interface{}{nil}}, 955 wantError: errArrayNilElement, 956 }, 957 "(error) slice with 1 nil element (and other non-nil elements)": { 958 input: struct{ NilElement []interface{} }{ 959 []interface{}{1, nil}, 960 }, 961 wantError: errArrayNilElement, 962 }, 963 "simple map": { 964 input: map[string]int{"a": 1, "b": 2}, 965 wantOutput: "a = 1\nb = 2\n", 966 }, 967 "map with interface{} value type": { 968 input: map[string]interface{}{"a": 1, "b": "c"}, 969 wantOutput: "a = 1\nb = \"c\"\n", 970 }, 971 "map with interface{} value type, some of which are structs": { 972 input: map[string]interface{}{ 973 "a": struct{ Int int }{2}, 974 "b": 1, 975 }, 976 wantOutput: "b = 1\n\n[a]\n Int = 2\n", 977 }, 978 "nested map": { 979 input: map[string]map[string]int{ 980 "a": {"b": 1}, 981 "c": {"d": 2}, 982 }, 983 wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n", 984 }, 985 "nested struct": { 986 input: struct{ Struct struct{ Int int } }{ 987 struct{ Int int }{1}, 988 }, 989 wantOutput: "[Struct]\n Int = 1\n", 990 }, 991 "nested struct and non-struct field": { 992 input: struct { 993 Struct struct{ Int int } 994 Bool bool 995 }{struct{ Int int }{1}, true}, 996 wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n", 997 }, 998 "2 nested structs": { 999 input: struct{ Struct1, Struct2 struct{ Int int } }{ 1000 struct{ Int int }{1}, struct{ Int int }{2}, 1001 }, 1002 wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n", 1003 }, 1004 "deeply nested structs": { 1005 input: struct { 1006 Struct1, Struct2 struct{ Struct3 *struct{ Int int } } 1007 }{ 1008 struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}}, 1009 struct{ Struct3 *struct{ Int int } }{nil}, 1010 }, 1011 wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" + 1012 "\n\n[Struct2]\n", 1013 }, 1014 "nested struct with nil struct elem": { 1015 input: struct { 1016 Struct struct{ Inner *struct{ Int int } } 1017 }{ 1018 struct{ Inner *struct{ Int int } }{nil}, 1019 }, 1020 wantOutput: "[Struct]\n", 1021 }, 1022 "nested struct with no fields": { 1023 input: struct { 1024 Struct struct{ Inner struct{} } 1025 }{ 1026 struct{ Inner struct{} }{struct{}{}}, 1027 }, 1028 wantOutput: "[Struct]\n [Struct.Inner]\n", 1029 }, 1030 "struct with tags": { 1031 input: struct { 1032 Struct struct { 1033 Int int `toml:"_int"` 1034 } `toml:"_struct"` 1035 Bool bool `toml:"_bool"` 1036 }{ 1037 struct { 1038 Int int `toml:"_int"` 1039 }{1}, true, 1040 }, 1041 wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n", 1042 }, 1043 "embedded struct": { 1044 input: struct{ Embedded }{Embedded{1}}, 1045 wantOutput: "_int = 1\n", 1046 }, 1047 "embedded *struct": { 1048 input: struct{ *Embedded }{&Embedded{1}}, 1049 wantOutput: "_int = 1\n", 1050 }, 1051 "nested embedded struct": { 1052 input: struct { 1053 Struct struct{ Embedded } `toml:"_struct"` 1054 }{struct{ Embedded }{Embedded{1}}}, 1055 wantOutput: "[_struct]\n _int = 1\n", 1056 }, 1057 "nested embedded *struct": { 1058 input: struct { 1059 Struct struct{ *Embedded } `toml:"_struct"` 1060 }{struct{ *Embedded }{&Embedded{1}}}, 1061 wantOutput: "[_struct]\n _int = 1\n", 1062 }, 1063 "embedded non-struct": { 1064 input: struct{ NonStruct }{5}, 1065 wantOutput: "NonStruct = 5\n", 1066 }, 1067 "array of tables": { 1068 input: struct { 1069 Structs []*struct{ Int int } `toml:"struct"` 1070 }{ 1071 []*struct{ Int int }{{1}, {3}}, 1072 }, 1073 wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n", 1074 }, 1075 "array of tables order": { 1076 input: map[string]interface{}{ 1077 "map": map[string]interface{}{ 1078 "zero": 5, 1079 "arr": []map[string]int{ 1080 { 1081 "friend": 5, 1082 }, 1083 }, 1084 }, 1085 }, 1086 wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n", 1087 }, 1088 "empty key name": { 1089 input: map[string]int{"": 1}, 1090 wantOutput: `"" = 1` + "\n", 1091 }, 1092 "key with \\n escape": { 1093 input: map[string]string{"\n": "\n"}, 1094 wantOutput: `"\n" = "\n"` + "\n", 1095 }, 1096 1097 "empty map name": { 1098 input: map[string]interface{}{ 1099 "": map[string]int{"v": 1}, 1100 }, 1101 wantOutput: "[\"\"]\n v = 1\n", 1102 }, 1103 "(error) top-level slice": { 1104 input: []struct{ Int int }{{1}, {2}, {3}}, 1105 wantError: errNoKey, 1106 }, 1107 "(error) map no string key": { 1108 input: map[int]string{1: ""}, 1109 wantError: errNonString, 1110 }, 1111 1112 "tbl-in-arr-struct": { 1113 input: struct { 1114 Arr [][]struct{ A, B, C int } 1115 }{[][]struct{ A, B, C int }{{{1, 2, 3}, {4, 5, 6}}}}, 1116 wantOutput: "Arr = [[{A = 1, B = 2, C = 3}, {A = 4, B = 5, C = 6}]]", 1117 }, 1118 1119 "tbl-in-arr-map": { 1120 input: map[string]interface{}{ 1121 "arr": []interface{}{[]interface{}{ 1122 map[string]interface{}{ 1123 "a": []interface{}{"hello", "world"}, 1124 "b": []interface{}{1.12, 4.1}, 1125 "c": 1, 1126 "d": map[string]interface{}{"e": "E"}, 1127 "f": struct{ A, B int }{1, 2}, 1128 "g": []struct{ A, B int }{{3, 4}, {5, 6}}, 1129 }, 1130 }}, 1131 }, 1132 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}]}]]`, 1133 }, 1134 1135 "slice of slice": { 1136 input: struct { 1137 Slices [][]struct{ Int int } 1138 }{ 1139 [][]struct{ Int int }{{{1}}, {{2}}, {{3}}}, 1140 }, 1141 wantOutput: "Slices = [[{Int = 1}], [{Int = 2}], [{Int = 3}]]", 1142 }, 1143 } 1144 for label, test := range tests { 1145 encodeExpected(t, label, test.input, test.wantOutput, test.wantError) 1146 } 1147 } 1148 1149 func encodeExpected(t *testing.T, label string, val interface{}, want string, wantErr error) { 1150 t.Helper() 1151 t.Run(label, func(t *testing.T) { 1152 t.Helper() 1153 var buf bytes.Buffer 1154 err := NewEncoder(&buf).Encode(val) 1155 if err != wantErr { 1156 if wantErr != nil { 1157 if wantErr == errAnything && err != nil { 1158 return 1159 } 1160 t.Errorf("want Encode error %v, got %v", wantErr, err) 1161 } else { 1162 t.Errorf("Encode failed: %s", err) 1163 } 1164 } 1165 if err != nil { 1166 return 1167 } 1168 1169 have := strings.TrimSpace(buf.String()) 1170 want = strings.TrimSpace(want) 1171 if want != have { 1172 t.Errorf("\nhave:\n%s\nwant:\n%s\n", have, want) 1173 } 1174 }) 1175 }