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