git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/toml/decode_test.go (about) 1 package toml 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io/ioutil" 9 "math" 10 "os" 11 "reflect" 12 "strconv" 13 "strings" 14 "testing" 15 "time" 16 17 "git.sr.ht/~pingoo/stdx/toml/internal" 18 ) 19 20 func TestDecodeReader(t *testing.T) { 21 var i struct{ A int } 22 meta, err := DecodeReader(strings.NewReader("a = 42"), &i) 23 if err != nil { 24 t.Fatal(err) 25 } 26 have := fmt.Sprintf("%v %v %v", i, meta.Keys(), meta.Type("a")) 27 want := "{42} [a] Integer" 28 if have != want { 29 t.Errorf("\nhave: %s\nwant: %s", have, want) 30 } 31 } 32 33 func TestDecodeFile(t *testing.T) { 34 tmp, err := ioutil.TempFile("", "toml-") 35 if err != nil { 36 t.Fatal(err) 37 } 38 defer os.Remove(tmp.Name()) 39 if _, err := tmp.WriteString("a = 42"); err != nil { 40 t.Fatal(err) 41 } 42 if err := tmp.Close(); err != nil { 43 t.Fatal(err) 44 } 45 46 var i struct{ A int } 47 meta, err := DecodeFile(tmp.Name(), &i) 48 if err != nil { 49 t.Fatal(err) 50 } 51 52 have := fmt.Sprintf("%v %v %v", i, meta.Keys(), meta.Type("a")) 53 want := "{42} [a] Integer" 54 if have != want { 55 t.Errorf("\nhave: %s\nwant: %s", have, want) 56 } 57 } 58 59 func TestDecodeBOM(t *testing.T) { 60 for _, tt := range [][]byte{ 61 []byte("\xff\xfea = \"b\""), 62 []byte("\xfe\xffa = \"b\""), 63 } { 64 t.Run("", func(t *testing.T) { 65 var s struct{ A string } 66 _, err := Decode(string(tt), &s) 67 if err != nil { 68 t.Fatal(err) 69 } 70 if s.A != "b" { 71 t.Errorf(`s.A is not "b" but %q`, s.A) 72 } 73 }) 74 } 75 } 76 77 func TestDecodeEmbedded(t *testing.T) { 78 type Dog struct{ Name string } 79 type Age int 80 type cat struct{ Name string } 81 82 for _, test := range []struct { 83 label string 84 input string 85 decodeInto interface{} 86 wantDecoded interface{} 87 }{ 88 { 89 label: "embedded struct", 90 input: `Name = "milton"`, 91 decodeInto: &struct{ Dog }{}, 92 wantDecoded: &struct{ Dog }{Dog{"milton"}}, 93 }, 94 { 95 label: "embedded non-nil pointer to struct", 96 input: `Name = "milton"`, 97 decodeInto: &struct{ *Dog }{}, 98 wantDecoded: &struct{ *Dog }{&Dog{"milton"}}, 99 }, 100 { 101 label: "embedded nil pointer to struct", 102 input: ``, 103 decodeInto: &struct{ *Dog }{}, 104 wantDecoded: &struct{ *Dog }{nil}, 105 }, 106 { 107 label: "unexported embedded struct", 108 input: `Name = "socks"`, 109 decodeInto: &struct{ cat }{}, 110 wantDecoded: &struct{ cat }{cat{"socks"}}, 111 }, 112 { 113 label: "embedded int", 114 input: `Age = -5`, 115 decodeInto: &struct{ Age }{}, 116 wantDecoded: &struct{ Age }{-5}, 117 }, 118 } { 119 _, err := Decode(test.input, test.decodeInto) 120 if err != nil { 121 t.Fatal(err) 122 } 123 if !reflect.DeepEqual(test.wantDecoded, test.decodeInto) { 124 t.Errorf("%s: want decoded == %+v, got %+v", 125 test.label, test.wantDecoded, test.decodeInto) 126 } 127 } 128 } 129 130 func TestDecodeErrors(t *testing.T) { 131 tests := []struct { 132 s interface{} 133 toml string 134 wantErr string 135 }{ 136 { 137 &struct{ V int8 }{}, 138 `V = 999`, 139 `toml: line 1 (last key "V"): 999 is out of range for int8`, 140 }, 141 { 142 &struct{ V float32 }{}, 143 `V = 999999999999999`, 144 `toml: line 1 (last key "V"): 999999999999999 is out of range for float32`, 145 }, 146 { 147 &struct{ V string }{}, 148 `V = 5`, 149 `toml: line 1 (last key "V"): incompatible types: TOML value has type int64; destination has type string`, 150 }, 151 { 152 &struct{ V interface{ ASD() } }{}, 153 `V = 999`, 154 `toml: line 1 (last key "V"): unsupported type interface { ASD() }`, 155 }, 156 { 157 &struct{ V struct{ V int } }{}, 158 `V = 999`, 159 `toml: line 1 (last key "V"): type mismatch for struct { V int }: expected table but found int64`, 160 }, 161 { 162 &struct{ V [1]int }{}, 163 `V = [1,2,3]`, 164 `toml: line 1 (last key "V"): expected array length 1; got TOML array of length 3`, 165 }, 166 { 167 &struct{ V struct{ N int8 } }{}, 168 `V.N = 999`, 169 `toml: line 1 (last key "V.N"): 999 is out of range for int8`, 170 }, 171 { 172 &struct{ V struct{ N float32 } }{}, 173 `V.N = 999999999999999`, 174 `toml: line 1 (last key "V.N"): 999999999999999 is out of range for float32`, 175 }, 176 { 177 &struct{ V struct{ N string } }{}, 178 `V.N = 5`, 179 `toml: line 1 (last key "V.N"): incompatible types: TOML value has type int64; destination has type string`, 180 }, 181 { 182 &struct { 183 V struct{ N interface{ ASD() } } 184 }{}, 185 `V.N = 999`, 186 `toml: line 1 (last key "V.N"): unsupported type interface { ASD() }`, 187 }, 188 { 189 &struct{ V struct{ N struct{ V int } } }{}, 190 `V.N = 999`, 191 `toml: line 1 (last key "V.N"): type mismatch for struct { V int }: expected table but found int64`, 192 }, 193 { 194 &struct{ V struct{ N [1]int } }{}, 195 `V.N = [1,2,3]`, 196 `toml: line 1 (last key "V.N"): expected array length 1; got TOML array of length 3`, 197 }, 198 } 199 200 for _, tt := range tests { 201 _, err := Decode(tt.toml, tt.s) 202 if err == nil { 203 t.Fatal("err is nil") 204 } 205 if err.Error() != tt.wantErr { 206 t.Errorf("\nhave: %q\nwant: %q", err, tt.wantErr) 207 } 208 } 209 } 210 211 func TestDecodeIgnoreFields(t *testing.T) { 212 const input = ` 213 Number = 123 214 - = 234 215 ` 216 var s struct { 217 Number int `toml:"-"` 218 } 219 if _, err := Decode(input, &s); err != nil { 220 t.Fatal(err) 221 } 222 if s.Number != 0 { 223 t.Errorf("got: %d; want 0", s.Number) 224 } 225 } 226 227 func TestDecodeTableArrays(t *testing.T) { 228 var tomlTableArrays = ` 229 [[albums]] 230 name = "Born to Run" 231 232 [[albums.songs]] 233 name = "Jungleland" 234 235 [[albums.songs]] 236 name = "Meeting Across the River" 237 238 [[albums]] 239 name = "Born in the USA" 240 241 [[albums.songs]] 242 name = "Glory Days" 243 244 [[albums.songs]] 245 name = "Dancing in the Dark" 246 ` 247 248 type Song struct { 249 Name string 250 } 251 252 type Album struct { 253 Name string 254 Songs []Song 255 } 256 257 type Music struct { 258 Albums []Album 259 } 260 261 expected := Music{[]Album{ 262 {"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}}, 263 {"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}}, 264 }} 265 var got Music 266 if _, err := Decode(tomlTableArrays, &got); err != nil { 267 t.Fatal(err) 268 } 269 if !reflect.DeepEqual(expected, got) { 270 t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) 271 } 272 } 273 274 func TestDecodePointers(t *testing.T) { 275 type Object struct { 276 Type string 277 Description string 278 } 279 280 type Dict struct { 281 NamedObject map[string]*Object 282 BaseObject *Object 283 Strptr *string 284 Strptrs []*string 285 } 286 s1, s2, s3 := "blah", "abc", "def" 287 expected := &Dict{ 288 Strptr: &s1, 289 Strptrs: []*string{&s2, &s3}, 290 NamedObject: map[string]*Object{ 291 "foo": {"FOO", "fooooo!!!"}, 292 "bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"}, 293 }, 294 BaseObject: &Object{"BASE", "da base"}, 295 } 296 297 ex1 := ` 298 Strptr = "blah" 299 Strptrs = ["abc", "def"] 300 301 [NamedObject.foo] 302 Type = "FOO" 303 Description = "fooooo!!!" 304 305 [NamedObject.bar] 306 Type = "BAR" 307 Description = "ba-ba-ba-ba-barrrr!!!" 308 309 [BaseObject] 310 Type = "BASE" 311 Description = "da base" 312 ` 313 dict := new(Dict) 314 _, err := Decode(ex1, dict) 315 if err != nil { 316 t.Errorf("Decode error: %v", err) 317 } 318 if !reflect.DeepEqual(expected, dict) { 319 t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict) 320 } 321 } 322 323 func TestDecodeBadDatetime(t *testing.T) { 324 var x struct{ T time.Time } 325 for _, s := range []string{"123", "1230"} { 326 input := "T = " + s 327 if _, err := Decode(input, &x); err == nil { 328 t.Errorf("Expected invalid DateTime error for %q", s) 329 } 330 } 331 } 332 333 type sphere struct { 334 Center [3]float64 335 Radius float64 336 } 337 338 func TestDecodeArrayWrongSize(t *testing.T) { 339 var s1 sphere 340 if _, err := Decode(`center = [0.1, 2.3]`, &s1); err == nil { 341 t.Fatal("Expected array type mismatch error") 342 } 343 } 344 345 func TestDecodeIntOverflow(t *testing.T) { 346 type table struct { 347 Value int8 348 } 349 var tab table 350 if _, err := Decode(`value = 500`, &tab); err == nil { 351 t.Fatal("Expected integer out-of-bounds error.") 352 } 353 } 354 355 func TestDecodeFloatOverflow(t *testing.T) { 356 tests := []struct { 357 value string 358 overflow bool 359 }{ 360 {fmt.Sprintf(`F32 = %f`, math.MaxFloat64), true}, 361 {fmt.Sprintf(`F32 = %f`, -math.MaxFloat64), true}, 362 {fmt.Sprintf(`F32 = %f`, math.MaxFloat32*1.1), true}, 363 {fmt.Sprintf(`F32 = %f`, -math.MaxFloat32*1.1), true}, 364 {fmt.Sprintf(`F32 = %d`, maxSafeFloat32Int+1), true}, 365 {fmt.Sprintf(`F32 = %d`, -maxSafeFloat32Int-1), true}, 366 {fmt.Sprintf(`F64 = %d`, maxSafeFloat64Int+1), true}, 367 {fmt.Sprintf(`F64 = %d`, -maxSafeFloat64Int-1), true}, 368 369 {fmt.Sprintf(`F32 = %f`, math.MaxFloat32), false}, 370 {fmt.Sprintf(`F32 = %f`, -math.MaxFloat32), false}, 371 {fmt.Sprintf(`F32 = %d`, maxSafeFloat32Int), false}, 372 {fmt.Sprintf(`F32 = %d`, -maxSafeFloat32Int), false}, 373 {fmt.Sprintf(`F64 = %f`, math.MaxFloat64), false}, 374 {fmt.Sprintf(`F64 = %f`, -math.MaxFloat64), false}, 375 {fmt.Sprintf(`F64 = %f`, math.MaxFloat32), false}, 376 {fmt.Sprintf(`F64 = %f`, -math.MaxFloat32), false}, 377 {fmt.Sprintf(`F64 = %d`, maxSafeFloat64Int), false}, 378 {fmt.Sprintf(`F64 = %d`, -maxSafeFloat64Int), false}, 379 } 380 381 for _, tt := range tests { 382 t.Run("", func(t *testing.T) { 383 var tab struct { 384 F32 float32 385 F64 float64 386 } 387 _, err := Decode(tt.value, &tab) 388 389 if tt.overflow && err == nil { 390 t.Fatal("expected error, but err is nil") 391 } 392 if (tt.overflow && !errorContains(err, "out of range")) || (!tt.overflow && err != nil) { 393 t.Fatalf("unexpected error:\n%v", err) 394 } 395 }) 396 } 397 } 398 399 func TestDecodeSizedInts(t *testing.T) { 400 type table struct { 401 U8 uint8 402 U16 uint16 403 U32 uint32 404 U64 uint64 405 U uint 406 I8 int8 407 I16 int16 408 I32 int32 409 I64 int64 410 I int 411 } 412 answer := table{1, 1, 1, 1, 1, -1, -1, -1, -1, -1} 413 toml := ` 414 u8 = 1 415 u16 = 1 416 u32 = 1 417 u64 = 1 418 u = 1 419 i8 = -1 420 i16 = -1 421 i32 = -1 422 i64 = -1 423 i = -1 424 ` 425 var tab table 426 if _, err := Decode(toml, &tab); err != nil { 427 t.Fatal(err.Error()) 428 } 429 if answer != tab { 430 t.Fatalf("Expected %#v but got %#v", answer, tab) 431 } 432 } 433 434 type NopUnmarshalTOML int 435 436 func (n *NopUnmarshalTOML) UnmarshalTOML(p interface{}) error { 437 *n = 42 438 return nil 439 } 440 441 func TestDecodeTypes(t *testing.T) { 442 type ( 443 mystr string 444 myiface interface{} 445 ) 446 447 for _, tt := range []struct { 448 v interface{} 449 want string 450 wantErr string 451 }{ 452 {new(map[string]bool), "&map[F:true]", ""}, 453 {new(map[mystr]bool), "&map[F:true]", ""}, 454 {new(NopUnmarshalTOML), "42", ""}, 455 {new(map[interface{}]bool), "&map[F:true]", ""}, 456 {new(map[myiface]bool), "&map[F:true]", ""}, 457 458 {3, "", `toml: cannot decode to non-pointer "int"`}, 459 {map[string]interface{}{}, "", `toml: cannot decode to non-pointer "map[string]interface {}"`}, 460 461 {(*int)(nil), "", `toml: cannot decode to nil value of "*int"`}, 462 {(*Unmarshaler)(nil), "", `toml: cannot decode to nil value of "*toml.Unmarshaler"`}, 463 {nil, "", `toml: cannot decode to non-pointer <nil>`}, 464 465 {new(map[int]string), "", "toml: cannot decode to a map with non-string key type"}, 466 467 {new(struct{ F int }), "", `toml: line 1 (last key "F"): incompatible types: TOML value has type bool; destination has type integer`}, 468 {new(map[string]int), "", `toml: line 1 (last key "F"): incompatible types: TOML value has type bool; destination has type integer`}, 469 {new(int), "", `toml: cannot decode to type int`}, 470 {new([]int), "", "toml: cannot decode to type []int"}, 471 } { 472 t.Run(fmt.Sprintf("%T", tt.v), func(t *testing.T) { 473 _, err := Decode(`F = true`, tt.v) 474 if !errorContains(err, tt.wantErr) { 475 t.Fatalf("wrong error\nhave: %q\nwant: %q", err, tt.wantErr) 476 } 477 478 if err == nil { 479 have := fmt.Sprintf("%v", tt.v) 480 if n, ok := tt.v.(*NopUnmarshalTOML); ok { 481 have = fmt.Sprintf("%v", *n) 482 } 483 if have != tt.want { 484 t.Errorf("\nhave: %s\nwant: %s", have, tt.want) 485 } 486 } 487 }) 488 } 489 } 490 491 func TestUnmarshaler(t *testing.T) { 492 var tomlBlob = ` 493 [dishes.hamboogie] 494 name = "Hamboogie with fries" 495 price = 10.99 496 497 [[dishes.hamboogie.ingredients]] 498 name = "Bread Bun" 499 500 [[dishes.hamboogie.ingredients]] 501 name = "Lettuce" 502 503 [[dishes.hamboogie.ingredients]] 504 name = "Real Beef Patty" 505 506 [[dishes.hamboogie.ingredients]] 507 name = "Tomato" 508 509 [dishes.eggsalad] 510 name = "Egg Salad with rice" 511 price = 3.99 512 513 [[dishes.eggsalad.ingredients]] 514 name = "Egg" 515 516 [[dishes.eggsalad.ingredients]] 517 name = "Mayo" 518 519 [[dishes.eggsalad.ingredients]] 520 name = "Rice" 521 ` 522 m := &menu{} 523 if _, err := Decode(tomlBlob, m); err != nil { 524 t.Fatal(err) 525 } 526 527 if len(m.Dishes) != 2 { 528 t.Log("two dishes should be loaded with UnmarshalTOML()") 529 t.Errorf("expected %d but got %d", 2, len(m.Dishes)) 530 } 531 532 eggSalad := m.Dishes["eggsalad"] 533 if _, ok := interface{}(eggSalad).(dish); !ok { 534 t.Errorf("expected a dish") 535 } 536 537 if eggSalad.Name != "Egg Salad with rice" { 538 t.Errorf("expected the dish to be named 'Egg Salad with rice'") 539 } 540 541 if len(eggSalad.Ingredients) != 3 { 542 t.Log("dish should be loaded with UnmarshalTOML()") 543 t.Errorf("expected %d but got %d", 3, len(eggSalad.Ingredients)) 544 } 545 546 found := false 547 for _, i := range eggSalad.Ingredients { 548 if i.Name == "Rice" { 549 found = true 550 break 551 } 552 } 553 if !found { 554 t.Error("Rice was not loaded in UnmarshalTOML()") 555 } 556 557 // test on a value - must be passed as * 558 o := menu{} 559 if _, err := Decode(tomlBlob, &o); err != nil { 560 t.Fatal(err) 561 } 562 563 } 564 565 func TestDecodeInlineTable(t *testing.T) { 566 input := ` 567 [CookieJar] 568 Types = {Chocolate = "yummy", Oatmeal = "best ever"} 569 570 [Seasons] 571 Locations = {NY = {Temp = "not cold", Rating = 4}, MI = {Temp = "freezing", Rating = 9}} 572 ` 573 type cookieJar struct { 574 Types map[string]string 575 } 576 type properties struct { 577 Temp string 578 Rating int 579 } 580 type seasons struct { 581 Locations map[string]properties 582 } 583 type wrapper struct { 584 CookieJar cookieJar 585 Seasons seasons 586 } 587 var got wrapper 588 589 meta, err := Decode(input, &got) 590 if err != nil { 591 t.Fatal(err) 592 } 593 want := wrapper{ 594 CookieJar: cookieJar{ 595 Types: map[string]string{ 596 "Chocolate": "yummy", 597 "Oatmeal": "best ever", 598 }, 599 }, 600 Seasons: seasons{ 601 Locations: map[string]properties{ 602 "NY": { 603 Temp: "not cold", 604 Rating: 4, 605 }, 606 "MI": { 607 Temp: "freezing", 608 Rating: 9, 609 }, 610 }, 611 }, 612 } 613 if !reflect.DeepEqual(got, want) { 614 t.Fatalf("after decode, got:\n\n%#v\n\nwant:\n\n%#v", got, want) 615 } 616 if len(meta.keys) != 12 { 617 t.Errorf("after decode, got %d meta keys; want 12", len(meta.keys)) 618 } 619 if len(meta.keyInfo) != 12 { 620 t.Errorf("after decode, got %d meta keyInfo; want 12", len(meta.keyInfo)) 621 } 622 } 623 624 func TestDecodeInlineTableArray(t *testing.T) { 625 type point struct { 626 X, Y, Z int 627 } 628 var got struct { 629 Points []point 630 } 631 // Example inline table array from the spec. 632 const in = ` 633 points = [ { x = 1, y = 2, z = 3 }, 634 { x = 7, y = 8, z = 9 }, 635 { x = 2, y = 4, z = 8 } ] 636 637 ` 638 if _, err := Decode(in, &got); err != nil { 639 t.Fatal(err) 640 } 641 want := []point{ 642 {X: 1, Y: 2, Z: 3}, 643 {X: 7, Y: 8, Z: 9}, 644 {X: 2, Y: 4, Z: 8}, 645 } 646 if !reflect.DeepEqual(got.Points, want) { 647 t.Errorf("got %#v; want %#v", got.Points, want) 648 } 649 } 650 651 type menu struct { 652 Dishes map[string]dish 653 } 654 655 func (m *menu) UnmarshalTOML(p interface{}) error { 656 m.Dishes = make(map[string]dish) 657 data, _ := p.(map[string]interface{}) 658 dishes := data["dishes"].(map[string]interface{}) 659 for n, v := range dishes { 660 if d, ok := v.(map[string]interface{}); ok { 661 nd := dish{} 662 nd.UnmarshalTOML(d) 663 m.Dishes[n] = nd 664 } else { 665 return fmt.Errorf("not a dish") 666 } 667 } 668 return nil 669 } 670 671 type dish struct { 672 Name string 673 Price float32 674 Ingredients []ingredient 675 } 676 677 func (d *dish) UnmarshalTOML(p interface{}) error { 678 data, _ := p.(map[string]interface{}) 679 d.Name, _ = data["name"].(string) 680 d.Price, _ = data["price"].(float32) 681 ingredients, _ := data["ingredients"].([]map[string]interface{}) 682 for _, e := range ingredients { 683 n, _ := interface{}(e).(map[string]interface{}) 684 name, _ := n["name"].(string) 685 i := ingredient{name} 686 d.Ingredients = append(d.Ingredients, i) 687 } 688 return nil 689 } 690 691 type ingredient struct { 692 Name string 693 } 694 695 func TestDecodeSlices(t *testing.T) { 696 type ( 697 T struct { 698 Arr []string 699 Tbl map[string]interface{} 700 } 701 M map[string]interface{} 702 ) 703 tests := []struct { 704 input string 705 in, want T 706 }{ 707 {"", 708 T{}, T{}}, 709 710 // Leave existing values alone. 711 {"", 712 T{[]string{}, M{"arr": []string{}}}, 713 T{[]string{}, M{"arr": []string{}}}}, 714 {"", 715 T{[]string{"a"}, M{"arr": []string{"b"}}}, 716 T{[]string{"a"}, M{"arr": []string{"b"}}}}, 717 718 // Empty array always allocates (see #339) 719 {`arr = [] 720 tbl = {arr = []}`, 721 T{}, 722 T{[]string{}, M{"arr": []interface{}{}}}}, 723 {`arr = [] 724 tbl = {}`, 725 T{[]string{}, M{}}, 726 T{[]string{}, M{}}}, 727 728 {`arr = []`, 729 T{[]string{"a"}, M{}}, 730 T{[]string{}, M{}}}, 731 732 {`arr = ["x"] 733 tbl = {arr=["y"]}`, 734 T{}, 735 T{[]string{"x"}, M{"arr": []interface{}{"y"}}}}, 736 {`arr = ["x"] 737 tbl = {arr=["y"]}`, 738 T{[]string{}, M{}}, 739 T{[]string{"x"}, M{"arr": []interface{}{"y"}}}}, 740 {`arr = ["x"] 741 tbl = {arr=["y"]}`, 742 T{[]string{"a", "b"}, M{"arr": []interface{}{"c", "d"}}}, 743 T{[]string{"x"}, M{"arr": []interface{}{"y"}}}}, 744 } 745 746 for _, tt := range tests { 747 t.Run("", func(t *testing.T) { 748 _, err := Decode(tt.input, &tt.in) 749 if err != nil { 750 t.Error(err) 751 } 752 if !reflect.DeepEqual(tt.in, tt.want) { 753 t.Errorf("\nhave: %#v\nwant: %#v", tt.in, tt.want) 754 } 755 }) 756 } 757 } 758 759 func TestDecodePrimitive(t *testing.T) { 760 type S struct { 761 P Primitive 762 } 763 type T struct { 764 S []int 765 } 766 slicep := func(s []int) *[]int { return &s } 767 arrayp := func(a [2]int) *[2]int { return &a } 768 mapp := func(m map[string]int) *map[string]int { return &m } 769 for i, tt := range []struct { 770 v interface{} 771 input string 772 want interface{} 773 }{ 774 // slices 775 {slicep(nil), "", slicep(nil)}, 776 {slicep([]int{}), "", slicep([]int{})}, 777 {slicep([]int{1, 2, 3}), "", slicep([]int{1, 2, 3})}, 778 {slicep(nil), "P = [1,2]", slicep([]int{1, 2})}, 779 {slicep([]int{}), "P = [1,2]", slicep([]int{1, 2})}, 780 {slicep([]int{1, 2, 3}), "P = [1,2]", slicep([]int{1, 2})}, 781 782 // arrays 783 {arrayp([2]int{2, 3}), "", arrayp([2]int{2, 3})}, 784 {arrayp([2]int{2, 3}), "P = [3,4]", arrayp([2]int{3, 4})}, 785 786 // maps 787 {mapp(nil), "", mapp(nil)}, 788 {mapp(map[string]int{}), "", mapp(map[string]int{})}, 789 {mapp(map[string]int{"a": 1}), "", mapp(map[string]int{"a": 1})}, 790 {mapp(nil), "[P]\na = 2", mapp(map[string]int{"a": 2})}, 791 {mapp(map[string]int{}), "[P]\na = 2", mapp(map[string]int{"a": 2})}, 792 {mapp(map[string]int{"a": 1, "b": 3}), "[P]\na = 2", mapp(map[string]int{"a": 2, "b": 3})}, 793 794 // structs 795 {&T{nil}, "[P]", &T{nil}}, 796 {&T{[]int{}}, "[P]", &T{[]int{}}}, 797 {&T{[]int{1, 2, 3}}, "[P]", &T{[]int{1, 2, 3}}}, 798 {&T{nil}, "[P]\nS = [1,2]", &T{[]int{1, 2}}}, 799 {&T{[]int{}}, "[P]\nS = [1,2]", &T{[]int{1, 2}}}, 800 {&T{[]int{1, 2, 3}}, "[P]\nS = [1,2]", &T{[]int{1, 2}}}, 801 } { 802 var s S 803 md, err := Decode(tt.input, &s) 804 if err != nil { 805 t.Errorf("[%d] Decode error: %s", i, err) 806 continue 807 } 808 if err := md.PrimitiveDecode(s.P, tt.v); err != nil { 809 t.Errorf("[%d] PrimitiveDecode error: %s", i, err) 810 continue 811 } 812 if !reflect.DeepEqual(tt.v, tt.want) { 813 t.Errorf("[%d] got %#v; want %#v", i, tt.v, tt.want) 814 } 815 } 816 } 817 818 func TestDecodeDatetime(t *testing.T) { 819 // Test here in addition to toml-test to ensure the TZs are correct. 820 tz7 := time.FixedZone("", -3600*7) 821 822 for _, tt := range []struct { 823 in string 824 want time.Time 825 }{ 826 // Offset datetime 827 {"1979-05-27T07:32:00Z", time.Date(1979, 05, 27, 07, 32, 0, 0, time.UTC)}, 828 {"1979-05-27T07:32:00.999999Z", time.Date(1979, 05, 27, 07, 32, 0, 999999000, time.UTC)}, 829 {"1979-05-27T00:32:00-07:00", time.Date(1979, 05, 27, 00, 32, 0, 0, tz7)}, 830 {"1979-05-27T00:32:00.999999-07:00", time.Date(1979, 05, 27, 00, 32, 0, 999999000, tz7)}, 831 {"1979-05-27T00:32:00.24-07:00", time.Date(1979, 05, 27, 00, 32, 0, 240000000, tz7)}, 832 {"1979-05-27 07:32:00Z", time.Date(1979, 05, 27, 07, 32, 0, 0, time.UTC)}, 833 {"1979-05-27t07:32:00z", time.Date(1979, 05, 27, 07, 32, 0, 0, time.UTC)}, 834 835 // Make sure the space between the datetime and "#" isn't lexed. 836 {"1979-05-27T07:32:12-07:00 # c", time.Date(1979, 05, 27, 07, 32, 12, 0, tz7)}, 837 838 // Local times. 839 {"1979-05-27T07:32:00", time.Date(1979, 05, 27, 07, 32, 0, 0, internal.LocalDatetime)}, 840 {"1979-05-27T07:32:00.999999", time.Date(1979, 05, 27, 07, 32, 0, 999999000, internal.LocalDatetime)}, 841 {"1979-05-27T07:32:00.25", time.Date(1979, 05, 27, 07, 32, 0, 250000000, internal.LocalDatetime)}, 842 {"1979-05-27", time.Date(1979, 05, 27, 0, 0, 0, 0, internal.LocalDate)}, 843 {"07:32:00", time.Date(0, 1, 1, 07, 32, 0, 0, internal.LocalTime)}, 844 {"07:32:00.999999", time.Date(0, 1, 1, 07, 32, 0, 999999000, internal.LocalTime)}, 845 } { 846 t.Run(tt.in, func(t *testing.T) { 847 var x struct{ D time.Time } 848 input := "d = " + tt.in 849 if _, err := Decode(input, &x); err != nil { 850 t.Fatalf("got error: %s", err) 851 } 852 853 if h, w := x.D.Format(time.RFC3339Nano), tt.want.Format(time.RFC3339Nano); h != w { 854 t.Errorf("\nhave: %s\nwant: %s", h, w) 855 } 856 }) 857 } 858 } 859 860 func TestDecodeTextUnmarshaler(t *testing.T) { 861 tests := []struct { 862 name string 863 t interface{} 864 toml string 865 want string 866 }{ 867 { 868 "time.Time", 869 struct{ Time time.Time }{}, 870 "Time = 1987-07-05T05:45:00Z", 871 "map[Time:1987-07-05 05:45:00 +0000 UTC]", 872 }, 873 { 874 "*time.Time", 875 struct{ Time *time.Time }{}, 876 "Time = 1988-07-05T05:45:00Z", 877 "map[Time:1988-07-05 05:45:00 +0000 UTC]", 878 }, 879 { 880 "map[string]time.Time", 881 struct{ Times map[string]time.Time }{}, 882 "Times.one = 1989-07-05T05:45:00Z\nTimes.two = 1990-07-05T05:45:00Z", 883 "map[Times:map[one:1989-07-05 05:45:00 +0000 UTC two:1990-07-05 05:45:00 +0000 UTC]]", 884 }, 885 { 886 "map[string]*time.Time", 887 struct{ Times map[string]*time.Time }{}, 888 "Times.one = 1989-07-05T05:45:00Z\nTimes.two = 1990-07-05T05:45:00Z", 889 "map[Times:map[one:1989-07-05 05:45:00 +0000 UTC two:1990-07-05 05:45:00 +0000 UTC]]", 890 }, 891 } 892 893 for _, tt := range tests { 894 t.Run(tt.name, func(t *testing.T) { 895 _, err := Decode(tt.toml, &tt.t) 896 if err != nil { 897 t.Fatal(err) 898 } 899 900 have := fmt.Sprintf("%v", tt.t) 901 if have != tt.want { 902 t.Errorf("\nhave: %s\nwant: %s", have, tt.want) 903 } 904 }) 905 } 906 } 907 908 func TestDecodeDuration(t *testing.T) { 909 tests := []struct { 910 in interface{} 911 toml, want, wantErr string 912 }{ 913 {&struct{ T time.Duration }{}, `t = "0s"`, 914 "&{0s}", ""}, 915 {&struct{ T time.Duration }{}, `t = "5m4s"`, 916 "&{5m4s}", ""}, 917 {&struct{ T time.Duration }{}, `t = "4.000000002s"`, 918 "&{4.000000002s}", ""}, 919 920 {&struct{ T time.Duration }{}, `t = 0`, 921 "&{0s}", ""}, 922 {&struct{ T time.Duration }{}, `t = 12345678`, 923 "&{12.345678ms}", ""}, 924 925 {&struct{ T *time.Duration }{}, `T = "5s"`, 926 "&{5s}", ""}, 927 {&struct{ T *time.Duration }{}, `T = 5`, 928 "&{5ns}", ""}, 929 930 {&struct{ T map[string]time.Duration }{}, `T.dur = "5s"`, 931 "&{map[dur:5s]}", ""}, 932 {&struct{ T map[string]*time.Duration }{}, `T.dur = "5s"`, 933 "&{map[dur:5s]}", ""}, 934 935 {&struct{ T []time.Duration }{}, `T = ["5s"]`, 936 "&{[5s]}", ""}, 937 {&struct{ T []*time.Duration }{}, `T = ["5s"]`, 938 "&{[5s]}", ""}, 939 940 {&struct{ T time.Duration }{}, `t = "99 bottles of beer"`, "&{0s}", `invalid duration: "99 bottles of beer"`}, 941 {&struct{ T time.Duration }{}, `t = "one bottle of beer"`, "&{0s}", `invalid duration: "one bottle of beer"`}, 942 {&struct{ T time.Duration }{}, `t = 1.2`, "&{0s}", "incompatible types:"}, 943 {&struct{ T time.Duration }{}, `t = {}`, "&{0s}", "incompatible types:"}, 944 {&struct{ T time.Duration }{}, `t = []`, "&{0s}", "incompatible types:"}, 945 } 946 947 for _, tt := range tests { 948 t.Run("", func(t *testing.T) { 949 _, err := Decode(tt.toml, tt.in) 950 if !errorContains(err, tt.wantErr) { 951 t.Fatal(err) 952 } 953 954 have := fmt.Sprintf("%s", tt.in) 955 if have != tt.want { 956 t.Errorf("\nhave: %s\nwant: %s", have, tt.want) 957 } 958 }) 959 } 960 } 961 962 func TestDecodeJSONNumber(t *testing.T) { 963 tests := []struct { 964 in interface{} 965 toml, want, wantErr string 966 }{ 967 {&struct{ D json.Number }{}, `D = 2`, "&{2}", ""}, 968 {&struct{ D json.Number }{}, `D = 2.002`, "&{2.002}", ""}, 969 {&struct{ D *json.Number }{}, `D = 2`, "&{2}", ""}, 970 {&struct{ D *json.Number }{}, `D = 2.002`, "&{2.002}", ""}, 971 {&struct{ D []json.Number }{}, `D = [2, 3.03]`, "&{[2 3.03]}", ""}, 972 {&struct{ D []*json.Number }{}, `D = [2, 3.03]`, "&{[2 3.03]}", ""}, 973 {&struct{ D map[string]json.Number }{}, `D = {a=2, b=3.03}`, "&{map[a:2 b:3.03]}", ""}, 974 {&struct{ D map[string]*json.Number }{}, `D = {a=2, b=3.03}`, "&{map[a:2 b:3.03]}", ""}, 975 976 {&struct{ D json.Number }{}, `D = {}`, "&{}", "incompatible types"}, 977 {&struct{ D json.Number }{}, `D = []`, "&{}", "incompatible types"}, 978 {&struct{ D json.Number }{}, `D = "2"`, "&{}", "incompatible types"}, 979 } 980 981 for _, tt := range tests { 982 t.Run("", func(t *testing.T) { 983 _, err := Decode(tt.toml, tt.in) 984 if !errorContains(err, tt.wantErr) { 985 t.Fatal(err) 986 } 987 988 have := fmt.Sprintf("%s", tt.in) 989 if have != tt.want { 990 t.Errorf("\nhave: %s\nwant: %s", have, tt.want) 991 } 992 }) 993 } 994 } 995 996 func TestMetaDotConflict(t *testing.T) { 997 var m map[string]interface{} 998 meta, err := Decode(` 999 "a.b" = "str" 1000 a.b = 1 1001 "" = 2 1002 `, &m) 1003 if err != nil { 1004 t.Fatal(err) 1005 } 1006 1007 want := `"a.b"=String; a.b=Integer; ""=Integer` 1008 have := "" 1009 for i, k := range meta.Keys() { 1010 if i > 0 { 1011 have += "; " 1012 } 1013 have += k.String() + "=" + meta.Type(k...) 1014 } 1015 if have != want { 1016 t.Errorf("\nhave: %s\nwant: %s", have, want) 1017 } 1018 } 1019 1020 type ( 1021 Outer struct { 1022 Int *InnerInt 1023 Enum *Enum 1024 Slice *InnerArrayString 1025 } 1026 Enum int 1027 InnerString struct{ value string } 1028 InnerInt struct{ value int } 1029 InnerBool struct{ value bool } 1030 InnerArrayString struct{ value []string } 1031 ) 1032 1033 const ( 1034 NoValue Enum = iota 1035 OtherValue 1036 ) 1037 1038 func (e *Enum) Value() string { 1039 switch *e { 1040 case OtherValue: 1041 return "OTHER_VALUE" 1042 } 1043 return "" 1044 } 1045 1046 func (e *Enum) MarshalTOML() ([]byte, error) { 1047 return []byte(`"` + e.Value() + `"`), nil 1048 } 1049 1050 func (e *Enum) UnmarshalTOML(value interface{}) error { 1051 sValue, ok := value.(string) 1052 if !ok { 1053 return fmt.Errorf("value %v is not a string type", value) 1054 } 1055 for _, enum := range []Enum{NoValue, OtherValue} { 1056 if enum.Value() == sValue { 1057 *e = enum 1058 return nil 1059 } 1060 } 1061 return errors.New("invalid enum value") 1062 } 1063 1064 func (i *InnerInt) MarshalTOML() ([]byte, error) { 1065 return []byte(strconv.Itoa(i.value)), nil 1066 } 1067 func (i *InnerInt) UnmarshalTOML(value interface{}) error { 1068 iValue, ok := value.(int64) 1069 if !ok { 1070 return fmt.Errorf("value %v is not a int type", value) 1071 } 1072 i.value = int(iValue) 1073 return nil 1074 } 1075 1076 func (as *InnerArrayString) MarshalTOML() ([]byte, error) { 1077 return []byte("[\"" + strings.Join(as.value, "\", \"") + "\"]"), nil 1078 } 1079 1080 func (as *InnerArrayString) UnmarshalTOML(value interface{}) error { 1081 if value != nil { 1082 asValue, ok := value.([]interface{}) 1083 if !ok { 1084 return fmt.Errorf("value %v is not a [] type", value) 1085 } 1086 as.value = []string{} 1087 for _, value := range asValue { 1088 as.value = append(as.value, value.(string)) 1089 } 1090 } 1091 return nil 1092 } 1093 1094 // Test for #341 1095 func TestCustomEncode(t *testing.T) { 1096 enum := OtherValue 1097 outer := Outer{ 1098 Int: &InnerInt{value: 10}, 1099 Enum: &enum, 1100 Slice: &InnerArrayString{value: []string{"text1", "text2"}}, 1101 } 1102 1103 var buf bytes.Buffer 1104 err := NewEncoder(&buf).Encode(outer) 1105 if err != nil { 1106 t.Errorf("Encode failed: %s", err) 1107 } 1108 1109 have := strings.TrimSpace(buf.String()) 1110 want := strings.ReplaceAll(strings.TrimSpace(` 1111 Int = 10 1112 Enum = "OTHER_VALUE" 1113 Slice = ["text1", "text2"] 1114 `), "\t", "") 1115 if want != have { 1116 t.Errorf("\nhave: %s\nwant: %s\n", have, want) 1117 } 1118 } 1119 1120 // Test for #341 1121 func TestCustomDecode(t *testing.T) { 1122 var outer Outer 1123 _, err := Decode(` 1124 Int = 10 1125 Enum = "OTHER_VALUE" 1126 Slice = ["text1", "text2"] 1127 `, &outer) 1128 if err != nil { 1129 t.Fatal(fmt.Sprintf("Decode failed: %s", err)) 1130 } 1131 1132 if outer.Int.value != 10 { 1133 t.Errorf("\nhave:\n%v\nwant:\n%v\n", outer.Int.value, 10) 1134 } 1135 if *outer.Enum != OtherValue { 1136 t.Errorf("\nhave:\n%v\nwant:\n%v\n", outer.Enum, OtherValue) 1137 } 1138 if fmt.Sprint(outer.Slice.value) != fmt.Sprint([]string{"text1", "text2"}) { 1139 t.Errorf("\nhave:\n%v\nwant:\n%v\n", outer.Slice.value, []string{"text1", "text2"}) 1140 } 1141 } 1142 1143 // errorContains checks if the error message in have contains the text in 1144 // want. 1145 // 1146 // This is safe when have is nil. Use an empty string for want if you want to 1147 // test that err is nil. 1148 func errorContains(have error, want string) bool { 1149 if have == nil { 1150 return want == "" 1151 } 1152 if want == "" { 1153 return false 1154 } 1155 return strings.Contains(have.Error(), want) 1156 }