github.com/vuuvv/viper@v1.10.6/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 "io" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path" 16 "path/filepath" 17 "reflect" 18 "runtime" 19 "sort" 20 "strings" 21 "sync" 22 "testing" 23 "time" 24 25 "github.com/fsnotify/fsnotify" 26 "github.com/spf13/afero" 27 "github.com/spf13/cast" 28 "github.com/spf13/pflag" 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 "github.com/vuuvv/mapstructure" 32 33 "github.com/vuuvv/viper/internal/testutil" 34 ) 35 36 var yamlExample = []byte(`Hacker: true 37 name: steve 38 hobbies: 39 - skateboarding 40 - snowboarding 41 - go 42 clothing: 43 jacket: leather 44 trousers: denim 45 pants: 46 size: large 47 age: 35 48 eyes : brown 49 beard: true 50 `) 51 52 var yamlExampleWithExtras = []byte(`Existing: true 53 Bogus: true 54 `) 55 56 type testUnmarshalExtra struct { 57 Existing bool 58 } 59 60 var tomlExample = []byte(` 61 title = "TOML Example" 62 63 [owner] 64 organization = "MongoDB" 65 Bio = "MongoDB Chief Developer Advocate & Hacker at Large" 66 dob = 1979-05-27T07:32:00Z # First class dates? Why not?`) 67 68 var dotenvExample = []byte(` 69 TITLE_DOTENV="DotEnv Example" 70 TYPE_DOTENV=donut 71 NAME_DOTENV=Cake`) 72 73 var jsonExample = []byte(`{ 74 "id": "0001", 75 "type": "donut", 76 "name": "Cake", 77 "ppu": 0.55, 78 "batters": { 79 "batter": [ 80 { "type": "Regular" }, 81 { "type": "Chocolate" }, 82 { "type": "Blueberry" }, 83 { "type": "Devil's Food" } 84 ] 85 } 86 }`) 87 88 var hclExample = []byte(` 89 id = "0001" 90 type = "donut" 91 name = "Cake" 92 ppu = 0.55 93 foos { 94 foo { 95 key = 1 96 } 97 foo { 98 key = 2 99 } 100 foo { 101 key = 3 102 } 103 foo { 104 key = 4 105 } 106 }`) 107 108 var propertiesExample = []byte(` 109 p_id: 0001 110 p_type: donut 111 p_name: Cake 112 p_ppu: 0.55 113 p_batters.batter.type: Regular 114 `) 115 116 var remoteExample = []byte(`{ 117 "id":"0002", 118 "type":"cronut", 119 "newkey":"remote" 120 }`) 121 122 var iniExample = []byte(`; Package name 123 NAME = ini 124 ; Package version 125 VERSION = v1 126 ; Package import path 127 IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s 128 129 # Information about package author 130 # Bio can be written in multiple lines. 131 [author] 132 NAME = Unknown ; Succeeding comment 133 E-MAIL = fake@localhost 134 GITHUB = https://github.com/%(NAME)s 135 BIO = """Gopher. 136 Coding addict. 137 Good man. 138 """ # Succeeding comment`) 139 140 func initConfigs() { 141 Reset() 142 var r io.Reader 143 SetConfigType("yaml") 144 r = bytes.NewReader(yamlExample) 145 unmarshalReader(r, v.config) 146 147 SetConfigType("json") 148 r = bytes.NewReader(jsonExample) 149 unmarshalReader(r, v.config) 150 151 SetConfigType("hcl") 152 r = bytes.NewReader(hclExample) 153 unmarshalReader(r, v.config) 154 155 SetConfigType("properties") 156 r = bytes.NewReader(propertiesExample) 157 unmarshalReader(r, v.config) 158 159 SetConfigType("toml") 160 r = bytes.NewReader(tomlExample) 161 unmarshalReader(r, v.config) 162 163 SetConfigType("env") 164 r = bytes.NewReader(dotenvExample) 165 unmarshalReader(r, v.config) 166 167 SetConfigType("json") 168 remote := bytes.NewReader(remoteExample) 169 unmarshalReader(remote, v.kvstore) 170 171 SetConfigType("ini") 172 r = bytes.NewReader(iniExample) 173 unmarshalReader(r, v.config) 174 } 175 176 func initConfig(typ, config string) { 177 Reset() 178 SetConfigType(typ) 179 r := strings.NewReader(config) 180 181 if err := unmarshalReader(r, v.config); err != nil { 182 panic(err) 183 } 184 } 185 186 func initYAML() { 187 initConfig("yaml", string(yamlExample)) 188 } 189 190 func initJSON() { 191 Reset() 192 SetConfigType("json") 193 r := bytes.NewReader(jsonExample) 194 195 unmarshalReader(r, v.config) 196 } 197 198 func initProperties() { 199 Reset() 200 SetConfigType("properties") 201 r := bytes.NewReader(propertiesExample) 202 203 unmarshalReader(r, v.config) 204 } 205 206 func initTOML() { 207 Reset() 208 SetConfigType("toml") 209 r := bytes.NewReader(tomlExample) 210 211 unmarshalReader(r, v.config) 212 } 213 214 func initDotEnv() { 215 Reset() 216 SetConfigType("env") 217 r := bytes.NewReader(dotenvExample) 218 219 unmarshalReader(r, v.config) 220 } 221 222 func initHcl() { 223 Reset() 224 SetConfigType("hcl") 225 r := bytes.NewReader(hclExample) 226 227 unmarshalReader(r, v.config) 228 } 229 230 func initIni() { 231 Reset() 232 SetConfigType("ini") 233 r := bytes.NewReader(iniExample) 234 235 unmarshalReader(r, v.config) 236 } 237 238 // make directories for testing 239 func initDirs(t *testing.T) (string, string, func()) { 240 var ( 241 testDirs = []string{`a a`, `b`, `C_`} 242 config = `improbable` 243 ) 244 245 if runtime.GOOS != "windows" { 246 testDirs = append(testDirs, `d\d`) 247 } 248 249 root, err := ioutil.TempDir("", "") 250 require.NoError(t, err, "Failed to create temporary directory") 251 252 cleanup := true 253 defer func() { 254 if cleanup { 255 os.Chdir("..") 256 os.RemoveAll(root) 257 } 258 }() 259 260 assert.Nil(t, err) 261 262 err = os.Chdir(root) 263 require.Nil(t, err) 264 265 for _, dir := range testDirs { 266 err = os.Mkdir(dir, 0o750) 267 assert.Nil(t, err) 268 269 err = ioutil.WriteFile( 270 path.Join(dir, config+".toml"), 271 []byte("key = \"value is "+dir+"\"\n"), 272 0o640) 273 assert.Nil(t, err) 274 } 275 276 cleanup = false 277 return root, config, func() { 278 os.Chdir("..") 279 os.RemoveAll(root) 280 } 281 } 282 283 // stubs for PFlag Values 284 type stringValue string 285 286 func newStringValue(val string, p *string) *stringValue { 287 *p = val 288 return (*stringValue)(p) 289 } 290 291 func (s *stringValue) Set(val string) error { 292 *s = stringValue(val) 293 return nil 294 } 295 296 func (s *stringValue) Type() string { 297 return "string" 298 } 299 300 func (s *stringValue) String() string { 301 return string(*s) 302 } 303 304 func TestGetConfigFile(t *testing.T) { 305 t.Run("config file set", func(t *testing.T) { 306 fs := afero.NewMemMapFs() 307 308 err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777) 309 require.NoError(t, err) 310 311 _, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml")) 312 require.NoError(t, err) 313 314 v := New() 315 316 v.SetFs(fs) 317 v.AddConfigPath("/etc/viper") 318 v.SetConfigFile(testutil.AbsFilePath(t, "/etc/viper/config.yaml")) 319 320 filename, err := v.getConfigFile() 321 assert.Equal(t, testutil.AbsFilePath(t, "/etc/viper/config.yaml"), filename) 322 assert.NoError(t, err) 323 }) 324 325 t.Run("find file", func(t *testing.T) { 326 fs := afero.NewMemMapFs() 327 328 err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777) 329 require.NoError(t, err) 330 331 _, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml")) 332 require.NoError(t, err) 333 334 v := New() 335 336 v.SetFs(fs) 337 v.AddConfigPath("/etc/viper") 338 339 filename, err := v.getConfigFile() 340 assert.Equal(t, testutil.AbsFilePath(t, "/etc/viper/config.yaml"), filename) 341 assert.NoError(t, err) 342 }) 343 344 t.Run("find files only", func(t *testing.T) { 345 fs := afero.NewMemMapFs() 346 347 err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/config"), 0o777) 348 require.NoError(t, err) 349 350 _, err = fs.Create(testutil.AbsFilePath(t, "/etc/config/config.yaml")) 351 require.NoError(t, err) 352 353 v := New() 354 355 v.SetFs(fs) 356 v.AddConfigPath("/etc") 357 v.AddConfigPath("/etc/config") 358 359 filename, err := v.getConfigFile() 360 assert.Equal(t, testutil.AbsFilePath(t, "/etc/config/config.yaml"), filename) 361 assert.NoError(t, err) 362 }) 363 364 t.Run("precedence", func(t *testing.T) { 365 fs := afero.NewMemMapFs() 366 367 err := fs.Mkdir(testutil.AbsFilePath(t, "/home/viper"), 0o777) 368 require.NoError(t, err) 369 370 _, err = fs.Create(testutil.AbsFilePath(t, "/home/viper/config.zml")) 371 require.NoError(t, err) 372 373 err = fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777) 374 require.NoError(t, err) 375 376 _, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.bml")) 377 require.NoError(t, err) 378 379 err = fs.Mkdir(testutil.AbsFilePath(t, "/var/viper"), 0o777) 380 require.NoError(t, err) 381 382 _, err = fs.Create(testutil.AbsFilePath(t, "/var/viper/config.yaml")) 383 require.NoError(t, err) 384 385 v := New() 386 387 v.SetFs(fs) 388 v.AddConfigPath("/home/viper") 389 v.AddConfigPath("/etc/viper") 390 v.AddConfigPath("/var/viper") 391 392 filename, err := v.getConfigFile() 393 assert.Equal(t, testutil.AbsFilePath(t, "/var/viper/config.yaml"), filename) 394 assert.NoError(t, err) 395 }) 396 397 t.Run("without extension", func(t *testing.T) { 398 fs := afero.NewMemMapFs() 399 400 err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777) 401 require.NoError(t, err) 402 403 _, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/.dotfilenoext")) 404 require.NoError(t, err) 405 406 v := New() 407 408 v.SetFs(fs) 409 v.AddConfigPath("/etc/viper") 410 v.SetConfigName(".dotfilenoext") 411 v.SetConfigType("yaml") 412 413 filename, err := v.getConfigFile() 414 assert.Equal(t, testutil.AbsFilePath(t, "/etc/viper/.dotfilenoext"), filename) 415 assert.NoError(t, err) 416 }) 417 418 t.Run("without extension and config type", func(t *testing.T) { 419 fs := afero.NewMemMapFs() 420 421 err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777) 422 require.NoError(t, err) 423 424 _, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/.dotfilenoext")) 425 require.NoError(t, err) 426 427 v := New() 428 429 v.SetFs(fs) 430 v.AddConfigPath("/etc/viper") 431 v.SetConfigName(".dotfilenoext") 432 433 _, err = v.getConfigFile() 434 // unless config type is set, files without extension 435 // are not considered 436 assert.Error(t, err) 437 }) 438 } 439 440 func TestReadInConfig(t *testing.T) { 441 t.Run("config file set", func(t *testing.T) { 442 fs := afero.NewMemMapFs() 443 444 err := fs.Mkdir("/etc/viper", 0o777) 445 require.NoError(t, err) 446 447 file, err := fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml")) 448 require.NoError(t, err) 449 450 _, err = file.Write([]byte(`key: value`)) 451 require.NoError(t, err) 452 453 file.Close() 454 455 v := New() 456 457 v.SetFs(fs) 458 v.SetConfigFile(testutil.AbsFilePath(t, "/etc/viper/config.yaml")) 459 460 err = v.ReadInConfig() 461 require.NoError(t, err) 462 463 assert.Equal(t, "value", v.Get("key")) 464 }) 465 466 t.Run("find file", func(t *testing.T) { 467 fs := afero.NewMemMapFs() 468 469 err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777) 470 require.NoError(t, err) 471 472 file, err := fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml")) 473 require.NoError(t, err) 474 475 _, err = file.Write([]byte(`key: value`)) 476 require.NoError(t, err) 477 478 file.Close() 479 480 v := New() 481 482 v.SetFs(fs) 483 v.AddConfigPath("/etc/viper") 484 485 err = v.ReadInConfig() 486 require.NoError(t, err) 487 488 assert.Equal(t, "value", v.Get("key")) 489 }) 490 } 491 492 func TestDefault(t *testing.T) { 493 SetDefault("age", 45) 494 assert.Equal(t, 45, Get("age")) 495 496 SetDefault("clothing.jacket", "slacks") 497 assert.Equal(t, "slacks", Get("clothing.jacket")) 498 499 SetConfigType("yaml") 500 err := ReadConfig(bytes.NewBuffer(yamlExample)) 501 502 assert.NoError(t, err) 503 assert.Equal(t, "leather", Get("clothing.jacket")) 504 } 505 506 func TestUnmarshaling(t *testing.T) { 507 SetConfigType("yaml") 508 r := bytes.NewReader(yamlExample) 509 510 unmarshalReader(r, v.config) 511 assert.True(t, InConfig("name")) 512 assert.True(t, InConfig("clothing.jacket")) 513 assert.False(t, InConfig("state")) 514 assert.False(t, InConfig("clothing.hat")) 515 assert.Equal(t, "steve", Get("name")) 516 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies")) 517 assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, Get("clothing")) 518 assert.Equal(t, 35, Get("age")) 519 } 520 521 func TestUnmarshalExact(t *testing.T) { 522 vip := New() 523 target := &testUnmarshalExtra{} 524 vip.SetConfigType("yaml") 525 r := bytes.NewReader(yamlExampleWithExtras) 526 vip.ReadConfig(r) 527 err := vip.UnmarshalExact(target) 528 if err == nil { 529 t.Fatal("UnmarshalExact should error when populating a struct from a conf that contains unused fields") 530 } 531 } 532 533 func TestOverrides(t *testing.T) { 534 Set("age", 40) 535 assert.Equal(t, 40, Get("age")) 536 } 537 538 func TestDefaultPost(t *testing.T) { 539 assert.NotEqual(t, "NYC", Get("state")) 540 SetDefault("state", "NYC") 541 assert.Equal(t, "NYC", Get("state")) 542 } 543 544 func TestAliases(t *testing.T) { 545 RegisterAlias("years", "age") 546 assert.Equal(t, 40, Get("years")) 547 Set("years", 45) 548 assert.Equal(t, 45, Get("age")) 549 } 550 551 func TestAliasInConfigFile(t *testing.T) { 552 // the config file specifies "beard". If we make this an alias for 553 // "hasbeard", we still want the old config file to work with beard. 554 RegisterAlias("beard", "hasbeard") 555 assert.Equal(t, true, Get("hasbeard")) 556 Set("hasbeard", false) 557 assert.Equal(t, false, Get("beard")) 558 } 559 560 func TestYML(t *testing.T) { 561 initYAML() 562 assert.Equal(t, "steve", Get("name")) 563 } 564 565 func TestJSON(t *testing.T) { 566 initJSON() 567 assert.Equal(t, "0001", Get("id")) 568 } 569 570 func TestProperties(t *testing.T) { 571 initProperties() 572 assert.Equal(t, "0001", Get("p_id")) 573 } 574 575 func TestTOML(t *testing.T) { 576 initTOML() 577 assert.Equal(t, "TOML Example", Get("title")) 578 } 579 580 func TestDotEnv(t *testing.T) { 581 initDotEnv() 582 assert.Equal(t, "DotEnv Example", Get("title_dotenv")) 583 } 584 585 func TestHCL(t *testing.T) { 586 initHcl() 587 assert.Equal(t, "0001", Get("id")) 588 assert.Equal(t, 0.55, Get("ppu")) 589 assert.Equal(t, "donut", Get("type")) 590 assert.Equal(t, "Cake", Get("name")) 591 Set("id", "0002") 592 assert.Equal(t, "0002", Get("id")) 593 assert.NotEqual(t, "cronut", Get("type")) 594 } 595 596 func TestIni(t *testing.T) { 597 initIni() 598 assert.Equal(t, "ini", Get("default.name")) 599 } 600 601 func TestRemotePrecedence(t *testing.T) { 602 initJSON() 603 604 remote := bytes.NewReader(remoteExample) 605 assert.Equal(t, "0001", Get("id")) 606 unmarshalReader(remote, v.kvstore) 607 assert.Equal(t, "0001", Get("id")) 608 assert.NotEqual(t, "cronut", Get("type")) 609 assert.Equal(t, "remote", Get("newkey")) 610 Set("newkey", "newvalue") 611 assert.NotEqual(t, "remote", Get("newkey")) 612 assert.Equal(t, "newvalue", Get("newkey")) 613 Set("newkey", "remote") 614 } 615 616 func TestEnv(t *testing.T) { 617 initJSON() 618 619 BindEnv("id") 620 BindEnv("f", "FOOD", "OLD_FOOD") 621 622 testutil.Setenv(t, "ID", "13") 623 testutil.Setenv(t, "FOOD", "apple") 624 testutil.Setenv(t, "OLD_FOOD", "banana") 625 testutil.Setenv(t, "NAME", "crunk") 626 627 assert.Equal(t, "13", Get("id")) 628 assert.Equal(t, "apple", Get("f")) 629 assert.Equal(t, "Cake", Get("name")) 630 631 AutomaticEnv() 632 633 assert.Equal(t, "crunk", Get("name")) 634 } 635 636 func TestMultipleEnv(t *testing.T) { 637 initJSON() 638 639 BindEnv("f", "FOOD", "OLD_FOOD") 640 641 testutil.Setenv(t, "OLD_FOOD", "banana") 642 643 assert.Equal(t, "banana", Get("f")) 644 } 645 646 func TestEmptyEnv(t *testing.T) { 647 initJSON() 648 649 BindEnv("type") // Empty environment variable 650 BindEnv("name") // Bound, but not set environment variable 651 652 testutil.Setenv(t, "TYPE", "") 653 654 assert.Equal(t, "donut", Get("type")) 655 assert.Equal(t, "Cake", Get("name")) 656 } 657 658 func TestEmptyEnv_Allowed(t *testing.T) { 659 initJSON() 660 661 AllowEmptyEnv(true) 662 663 BindEnv("type") // Empty environment variable 664 BindEnv("name") // Bound, but not set environment variable 665 666 testutil.Setenv(t, "TYPE", "") 667 668 assert.Equal(t, "", Get("type")) 669 assert.Equal(t, "Cake", Get("name")) 670 } 671 672 func TestEnvPrefix(t *testing.T) { 673 initJSON() 674 675 SetEnvPrefix("foo") // will be uppercased automatically 676 BindEnv("id") 677 BindEnv("f", "FOOD") // not using prefix 678 679 testutil.Setenv(t, "FOO_ID", "13") 680 testutil.Setenv(t, "FOOD", "apple") 681 testutil.Setenv(t, "FOO_NAME", "crunk") 682 683 assert.Equal(t, "13", Get("id")) 684 assert.Equal(t, "apple", Get("f")) 685 assert.Equal(t, "Cake", Get("name")) 686 687 AutomaticEnv() 688 689 assert.Equal(t, "crunk", Get("name")) 690 } 691 692 func TestAutoEnv(t *testing.T) { 693 Reset() 694 695 AutomaticEnv() 696 697 testutil.Setenv(t, "FOO_BAR", "13") 698 699 assert.Equal(t, "13", Get("foo_bar")) 700 } 701 702 func TestAutoEnvWithPrefix(t *testing.T) { 703 Reset() 704 705 AutomaticEnv() 706 SetEnvPrefix("Baz") 707 708 testutil.Setenv(t, "BAZ_BAR", "13") 709 710 assert.Equal(t, "13", Get("bar")) 711 } 712 713 func TestSetEnvKeyReplacer(t *testing.T) { 714 Reset() 715 716 AutomaticEnv() 717 718 testutil.Setenv(t, "REFRESH_INTERVAL", "30s") 719 720 replacer := strings.NewReplacer("-", "_") 721 SetEnvKeyReplacer(replacer) 722 723 assert.Equal(t, "30s", Get("refresh-interval")) 724 } 725 726 func TestEnvKeyReplacer(t *testing.T) { 727 v := NewWithOptions(EnvKeyReplacer(strings.NewReplacer("-", "_"))) 728 729 v.AutomaticEnv() 730 731 testutil.Setenv(t, "REFRESH_INTERVAL", "30s") 732 733 assert.Equal(t, "30s", v.Get("refresh-interval")) 734 } 735 736 func TestAllKeys(t *testing.T) { 737 initConfigs() 738 739 ks := sort.StringSlice{ 740 "title", 741 "author.bio", 742 "author.e-mail", 743 "author.github", 744 "author.name", 745 "newkey", 746 "owner.organization", 747 "owner.dob", 748 "owner.bio", 749 "name", 750 "beard", 751 "ppu", 752 "batters.batter", 753 "hobbies", 754 "clothing.jacket", 755 "clothing.trousers", 756 "default.import_path", 757 "default.name", 758 "default.version", 759 "clothing.pants.size", 760 "age", 761 "hacker", 762 "id", 763 "type", 764 "eyes", 765 "p_id", 766 "p_ppu", 767 "p_batters.batter.type", 768 "p_type", 769 "p_name", 770 "foos", 771 "title_dotenv", 772 "type_dotenv", 773 "name_dotenv", 774 } 775 dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 776 all := map[string]interface{}{ 777 "owner": map[string]interface{}{ 778 "organization": "MongoDB", 779 "bio": "MongoDB Chief Developer Advocate & Hacker at Large", 780 "dob": dob, 781 }, 782 "title": "TOML Example", 783 "author": map[string]interface{}{ 784 "e-mail": "fake@localhost", 785 "github": "https://github.com/Unknown", 786 "name": "Unknown", 787 "bio": "Gopher.\nCoding addict.\nGood man.\n", 788 }, 789 "ppu": 0.55, 790 "eyes": "brown", 791 "clothing": map[string]interface{}{ 792 "trousers": "denim", 793 "jacket": "leather", 794 "pants": map[string]interface{}{"size": "large"}, 795 }, 796 "default": map[string]interface{}{ 797 "import_path": "gopkg.in/ini.v1", 798 "name": "ini", 799 "version": "v1", 800 }, 801 "id": "0001", 802 "batters": map[string]interface{}{ 803 "batter": []interface{}{ 804 map[string]interface{}{"type": "Regular"}, 805 map[string]interface{}{"type": "Chocolate"}, 806 map[string]interface{}{"type": "Blueberry"}, 807 map[string]interface{}{"type": "Devil's Food"}, 808 }, 809 }, 810 "hacker": true, 811 "beard": true, 812 "hobbies": []interface{}{ 813 "skateboarding", 814 "snowboarding", 815 "go", 816 }, 817 "age": 35, 818 "type": "donut", 819 "newkey": "remote", 820 "name": "Cake", 821 "p_id": "0001", 822 "p_ppu": "0.55", 823 "p_name": "Cake", 824 "p_batters": map[string]interface{}{ 825 "batter": map[string]interface{}{"type": "Regular"}, 826 }, 827 "p_type": "donut", 828 "foos": []map[string]interface{}{ 829 { 830 "foo": []map[string]interface{}{ 831 {"key": 1}, 832 {"key": 2}, 833 {"key": 3}, 834 {"key": 4}, 835 }, 836 }, 837 }, 838 "title_dotenv": "DotEnv Example", 839 "type_dotenv": "donut", 840 "name_dotenv": "Cake", 841 } 842 843 allkeys := sort.StringSlice(AllKeys()) 844 allkeys.Sort() 845 ks.Sort() 846 847 assert.Equal(t, ks, allkeys) 848 assert.Equal(t, all, AllSettings()) 849 } 850 851 func TestAllKeysWithEnv(t *testing.T) { 852 v := New() 853 854 // bind and define environment variables (including a nested one) 855 v.BindEnv("id") 856 v.BindEnv("foo.bar") 857 v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 858 859 testutil.Setenv(t, "ID", "13") 860 testutil.Setenv(t, "FOO_BAR", "baz") 861 862 expectedKeys := sort.StringSlice{"id", "foo.bar"} 863 expectedKeys.Sort() 864 keys := sort.StringSlice(v.AllKeys()) 865 keys.Sort() 866 assert.Equal(t, expectedKeys, keys) 867 } 868 869 func TestAliasesOfAliases(t *testing.T) { 870 Set("Title", "Checking Case") 871 RegisterAlias("Foo", "Bar") 872 RegisterAlias("Bar", "Title") 873 assert.Equal(t, "Checking Case", Get("FOO")) 874 } 875 876 func TestRecursiveAliases(t *testing.T) { 877 RegisterAlias("Baz", "Roo") 878 RegisterAlias("Roo", "baz") 879 } 880 881 func TestUnmarshal(t *testing.T) { 882 SetDefault("port", 1313) 883 Set("name", "Steve") 884 Set("duration", "1s1ms") 885 Set("modes", []int{1, 2, 3}) 886 887 type config struct { 888 Port int 889 Name string 890 Duration time.Duration 891 Modes []int 892 } 893 894 var C config 895 896 err := Unmarshal(&C) 897 if err != nil { 898 t.Fatalf("unable to decode into struct, %v", err) 899 } 900 901 assert.Equal( 902 t, 903 &config{ 904 Name: "Steve", 905 Port: 1313, 906 Duration: time.Second + time.Millisecond, 907 Modes: []int{1, 2, 3}, 908 }, 909 &C, 910 ) 911 912 Set("port", 1234) 913 err = Unmarshal(&C) 914 if err != nil { 915 t.Fatalf("unable to decode into struct, %v", err) 916 } 917 918 assert.Equal( 919 t, 920 &config{ 921 Name: "Steve", 922 Port: 1234, 923 Duration: time.Second + time.Millisecond, 924 Modes: []int{1, 2, 3}, 925 }, 926 &C, 927 ) 928 } 929 930 func TestUnmarshalWithDecoderOptions(t *testing.T) { 931 Set("credentials", "{\"foo\":\"bar\"}") 932 933 opt := DecodeHook(mapstructure.ComposeDecodeHookFunc( 934 mapstructure.StringToTimeDurationHookFunc(), 935 mapstructure.StringToSliceHookFunc(","), 936 // Custom Decode Hook Function 937 func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) { 938 if rf != reflect.String || rt != reflect.Map { 939 return data, nil 940 } 941 m := map[string]string{} 942 raw := data.(string) 943 if raw == "" { 944 return m, nil 945 } 946 return m, json.Unmarshal([]byte(raw), &m) 947 }, 948 )) 949 950 type config struct { 951 Credentials map[string]string 952 } 953 954 var C config 955 956 err := Unmarshal(&C, opt) 957 if err != nil { 958 t.Fatalf("unable to decode into struct, %v", err) 959 } 960 961 assert.Equal(t, &config{ 962 Credentials: map[string]string{"foo": "bar"}, 963 }, &C) 964 } 965 966 func TestBindPFlags(t *testing.T) { 967 v := New() // create independent Viper object 968 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 969 970 testValues := map[string]*string{ 971 "host": nil, 972 "port": nil, 973 "endpoint": nil, 974 } 975 976 mutatedTestValues := map[string]string{ 977 "host": "localhost", 978 "port": "6060", 979 "endpoint": "/public", 980 } 981 982 for name := range testValues { 983 testValues[name] = flagSet.String(name, "", "test") 984 } 985 986 err := v.BindPFlags(flagSet) 987 if err != nil { 988 t.Fatalf("error binding flag set, %v", err) 989 } 990 991 flagSet.VisitAll(func(flag *pflag.Flag) { 992 flag.Value.Set(mutatedTestValues[flag.Name]) 993 flag.Changed = true 994 }) 995 996 for name, expected := range mutatedTestValues { 997 assert.Equal(t, expected, v.Get(name)) 998 } 999 } 1000 1001 // nolint: dupl 1002 func TestBindPFlagsStringSlice(t *testing.T) { 1003 tests := []struct { 1004 Expected []string 1005 Value string 1006 }{ 1007 {[]string{}, ""}, 1008 {[]string{"jeden"}, "jeden"}, 1009 {[]string{"dwa", "trzy"}, "dwa,trzy"}, 1010 {[]string{"cztery", "piec , szesc"}, "cztery,\"piec , szesc\""}, 1011 } 1012 1013 v := New() // create independent Viper object 1014 defaultVal := []string{"default"} 1015 v.SetDefault("stringslice", defaultVal) 1016 1017 for _, testValue := range tests { 1018 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 1019 flagSet.StringSlice("stringslice", testValue.Expected, "test") 1020 1021 for _, changed := range []bool{true, false} { 1022 flagSet.VisitAll(func(f *pflag.Flag) { 1023 f.Value.Set(testValue.Value) 1024 f.Changed = changed 1025 }) 1026 1027 err := v.BindPFlags(flagSet) 1028 if err != nil { 1029 t.Fatalf("error binding flag set, %v", err) 1030 } 1031 1032 type TestStr struct { 1033 StringSlice []string 1034 } 1035 val := &TestStr{} 1036 if err := v.Unmarshal(val); err != nil { 1037 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 1038 } 1039 if changed { 1040 assert.Equal(t, testValue.Expected, val.StringSlice) 1041 assert.Equal(t, testValue.Expected, v.Get("stringslice")) 1042 } else { 1043 assert.Equal(t, defaultVal, val.StringSlice) 1044 } 1045 } 1046 } 1047 } 1048 1049 // nolint: dupl 1050 func TestBindPFlagsStringArray(t *testing.T) { 1051 tests := []struct { 1052 Expected []string 1053 Value string 1054 }{ 1055 {[]string{}, ""}, 1056 {[]string{"jeden"}, "jeden"}, 1057 {[]string{"dwa,trzy"}, "dwa,trzy"}, 1058 {[]string{"cztery,\"piec , szesc\""}, "cztery,\"piec , szesc\""}, 1059 } 1060 1061 v := New() // create independent Viper object 1062 defaultVal := []string{"default"} 1063 v.SetDefault("stringarray", defaultVal) 1064 1065 for _, testValue := range tests { 1066 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 1067 flagSet.StringArray("stringarray", testValue.Expected, "test") 1068 1069 for _, changed := range []bool{true, false} { 1070 flagSet.VisitAll(func(f *pflag.Flag) { 1071 f.Value.Set(testValue.Value) 1072 f.Changed = changed 1073 }) 1074 1075 err := v.BindPFlags(flagSet) 1076 if err != nil { 1077 t.Fatalf("error binding flag set, %v", err) 1078 } 1079 1080 type TestStr struct { 1081 StringArray []string 1082 } 1083 val := &TestStr{} 1084 if err := v.Unmarshal(val); err != nil { 1085 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 1086 } 1087 if changed { 1088 assert.Equal(t, testValue.Expected, val.StringArray) 1089 assert.Equal(t, testValue.Expected, v.Get("stringarray")) 1090 } else { 1091 assert.Equal(t, defaultVal, val.StringArray) 1092 } 1093 } 1094 } 1095 } 1096 1097 // nolint: dupl 1098 func TestBindPFlagsIntSlice(t *testing.T) { 1099 tests := []struct { 1100 Expected []int 1101 Value string 1102 }{ 1103 {[]int{}, ""}, 1104 {[]int{1}, "1"}, 1105 {[]int{2, 3}, "2,3"}, 1106 } 1107 1108 v := New() // create independent Viper object 1109 defaultVal := []int{0} 1110 v.SetDefault("intslice", defaultVal) 1111 1112 for _, testValue := range tests { 1113 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 1114 flagSet.IntSlice("intslice", testValue.Expected, "test") 1115 1116 for _, changed := range []bool{true, false} { 1117 flagSet.VisitAll(func(f *pflag.Flag) { 1118 f.Value.Set(testValue.Value) 1119 f.Changed = changed 1120 }) 1121 1122 err := v.BindPFlags(flagSet) 1123 if err != nil { 1124 t.Fatalf("error binding flag set, %v", err) 1125 } 1126 1127 type TestInt struct { 1128 IntSlice []int 1129 } 1130 val := &TestInt{} 1131 if err := v.Unmarshal(val); err != nil { 1132 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 1133 } 1134 if changed { 1135 assert.Equal(t, testValue.Expected, val.IntSlice) 1136 assert.Equal(t, testValue.Expected, v.Get("intslice")) 1137 } else { 1138 assert.Equal(t, defaultVal, val.IntSlice) 1139 } 1140 } 1141 } 1142 } 1143 1144 func TestBindPFlag(t *testing.T) { 1145 testString := "testing" 1146 testValue := newStringValue(testString, &testString) 1147 1148 flag := &pflag.Flag{ 1149 Name: "testflag", 1150 Value: testValue, 1151 Changed: false, 1152 } 1153 1154 BindPFlag("testvalue", flag) 1155 1156 assert.Equal(t, testString, Get("testvalue")) 1157 1158 flag.Value.Set("testing_mutate") 1159 flag.Changed = true // hack for pflag usage 1160 1161 assert.Equal(t, "testing_mutate", Get("testvalue")) 1162 } 1163 1164 func TestBindPFlagDetectNilFlag(t *testing.T) { 1165 result := BindPFlag("testvalue", nil) 1166 assert.Error(t, result) 1167 } 1168 1169 func TestBindPFlagStringToString(t *testing.T) { 1170 tests := []struct { 1171 Expected map[string]string 1172 Value string 1173 }{ 1174 {map[string]string{}, ""}, 1175 {map[string]string{"yo": "hi"}, "yo=hi"}, 1176 {map[string]string{"yo": "hi", "oh": "hi=there"}, "yo=hi,oh=hi=there"}, 1177 {map[string]string{"yo": ""}, "yo="}, 1178 {map[string]string{"yo": "", "oh": "hi=there"}, "yo=,oh=hi=there"}, 1179 } 1180 1181 v := New() // create independent Viper object 1182 defaultVal := map[string]string{} 1183 v.SetDefault("stringtostring", defaultVal) 1184 1185 for _, testValue := range tests { 1186 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 1187 flagSet.StringToString("stringtostring", testValue.Expected, "test") 1188 1189 for _, changed := range []bool{true, false} { 1190 flagSet.VisitAll(func(f *pflag.Flag) { 1191 f.Value.Set(testValue.Value) 1192 f.Changed = changed 1193 }) 1194 1195 err := v.BindPFlags(flagSet) 1196 if err != nil { 1197 t.Fatalf("error binding flag set, %v", err) 1198 } 1199 1200 type TestMap struct { 1201 StringToString map[string]string 1202 } 1203 val := &TestMap{} 1204 if err := v.Unmarshal(val); err != nil { 1205 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 1206 } 1207 if changed { 1208 assert.Equal(t, testValue.Expected, val.StringToString) 1209 } else { 1210 assert.Equal(t, defaultVal, val.StringToString) 1211 } 1212 } 1213 } 1214 } 1215 1216 func TestBoundCaseSensitivity(t *testing.T) { 1217 assert.Equal(t, "brown", Get("eyes")) 1218 1219 BindEnv("eYEs", "TURTLE_EYES") 1220 1221 testutil.Setenv(t, "TURTLE_EYES", "blue") 1222 1223 assert.Equal(t, "blue", Get("eyes")) 1224 1225 testString := "green" 1226 testValue := newStringValue(testString, &testString) 1227 1228 flag := &pflag.Flag{ 1229 Name: "eyeballs", 1230 Value: testValue, 1231 Changed: true, 1232 } 1233 1234 BindPFlag("eYEs", flag) 1235 assert.Equal(t, "green", Get("eyes")) 1236 } 1237 1238 func TestSizeInBytes(t *testing.T) { 1239 input := map[string]uint{ 1240 "": 0, 1241 "b": 0, 1242 "12 bytes": 0, 1243 "200000000000gb": 0, 1244 "12 b": 12, 1245 "43 MB": 43 * (1 << 20), 1246 "10mb": 10 * (1 << 20), 1247 "1gb": 1 << 30, 1248 } 1249 1250 for str, expected := range input { 1251 assert.Equal(t, expected, parseSizeInBytes(str), str) 1252 } 1253 } 1254 1255 func TestFindsNestedKeys(t *testing.T) { 1256 initConfigs() 1257 dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 1258 1259 Set("super", map[string]interface{}{ 1260 "deep": map[string]interface{}{ 1261 "nested": "value", 1262 }, 1263 }) 1264 1265 expected := map[string]interface{}{ 1266 "super": map[string]interface{}{ 1267 "deep": map[string]interface{}{ 1268 "nested": "value", 1269 }, 1270 }, 1271 "super.deep": map[string]interface{}{ 1272 "nested": "value", 1273 }, 1274 "super.deep.nested": "value", 1275 "owner.organization": "MongoDB", 1276 "batters.batter": []interface{}{ 1277 map[string]interface{}{ 1278 "type": "Regular", 1279 }, 1280 map[string]interface{}{ 1281 "type": "Chocolate", 1282 }, 1283 map[string]interface{}{ 1284 "type": "Blueberry", 1285 }, 1286 map[string]interface{}{ 1287 "type": "Devil's Food", 1288 }, 1289 }, 1290 "hobbies": []interface{}{ 1291 "skateboarding", "snowboarding", "go", 1292 }, 1293 "TITLE_DOTENV": "DotEnv Example", 1294 "TYPE_DOTENV": "donut", 1295 "NAME_DOTENV": "Cake", 1296 "title": "TOML Example", 1297 "newkey": "remote", 1298 "batters": map[string]interface{}{ 1299 "batter": []interface{}{ 1300 map[string]interface{}{ 1301 "type": "Regular", 1302 }, 1303 map[string]interface{}{ 1304 "type": "Chocolate", 1305 }, 1306 map[string]interface{}{ 1307 "type": "Blueberry", 1308 }, 1309 map[string]interface{}{ 1310 "type": "Devil's Food", 1311 }, 1312 }, 1313 }, 1314 "eyes": "brown", 1315 "age": 35, 1316 "owner": map[string]interface{}{ 1317 "organization": "MongoDB", 1318 "bio": "MongoDB Chief Developer Advocate & Hacker at Large", 1319 "dob": dob, 1320 }, 1321 "owner.bio": "MongoDB Chief Developer Advocate & Hacker at Large", 1322 "type": "donut", 1323 "id": "0001", 1324 "name": "Cake", 1325 "hacker": true, 1326 "ppu": 0.55, 1327 "clothing": map[string]interface{}{ 1328 "jacket": "leather", 1329 "trousers": "denim", 1330 "pants": map[string]interface{}{ 1331 "size": "large", 1332 }, 1333 }, 1334 "clothing.jacket": "leather", 1335 "clothing.pants.size": "large", 1336 "clothing.trousers": "denim", 1337 "owner.dob": dob, 1338 "beard": true, 1339 "foos": []map[string]interface{}{ 1340 { 1341 "foo": []map[string]interface{}{ 1342 { 1343 "key": 1, 1344 }, 1345 { 1346 "key": 2, 1347 }, 1348 { 1349 "key": 3, 1350 }, 1351 { 1352 "key": 4, 1353 }, 1354 }, 1355 }, 1356 }, 1357 } 1358 1359 for key, expectedValue := range expected { 1360 assert.Equal(t, expectedValue, v.Get(key)) 1361 } 1362 } 1363 1364 func TestReadBufConfig(t *testing.T) { 1365 v := New() 1366 v.SetConfigType("yaml") 1367 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1368 t.Log(v.AllKeys()) 1369 1370 assert.True(t, v.InConfig("name")) 1371 assert.True(t, v.InConfig("clothing.jacket")) 1372 assert.False(t, v.InConfig("state")) 1373 assert.False(t, v.InConfig("clothing.hat")) 1374 assert.Equal(t, "steve", v.Get("name")) 1375 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies")) 1376 assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, v.Get("clothing")) 1377 assert.Equal(t, 35, v.Get("age")) 1378 } 1379 1380 func TestIsSet(t *testing.T) { 1381 v := New() 1382 v.SetConfigType("yaml") 1383 1384 /* config and defaults */ 1385 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1386 v.SetDefault("clothing.shoes", "sneakers") 1387 1388 assert.True(t, v.IsSet("clothing")) 1389 assert.True(t, v.IsSet("clothing.jacket")) 1390 assert.False(t, v.IsSet("clothing.jackets")) 1391 assert.True(t, v.IsSet("clothing.shoes")) 1392 1393 /* state change */ 1394 assert.False(t, v.IsSet("helloworld")) 1395 v.Set("helloworld", "fubar") 1396 assert.True(t, v.IsSet("helloworld")) 1397 1398 /* env */ 1399 v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 1400 v.BindEnv("eyes") 1401 v.BindEnv("foo") 1402 v.BindEnv("clothing.hat") 1403 v.BindEnv("clothing.hats") 1404 1405 testutil.Setenv(t, "FOO", "bar") 1406 testutil.Setenv(t, "CLOTHING_HAT", "bowler") 1407 1408 assert.True(t, v.IsSet("eyes")) // in the config file 1409 assert.True(t, v.IsSet("foo")) // in the environment 1410 assert.True(t, v.IsSet("clothing.hat")) // in the environment 1411 assert.False(t, v.IsSet("clothing.hats")) // not defined 1412 1413 /* flags */ 1414 flagset := pflag.NewFlagSet("testisset", pflag.ContinueOnError) 1415 flagset.Bool("foobaz", false, "foobaz") 1416 flagset.Bool("barbaz", false, "barbaz") 1417 foobaz, barbaz := flagset.Lookup("foobaz"), flagset.Lookup("barbaz") 1418 v.BindPFlag("foobaz", foobaz) 1419 v.BindPFlag("barbaz", barbaz) 1420 barbaz.Value.Set("true") 1421 barbaz.Changed = true // hack for pflag usage 1422 1423 assert.False(t, v.IsSet("foobaz")) 1424 assert.True(t, v.IsSet("barbaz")) 1425 } 1426 1427 func TestDirsSearch(t *testing.T) { 1428 root, config, cleanup := initDirs(t) 1429 defer cleanup() 1430 1431 v := New() 1432 v.SetConfigName(config) 1433 v.SetDefault(`key`, `default`) 1434 1435 entries, err := ioutil.ReadDir(root) 1436 assert.Nil(t, err) 1437 for _, e := range entries { 1438 if e.IsDir() { 1439 v.AddConfigPath(e.Name()) 1440 } 1441 } 1442 1443 err = v.ReadInConfig() 1444 assert.Nil(t, err) 1445 1446 assert.Equal(t, `value is `+filepath.Base(v.configPaths[0]), v.GetString(`key`)) 1447 } 1448 1449 func TestWrongDirsSearchNotFound(t *testing.T) { 1450 _, config, cleanup := initDirs(t) 1451 defer cleanup() 1452 1453 v := New() 1454 v.SetConfigName(config) 1455 v.SetDefault(`key`, `default`) 1456 1457 v.AddConfigPath(`whattayoutalkingbout`) 1458 v.AddConfigPath(`thispathaintthere`) 1459 1460 err := v.ReadInConfig() 1461 assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) 1462 1463 // Even though config did not load and the error might have 1464 // been ignored by the client, the default still loads 1465 assert.Equal(t, `default`, v.GetString(`key`)) 1466 } 1467 1468 func TestWrongDirsSearchNotFoundForMerge(t *testing.T) { 1469 _, config, cleanup := initDirs(t) 1470 defer cleanup() 1471 1472 v := New() 1473 v.SetConfigName(config) 1474 v.SetDefault(`key`, `default`) 1475 1476 v.AddConfigPath(`whattayoutalkingbout`) 1477 v.AddConfigPath(`thispathaintthere`) 1478 1479 err := v.MergeInConfig() 1480 assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) 1481 1482 // Even though config did not load and the error might have 1483 // been ignored by the client, the default still loads 1484 assert.Equal(t, `default`, v.GetString(`key`)) 1485 } 1486 1487 func TestSub(t *testing.T) { 1488 v := New() 1489 v.SetConfigType("yaml") 1490 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1491 1492 subv := v.Sub("clothing") 1493 assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("pants.size")) 1494 1495 subv = v.Sub("clothing.pants") 1496 assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("size")) 1497 1498 subv = v.Sub("clothing.pants.size") 1499 assert.Equal(t, (*Viper)(nil), subv) 1500 1501 subv = v.Sub("missing.key") 1502 assert.Equal(t, (*Viper)(nil), subv) 1503 } 1504 1505 var hclWriteExpected = []byte(`"foos" = { 1506 "foo" = { 1507 "key" = 1 1508 } 1509 1510 "foo" = { 1511 "key" = 2 1512 } 1513 1514 "foo" = { 1515 "key" = 3 1516 } 1517 1518 "foo" = { 1519 "key" = 4 1520 } 1521 } 1522 1523 "id" = "0001" 1524 1525 "name" = "Cake" 1526 1527 "ppu" = 0.55 1528 1529 "type" = "donut"`) 1530 1531 var jsonWriteExpected = []byte(`{ 1532 "batters": { 1533 "batter": [ 1534 { 1535 "type": "Regular" 1536 }, 1537 { 1538 "type": "Chocolate" 1539 }, 1540 { 1541 "type": "Blueberry" 1542 }, 1543 { 1544 "type": "Devil's Food" 1545 } 1546 ] 1547 }, 1548 "id": "0001", 1549 "name": "Cake", 1550 "ppu": 0.55, 1551 "type": "donut" 1552 }`) 1553 1554 var propertiesWriteExpected = []byte(`p_id = 0001 1555 p_type = donut 1556 p_name = Cake 1557 p_ppu = 0.55 1558 p_batters.batter.type = Regular 1559 `) 1560 1561 var yamlWriteExpected = []byte(`age: 35 1562 beard: true 1563 clothing: 1564 jacket: leather 1565 pants: 1566 size: large 1567 trousers: denim 1568 eyes: brown 1569 hacker: true 1570 hobbies: 1571 - skateboarding 1572 - snowboarding 1573 - go 1574 name: steve 1575 `) 1576 1577 func TestWriteConfig(t *testing.T) { 1578 fs := afero.NewMemMapFs() 1579 testCases := map[string]struct { 1580 configName string 1581 inConfigType string 1582 outConfigType string 1583 fileName string 1584 input []byte 1585 expectedContent []byte 1586 }{ 1587 "hcl with file extension": { 1588 configName: "c", 1589 inConfigType: "hcl", 1590 outConfigType: "hcl", 1591 fileName: "c.hcl", 1592 input: hclExample, 1593 expectedContent: hclWriteExpected, 1594 }, 1595 "hcl without file extension": { 1596 configName: "c", 1597 inConfigType: "hcl", 1598 outConfigType: "hcl", 1599 fileName: "c", 1600 input: hclExample, 1601 expectedContent: hclWriteExpected, 1602 }, 1603 "hcl with file extension and mismatch type": { 1604 configName: "c", 1605 inConfigType: "hcl", 1606 outConfigType: "json", 1607 fileName: "c.hcl", 1608 input: hclExample, 1609 expectedContent: hclWriteExpected, 1610 }, 1611 "json with file extension": { 1612 configName: "c", 1613 inConfigType: "json", 1614 outConfigType: "json", 1615 fileName: "c.json", 1616 input: jsonExample, 1617 expectedContent: jsonWriteExpected, 1618 }, 1619 "json without file extension": { 1620 configName: "c", 1621 inConfigType: "json", 1622 outConfigType: "json", 1623 fileName: "c", 1624 input: jsonExample, 1625 expectedContent: jsonWriteExpected, 1626 }, 1627 "json with file extension and mismatch type": { 1628 configName: "c", 1629 inConfigType: "json", 1630 outConfigType: "hcl", 1631 fileName: "c.json", 1632 input: jsonExample, 1633 expectedContent: jsonWriteExpected, 1634 }, 1635 "properties with file extension": { 1636 configName: "c", 1637 inConfigType: "properties", 1638 outConfigType: "properties", 1639 fileName: "c.properties", 1640 input: propertiesExample, 1641 expectedContent: propertiesWriteExpected, 1642 }, 1643 "properties without file extension": { 1644 configName: "c", 1645 inConfigType: "properties", 1646 outConfigType: "properties", 1647 fileName: "c", 1648 input: propertiesExample, 1649 expectedContent: propertiesWriteExpected, 1650 }, 1651 "yaml with file extension": { 1652 configName: "c", 1653 inConfigType: "yaml", 1654 outConfigType: "yaml", 1655 fileName: "c.yaml", 1656 input: yamlExample, 1657 expectedContent: yamlWriteExpected, 1658 }, 1659 "yaml without file extension": { 1660 configName: "c", 1661 inConfigType: "yaml", 1662 outConfigType: "yaml", 1663 fileName: "c", 1664 input: yamlExample, 1665 expectedContent: yamlWriteExpected, 1666 }, 1667 "yaml with file extension and mismatch type": { 1668 configName: "c", 1669 inConfigType: "yaml", 1670 outConfigType: "json", 1671 fileName: "c.yaml", 1672 input: yamlExample, 1673 expectedContent: yamlWriteExpected, 1674 }, 1675 } 1676 for name, tc := range testCases { 1677 t.Run(name, func(t *testing.T) { 1678 v := New() 1679 v.SetFs(fs) 1680 v.SetConfigName(tc.fileName) 1681 v.SetConfigType(tc.inConfigType) 1682 1683 err := v.ReadConfig(bytes.NewBuffer(tc.input)) 1684 if err != nil { 1685 t.Fatal(err) 1686 } 1687 v.SetConfigType(tc.outConfigType) 1688 if err := v.WriteConfigAs(tc.fileName); err != nil { 1689 t.Fatal(err) 1690 } 1691 read, err := afero.ReadFile(fs, tc.fileName) 1692 if err != nil { 1693 t.Fatal(err) 1694 } 1695 assert.Equal(t, tc.expectedContent, read) 1696 }) 1697 } 1698 } 1699 1700 func TestWriteConfigTOML(t *testing.T) { 1701 fs := afero.NewMemMapFs() 1702 1703 testCases := map[string]struct { 1704 configName string 1705 configType string 1706 fileName string 1707 input []byte 1708 }{ 1709 "with file extension": { 1710 configName: "c", 1711 configType: "toml", 1712 fileName: "c.toml", 1713 input: tomlExample, 1714 }, 1715 "without file extension": { 1716 configName: "c", 1717 configType: "toml", 1718 fileName: "c", 1719 input: tomlExample, 1720 }, 1721 } 1722 for name, tc := range testCases { 1723 t.Run(name, func(t *testing.T) { 1724 v := New() 1725 v.SetFs(fs) 1726 v.SetConfigName(tc.configName) 1727 v.SetConfigType(tc.configType) 1728 err := v.ReadConfig(bytes.NewBuffer(tc.input)) 1729 if err != nil { 1730 t.Fatal(err) 1731 } 1732 if err := v.WriteConfigAs(tc.fileName); err != nil { 1733 t.Fatal(err) 1734 } 1735 1736 // The TOML String method does not order the contents. 1737 // Therefore, we must read the generated file and compare the data. 1738 v2 := New() 1739 v2.SetFs(fs) 1740 v2.SetConfigName(tc.configName) 1741 v2.SetConfigType(tc.configType) 1742 v2.SetConfigFile(tc.fileName) 1743 err = v2.ReadInConfig() 1744 if err != nil { 1745 t.Fatal(err) 1746 } 1747 1748 assert.Equal(t, v.GetString("title"), v2.GetString("title")) 1749 assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio")) 1750 assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob")) 1751 assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization")) 1752 }) 1753 } 1754 } 1755 1756 func TestWriteConfigDotEnv(t *testing.T) { 1757 fs := afero.NewMemMapFs() 1758 testCases := map[string]struct { 1759 configName string 1760 configType string 1761 fileName string 1762 input []byte 1763 }{ 1764 "with file extension": { 1765 configName: "c", 1766 configType: "env", 1767 fileName: "c.env", 1768 input: dotenvExample, 1769 }, 1770 "without file extension": { 1771 configName: "c", 1772 configType: "env", 1773 fileName: "c", 1774 input: dotenvExample, 1775 }, 1776 } 1777 for name, tc := range testCases { 1778 t.Run(name, func(t *testing.T) { 1779 v := New() 1780 v.SetFs(fs) 1781 v.SetConfigName(tc.configName) 1782 v.SetConfigType(tc.configType) 1783 err := v.ReadConfig(bytes.NewBuffer(tc.input)) 1784 if err != nil { 1785 t.Fatal(err) 1786 } 1787 if err := v.WriteConfigAs(tc.fileName); err != nil { 1788 t.Fatal(err) 1789 } 1790 1791 // The TOML String method does not order the contents. 1792 // Therefore, we must read the generated file and compare the data. 1793 v2 := New() 1794 v2.SetFs(fs) 1795 v2.SetConfigName(tc.configName) 1796 v2.SetConfigType(tc.configType) 1797 v2.SetConfigFile(tc.fileName) 1798 err = v2.ReadInConfig() 1799 if err != nil { 1800 t.Fatal(err) 1801 } 1802 1803 assert.Equal(t, v.GetString("title_dotenv"), v2.GetString("title_dotenv")) 1804 assert.Equal(t, v.GetString("type_dotenv"), v2.GetString("type_dotenv")) 1805 assert.Equal(t, v.GetString("kind_dotenv"), v2.GetString("kind_dotenv")) 1806 }) 1807 } 1808 } 1809 1810 func TestSafeWriteConfig(t *testing.T) { 1811 v := New() 1812 fs := afero.NewMemMapFs() 1813 v.SetFs(fs) 1814 v.AddConfigPath("/test") 1815 v.SetConfigName("c") 1816 v.SetConfigType("yaml") 1817 require.NoError(t, v.ReadConfig(bytes.NewBuffer(yamlExample))) 1818 require.NoError(t, v.SafeWriteConfig()) 1819 read, err := afero.ReadFile(fs, testutil.AbsFilePath(t, "/test/c.yaml")) 1820 require.NoError(t, err) 1821 assert.Equal(t, yamlWriteExpected, read) 1822 } 1823 1824 func TestSafeWriteConfigWithMissingConfigPath(t *testing.T) { 1825 v := New() 1826 fs := afero.NewMemMapFs() 1827 v.SetFs(fs) 1828 v.SetConfigName("c") 1829 v.SetConfigType("yaml") 1830 require.EqualError(t, v.SafeWriteConfig(), "missing configuration for 'configPath'") 1831 } 1832 1833 func TestSafeWriteConfigWithExistingFile(t *testing.T) { 1834 v := New() 1835 fs := afero.NewMemMapFs() 1836 fs.Create(testutil.AbsFilePath(t, "/test/c.yaml")) 1837 v.SetFs(fs) 1838 v.AddConfigPath("/test") 1839 v.SetConfigName("c") 1840 v.SetConfigType("yaml") 1841 err := v.SafeWriteConfig() 1842 require.Error(t, err) 1843 _, ok := err.(ConfigFileAlreadyExistsError) 1844 assert.True(t, ok, "Expected ConfigFileAlreadyExistsError") 1845 } 1846 1847 func TestSafeWriteAsConfig(t *testing.T) { 1848 v := New() 1849 fs := afero.NewMemMapFs() 1850 v.SetFs(fs) 1851 err := v.ReadConfig(bytes.NewBuffer(yamlExample)) 1852 if err != nil { 1853 t.Fatal(err) 1854 } 1855 require.NoError(t, v.SafeWriteConfigAs("/test/c.yaml")) 1856 if _, err = afero.ReadFile(fs, "/test/c.yaml"); err != nil { 1857 t.Fatal(err) 1858 } 1859 } 1860 1861 func TestSafeWriteConfigAsWithExistingFile(t *testing.T) { 1862 v := New() 1863 fs := afero.NewMemMapFs() 1864 fs.Create("/test/c.yaml") 1865 v.SetFs(fs) 1866 err := v.SafeWriteConfigAs("/test/c.yaml") 1867 require.Error(t, err) 1868 _, ok := err.(ConfigFileAlreadyExistsError) 1869 assert.True(t, ok, "Expected ConfigFileAlreadyExistsError") 1870 } 1871 1872 func TestWriteHiddenFile(t *testing.T) { 1873 v := New() 1874 fs := afero.NewMemMapFs() 1875 fs.Create(testutil.AbsFilePath(t, "/test/.config")) 1876 v.SetFs(fs) 1877 1878 v.SetConfigName(".config") 1879 v.SetConfigType("yaml") 1880 v.AddConfigPath("/test") 1881 1882 err := v.ReadInConfig() 1883 require.NoError(t, err) 1884 1885 err = v.WriteConfig() 1886 require.NoError(t, err) 1887 } 1888 1889 var yamlMergeExampleTgt = []byte(` 1890 hello: 1891 pop: 37890 1892 largenum: 765432101234567 1893 num2pow63: 9223372036854775808 1894 universe: null 1895 world: 1896 - us 1897 - uk 1898 - fr 1899 - de 1900 `) 1901 1902 var yamlMergeExampleSrc = []byte(` 1903 hello: 1904 pop: 45000 1905 largenum: 7654321001234567 1906 universe: 1907 - mw 1908 - ad 1909 ints: 1910 - 1 1911 - 2 1912 fu: bar 1913 `) 1914 1915 func TestMergeConfig(t *testing.T) { 1916 v := New() 1917 v.SetConfigType("yml") 1918 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1919 t.Fatal(err) 1920 } 1921 1922 if pop := v.GetInt("hello.pop"); pop != 37890 { 1923 t.Fatalf("pop != 37890, = %d", pop) 1924 } 1925 1926 if pop := v.GetInt32("hello.pop"); pop != int32(37890) { 1927 t.Fatalf("pop != 37890, = %d", pop) 1928 } 1929 1930 if pop := v.GetInt64("hello.largenum"); pop != int64(765432101234567) { 1931 t.Fatalf("int64 largenum != 765432101234567, = %d", pop) 1932 } 1933 1934 if pop := v.GetUint("hello.pop"); pop != 37890 { 1935 t.Fatalf("uint pop != 37890, = %d", pop) 1936 } 1937 1938 if pop := v.GetUint32("hello.pop"); pop != 37890 { 1939 t.Fatalf("uint32 pop != 37890, = %d", pop) 1940 } 1941 1942 if pop := v.GetUint64("hello.num2pow63"); pop != 9223372036854775808 { 1943 t.Fatalf("uint64 num2pow63 != 9223372036854775808, = %d", pop) 1944 } 1945 1946 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1947 t.Fatalf("len(world) != 4, = %d", len(world)) 1948 } 1949 1950 if fu := v.GetString("fu"); fu != "" { 1951 t.Fatalf("fu != \"\", = %s", fu) 1952 } 1953 1954 if err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1955 t.Fatal(err) 1956 } 1957 1958 if pop := v.GetInt("hello.pop"); pop != 45000 { 1959 t.Fatalf("pop != 45000, = %d", pop) 1960 } 1961 1962 if pop := v.GetInt32("hello.pop"); pop != int32(45000) { 1963 t.Fatalf("pop != 45000, = %d", pop) 1964 } 1965 1966 if pop := v.GetInt64("hello.largenum"); pop != int64(7654321001234567) { 1967 t.Fatalf("int64 largenum != 7654321001234567, = %d", pop) 1968 } 1969 1970 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1971 t.Fatalf("len(world) != 4, = %d", len(world)) 1972 } 1973 1974 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1975 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1976 } 1977 1978 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 1979 t.Fatalf("len(ints) != 2, = %d", len(ints)) 1980 } 1981 1982 if fu := v.GetString("fu"); fu != "bar" { 1983 t.Fatalf("fu != \"bar\", = %s", fu) 1984 } 1985 } 1986 1987 func TestMergeConfigNoMerge(t *testing.T) { 1988 v := New() 1989 v.SetConfigType("yml") 1990 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1991 t.Fatal(err) 1992 } 1993 1994 if pop := v.GetInt("hello.pop"); pop != 37890 { 1995 t.Fatalf("pop != 37890, = %d", pop) 1996 } 1997 1998 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1999 t.Fatalf("len(world) != 4, = %d", len(world)) 2000 } 2001 2002 if fu := v.GetString("fu"); fu != "" { 2003 t.Fatalf("fu != \"\", = %s", fu) 2004 } 2005 2006 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 2007 t.Fatal(err) 2008 } 2009 2010 if pop := v.GetInt("hello.pop"); pop != 45000 { 2011 t.Fatalf("pop != 45000, = %d", pop) 2012 } 2013 2014 if world := v.GetStringSlice("hello.world"); len(world) != 0 { 2015 t.Fatalf("len(world) != 0, = %d", len(world)) 2016 } 2017 2018 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 2019 t.Fatalf("len(universe) != 2, = %d", len(universe)) 2020 } 2021 2022 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 2023 t.Fatalf("len(ints) != 2, = %d", len(ints)) 2024 } 2025 2026 if fu := v.GetString("fu"); fu != "bar" { 2027 t.Fatalf("fu != \"bar\", = %s", fu) 2028 } 2029 } 2030 2031 func TestMergeConfigMap(t *testing.T) { 2032 v := New() 2033 v.SetConfigType("yml") 2034 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 2035 t.Fatal(err) 2036 } 2037 2038 assert := func(i int) { 2039 large := v.GetInt64("hello.largenum") 2040 pop := v.GetInt("hello.pop") 2041 if large != 765432101234567 { 2042 t.Fatal("Got large num:", large) 2043 } 2044 2045 if pop != i { 2046 t.Fatal("Got pop:", pop) 2047 } 2048 } 2049 2050 assert(37890) 2051 2052 update := map[string]interface{}{ 2053 "Hello": map[string]interface{}{ 2054 "Pop": 1234, 2055 }, 2056 "World": map[interface{}]interface{}{ 2057 "Rock": 345, 2058 }, 2059 } 2060 2061 if err := v.MergeConfigMap(update); err != nil { 2062 t.Fatal(err) 2063 } 2064 2065 if rock := v.GetInt("world.rock"); rock != 345 { 2066 t.Fatal("Got rock:", rock) 2067 } 2068 2069 assert(1234) 2070 } 2071 2072 func TestUnmarshalingWithAliases(t *testing.T) { 2073 v := New() 2074 v.SetDefault("ID", 1) 2075 v.Set("name", "Steve") 2076 v.Set("lastname", "Owen") 2077 2078 v.RegisterAlias("UserID", "ID") 2079 v.RegisterAlias("Firstname", "name") 2080 v.RegisterAlias("Surname", "lastname") 2081 2082 type config struct { 2083 ID int 2084 FirstName string 2085 Surname string 2086 } 2087 2088 var C config 2089 err := v.Unmarshal(&C) 2090 if err != nil { 2091 t.Fatalf("unable to decode into struct, %v", err) 2092 } 2093 2094 assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C) 2095 } 2096 2097 func TestSetConfigNameClearsFileCache(t *testing.T) { 2098 SetConfigFile("/tmp/config.yaml") 2099 SetConfigName("default") 2100 f, err := v.getConfigFile() 2101 if err == nil { 2102 t.Fatalf("config file cache should have been cleared") 2103 } 2104 assert.Empty(t, f) 2105 } 2106 2107 func TestShadowedNestedValue(t *testing.T) { 2108 config := `name: steve 2109 clothing: 2110 jacket: leather 2111 trousers: denim 2112 pants: 2113 size: large 2114 ` 2115 initConfig("yaml", config) 2116 2117 assert.Equal(t, "steve", GetString("name")) 2118 2119 polyester := "polyester" 2120 SetDefault("clothing.shirt", polyester) 2121 SetDefault("clothing.jacket.price", 100) 2122 2123 assert.Equal(t, "leather", GetString("clothing.jacket")) 2124 assert.Nil(t, Get("clothing.jacket.price")) 2125 assert.Equal(t, polyester, GetString("clothing.shirt")) 2126 2127 clothingSettings := AllSettings()["clothing"].(map[string]interface{}) 2128 assert.Equal(t, "leather", clothingSettings["jacket"]) 2129 assert.Equal(t, polyester, clothingSettings["shirt"]) 2130 } 2131 2132 func TestDotParameter(t *testing.T) { 2133 initJSON() 2134 // shoud take precedence over batters defined in jsonExample 2135 r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`)) 2136 unmarshalReader(r, v.config) 2137 2138 actual := Get("batters.batter") 2139 expected := []interface{}{map[string]interface{}{"type": "Small"}} 2140 assert.Equal(t, expected, actual) 2141 } 2142 2143 func TestCaseInsensitive(t *testing.T) { 2144 for _, config := range []struct { 2145 typ string 2146 content string 2147 }{ 2148 {"yaml", ` 2149 aBcD: 1 2150 eF: 2151 gH: 2 2152 iJk: 3 2153 Lm: 2154 nO: 4 2155 P: 2156 Q: 5 2157 R: 6 2158 `}, 2159 {"json", `{ 2160 "aBcD": 1, 2161 "eF": { 2162 "iJk": 3, 2163 "Lm": { 2164 "P": { 2165 "Q": 5, 2166 "R": 6 2167 }, 2168 "nO": 4 2169 }, 2170 "gH": 2 2171 } 2172 }`}, 2173 {"toml", `aBcD = 1 2174 [eF] 2175 gH = 2 2176 iJk = 3 2177 [eF.Lm] 2178 nO = 4 2179 [eF.Lm.P] 2180 Q = 5 2181 R = 6 2182 `}, 2183 } { 2184 doTestCaseInsensitive(t, config.typ, config.content) 2185 } 2186 } 2187 2188 func TestCaseInsensitiveSet(t *testing.T) { 2189 Reset() 2190 m1 := map[string]interface{}{ 2191 "Foo": 32, 2192 "Bar": map[interface{}]interface{}{ 2193 "ABc": "A", 2194 "cDE": "B", 2195 }, 2196 } 2197 2198 m2 := map[string]interface{}{ 2199 "Foo": 52, 2200 "Bar": map[interface{}]interface{}{ 2201 "bCd": "A", 2202 "eFG": "B", 2203 }, 2204 } 2205 2206 Set("Given1", m1) 2207 Set("Number1", 42) 2208 2209 SetDefault("Given2", m2) 2210 SetDefault("Number2", 52) 2211 2212 // Verify SetDefault 2213 if v := Get("number2"); v != 52 { 2214 t.Fatalf("Expected 52 got %q", v) 2215 } 2216 2217 if v := Get("given2.foo"); v != 52 { 2218 t.Fatalf("Expected 52 got %q", v) 2219 } 2220 2221 if v := Get("given2.bar.bcd"); v != "A" { 2222 t.Fatalf("Expected A got %q", v) 2223 } 2224 2225 if _, ok := m2["Foo"]; !ok { 2226 t.Fatal("Input map changed") 2227 } 2228 2229 // Verify Set 2230 if v := Get("number1"); v != 42 { 2231 t.Fatalf("Expected 42 got %q", v) 2232 } 2233 2234 if v := Get("given1.foo"); v != 32 { 2235 t.Fatalf("Expected 32 got %q", v) 2236 } 2237 2238 if v := Get("given1.bar.abc"); v != "A" { 2239 t.Fatalf("Expected A got %q", v) 2240 } 2241 2242 if _, ok := m1["Foo"]; !ok { 2243 t.Fatal("Input map changed") 2244 } 2245 } 2246 2247 func TestParseNested(t *testing.T) { 2248 type duration struct { 2249 Delay time.Duration 2250 } 2251 2252 type item struct { 2253 Name string 2254 Delay time.Duration 2255 Nested duration 2256 } 2257 2258 config := `[[parent]] 2259 delay="100ms" 2260 [parent.nested] 2261 delay="200ms" 2262 ` 2263 initConfig("toml", config) 2264 2265 var items []item 2266 err := v.UnmarshalKey("parent", &items) 2267 if err != nil { 2268 t.Fatalf("unable to decode into struct, %v", err) 2269 } 2270 2271 assert.Equal(t, 1, len(items)) 2272 assert.Equal(t, 100*time.Millisecond, items[0].Delay) 2273 assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) 2274 } 2275 2276 func doTestCaseInsensitive(t *testing.T, typ, config string) { 2277 initConfig(typ, config) 2278 Set("RfD", true) 2279 assert.Equal(t, true, Get("rfd")) 2280 assert.Equal(t, true, Get("rFD")) 2281 assert.Equal(t, 1, cast.ToInt(Get("abcd"))) 2282 assert.Equal(t, 1, cast.ToInt(Get("Abcd"))) 2283 assert.Equal(t, 2, cast.ToInt(Get("ef.gh"))) 2284 assert.Equal(t, 3, cast.ToInt(Get("ef.ijk"))) 2285 assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no"))) 2286 assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q"))) 2287 } 2288 2289 func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) { 2290 watchDir, err := ioutil.TempDir("", "") 2291 require.Nil(t, err) 2292 configFile := path.Join(watchDir, "config.yaml") 2293 err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0o640) 2294 require.Nil(t, err) 2295 cleanup := func() { 2296 os.RemoveAll(watchDir) 2297 } 2298 v := New() 2299 v.SetConfigFile(configFile) 2300 err = v.ReadInConfig() 2301 require.Nil(t, err) 2302 require.Equal(t, "bar", v.Get("foo")) 2303 return v, configFile, cleanup 2304 } 2305 2306 func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func()) { 2307 watchDir, err := ioutil.TempDir("", "") 2308 require.Nil(t, err) 2309 dataDir1 := path.Join(watchDir, "data1") 2310 err = os.Mkdir(dataDir1, 0o777) 2311 require.Nil(t, err) 2312 realConfigFile := path.Join(dataDir1, "config.yaml") 2313 t.Logf("Real config file location: %s\n", realConfigFile) 2314 err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0o640) 2315 require.Nil(t, err) 2316 cleanup := func() { 2317 os.RemoveAll(watchDir) 2318 } 2319 // now, symlink the tm `data1` dir to `data` in the baseDir 2320 os.Symlink(dataDir1, path.Join(watchDir, "data")) 2321 // and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml` 2322 configFile := path.Join(watchDir, "config.yaml") 2323 os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile) 2324 t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml")) 2325 // init Viper 2326 v := New() 2327 v.SetConfigFile(configFile) 2328 err = v.ReadInConfig() 2329 require.Nil(t, err) 2330 require.Equal(t, "bar", v.Get("foo")) 2331 return v, watchDir, configFile, cleanup 2332 } 2333 2334 func TestWatchFile(t *testing.T) { 2335 if runtime.GOOS == "linux" { 2336 // TODO(bep) FIX ME 2337 t.Skip("Skip test on Linux ...") 2338 } 2339 2340 t.Run("file content changed", func(t *testing.T) { 2341 // given a `config.yaml` file being watched 2342 v, configFile, cleanup := newViperWithConfigFile(t) 2343 defer cleanup() 2344 _, err := os.Stat(configFile) 2345 require.NoError(t, err) 2346 t.Logf("test config file: %s\n", configFile) 2347 wg := sync.WaitGroup{} 2348 wg.Add(1) 2349 var wgDoneOnce sync.Once // OnConfigChange is called twice on Windows 2350 v.OnConfigChange(func(in fsnotify.Event) { 2351 t.Logf("config file changed") 2352 wgDoneOnce.Do(func() { 2353 wg.Done() 2354 }) 2355 }) 2356 v.WatchConfig() 2357 // when overwriting the file and waiting for the custom change notification handler to be triggered 2358 err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0o640) 2359 wg.Wait() 2360 // then the config value should have changed 2361 require.Nil(t, err) 2362 assert.Equal(t, "baz", v.Get("foo")) 2363 }) 2364 2365 t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) { 2366 // skip if not executed on Linux 2367 if runtime.GOOS != "linux" { 2368 t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...") 2369 } 2370 v, watchDir, _, _ := newViperWithSymlinkedConfigFile(t) 2371 // defer cleanup() 2372 wg := sync.WaitGroup{} 2373 v.WatchConfig() 2374 v.OnConfigChange(func(in fsnotify.Event) { 2375 t.Logf("config file changed") 2376 wg.Done() 2377 }) 2378 wg.Add(1) 2379 // when link to another `config.yaml` file 2380 dataDir2 := path.Join(watchDir, "data2") 2381 err := os.Mkdir(dataDir2, 0o777) 2382 require.Nil(t, err) 2383 configFile2 := path.Join(dataDir2, "config.yaml") 2384 err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0o640) 2385 require.Nil(t, err) 2386 // change the symlink using the `ln -sfn` command 2387 err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run() 2388 require.Nil(t, err) 2389 wg.Wait() 2390 // then 2391 require.Nil(t, err) 2392 assert.Equal(t, "baz", v.Get("foo")) 2393 }) 2394 } 2395 2396 func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) { 2397 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 2398 flags.String("foo.bar", "cobra_flag", "") 2399 2400 v := New() 2401 assert.NoError(t, v.BindPFlags(flags)) 2402 2403 config := &struct { 2404 Foo struct { 2405 Bar string 2406 } 2407 }{} 2408 2409 assert.NoError(t, v.Unmarshal(config)) 2410 assert.Equal(t, "cobra_flag", config.Foo.Bar) 2411 } 2412 2413 var yamlExampleWithDot = []byte(`Hacker: true 2414 name: steve 2415 hobbies: 2416 - skateboarding 2417 - snowboarding 2418 - go 2419 clothing: 2420 jacket: leather 2421 trousers: denim 2422 pants: 2423 size: large 2424 age: 35 2425 eyes : brown 2426 beard: true 2427 emails: 2428 steve@hacker.com: 2429 created: 01/02/03 2430 active: true 2431 `) 2432 2433 func TestKeyDelimiter(t *testing.T) { 2434 v := NewWithOptions(KeyDelimiter("::")) 2435 v.SetConfigType("yaml") 2436 r := strings.NewReader(string(yamlExampleWithDot)) 2437 2438 err := v.unmarshalReader(r, v.config) 2439 require.NoError(t, err) 2440 2441 values := map[string]interface{}{ 2442 "image": map[string]interface{}{ 2443 "repository": "someImage", 2444 "tag": "1.0.0", 2445 }, 2446 "ingress": map[string]interface{}{ 2447 "annotations": map[string]interface{}{ 2448 "traefik.frontend.rule.type": "PathPrefix", 2449 "traefik.ingress.kubernetes.io/ssl-redirect": "true", 2450 }, 2451 }, 2452 } 2453 2454 v.SetDefault("charts::values", values) 2455 2456 assert.Equal(t, "leather", v.GetString("clothing::jacket")) 2457 assert.Equal(t, "01/02/03", v.GetString("emails::steve@hacker.com::created")) 2458 2459 type config struct { 2460 Charts struct { 2461 Values map[string]interface{} 2462 } 2463 } 2464 2465 expected := config{ 2466 Charts: struct { 2467 Values map[string]interface{} 2468 }{ 2469 Values: values, 2470 }, 2471 } 2472 2473 var actual config 2474 2475 assert.NoError(t, v.Unmarshal(&actual)) 2476 2477 assert.Equal(t, expected, actual) 2478 } 2479 2480 var yamlDeepNestedSlices = []byte(`TV: 2481 - title: "The expanse" 2482 seasons: 2483 - first_released: "December 14, 2015" 2484 episodes: 2485 - title: "Dulcinea" 2486 air_date: "December 14, 2015" 2487 - title: "The Big Empty" 2488 air_date: "December 15, 2015" 2489 - title: "Remember the Cant" 2490 air_date: "December 22, 2015" 2491 - first_released: "February 1, 2017" 2492 episodes: 2493 - title: "Safe" 2494 air_date: "February 1, 2017" 2495 - title: "Doors & Corners" 2496 air_date: "February 1, 2017" 2497 - title: "Static" 2498 air_date: "February 8, 2017" 2499 episodes: 2500 - ["Dulcinea", "The Big Empty", "Remember the Cant"] 2501 - ["Safe", "Doors & Corners", "Static"] 2502 `) 2503 2504 func TestSliceIndexAccess(t *testing.T) { 2505 v.SetConfigType("yaml") 2506 r := strings.NewReader(string(yamlDeepNestedSlices)) 2507 2508 err := v.unmarshalReader(r, v.config) 2509 require.NoError(t, err) 2510 2511 assert.Equal(t, "The expanse", v.GetString("tv.0.title")) 2512 assert.Equal(t, "February 1, 2017", v.GetString("tv.0.seasons.1.first_released")) 2513 assert.Equal(t, "Static", v.GetString("tv.0.seasons.1.episodes.2.title")) 2514 assert.Equal(t, "December 15, 2015", v.GetString("tv.0.seasons.0.episodes.1.air_date")) 2515 2516 // Test for index out of bounds 2517 assert.Equal(t, "", v.GetString("tv.0.seasons.2.first_released")) 2518 2519 // Accessing multidimensional arrays 2520 assert.Equal(t, "Static", v.GetString("tv.0.episodes.1.2")) 2521 } 2522 2523 func BenchmarkGetBool(b *testing.B) { 2524 key := "BenchmarkGetBool" 2525 v = New() 2526 v.Set(key, true) 2527 2528 for i := 0; i < b.N; i++ { 2529 if !v.GetBool(key) { 2530 b.Fatal("GetBool returned false") 2531 } 2532 } 2533 } 2534 2535 func BenchmarkGet(b *testing.B) { 2536 key := "BenchmarkGet" 2537 v = New() 2538 v.Set(key, true) 2539 2540 for i := 0; i < b.N; i++ { 2541 if !v.Get(key).(bool) { 2542 b.Fatal("Get returned false") 2543 } 2544 } 2545 } 2546 2547 // BenchmarkGetBoolFromMap is the "perfect result" for the above. 2548 func BenchmarkGetBoolFromMap(b *testing.B) { 2549 m := make(map[string]bool) 2550 key := "BenchmarkGetBool" 2551 m[key] = true 2552 2553 for i := 0; i < b.N; i++ { 2554 if !m[key] { 2555 b.Fatal("Map value was false") 2556 } 2557 } 2558 } 2559 2560 // Skip some tests on Windows that kept failing when Windows was added to the CI as a target. 2561 func skipWindows(t *testing.T) { 2562 if runtime.GOOS == "windows" { 2563 t.Skip("Skip test on Windows") 2564 } 2565 }