1// Copyright 2014 Unknwon 2// 3// Licensed under the Apache License, Version 2.0 (the "License"): you may 4// not use this file except in compliance with the License. You may obtain 5// a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12// License for the specific language governing permissions and limitations 13// under the License. 14 15package ini_test 16 17import ( 18 "bytes" 19 "fmt" 20 "strings" 21 "testing" 22 "time" 23 24 . "github.com/smartystreets/goconvey/convey" 25 26 "gopkg.in/ini.v1" 27) 28 29type testNested struct { 30 Cities []string `delim:"|"` 31 Visits []time.Time 32 Years []int 33 Numbers []int64 34 Ages []uint 35 Populations []uint64 36 Coordinates []float64 37 Flags []bool 38 Note string 39 Unused int `ini:"-"` 40} 41 42type TestEmbeded struct { 43 GPA float64 44} 45 46type testStruct struct { 47 Name string `ini:"NAME"` 48 Age int 49 Male bool 50 Money float64 51 Born time.Time 52 Time time.Duration `ini:"Duration"` 53 OldVersionTime time.Duration 54 Others testNested 55 OthersPtr *testNested 56 NilPtr *testNested 57 *TestEmbeded `ini:"grade"` 58 Unused int `ini:"-"` 59 Unsigned uint 60 Omitted bool `ini:"omitthis,omitempty"` 61 Shadows []string `ini:",allowshadow"` 62 ShadowInts []int `ini:"Shadows,allowshadow"` 63 BoolPtr *bool 64 BoolPtrNil *bool 65 FloatPtr *float64 66 FloatPtrNil *float64 67 IntPtr *int 68 IntPtrNil *int 69 UintPtr *uint 70 UintPtrNil *uint 71 StringPtr *string 72 StringPtrNil *string 73 TimePtr *time.Time 74 TimePtrNil *time.Time 75 DurationPtr *time.Duration 76 DurationPtrNil *time.Duration 77} 78 79type testInterface struct { 80 Address string 81 ListenPort int 82 PrivateKey string 83} 84 85type testPeer struct { 86 PublicKey string 87 PresharedKey string 88 AllowedIPs []string `delim:","` 89} 90 91type testNonUniqueSectionsStruct struct { 92 Interface testInterface 93 Peer []testPeer `ini:",nonunique"` 94} 95 96type BaseStruct struct { 97 Base bool 98} 99 100type testExtend struct { 101 BaseStruct `ini:",extends"` 102 Extend bool 103} 104 105const confDataStruct = ` 106NAME = Unknwon 107Age = 21 108Male = true 109Money = 1.25 110Born = 1993-10-07T20:17:05Z 111Duration = 2h45m 112OldVersionTime = 30 113Unsigned = 3 114omitthis = true 115Shadows = 1, 2 116Shadows = 3, 4 117BoolPtr = false 118FloatPtr = 0 119IntPtr = 0 120UintPtr = 0 121StringPtr = "" 122TimePtr = 0001-01-01T00:00:00Z 123DurationPtr = 0s 124 125[Others] 126Cities = HangZhou|Boston 127Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z 128Years = 1993,1994 129Numbers = 10010,10086 130Ages = 18,19 131Populations = 12345678,98765432 132Coordinates = 192.168,10.11 133Flags = true,false 134Note = Hello world! 135 136[OthersPtr] 137Cities = HangZhou|Boston 138Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z 139Years = 1993,1994 140Numbers = 10010,10086 141Ages = 18,19 142Populations = 12345678,98765432 143Coordinates = 192.168,10.11 144Flags = true,false 145Note = Hello world! 146 147[grade] 148GPA = 2.8 149 150[foo.bar] 151Here = there 152When = then 153 154[extended] 155Base = true 156Extend = true 157` 158 159const confNonUniqueSectionDataStruct = `[Interface] 160Address = 10.2.0.1/24 161ListenPort = 34777 162PrivateKey = privServerKey 163 164[Peer] 165PublicKey = pubClientKey 166PresharedKey = psKey 167AllowedIPs = 10.2.0.2/32,fd00:2::2/128 168 169[Peer] 170PublicKey = pubClientKey2 171PresharedKey = psKey2 172AllowedIPs = 10.2.0.3/32,fd00:2::3/128 173 174` 175 176type unsupport struct { 177 Byte byte 178} 179 180type unsupport2 struct { 181 Others struct { 182 Cities byte 183 } 184} 185 186type Unsupport3 struct { 187 Cities byte 188} 189 190type unsupport4 struct { 191 *Unsupport3 `ini:"Others"` 192} 193 194type defaultValue struct { 195 Name string 196 Age int 197 Male bool 198 Optional *bool 199 Money float64 200 Born time.Time 201 Cities []string 202} 203 204type fooBar struct { 205 Here, When string 206} 207 208const invalidDataConfStruct = ` 209Name = 210Age = age 211Male = 123 212Money = money 213Born = nil 214Cities = 215` 216 217func Test_MapToStruct(t *testing.T) { 218 Convey("Map to struct", t, func() { 219 Convey("Map file to struct", func() { 220 ts := new(testStruct) 221 So(ini.MapTo(ts, []byte(confDataStruct)), ShouldBeNil) 222 223 So(ts.Name, ShouldEqual, "Unknwon") 224 So(ts.Age, ShouldEqual, 21) 225 So(ts.Male, ShouldBeTrue) 226 So(ts.Money, ShouldEqual, 1.25) 227 So(ts.Unsigned, ShouldEqual, 3) 228 229 t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z") 230 So(err, ShouldBeNil) 231 So(ts.Born.String(), ShouldEqual, t.String()) 232 233 dur, err := time.ParseDuration("2h45m") 234 So(err, ShouldBeNil) 235 So(ts.Time.Seconds(), ShouldEqual, dur.Seconds()) 236 237 So(ts.OldVersionTime*time.Second, ShouldEqual, 30*time.Second) 238 239 So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston") 240 So(ts.Others.Visits[0].String(), ShouldEqual, t.String()) 241 So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]") 242 So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]") 243 So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]") 244 So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]") 245 So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]") 246 So(fmt.Sprint(ts.Others.Flags), ShouldEqual, "[true false]") 247 So(ts.Others.Note, ShouldEqual, "Hello world!") 248 So(ts.TestEmbeded.GPA, ShouldEqual, 2.8) 249 250 So(strings.Join(ts.OthersPtr.Cities, ","), ShouldEqual, "HangZhou,Boston") 251 So(ts.OthersPtr.Visits[0].String(), ShouldEqual, t.String()) 252 So(fmt.Sprint(ts.OthersPtr.Years), ShouldEqual, "[1993 1994]") 253 So(fmt.Sprint(ts.OthersPtr.Numbers), ShouldEqual, "[10010 10086]") 254 So(fmt.Sprint(ts.OthersPtr.Ages), ShouldEqual, "[18 19]") 255 So(fmt.Sprint(ts.OthersPtr.Populations), ShouldEqual, "[12345678 98765432]") 256 So(fmt.Sprint(ts.OthersPtr.Coordinates), ShouldEqual, "[192.168 10.11]") 257 So(fmt.Sprint(ts.OthersPtr.Flags), ShouldEqual, "[true false]") 258 So(ts.OthersPtr.Note, ShouldEqual, "Hello world!") 259 260 So(ts.NilPtr, ShouldBeNil) 261 262 So(*ts.BoolPtr, ShouldEqual, false) 263 So(ts.BoolPtrNil, ShouldEqual, nil) 264 So(*ts.FloatPtr, ShouldEqual, 0) 265 So(ts.FloatPtrNil, ShouldEqual, nil) 266 So(*ts.IntPtr, ShouldEqual, 0) 267 So(ts.IntPtrNil, ShouldEqual, nil) 268 So(*ts.UintPtr, ShouldEqual, 0) 269 So(ts.UintPtrNil, ShouldEqual, nil) 270 So(*ts.StringPtr, ShouldEqual, "") 271 So(ts.StringPtrNil, ShouldEqual, nil) 272 So(*ts.TimePtr, ShouldNotEqual, nil) 273 So(ts.TimePtrNil, ShouldEqual, nil) 274 So(*ts.DurationPtr, ShouldEqual, 0) 275 So(ts.DurationPtrNil, ShouldEqual, nil) 276 }) 277 278 Convey("Map section to struct", func() { 279 foobar := new(fooBar) 280 f, err := ini.Load([]byte(confDataStruct)) 281 So(err, ShouldBeNil) 282 283 So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil) 284 So(foobar.Here, ShouldEqual, "there") 285 So(foobar.When, ShouldEqual, "then") 286 }) 287 288 Convey("Map to non-pointer struct", func() { 289 f, err := ini.Load([]byte(confDataStruct)) 290 So(err, ShouldBeNil) 291 So(f, ShouldNotBeNil) 292 293 So(f.MapTo(testStruct{}), ShouldNotBeNil) 294 }) 295 296 Convey("Map to unsupported type", func() { 297 f, err := ini.Load([]byte(confDataStruct)) 298 So(err, ShouldBeNil) 299 So(f, ShouldNotBeNil) 300 301 f.NameMapper = func(raw string) string { 302 if raw == "Byte" { 303 return "NAME" 304 } 305 return raw 306 } 307 So(f.MapTo(&unsupport{}), ShouldNotBeNil) 308 So(f.MapTo(&unsupport2{}), ShouldNotBeNil) 309 So(f.MapTo(&unsupport4{}), ShouldNotBeNil) 310 }) 311 312 Convey("Map to omitempty field", func() { 313 ts := new(testStruct) 314 So(ini.MapTo(ts, []byte(confDataStruct)), ShouldBeNil) 315 316 So(ts.Omitted, ShouldEqual, true) 317 }) 318 319 Convey("Map with shadows", func() { 320 f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true}, []byte(confDataStruct)) 321 So(err, ShouldBeNil) 322 ts := new(testStruct) 323 So(f.MapTo(ts), ShouldBeNil) 324 325 So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4") 326 So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]") 327 }) 328 329 Convey("Map from invalid data source", func() { 330 So(ini.MapTo(&testStruct{}, "hi"), ShouldNotBeNil) 331 }) 332 333 Convey("Map to wrong types and gain default values", func() { 334 f, err := ini.Load([]byte(invalidDataConfStruct)) 335 So(err, ShouldBeNil) 336 337 t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z") 338 So(err, ShouldBeNil) 339 dv := &defaultValue{"Joe", 10, true, nil, 1.25, t, []string{"HangZhou", "Boston"}} 340 So(f.MapTo(dv), ShouldBeNil) 341 So(dv.Name, ShouldEqual, "Joe") 342 So(dv.Age, ShouldEqual, 10) 343 So(dv.Male, ShouldBeTrue) 344 So(dv.Money, ShouldEqual, 1.25) 345 So(dv.Born.String(), ShouldEqual, t.String()) 346 So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston") 347 }) 348 349 Convey("Map to extended base", func() { 350 f, err := ini.Load([]byte(confDataStruct)) 351 So(err, ShouldBeNil) 352 So(f, ShouldNotBeNil) 353 te := testExtend{} 354 So(f.Section("extended").MapTo(&te), ShouldBeNil) 355 So(te.Base, ShouldBeTrue) 356 So(te.Extend, ShouldBeTrue) 357 }) 358 }) 359 360 Convey("Map to struct in strict mode", t, func() { 361 f, err := ini.Load([]byte(` 362name=bruce 363age=a30`)) 364 So(err, ShouldBeNil) 365 366 type Strict struct { 367 Name string `ini:"name"` 368 Age int `ini:"age"` 369 } 370 s := new(Strict) 371 372 So(f.Section("").StrictMapTo(s), ShouldNotBeNil) 373 }) 374 375 Convey("Map slice in strict mode", t, func() { 376 f, err := ini.Load([]byte(` 377names=alice, bruce`)) 378 So(err, ShouldBeNil) 379 380 type Strict struct { 381 Names []string `ini:"names"` 382 } 383 s := new(Strict) 384 385 So(f.Section("").StrictMapTo(s), ShouldBeNil) 386 So(fmt.Sprint(s.Names), ShouldEqual, "[alice bruce]") 387 }) 388} 389 390func Test_MapToStructNonUniqueSections(t *testing.T) { 391 Convey("Map to struct non unique", t, func() { 392 Convey("Map file to struct non unique", func() { 393 f, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct)) 394 So(err, ShouldBeNil) 395 ts := new(testNonUniqueSectionsStruct) 396 397 So(f.MapTo(ts), ShouldBeNil) 398 399 So(ts.Interface.Address, ShouldEqual, "10.2.0.1/24") 400 So(ts.Interface.ListenPort, ShouldEqual, 34777) 401 So(ts.Interface.PrivateKey, ShouldEqual, "privServerKey") 402 403 So(ts.Peer[0].PublicKey, ShouldEqual, "pubClientKey") 404 So(ts.Peer[0].PresharedKey, ShouldEqual, "psKey") 405 So(ts.Peer[0].AllowedIPs[0], ShouldEqual, "10.2.0.2/32") 406 So(ts.Peer[0].AllowedIPs[1], ShouldEqual, "fd00:2::2/128") 407 408 So(ts.Peer[1].PublicKey, ShouldEqual, "pubClientKey2") 409 So(ts.Peer[1].PresharedKey, ShouldEqual, "psKey2") 410 So(ts.Peer[1].AllowedIPs[0], ShouldEqual, "10.2.0.3/32") 411 So(ts.Peer[1].AllowedIPs[1], ShouldEqual, "fd00:2::3/128") 412 }) 413 414 Convey("Map non unique section to struct", func() { 415 newPeer := new(testPeer) 416 newPeerSlice := make([]testPeer, 0) 417 418 f, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct)) 419 So(err, ShouldBeNil) 420 421 // try only first one 422 So(f.Section("Peer").MapTo(newPeer), ShouldBeNil) 423 So(newPeer.PublicKey, ShouldEqual, "pubClientKey") 424 So(newPeer.PresharedKey, ShouldEqual, "psKey") 425 So(newPeer.AllowedIPs[0], ShouldEqual, "10.2.0.2/32") 426 So(newPeer.AllowedIPs[1], ShouldEqual, "fd00:2::2/128") 427 428 // try all 429 So(f.Section("Peer").MapTo(&newPeerSlice), ShouldBeNil) 430 So(newPeerSlice[0].PublicKey, ShouldEqual, "pubClientKey") 431 So(newPeerSlice[0].PresharedKey, ShouldEqual, "psKey") 432 So(newPeerSlice[0].AllowedIPs[0], ShouldEqual, "10.2.0.2/32") 433 So(newPeerSlice[0].AllowedIPs[1], ShouldEqual, "fd00:2::2/128") 434 435 So(newPeerSlice[1].PublicKey, ShouldEqual, "pubClientKey2") 436 So(newPeerSlice[1].PresharedKey, ShouldEqual, "psKey2") 437 So(newPeerSlice[1].AllowedIPs[0], ShouldEqual, "10.2.0.3/32") 438 So(newPeerSlice[1].AllowedIPs[1], ShouldEqual, "fd00:2::3/128") 439 }) 440 441 Convey("Map non unique sections with subsections to struct", func() { 442 iniFile, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, strings.NewReader(` 443[Section] 444FieldInSubSection = 1 445FieldInSubSection2 = 2 446FieldInSection = 3 447 448[Section] 449FieldInSubSection = 4 450FieldInSubSection2 = 5 451FieldInSection = 6 452`)) 453 So(err, ShouldBeNil) 454 455 type SubSection struct { 456 FieldInSubSection string `ini:"FieldInSubSection"` 457 } 458 type SubSection2 struct { 459 FieldInSubSection2 string `ini:"FieldInSubSection2"` 460 } 461 462 type Section struct { 463 SubSection `ini:"Section"` 464 SubSection2 `ini:"Section"` 465 FieldInSection string `ini:"FieldInSection"` 466 } 467 468 type File struct { 469 Sections []Section `ini:"Section,nonunique"` 470 } 471 472 f := new(File) 473 err = iniFile.MapTo(f) 474 So(err, ShouldBeNil) 475 476 So(f.Sections[0].FieldInSubSection, ShouldEqual, "1") 477 So(f.Sections[0].FieldInSubSection2, ShouldEqual, "2") 478 So(f.Sections[0].FieldInSection, ShouldEqual, "3") 479 480 So(f.Sections[1].FieldInSubSection, ShouldEqual, "4") 481 So(f.Sections[1].FieldInSubSection2, ShouldEqual, "5") 482 So(f.Sections[1].FieldInSection, ShouldEqual, "6") 483 }) 484 }) 485} 486 487func Test_ReflectFromStruct(t *testing.T) { 488 Convey("Reflect from struct", t, func() { 489 type Embeded struct { 490 Dates []time.Time `delim:"|" comment:"Time data"` 491 Places []string 492 Years []int 493 Numbers []int64 494 Ages []uint 495 Populations []uint64 496 Coordinates []float64 497 Flags []bool 498 None []int 499 } 500 type Author struct { 501 Name string `ini:"NAME"` 502 Male bool 503 Optional *bool 504 Age int `comment:"Author's age"` 505 Height uint 506 GPA float64 507 Date time.Time 508 NeverMind string `ini:"-"` 509 ignored string 510 *Embeded `ini:"infos" comment:"Embeded section"` 511 } 512 513 t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z") 514 So(err, ShouldBeNil) 515 a := &Author{"Unknwon", true, nil, 21, 100, 2.8, t, "", "ignored", 516 &Embeded{ 517 []time.Time{t, t}, 518 []string{"HangZhou", "Boston"}, 519 []int{1993, 1994}, 520 []int64{10010, 10086}, 521 []uint{18, 19}, 522 []uint64{12345678, 98765432}, 523 []float64{192.168, 10.11}, 524 []bool{true, false}, 525 []int{}, 526 }} 527 cfg := ini.Empty() 528 So(ini.ReflectFrom(cfg, a), ShouldBeNil) 529 530 var buf bytes.Buffer 531 _, err = cfg.WriteTo(&buf) 532 So(err, ShouldBeNil) 533 So(buf.String(), ShouldEqual, `NAME = Unknwon 534Male = true 535Optional = 536; Author's age 537Age = 21 538Height = 100 539GPA = 2.8 540Date = 1993-10-07T20:17:05Z 541 542; Embeded section 543[infos] 544; Time data 545Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z 546Places = HangZhou,Boston 547Years = 1993,1994 548Numbers = 10010,10086 549Ages = 18,19 550Populations = 12345678,98765432 551Coordinates = 192.168,10.11 552Flags = true,false 553None = 554 555`) 556 557 Convey("Reflect from non-point struct", func() { 558 So(ini.ReflectFrom(cfg, Author{}), ShouldNotBeNil) 559 }) 560 561 Convey("Reflect from struct with omitempty", func() { 562 cfg := ini.Empty() 563 type SpecialStruct struct { 564 FirstName string `ini:"first_name"` 565 LastName string `ini:"last_name,omitempty"` 566 JustOmitMe string `ini:"omitempty"` 567 LastLogin time.Time `ini:"last_login,omitempty"` 568 LastLogin2 time.Time `ini:",omitempty"` 569 NotEmpty int `ini:"omitempty"` 570 Number int64 `ini:",omitempty"` 571 Ages uint `ini:",omitempty"` 572 Population uint64 `ini:",omitempty"` 573 Coordinate float64 `ini:",omitempty"` 574 Flag bool `ini:",omitempty"` 575 Note *string `ini:",omitempty"` 576 } 577 special := &SpecialStruct{ 578 FirstName: "John", 579 LastName: "Doe", 580 NotEmpty: 9, 581 } 582 583 So(ini.ReflectFrom(cfg, special), ShouldBeNil) 584 585 var buf bytes.Buffer 586 _, err = cfg.WriteTo(&buf) 587 So(buf.String(), ShouldEqual, `first_name = John 588last_name = Doe 589omitempty = 9 590 591`) 592 }) 593 594 Convey("Reflect from struct with non-anonymous structure pointer", func() { 595 cfg := ini.Empty() 596 type Rpc struct { 597 Enable bool `ini:"enable"` 598 Type string `ini:"type"` 599 Address string `ini:"addr"` 600 Name string `ini:"name"` 601 } 602 type Cfg struct { 603 Rpc *Rpc `ini:"rpc"` 604 } 605 606 config := &Cfg{ 607 Rpc: &Rpc{ 608 Enable: true, 609 Type: "type", 610 Address: "address", 611 Name: "name", 612 }, 613 } 614 So(cfg.ReflectFrom(config), ShouldBeNil) 615 616 var buf bytes.Buffer 617 _, err = cfg.WriteTo(&buf) 618 So(buf.String(), ShouldEqual, `[rpc] 619enable = true 620type = type 621addr = address 622name = name 623 624`) 625 }) 626 }) 627} 628 629func Test_ReflectFromStructNonUniqueSections(t *testing.T) { 630 Convey("Reflect from struct with non unique sections", t, func() { 631 nonUnique := &testNonUniqueSectionsStruct{ 632 Interface: testInterface{ 633 Address: "10.2.0.1/24", 634 ListenPort: 34777, 635 PrivateKey: "privServerKey", 636 }, 637 Peer: []testPeer{ 638 { 639 PublicKey: "pubClientKey", 640 PresharedKey: "psKey", 641 AllowedIPs: []string{"10.2.0.2/32,fd00:2::2/128"}, 642 }, 643 { 644 PublicKey: "pubClientKey2", 645 PresharedKey: "psKey2", 646 AllowedIPs: []string{"10.2.0.3/32,fd00:2::3/128"}, 647 }, 648 }, 649 } 650 651 cfg := ini.Empty(ini.LoadOptions{ 652 AllowNonUniqueSections: true, 653 }) 654 655 So(ini.ReflectFrom(cfg, nonUnique), ShouldBeNil) 656 657 var buf bytes.Buffer 658 _, err := cfg.WriteTo(&buf) 659 So(err, ShouldBeNil) 660 So(buf.String(), ShouldEqual, confNonUniqueSectionDataStruct) 661 662 // note: using ReflectFrom from should overwrite the existing sections 663 err = cfg.Section("Peer").ReflectFrom([]*testPeer{ 664 { 665 PublicKey: "pubClientKey3", 666 PresharedKey: "psKey3", 667 AllowedIPs: []string{"10.2.0.4/32,fd00:2::4/128"}, 668 }, 669 { 670 PublicKey: "pubClientKey4", 671 PresharedKey: "psKey4", 672 AllowedIPs: []string{"10.2.0.5/32,fd00:2::5/128"}, 673 }, 674 }) 675 676 So(err, ShouldBeNil) 677 678 buf = bytes.Buffer{} 679 _, err = cfg.WriteTo(&buf) 680 So(err, ShouldBeNil) 681 So(buf.String(), ShouldEqual, `[Interface] 682Address = 10.2.0.1/24 683ListenPort = 34777 684PrivateKey = privServerKey 685 686[Peer] 687PublicKey = pubClientKey3 688PresharedKey = psKey3 689AllowedIPs = 10.2.0.4/32,fd00:2::4/128 690 691[Peer] 692PublicKey = pubClientKey4 693PresharedKey = psKey4 694AllowedIPs = 10.2.0.5/32,fd00:2::5/128 695 696`) 697 698 // note: using ReflectFrom from should overwrite the existing sections 699 err = cfg.Section("Peer").ReflectFrom(&testPeer{ 700 PublicKey: "pubClientKey5", 701 PresharedKey: "psKey5", 702 AllowedIPs: []string{"10.2.0.6/32,fd00:2::6/128"}, 703 }) 704 705 So(err, ShouldBeNil) 706 707 buf = bytes.Buffer{} 708 _, err = cfg.WriteTo(&buf) 709 So(err, ShouldBeNil) 710 So(buf.String(), ShouldEqual, `[Interface] 711Address = 10.2.0.1/24 712ListenPort = 34777 713PrivateKey = privServerKey 714 715[Peer] 716PublicKey = pubClientKey5 717PresharedKey = psKey5 718AllowedIPs = 10.2.0.6/32,fd00:2::6/128 719 720`) 721 }) 722} 723 724// Inspired by https://github.com/go-ini/ini/issues/196 725func TestMapToAndReflectFromStructWithShadows(t *testing.T) { 726 Convey("Map to struct and then reflect with shadows should generate original config content", t, func() { 727 type include struct { 728 Paths []string `ini:"path,omitempty,allowshadow"` 729 } 730 731 cfg, err := ini.LoadSources(ini.LoadOptions{ 732 AllowShadows: true, 733 }, []byte(` 734[include] 735path = /tmp/gpm-profiles/test5.profile 736path = /tmp/gpm-profiles/test1.profile`)) 737 So(err, ShouldBeNil) 738 739 sec := cfg.Section("include") 740 inc := new(include) 741 err = sec.MapTo(inc) 742 So(err, ShouldBeNil) 743 744 err = sec.ReflectFrom(inc) 745 So(err, ShouldBeNil) 746 747 var buf bytes.Buffer 748 _, err = cfg.WriteTo(&buf) 749 So(err, ShouldBeNil) 750 So(buf.String(), ShouldEqual, `[include] 751path = /tmp/gpm-profiles/test5.profile 752path = /tmp/gpm-profiles/test1.profile 753 754`) 755 756 Convey("Reflect from struct with shadows", func() { 757 cfg := ini.Empty(ini.LoadOptions{ 758 AllowShadows: true, 759 }) 760 type ShadowStruct struct { 761 StringArray []string `ini:"sa,allowshadow"` 762 EmptyStringArrat []string `ini:"empty,omitempty,allowshadow"` 763 Allowshadow []string `ini:"allowshadow,allowshadow"` 764 Dates []time.Time `ini:",allowshadow"` 765 Places []string `ini:",allowshadow"` 766 Years []int `ini:",allowshadow"` 767 Numbers []int64 `ini:",allowshadow"` 768 Ages []uint `ini:",allowshadow"` 769 Populations []uint64 `ini:",allowshadow"` 770 Coordinates []float64 `ini:",allowshadow"` 771 Flags []bool `ini:",allowshadow"` 772 None []int `ini:",allowshadow"` 773 } 774 775 shadow := &ShadowStruct{ 776 StringArray: []string{"s1", "s2"}, 777 Allowshadow: []string{"s3", "s4"}, 778 Dates: []time.Time{time.Date(2020, 9, 12, 00, 00, 00, 651387237, time.UTC), 779 time.Date(2020, 9, 12, 00, 00, 00, 651387237, time.UTC)}, 780 Places: []string{"HangZhou", "Boston"}, 781 Years: []int{1993, 1994}, 782 Numbers: []int64{10010, 10086}, 783 Ages: []uint{18, 19}, 784 Populations: []uint64{12345678, 98765432}, 785 Coordinates: []float64{192.168, 10.11}, 786 Flags: []bool{true, false}, 787 None: []int{}, 788 } 789 790 So(ini.ReflectFrom(cfg, shadow), ShouldBeNil) 791 792 var buf bytes.Buffer 793 _, err := cfg.WriteTo(&buf) 794 So(err, ShouldBeNil) 795 So(buf.String(), ShouldEqual, `sa = s1 796sa = s2 797allowshadow = s3 798allowshadow = s4 799Dates = 2020-09-12T00:00:00Z 800Places = HangZhou 801Places = Boston 802Years = 1993 803Years = 1994 804Numbers = 10010 805Numbers = 10086 806Ages = 18 807Ages = 19 808Populations = 12345678 809Populations = 98765432 810Coordinates = 192.168 811Coordinates = 10.11 812Flags = true 813Flags = false 814None = 815 816`) 817 }) 818 }) 819} 820 821type testMapper struct { 822 PackageName string 823} 824 825func Test_NameGetter(t *testing.T) { 826 Convey("Test name mappers", t, func() { 827 So(ini.MapToWithMapper(&testMapper{}, ini.TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil) 828 829 cfg, err := ini.Load([]byte("PACKAGE_NAME=ini")) 830 So(err, ShouldBeNil) 831 So(cfg, ShouldNotBeNil) 832 833 cfg.NameMapper = ini.SnackCase 834 tg := new(testMapper) 835 So(cfg.MapTo(tg), ShouldBeNil) 836 So(tg.PackageName, ShouldEqual, "ini") 837 }) 838} 839 840type testDurationStruct struct { 841 Duration time.Duration `ini:"Duration"` 842} 843 844func Test_Duration(t *testing.T) { 845 Convey("Duration less than 16m50s", t, func() { 846 ds := new(testDurationStruct) 847 So(ini.MapTo(ds, []byte("Duration=16m49s")), ShouldBeNil) 848 849 dur, err := time.ParseDuration("16m49s") 850 So(err, ShouldBeNil) 851 So(ds.Duration.Seconds(), ShouldEqual, dur.Seconds()) 852 }) 853} 854 855type Employer struct { 856 Name string 857 Title string 858} 859 860type Employers []*Employer 861 862func (es Employers) ReflectINIStruct(f *ini.File) error { 863 for _, e := range es { 864 f.Section(e.Name).Key("Title").SetValue(e.Title) 865 } 866 return nil 867} 868 869// Inspired by https://github.com/go-ini/ini/issues/199 870func Test_StructReflector(t *testing.T) { 871 Convey("Reflect with StructReflector interface", t, func() { 872 p := &struct { 873 FirstName string 874 Employer Employers 875 }{ 876 FirstName: "Andrew", 877 Employer: []*Employer{ 878 { 879 Name: `Employer "VMware"`, 880 Title: "Staff II Engineer", 881 }, 882 { 883 Name: `Employer "EMC"`, 884 Title: "Consultant Engineer", 885 }, 886 }, 887 } 888 889 f := ini.Empty() 890 So(f.ReflectFrom(p), ShouldBeNil) 891 892 var buf bytes.Buffer 893 _, err := f.WriteTo(&buf) 894 So(err, ShouldBeNil) 895 896 So(buf.String(), ShouldEqual, `FirstName = Andrew 897 898[Employer "VMware"] 899Title = Staff II Engineer 900 901[Employer "EMC"] 902Title = Consultant Engineer 903 904`) 905 }) 906} 907