github.com/mschneider82/viper@v1.3.4/viper_test.go (about) 1 // Copyright © 2014 Steve Francia <spf@spf13.com>. 2 // 3 // Use of this source code is governed by an MIT-style 4 // license that can be found in the LICENSE file. 5 6 package viper 7 8 import ( 9 "bytes" 10 "encoding/json" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "os" 15 "os/exec" 16 "path" 17 "reflect" 18 "runtime" 19 "sort" 20 "strings" 21 "sync" 22 "testing" 23 "time" 24 25 "github.com/fsnotify/fsnotify" 26 "github.com/mitchellh/mapstructure" 27 "github.com/spf13/afero" 28 "github.com/spf13/cast" 29 30 "github.com/spf13/pflag" 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 ) 34 35 var yamlExample = []byte(`Hacker: true 36 name: steve 37 hobbies: 38 - skateboarding 39 - snowboarding 40 - go 41 clothing: 42 jacket: leather 43 trousers: denim 44 pants: 45 size: large 46 age: 35 47 eyes : brown 48 beard: true 49 `) 50 51 var yamlExampleWithExtras = []byte(`Existing: true 52 Bogus: true 53 `) 54 55 type testUnmarshalExtra struct { 56 Existing bool 57 } 58 59 var tomlExample = []byte(` 60 title = "TOML Example" 61 62 [owner] 63 organization = "MongoDB" 64 Bio = "MongoDB Chief Developer Advocate & Hacker at Large" 65 dob = 1979-05-27T07:32:00Z # First class dates? Why not?`) 66 67 var jsonExample = []byte(`{ 68 "id": "0001", 69 "type": "donut", 70 "name": "Cake", 71 "ppu": 0.55, 72 "batters": { 73 "batter": [ 74 { "type": "Regular" }, 75 { "type": "Chocolate" }, 76 { "type": "Blueberry" }, 77 { "type": "Devil's Food" } 78 ] 79 } 80 }`) 81 82 var hclExample = []byte(` 83 id = "0001" 84 type = "donut" 85 name = "Cake" 86 ppu = 0.55 87 foos { 88 foo { 89 key = 1 90 } 91 foo { 92 key = 2 93 } 94 foo { 95 key = 3 96 } 97 foo { 98 key = 4 99 } 100 }`) 101 102 var propertiesExample = []byte(` 103 p_id: 0001 104 p_type: donut 105 p_name: Cake 106 p_ppu: 0.55 107 p_batters.batter.type: Regular 108 `) 109 110 var remoteExample = []byte(`{ 111 "id":"0002", 112 "type":"cronut", 113 "newkey":"remote" 114 }`) 115 116 func initConfigs() { 117 Reset() 118 var r io.Reader 119 SetConfigType("yaml") 120 r = bytes.NewReader(yamlExample) 121 unmarshalReader(r, v.config) 122 123 SetConfigType("json") 124 r = bytes.NewReader(jsonExample) 125 unmarshalReader(r, v.config) 126 127 SetConfigType("hcl") 128 r = bytes.NewReader(hclExample) 129 unmarshalReader(r, v.config) 130 131 SetConfigType("properties") 132 r = bytes.NewReader(propertiesExample) 133 unmarshalReader(r, v.config) 134 135 SetConfigType("toml") 136 r = bytes.NewReader(tomlExample) 137 unmarshalReader(r, v.config) 138 139 SetConfigType("json") 140 remote := bytes.NewReader(remoteExample) 141 unmarshalReader(remote, v.kvstore) 142 } 143 144 func initConfig(typ, config string) { 145 Reset() 146 SetConfigType(typ) 147 r := strings.NewReader(config) 148 149 if err := unmarshalReader(r, v.config); err != nil { 150 panic(err) 151 } 152 } 153 154 func initYAML() { 155 initConfig("yaml", string(yamlExample)) 156 } 157 158 func initJSON() { 159 Reset() 160 SetConfigType("json") 161 r := bytes.NewReader(jsonExample) 162 163 unmarshalReader(r, v.config) 164 } 165 166 func initProperties() { 167 Reset() 168 SetConfigType("properties") 169 r := bytes.NewReader(propertiesExample) 170 171 unmarshalReader(r, v.config) 172 } 173 174 func initTOML() { 175 Reset() 176 SetConfigType("toml") 177 r := bytes.NewReader(tomlExample) 178 179 unmarshalReader(r, v.config) 180 } 181 182 func initHcl() { 183 Reset() 184 SetConfigType("hcl") 185 r := bytes.NewReader(hclExample) 186 187 unmarshalReader(r, v.config) 188 } 189 190 // make directories for testing 191 func initDirs(t *testing.T) (string, string, func()) { 192 193 var ( 194 testDirs = []string{`a a`, `b`, `c\c`, `D_`} 195 config = `improbable` 196 ) 197 198 root, err := ioutil.TempDir("", "") 199 200 cleanup := true 201 defer func() { 202 if cleanup { 203 os.Chdir("..") 204 os.RemoveAll(root) 205 } 206 }() 207 208 assert.Nil(t, err) 209 210 err = os.Chdir(root) 211 assert.Nil(t, err) 212 213 for _, dir := range testDirs { 214 err = os.Mkdir(dir, 0750) 215 assert.Nil(t, err) 216 217 err = ioutil.WriteFile( 218 path.Join(dir, config+".toml"), 219 []byte("key = \"value is "+dir+"\"\n"), 220 0640) 221 assert.Nil(t, err) 222 } 223 224 cleanup = false 225 return root, config, func() { 226 os.Chdir("..") 227 os.RemoveAll(root) 228 } 229 } 230 231 //stubs for PFlag Values 232 type stringValue string 233 234 func newStringValue(val string, p *string) *stringValue { 235 *p = val 236 return (*stringValue)(p) 237 } 238 239 func (s *stringValue) Set(val string) error { 240 *s = stringValue(val) 241 return nil 242 } 243 244 func (s *stringValue) Type() string { 245 return "string" 246 } 247 248 func (s *stringValue) String() string { 249 return fmt.Sprintf("%s", *s) 250 } 251 252 func TestBasics(t *testing.T) { 253 SetConfigFile("/tmp/config.yaml") 254 filename, err := v.getConfigFile() 255 assert.Equal(t, "/tmp/config.yaml", filename) 256 assert.NoError(t, err) 257 } 258 259 func TestDefault(t *testing.T) { 260 SetDefault("age", 45) 261 assert.Equal(t, 45, Get("age")) 262 263 SetDefault("clothing.jacket", "slacks") 264 assert.Equal(t, "slacks", Get("clothing.jacket")) 265 266 SetConfigType("yaml") 267 err := ReadConfig(bytes.NewBuffer(yamlExample)) 268 269 assert.NoError(t, err) 270 assert.Equal(t, "leather", Get("clothing.jacket")) 271 } 272 273 func TestUnmarshaling(t *testing.T) { 274 SetConfigType("yaml") 275 r := bytes.NewReader(yamlExample) 276 277 unmarshalReader(r, v.config) 278 assert.True(t, InConfig("name")) 279 assert.False(t, InConfig("state")) 280 assert.Equal(t, "steve", Get("name")) 281 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies")) 282 assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, Get("clothing")) 283 assert.Equal(t, 35, Get("age")) 284 } 285 286 func TestUnmarshalExact(t *testing.T) { 287 vip := New() 288 target := &testUnmarshalExtra{} 289 vip.SetConfigType("yaml") 290 r := bytes.NewReader(yamlExampleWithExtras) 291 vip.ReadConfig(r) 292 err := vip.UnmarshalExact(target) 293 if err == nil { 294 t.Fatal("UnmarshalExact should error when populating a struct from a conf that contains unused fields") 295 } 296 } 297 298 func TestOverrides(t *testing.T) { 299 Set("age", 40) 300 assert.Equal(t, 40, Get("age")) 301 } 302 303 func TestDefaultPost(t *testing.T) { 304 assert.NotEqual(t, "NYC", Get("state")) 305 SetDefault("state", "NYC") 306 assert.Equal(t, "NYC", Get("state")) 307 } 308 309 func TestAliases(t *testing.T) { 310 RegisterAlias("years", "age") 311 assert.Equal(t, 40, Get("years")) 312 Set("years", 45) 313 assert.Equal(t, 45, Get("age")) 314 } 315 316 func TestAliasInConfigFile(t *testing.T) { 317 // the config file specifies "beard". If we make this an alias for 318 // "hasbeard", we still want the old config file to work with beard. 319 RegisterAlias("beard", "hasbeard") 320 assert.Equal(t, true, Get("hasbeard")) 321 Set("hasbeard", false) 322 assert.Equal(t, false, Get("beard")) 323 } 324 325 func TestYML(t *testing.T) { 326 initYAML() 327 assert.Equal(t, "steve", Get("name")) 328 } 329 330 func TestJSON(t *testing.T) { 331 initJSON() 332 assert.Equal(t, "0001", Get("id")) 333 } 334 335 func TestProperties(t *testing.T) { 336 initProperties() 337 assert.Equal(t, "0001", Get("p_id")) 338 } 339 340 func TestTOML(t *testing.T) { 341 initTOML() 342 assert.Equal(t, "TOML Example", Get("title")) 343 } 344 345 func TestHCL(t *testing.T) { 346 initHcl() 347 assert.Equal(t, "0001", Get("id")) 348 assert.Equal(t, 0.55, Get("ppu")) 349 assert.Equal(t, "donut", Get("type")) 350 assert.Equal(t, "Cake", Get("name")) 351 Set("id", "0002") 352 assert.Equal(t, "0002", Get("id")) 353 assert.NotEqual(t, "cronut", Get("type")) 354 } 355 356 func TestRemotePrecedence(t *testing.T) { 357 initJSON() 358 359 remote := bytes.NewReader(remoteExample) 360 assert.Equal(t, "0001", Get("id")) 361 unmarshalReader(remote, v.kvstore) 362 assert.Equal(t, "0001", Get("id")) 363 assert.NotEqual(t, "cronut", Get("type")) 364 assert.Equal(t, "remote", Get("newkey")) 365 Set("newkey", "newvalue") 366 assert.NotEqual(t, "remote", Get("newkey")) 367 assert.Equal(t, "newvalue", Get("newkey")) 368 Set("newkey", "remote") 369 } 370 371 func TestEnv(t *testing.T) { 372 initJSON() 373 374 BindEnv("id") 375 BindEnv("f", "FOOD") 376 377 os.Setenv("ID", "13") 378 os.Setenv("FOOD", "apple") 379 os.Setenv("NAME", "crunk") 380 381 assert.Equal(t, "13", Get("id")) 382 assert.Equal(t, "apple", Get("f")) 383 assert.Equal(t, "Cake", Get("name")) 384 385 AutomaticEnv() 386 387 assert.Equal(t, "crunk", Get("name")) 388 389 } 390 391 func TestEmptyEnv(t *testing.T) { 392 initJSON() 393 394 BindEnv("type") // Empty environment variable 395 BindEnv("name") // Bound, but not set environment variable 396 397 os.Clearenv() 398 399 os.Setenv("TYPE", "") 400 401 assert.Equal(t, "donut", Get("type")) 402 assert.Equal(t, "Cake", Get("name")) 403 } 404 405 func TestEmptyEnv_Allowed(t *testing.T) { 406 initJSON() 407 408 AllowEmptyEnv(true) 409 410 BindEnv("type") // Empty environment variable 411 BindEnv("name") // Bound, but not set environment variable 412 413 os.Clearenv() 414 415 os.Setenv("TYPE", "") 416 417 assert.Equal(t, "", Get("type")) 418 assert.Equal(t, "Cake", Get("name")) 419 } 420 421 func TestEnvPrefix(t *testing.T) { 422 initJSON() 423 424 SetEnvPrefix("foo") // will be uppercased automatically 425 BindEnv("id") 426 BindEnv("f", "FOOD") // not using prefix 427 428 os.Setenv("FOO_ID", "13") 429 os.Setenv("FOOD", "apple") 430 os.Setenv("FOO_NAME", "crunk") 431 432 assert.Equal(t, "13", Get("id")) 433 assert.Equal(t, "apple", Get("f")) 434 assert.Equal(t, "Cake", Get("name")) 435 436 AutomaticEnv() 437 438 assert.Equal(t, "crunk", Get("name")) 439 } 440 441 func TestAutoEnv(t *testing.T) { 442 Reset() 443 444 AutomaticEnv() 445 os.Setenv("FOO_BAR", "13") 446 assert.Equal(t, "13", Get("foo_bar")) 447 } 448 449 func TestAutoEnvWithPrefix(t *testing.T) { 450 Reset() 451 452 AutomaticEnv() 453 SetEnvPrefix("Baz") 454 os.Setenv("BAZ_BAR", "13") 455 assert.Equal(t, "13", Get("bar")) 456 } 457 458 func TestSetEnvKeyReplacer(t *testing.T) { 459 Reset() 460 461 AutomaticEnv() 462 os.Setenv("REFRESH_INTERVAL", "30s") 463 464 replacer := strings.NewReplacer("-", "_") 465 SetEnvKeyReplacer(replacer) 466 467 assert.Equal(t, "30s", Get("refresh-interval")) 468 } 469 470 func TestAllKeys(t *testing.T) { 471 initConfigs() 472 473 ks := sort.StringSlice{"title", "newkey", "owner.organization", "owner.dob", "owner.bio", "name", "beard", "ppu", "batters.batter", "hobbies", "clothing.jacket", "clothing.trousers", "clothing.pants.size", "age", "hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name", "foos"} 474 dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 475 all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[string]interface{}{"trousers": "denim", "jacket": "leather", "pants": map[string]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters": map[string]interface{}{"batter": map[string]interface{}{"type": "Regular"}}, "p_type": "donut", "foos": []map[string]interface{}{map[string]interface{}{"foo": []map[string]interface{}{map[string]interface{}{"key": 1}, map[string]interface{}{"key": 2}, map[string]interface{}{"key": 3}, map[string]interface{}{"key": 4}}}}} 476 477 var allkeys sort.StringSlice 478 allkeys = AllKeys() 479 allkeys.Sort() 480 ks.Sort() 481 482 assert.Equal(t, ks, allkeys) 483 assert.Equal(t, all, AllSettings()) 484 } 485 486 func TestAllKeysWithEnv(t *testing.T) { 487 v := New() 488 489 // bind and define environment variables (including a nested one) 490 v.BindEnv("id") 491 v.BindEnv("foo.bar") 492 v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 493 os.Setenv("ID", "13") 494 os.Setenv("FOO_BAR", "baz") 495 496 expectedKeys := sort.StringSlice{"id", "foo.bar"} 497 expectedKeys.Sort() 498 keys := sort.StringSlice(v.AllKeys()) 499 keys.Sort() 500 assert.Equal(t, expectedKeys, keys) 501 } 502 503 func TestAliasesOfAliases(t *testing.T) { 504 Set("Title", "Checking Case") 505 RegisterAlias("Foo", "Bar") 506 RegisterAlias("Bar", "Title") 507 assert.Equal(t, "Checking Case", Get("FOO")) 508 } 509 510 func TestRecursiveAliases(t *testing.T) { 511 RegisterAlias("Baz", "Roo") 512 RegisterAlias("Roo", "baz") 513 } 514 515 func TestUnmarshal(t *testing.T) { 516 SetDefault("port", 1313) 517 Set("name", "Steve") 518 Set("duration", "1s1ms") 519 520 type config struct { 521 Port int 522 Name string 523 Duration time.Duration 524 } 525 526 var C config 527 528 err := Unmarshal(&C) 529 if err != nil { 530 t.Fatalf("unable to decode into struct, %v", err) 531 } 532 533 assert.Equal(t, &config{Name: "Steve", Port: 1313, Duration: time.Second + time.Millisecond}, &C) 534 535 Set("port", 1234) 536 err = Unmarshal(&C) 537 if err != nil { 538 t.Fatalf("unable to decode into struct, %v", err) 539 } 540 assert.Equal(t, &config{Name: "Steve", Port: 1234, Duration: time.Second + time.Millisecond}, &C) 541 } 542 543 func TestUnmarshalWithDecoderOptions(t *testing.T) { 544 Set("credentials", "{\"foo\":\"bar\"}") 545 546 opt := DecodeHook(mapstructure.ComposeDecodeHookFunc( 547 mapstructure.StringToTimeDurationHookFunc(), 548 mapstructure.StringToSliceHookFunc(","), 549 // Custom Decode Hook Function 550 func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) { 551 if rf != reflect.String || rt != reflect.Map { 552 return data, nil 553 } 554 m := map[string]string{} 555 raw := data.(string) 556 if raw == "" { 557 return m, nil 558 } 559 return m, json.Unmarshal([]byte(raw), &m) 560 }, 561 )) 562 563 type config struct { 564 Credentials map[string]string 565 } 566 567 var C config 568 569 err := Unmarshal(&C, opt) 570 if err != nil { 571 t.Fatalf("unable to decode into struct, %v", err) 572 } 573 574 assert.Equal(t, &config{ 575 Credentials: map[string]string{"foo": "bar"}, 576 }, &C) 577 } 578 579 func TestBindPFlags(t *testing.T) { 580 v := New() // create independent Viper object 581 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 582 583 var testValues = map[string]*string{ 584 "host": nil, 585 "port": nil, 586 "endpoint": nil, 587 } 588 589 var mutatedTestValues = map[string]string{ 590 "host": "localhost", 591 "port": "6060", 592 "endpoint": "/public", 593 } 594 595 for name := range testValues { 596 testValues[name] = flagSet.String(name, "", "test") 597 } 598 599 err := v.BindPFlags(flagSet) 600 if err != nil { 601 t.Fatalf("error binding flag set, %v", err) 602 } 603 604 flagSet.VisitAll(func(flag *pflag.Flag) { 605 flag.Value.Set(mutatedTestValues[flag.Name]) 606 flag.Changed = true 607 }) 608 609 for name, expected := range mutatedTestValues { 610 assert.Equal(t, expected, v.Get(name)) 611 } 612 613 } 614 615 func TestBindPFlagsStringSlice(t *testing.T) { 616 tests := []struct { 617 Expected []string 618 Value string 619 }{ 620 {nil, ""}, 621 {[]string{"jeden"}, "jeden"}, 622 {[]string{"dwa", "trzy"}, "dwa,trzy"}, 623 {[]string{"cztery", "piec , szesc"}, "cztery,\"piec , szesc\""}, 624 } 625 626 v := New() // create independent Viper object 627 defaultVal := []string{"default"} 628 v.SetDefault("stringslice", defaultVal) 629 630 for _, testValue := range tests { 631 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 632 flagSet.StringSlice("stringslice", testValue.Expected, "test") 633 634 for _, changed := range []bool{true, false} { 635 flagSet.VisitAll(func(f *pflag.Flag) { 636 f.Value.Set(testValue.Value) 637 f.Changed = changed 638 }) 639 640 err := v.BindPFlags(flagSet) 641 if err != nil { 642 t.Fatalf("error binding flag set, %v", err) 643 } 644 645 type TestStr struct { 646 StringSlice []string 647 } 648 val := &TestStr{} 649 if err := v.Unmarshal(val); err != nil { 650 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 651 } 652 if changed { 653 assert.Equal(t, testValue.Expected, val.StringSlice) 654 } else { 655 assert.Equal(t, defaultVal, val.StringSlice) 656 } 657 } 658 } 659 } 660 661 func TestBindPFlag(t *testing.T) { 662 var testString = "testing" 663 var testValue = newStringValue(testString, &testString) 664 665 flag := &pflag.Flag{ 666 Name: "testflag", 667 Value: testValue, 668 Changed: false, 669 } 670 671 BindPFlag("testvalue", flag) 672 673 assert.Equal(t, testString, Get("testvalue")) 674 675 flag.Value.Set("testing_mutate") 676 flag.Changed = true //hack for pflag usage 677 678 assert.Equal(t, "testing_mutate", Get("testvalue")) 679 680 } 681 682 func TestBoundCaseSensitivity(t *testing.T) { 683 assert.Equal(t, "brown", Get("eyes")) 684 685 BindEnv("eYEs", "TURTLE_EYES") 686 os.Setenv("TURTLE_EYES", "blue") 687 688 assert.Equal(t, "blue", Get("eyes")) 689 690 var testString = "green" 691 var testValue = newStringValue(testString, &testString) 692 693 flag := &pflag.Flag{ 694 Name: "eyeballs", 695 Value: testValue, 696 Changed: true, 697 } 698 699 BindPFlag("eYEs", flag) 700 assert.Equal(t, "green", Get("eyes")) 701 702 } 703 704 func TestSizeInBytes(t *testing.T) { 705 input := map[string]uint{ 706 "": 0, 707 "b": 0, 708 "12 bytes": 0, 709 "200000000000gb": 0, 710 "12 b": 12, 711 "43 MB": 43 * (1 << 20), 712 "10mb": 10 * (1 << 20), 713 "1gb": 1 << 30, 714 } 715 716 for str, expected := range input { 717 assert.Equal(t, expected, parseSizeInBytes(str), str) 718 } 719 } 720 721 func TestFindsNestedKeys(t *testing.T) { 722 initConfigs() 723 dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 724 725 Set("super", map[string]interface{}{ 726 "deep": map[string]interface{}{ 727 "nested": "value", 728 }, 729 }) 730 731 expected := map[string]interface{}{ 732 "super": map[string]interface{}{ 733 "deep": map[string]interface{}{ 734 "nested": "value", 735 }, 736 }, 737 "super.deep": map[string]interface{}{ 738 "nested": "value", 739 }, 740 "super.deep.nested": "value", 741 "owner.organization": "MongoDB", 742 "batters.batter": []interface{}{ 743 map[string]interface{}{ 744 "type": "Regular", 745 }, 746 map[string]interface{}{ 747 "type": "Chocolate", 748 }, 749 map[string]interface{}{ 750 "type": "Blueberry", 751 }, 752 map[string]interface{}{ 753 "type": "Devil's Food", 754 }, 755 }, 756 "hobbies": []interface{}{ 757 "skateboarding", "snowboarding", "go", 758 }, 759 "title": "TOML Example", 760 "newkey": "remote", 761 "batters": map[string]interface{}{ 762 "batter": []interface{}{ 763 map[string]interface{}{ 764 "type": "Regular", 765 }, 766 map[string]interface{}{ 767 "type": "Chocolate", 768 }, map[string]interface{}{ 769 "type": "Blueberry", 770 }, map[string]interface{}{ 771 "type": "Devil's Food", 772 }, 773 }, 774 }, 775 "eyes": "brown", 776 "age": 35, 777 "owner": map[string]interface{}{ 778 "organization": "MongoDB", 779 "bio": "MongoDB Chief Developer Advocate & Hacker at Large", 780 "dob": dob, 781 }, 782 "owner.bio": "MongoDB Chief Developer Advocate & Hacker at Large", 783 "type": "donut", 784 "id": "0001", 785 "name": "Cake", 786 "hacker": true, 787 "ppu": 0.55, 788 "clothing": map[string]interface{}{ 789 "jacket": "leather", 790 "trousers": "denim", 791 "pants": map[string]interface{}{ 792 "size": "large", 793 }, 794 }, 795 "clothing.jacket": "leather", 796 "clothing.pants.size": "large", 797 "clothing.trousers": "denim", 798 "owner.dob": dob, 799 "beard": true, 800 "foos": []map[string]interface{}{ 801 map[string]interface{}{ 802 "foo": []map[string]interface{}{ 803 map[string]interface{}{ 804 "key": 1, 805 }, 806 map[string]interface{}{ 807 "key": 2, 808 }, 809 map[string]interface{}{ 810 "key": 3, 811 }, 812 map[string]interface{}{ 813 "key": 4, 814 }, 815 }, 816 }, 817 }, 818 } 819 820 for key, expectedValue := range expected { 821 822 assert.Equal(t, expectedValue, v.Get(key)) 823 } 824 825 } 826 827 func TestReadBufConfig(t *testing.T) { 828 v := New() 829 v.SetConfigType("yaml") 830 v.ReadConfig(bytes.NewBuffer(yamlExample)) 831 t.Log(v.AllKeys()) 832 833 assert.True(t, v.InConfig("name")) 834 assert.False(t, v.InConfig("state")) 835 assert.Equal(t, "steve", v.Get("name")) 836 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies")) 837 assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, v.Get("clothing")) 838 assert.Equal(t, 35, v.Get("age")) 839 } 840 841 func TestIsSet(t *testing.T) { 842 v := New() 843 v.SetConfigType("yaml") 844 v.ReadConfig(bytes.NewBuffer(yamlExample)) 845 assert.True(t, v.IsSet("clothing.jacket")) 846 assert.False(t, v.IsSet("clothing.jackets")) 847 assert.False(t, v.IsSet("helloworld")) 848 v.Set("helloworld", "fubar") 849 assert.True(t, v.IsSet("helloworld")) 850 } 851 852 func TestDirsSearch(t *testing.T) { 853 854 root, config, cleanup := initDirs(t) 855 defer cleanup() 856 857 v := New() 858 v.SetConfigName(config) 859 v.SetDefault(`key`, `default`) 860 861 entries, err := ioutil.ReadDir(root) 862 for _, e := range entries { 863 if e.IsDir() { 864 v.AddConfigPath(e.Name()) 865 } 866 } 867 868 err = v.ReadInConfig() 869 assert.Nil(t, err) 870 871 assert.Equal(t, `value is `+path.Base(v.configPaths[0]), v.GetString(`key`)) 872 } 873 874 func TestWrongDirsSearchNotFound(t *testing.T) { 875 876 _, config, cleanup := initDirs(t) 877 defer cleanup() 878 879 v := New() 880 v.SetConfigName(config) 881 v.SetDefault(`key`, `default`) 882 883 v.AddConfigPath(`whattayoutalkingbout`) 884 v.AddConfigPath(`thispathaintthere`) 885 886 err := v.ReadInConfig() 887 assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) 888 889 // Even though config did not load and the error might have 890 // been ignored by the client, the default still loads 891 assert.Equal(t, `default`, v.GetString(`key`)) 892 } 893 894 func TestWrongDirsSearchNotFoundForMerge(t *testing.T) { 895 896 _, config, cleanup := initDirs(t) 897 defer cleanup() 898 899 v := New() 900 v.SetConfigName(config) 901 v.SetDefault(`key`, `default`) 902 903 v.AddConfigPath(`whattayoutalkingbout`) 904 v.AddConfigPath(`thispathaintthere`) 905 906 err := v.MergeInConfig() 907 assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) 908 909 // Even though config did not load and the error might have 910 // been ignored by the client, the default still loads 911 assert.Equal(t, `default`, v.GetString(`key`)) 912 } 913 914 func TestSub(t *testing.T) { 915 v := New() 916 v.SetConfigType("yaml") 917 v.ReadConfig(bytes.NewBuffer(yamlExample)) 918 919 subv := v.Sub("clothing") 920 assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("pants.size")) 921 922 subv = v.Sub("clothing.pants") 923 assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("size")) 924 925 subv = v.Sub("clothing.pants.size") 926 assert.Equal(t, (*Viper)(nil), subv) 927 928 subv = v.Sub("missing.key") 929 assert.Equal(t, (*Viper)(nil), subv) 930 } 931 932 var hclWriteExpected = []byte(`"foos" = { 933 "foo" = { 934 "key" = 1 935 } 936 937 "foo" = { 938 "key" = 2 939 } 940 941 "foo" = { 942 "key" = 3 943 } 944 945 "foo" = { 946 "key" = 4 947 } 948 } 949 950 "id" = "0001" 951 952 "name" = "Cake" 953 954 "ppu" = 0.55 955 956 "type" = "donut"`) 957 958 func TestWriteConfigHCL(t *testing.T) { 959 v := New() 960 fs := afero.NewMemMapFs() 961 v.SetFs(fs) 962 v.SetConfigName("c") 963 v.SetConfigType("hcl") 964 err := v.ReadConfig(bytes.NewBuffer(hclExample)) 965 if err != nil { 966 t.Fatal(err) 967 } 968 if err := v.WriteConfigAs("c.hcl"); err != nil { 969 t.Fatal(err) 970 } 971 read, err := afero.ReadFile(fs, "c.hcl") 972 if err != nil { 973 t.Fatal(err) 974 } 975 assert.Equal(t, hclWriteExpected, read) 976 } 977 978 var jsonWriteExpected = []byte(`{ 979 "batters": { 980 "batter": [ 981 { 982 "type": "Regular" 983 }, 984 { 985 "type": "Chocolate" 986 }, 987 { 988 "type": "Blueberry" 989 }, 990 { 991 "type": "Devil's Food" 992 } 993 ] 994 }, 995 "id": "0001", 996 "name": "Cake", 997 "ppu": 0.55, 998 "type": "donut" 999 }`) 1000 1001 func TestWriteConfigJson(t *testing.T) { 1002 v := New() 1003 fs := afero.NewMemMapFs() 1004 v.SetFs(fs) 1005 v.SetConfigName("c") 1006 v.SetConfigType("json") 1007 err := v.ReadConfig(bytes.NewBuffer(jsonExample)) 1008 if err != nil { 1009 t.Fatal(err) 1010 } 1011 if err := v.WriteConfigAs("c.json"); err != nil { 1012 t.Fatal(err) 1013 } 1014 read, err := afero.ReadFile(fs, "c.json") 1015 if err != nil { 1016 t.Fatal(err) 1017 } 1018 assert.Equal(t, jsonWriteExpected, read) 1019 } 1020 1021 var propertiesWriteExpected = []byte(`p_id = 0001 1022 p_type = donut 1023 p_name = Cake 1024 p_ppu = 0.55 1025 p_batters.batter.type = Regular 1026 `) 1027 1028 func TestWriteConfigProperties(t *testing.T) { 1029 v := New() 1030 fs := afero.NewMemMapFs() 1031 v.SetFs(fs) 1032 v.SetConfigName("c") 1033 v.SetConfigType("properties") 1034 err := v.ReadConfig(bytes.NewBuffer(propertiesExample)) 1035 if err != nil { 1036 t.Fatal(err) 1037 } 1038 if err := v.WriteConfigAs("c.properties"); err != nil { 1039 t.Fatal(err) 1040 } 1041 read, err := afero.ReadFile(fs, "c.properties") 1042 if err != nil { 1043 t.Fatal(err) 1044 } 1045 assert.Equal(t, propertiesWriteExpected, read) 1046 } 1047 1048 func TestWriteConfigTOML(t *testing.T) { 1049 fs := afero.NewMemMapFs() 1050 v := New() 1051 v.SetFs(fs) 1052 v.SetConfigName("c") 1053 v.SetConfigType("toml") 1054 err := v.ReadConfig(bytes.NewBuffer(tomlExample)) 1055 if err != nil { 1056 t.Fatal(err) 1057 } 1058 if err := v.WriteConfigAs("c.toml"); err != nil { 1059 t.Fatal(err) 1060 } 1061 1062 // The TOML String method does not order the contents. 1063 // Therefore, we must read the generated file and compare the data. 1064 v2 := New() 1065 v2.SetFs(fs) 1066 v2.SetConfigName("c") 1067 v2.SetConfigType("toml") 1068 v2.SetConfigFile("c.toml") 1069 err = v2.ReadInConfig() 1070 if err != nil { 1071 t.Fatal(err) 1072 } 1073 1074 assert.Equal(t, v.GetString("title"), v2.GetString("title")) 1075 assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio")) 1076 assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob")) 1077 assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization")) 1078 } 1079 1080 var yamlWriteExpected = []byte(`age: 35 1081 beard: true 1082 clothing: 1083 jacket: leather 1084 pants: 1085 size: large 1086 trousers: denim 1087 eyes: brown 1088 hacker: true 1089 hobbies: 1090 - skateboarding 1091 - snowboarding 1092 - go 1093 name: steve 1094 `) 1095 1096 func TestWriteConfigYAML(t *testing.T) { 1097 v := New() 1098 fs := afero.NewMemMapFs() 1099 v.SetFs(fs) 1100 v.SetConfigName("c") 1101 v.SetConfigType("yaml") 1102 err := v.ReadConfig(bytes.NewBuffer(yamlExample)) 1103 if err != nil { 1104 t.Fatal(err) 1105 } 1106 if err := v.WriteConfigAs("c.yaml"); err != nil { 1107 t.Fatal(err) 1108 } 1109 read, err := afero.ReadFile(fs, "c.yaml") 1110 if err != nil { 1111 t.Fatal(err) 1112 } 1113 assert.Equal(t, yamlWriteExpected, read) 1114 } 1115 1116 var yamlMergeExampleTgt = []byte(` 1117 hello: 1118 pop: 37890 1119 lagrenum: 765432101234567 1120 world: 1121 - us 1122 - uk 1123 - fr 1124 - de 1125 `) 1126 1127 var yamlMergeExampleSrc = []byte(` 1128 hello: 1129 pop: 45000 1130 lagrenum: 7654321001234567 1131 universe: 1132 - mw 1133 - ad 1134 fu: bar 1135 `) 1136 1137 func TestMergeConfig(t *testing.T) { 1138 v := New() 1139 v.SetConfigType("yml") 1140 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1141 t.Fatal(err) 1142 } 1143 1144 if pop := v.GetInt("hello.pop"); pop != 37890 { 1145 t.Fatalf("pop != 37890, = %d", pop) 1146 } 1147 1148 if pop := v.GetInt32("hello.pop"); pop != int32(37890) { 1149 t.Fatalf("pop != 37890, = %d", pop) 1150 } 1151 1152 if pop := v.GetInt64("hello.lagrenum"); pop != int64(765432101234567) { 1153 t.Fatalf("int64 lagrenum != 765432101234567, = %d", pop) 1154 } 1155 1156 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1157 t.Fatalf("len(world) != 4, = %d", len(world)) 1158 } 1159 1160 if fu := v.GetString("fu"); fu != "" { 1161 t.Fatalf("fu != \"\", = %s", fu) 1162 } 1163 1164 if err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1165 t.Fatal(err) 1166 } 1167 1168 if pop := v.GetInt("hello.pop"); pop != 45000 { 1169 t.Fatalf("pop != 45000, = %d", pop) 1170 } 1171 1172 if pop := v.GetInt32("hello.pop"); pop != int32(45000) { 1173 t.Fatalf("pop != 45000, = %d", pop) 1174 } 1175 1176 if pop := v.GetInt64("hello.lagrenum"); pop != int64(7654321001234567) { 1177 t.Fatalf("int64 lagrenum != 7654321001234567, = %d", pop) 1178 } 1179 1180 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1181 t.Fatalf("len(world) != 4, = %d", len(world)) 1182 } 1183 1184 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1185 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1186 } 1187 1188 if fu := v.GetString("fu"); fu != "bar" { 1189 t.Fatalf("fu != \"bar\", = %s", fu) 1190 } 1191 } 1192 1193 func TestMergeConfigNoMerge(t *testing.T) { 1194 v := New() 1195 v.SetConfigType("yml") 1196 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1197 t.Fatal(err) 1198 } 1199 1200 if pop := v.GetInt("hello.pop"); pop != 37890 { 1201 t.Fatalf("pop != 37890, = %d", pop) 1202 } 1203 1204 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1205 t.Fatalf("len(world) != 4, = %d", len(world)) 1206 } 1207 1208 if fu := v.GetString("fu"); fu != "" { 1209 t.Fatalf("fu != \"\", = %s", fu) 1210 } 1211 1212 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1213 t.Fatal(err) 1214 } 1215 1216 if pop := v.GetInt("hello.pop"); pop != 45000 { 1217 t.Fatalf("pop != 45000, = %d", pop) 1218 } 1219 1220 if world := v.GetStringSlice("hello.world"); len(world) != 0 { 1221 t.Fatalf("len(world) != 0, = %d", len(world)) 1222 } 1223 1224 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1225 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1226 } 1227 1228 if fu := v.GetString("fu"); fu != "bar" { 1229 t.Fatalf("fu != \"bar\", = %s", fu) 1230 } 1231 } 1232 1233 func TestMergeConfigMap(t *testing.T) { 1234 v := New() 1235 v.SetConfigType("yml") 1236 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1237 t.Fatal(err) 1238 } 1239 1240 assert := func(i int) { 1241 large := v.GetInt("hello.lagrenum") 1242 pop := v.GetInt("hello.pop") 1243 if large != 765432101234567 { 1244 t.Fatal("Got large num:", large) 1245 } 1246 1247 if pop != i { 1248 t.Fatal("Got pop:", pop) 1249 } 1250 } 1251 1252 assert(37890) 1253 1254 update := map[string]interface{}{ 1255 "Hello": map[string]interface{}{ 1256 "Pop": 1234, 1257 }, 1258 "World": map[interface{}]interface{}{ 1259 "Rock": 345, 1260 }, 1261 } 1262 1263 if err := v.MergeConfigMap(update); err != nil { 1264 t.Fatal(err) 1265 } 1266 1267 if rock := v.GetInt("world.rock"); rock != 345 { 1268 t.Fatal("Got rock:", rock) 1269 } 1270 1271 assert(1234) 1272 1273 } 1274 1275 func TestUnmarshalingWithAliases(t *testing.T) { 1276 v := New() 1277 v.SetDefault("ID", 1) 1278 v.Set("name", "Steve") 1279 v.Set("lastname", "Owen") 1280 1281 v.RegisterAlias("UserID", "ID") 1282 v.RegisterAlias("Firstname", "name") 1283 v.RegisterAlias("Surname", "lastname") 1284 1285 type config struct { 1286 ID int 1287 FirstName string 1288 Surname string 1289 } 1290 1291 var C config 1292 err := v.Unmarshal(&C) 1293 if err != nil { 1294 t.Fatalf("unable to decode into struct, %v", err) 1295 } 1296 1297 assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C) 1298 } 1299 1300 func TestSetConfigNameClearsFileCache(t *testing.T) { 1301 SetConfigFile("/tmp/config.yaml") 1302 SetConfigName("default") 1303 f, err := v.getConfigFile() 1304 if err == nil { 1305 t.Fatalf("config file cache should have been cleared") 1306 } 1307 assert.Empty(t, f) 1308 } 1309 1310 func TestShadowedNestedValue(t *testing.T) { 1311 1312 config := `name: steve 1313 clothing: 1314 jacket: leather 1315 trousers: denim 1316 pants: 1317 size: large 1318 ` 1319 initConfig("yaml", config) 1320 1321 assert.Equal(t, "steve", GetString("name")) 1322 1323 polyester := "polyester" 1324 SetDefault("clothing.shirt", polyester) 1325 SetDefault("clothing.jacket.price", 100) 1326 1327 assert.Equal(t, "leather", GetString("clothing.jacket")) 1328 assert.Nil(t, Get("clothing.jacket.price")) 1329 assert.Equal(t, polyester, GetString("clothing.shirt")) 1330 1331 clothingSettings := AllSettings()["clothing"].(map[string]interface{}) 1332 assert.Equal(t, "leather", clothingSettings["jacket"]) 1333 assert.Equal(t, polyester, clothingSettings["shirt"]) 1334 } 1335 1336 func TestDotParameter(t *testing.T) { 1337 initJSON() 1338 // shoud take precedence over batters defined in jsonExample 1339 r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`)) 1340 unmarshalReader(r, v.config) 1341 1342 actual := Get("batters.batter") 1343 expected := []interface{}{map[string]interface{}{"type": "Small"}} 1344 assert.Equal(t, expected, actual) 1345 } 1346 1347 func TestCaseInsensitive(t *testing.T) { 1348 for _, config := range []struct { 1349 typ string 1350 content string 1351 }{ 1352 {"yaml", ` 1353 aBcD: 1 1354 eF: 1355 gH: 2 1356 iJk: 3 1357 Lm: 1358 nO: 4 1359 P: 1360 Q: 5 1361 R: 6 1362 `}, 1363 {"json", `{ 1364 "aBcD": 1, 1365 "eF": { 1366 "iJk": 3, 1367 "Lm": { 1368 "P": { 1369 "Q": 5, 1370 "R": 6 1371 }, 1372 "nO": 4 1373 }, 1374 "gH": 2 1375 } 1376 }`}, 1377 {"toml", `aBcD = 1 1378 [eF] 1379 gH = 2 1380 iJk = 3 1381 [eF.Lm] 1382 nO = 4 1383 [eF.Lm.P] 1384 Q = 5 1385 R = 6 1386 `}, 1387 } { 1388 doTestCaseInsensitive(t, config.typ, config.content) 1389 } 1390 } 1391 1392 func TestCaseInsensitiveSet(t *testing.T) { 1393 Reset() 1394 m1 := map[string]interface{}{ 1395 "Foo": 32, 1396 "Bar": map[interface{}]interface { 1397 }{ 1398 "ABc": "A", 1399 "cDE": "B"}, 1400 } 1401 1402 m2 := map[string]interface{}{ 1403 "Foo": 52, 1404 "Bar": map[interface{}]interface { 1405 }{ 1406 "bCd": "A", 1407 "eFG": "B"}, 1408 } 1409 1410 Set("Given1", m1) 1411 Set("Number1", 42) 1412 1413 SetDefault("Given2", m2) 1414 SetDefault("Number2", 52) 1415 1416 // Verify SetDefault 1417 if v := Get("number2"); v != 52 { 1418 t.Fatalf("Expected 52 got %q", v) 1419 } 1420 1421 if v := Get("given2.foo"); v != 52 { 1422 t.Fatalf("Expected 52 got %q", v) 1423 } 1424 1425 if v := Get("given2.bar.bcd"); v != "A" { 1426 t.Fatalf("Expected A got %q", v) 1427 } 1428 1429 if _, ok := m2["Foo"]; !ok { 1430 t.Fatal("Input map changed") 1431 } 1432 1433 // Verify Set 1434 if v := Get("number1"); v != 42 { 1435 t.Fatalf("Expected 42 got %q", v) 1436 } 1437 1438 if v := Get("given1.foo"); v != 32 { 1439 t.Fatalf("Expected 32 got %q", v) 1440 } 1441 1442 if v := Get("given1.bar.abc"); v != "A" { 1443 t.Fatalf("Expected A got %q", v) 1444 } 1445 1446 if _, ok := m1["Foo"]; !ok { 1447 t.Fatal("Input map changed") 1448 } 1449 } 1450 1451 func TestParseNested(t *testing.T) { 1452 type duration struct { 1453 Delay time.Duration 1454 } 1455 1456 type item struct { 1457 Name string 1458 Delay time.Duration 1459 Nested duration 1460 } 1461 1462 config := `[[parent]] 1463 delay="100ms" 1464 [parent.nested] 1465 delay="200ms" 1466 ` 1467 initConfig("toml", config) 1468 1469 var items []item 1470 err := v.UnmarshalKey("parent", &items) 1471 if err != nil { 1472 t.Fatalf("unable to decode into struct, %v", err) 1473 } 1474 1475 assert.Equal(t, 1, len(items)) 1476 assert.Equal(t, 100*time.Millisecond, items[0].Delay) 1477 assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) 1478 } 1479 1480 func doTestCaseInsensitive(t *testing.T, typ, config string) { 1481 initConfig(typ, config) 1482 Set("RfD", true) 1483 assert.Equal(t, true, Get("rfd")) 1484 assert.Equal(t, true, Get("rFD")) 1485 assert.Equal(t, 1, cast.ToInt(Get("abcd"))) 1486 assert.Equal(t, 1, cast.ToInt(Get("Abcd"))) 1487 assert.Equal(t, 2, cast.ToInt(Get("ef.gh"))) 1488 assert.Equal(t, 3, cast.ToInt(Get("ef.ijk"))) 1489 assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no"))) 1490 assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q"))) 1491 1492 } 1493 1494 func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) { 1495 watchDir, err := ioutil.TempDir("", "") 1496 require.Nil(t, err) 1497 configFile := path.Join(watchDir, "config.yaml") 1498 err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0640) 1499 require.Nil(t, err) 1500 cleanup := func() { 1501 os.RemoveAll(watchDir) 1502 } 1503 v := New() 1504 v.SetConfigFile(configFile) 1505 err = v.ReadInConfig() 1506 require.Nil(t, err) 1507 require.Equal(t, "bar", v.Get("foo")) 1508 return v, configFile, cleanup 1509 } 1510 1511 func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func()) { 1512 watchDir, err := ioutil.TempDir("", "") 1513 require.Nil(t, err) 1514 dataDir1 := path.Join(watchDir, "data1") 1515 err = os.Mkdir(dataDir1, 0777) 1516 require.Nil(t, err) 1517 realConfigFile := path.Join(dataDir1, "config.yaml") 1518 t.Logf("Real config file location: %s\n", realConfigFile) 1519 err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0640) 1520 require.Nil(t, err) 1521 cleanup := func() { 1522 os.RemoveAll(watchDir) 1523 } 1524 // now, symlink the tm `data1` dir to `data` in the baseDir 1525 os.Symlink(dataDir1, path.Join(watchDir, "data")) 1526 // and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml` 1527 configFile := path.Join(watchDir, "config.yaml") 1528 os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile) 1529 t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml")) 1530 // init Viper 1531 v := New() 1532 v.SetConfigFile(configFile) 1533 err = v.ReadInConfig() 1534 require.Nil(t, err) 1535 require.Equal(t, "bar", v.Get("foo")) 1536 return v, watchDir, configFile, cleanup 1537 } 1538 1539 func TestWatchFile(t *testing.T) { 1540 if runtime.GOOS == "linux" { 1541 // TODO(bep) FIX ME 1542 t.Skip("Skip test on Linux ...") 1543 } 1544 1545 t.Run("file content changed", func(t *testing.T) { 1546 // given a `config.yaml` file being watched 1547 v, configFile, cleanup := newViperWithConfigFile(t) 1548 defer cleanup() 1549 _, err := os.Stat(configFile) 1550 require.NoError(t, err) 1551 t.Logf("test config file: %s\n", configFile) 1552 wg := sync.WaitGroup{} 1553 wg.Add(1) 1554 v.OnConfigChange(func(in fsnotify.Event) { 1555 t.Logf("config file changed") 1556 wg.Done() 1557 }) 1558 v.WatchConfig() 1559 // when overwriting the file and waiting for the custom change notification handler to be triggered 1560 err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640) 1561 wg.Wait() 1562 // then the config value should have changed 1563 require.Nil(t, err) 1564 assert.Equal(t, "baz", v.Get("foo")) 1565 }) 1566 1567 t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) { 1568 // skip if not executed on Linux 1569 if runtime.GOOS != "linux" { 1570 t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...") 1571 } 1572 v, watchDir, _, _ := newViperWithSymlinkedConfigFile(t) 1573 // defer cleanup() 1574 wg := sync.WaitGroup{} 1575 v.WatchConfig() 1576 v.OnConfigChange(func(in fsnotify.Event) { 1577 t.Logf("config file changed") 1578 wg.Done() 1579 }) 1580 wg.Add(1) 1581 // when link to another `config.yaml` file 1582 dataDir2 := path.Join(watchDir, "data2") 1583 err := os.Mkdir(dataDir2, 0777) 1584 require.Nil(t, err) 1585 configFile2 := path.Join(dataDir2, "config.yaml") 1586 err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0640) 1587 require.Nil(t, err) 1588 // change the symlink using the `ln -sfn` command 1589 err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run() 1590 require.Nil(t, err) 1591 wg.Wait() 1592 // then 1593 require.Nil(t, err) 1594 assert.Equal(t, "baz", v.Get("foo")) 1595 }) 1596 1597 } 1598 1599 func BenchmarkGetBool(b *testing.B) { 1600 key := "BenchmarkGetBool" 1601 v = New() 1602 v.Set(key, true) 1603 1604 for i := 0; i < b.N; i++ { 1605 if !v.GetBool(key) { 1606 b.Fatal("GetBool returned false") 1607 } 1608 } 1609 } 1610 1611 func BenchmarkGet(b *testing.B) { 1612 key := "BenchmarkGet" 1613 v = New() 1614 v.Set(key, true) 1615 1616 for i := 0; i < b.N; i++ { 1617 if !v.Get(key).(bool) { 1618 b.Fatal("Get returned false") 1619 } 1620 } 1621 } 1622 1623 // This is the "perfect result" for the above. 1624 func BenchmarkGetBoolFromMap(b *testing.B) { 1625 m := make(map[string]bool) 1626 key := "BenchmarkGetBool" 1627 m[key] = true 1628 1629 for i := 0; i < b.N; i++ { 1630 if !m[key] { 1631 b.Fatal("Map value was false") 1632 } 1633 } 1634 }