github.com/vmedinskiy/viper@v1.10.2/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/mitchellh/mapstructure" 27 "github.com/spf13/afero" 28 "github.com/spf13/cast" 29 "github.com/spf13/pflag" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 33 "github.com/spf13/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 var jsonMergeExampleTgt = []byte(` 1916 { 1917 "hello": { 1918 "foo": null, 1919 "pop": 123456 1920 } 1921 } 1922 `) 1923 1924 var jsonMergeExampleSrc = []byte(` 1925 { 1926 "hello": { 1927 "foo": "foo str", 1928 "pop": "pop str" 1929 } 1930 } 1931 `) 1932 1933 func TestMergeConfig(t *testing.T) { 1934 v := New() 1935 v.SetConfigType("yml") 1936 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1937 t.Fatal(err) 1938 } 1939 1940 if pop := v.GetInt("hello.pop"); pop != 37890 { 1941 t.Fatalf("pop != 37890, = %d", pop) 1942 } 1943 1944 if pop := v.GetInt32("hello.pop"); pop != int32(37890) { 1945 t.Fatalf("pop != 37890, = %d", pop) 1946 } 1947 1948 if pop := v.GetInt64("hello.largenum"); pop != int64(765432101234567) { 1949 t.Fatalf("int64 largenum != 765432101234567, = %d", pop) 1950 } 1951 1952 if pop := v.GetUint("hello.pop"); pop != 37890 { 1953 t.Fatalf("uint pop != 37890, = %d", pop) 1954 } 1955 1956 if pop := v.GetUint32("hello.pop"); pop != 37890 { 1957 t.Fatalf("uint32 pop != 37890, = %d", pop) 1958 } 1959 1960 if pop := v.GetUint64("hello.num2pow63"); pop != 9223372036854775808 { 1961 t.Fatalf("uint64 num2pow63 != 9223372036854775808, = %d", pop) 1962 } 1963 1964 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1965 t.Fatalf("len(world) != 4, = %d", len(world)) 1966 } 1967 1968 if fu := v.GetString("fu"); fu != "" { 1969 t.Fatalf("fu != \"\", = %s", fu) 1970 } 1971 1972 if err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1973 t.Fatal(err) 1974 } 1975 1976 if pop := v.GetInt("hello.pop"); pop != 45000 { 1977 t.Fatalf("pop != 45000, = %d", pop) 1978 } 1979 1980 if pop := v.GetInt32("hello.pop"); pop != int32(45000) { 1981 t.Fatalf("pop != 45000, = %d", pop) 1982 } 1983 1984 if pop := v.GetInt64("hello.largenum"); pop != int64(7654321001234567) { 1985 t.Fatalf("int64 largenum != 7654321001234567, = %d", pop) 1986 } 1987 1988 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1989 t.Fatalf("len(world) != 4, = %d", len(world)) 1990 } 1991 1992 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1993 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1994 } 1995 1996 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 1997 t.Fatalf("len(ints) != 2, = %d", len(ints)) 1998 } 1999 2000 if fu := v.GetString("fu"); fu != "bar" { 2001 t.Fatalf("fu != \"bar\", = %s", fu) 2002 } 2003 } 2004 2005 func TestMergeConfigOverrideType(t *testing.T) { 2006 v := New() 2007 v.SetConfigType("json") 2008 if err := v.ReadConfig(bytes.NewBuffer(jsonMergeExampleTgt)); err != nil { 2009 t.Fatal(err) 2010 } 2011 2012 if err := v.MergeConfig(bytes.NewBuffer(jsonMergeExampleSrc)); err != nil { 2013 t.Fatal(err) 2014 } 2015 2016 if pop := v.GetString("hello.pop"); pop != "pop str" { 2017 t.Fatalf("pop != \"pop str\", = %s", pop) 2018 } 2019 2020 if foo := v.GetString("hello.foo"); foo != "foo str" { 2021 t.Fatalf("foo != \"foo str\", = %s", foo) 2022 } 2023 } 2024 2025 func TestMergeConfigNoMerge(t *testing.T) { 2026 v := New() 2027 v.SetConfigType("yml") 2028 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 2029 t.Fatal(err) 2030 } 2031 2032 if pop := v.GetInt("hello.pop"); pop != 37890 { 2033 t.Fatalf("pop != 37890, = %d", pop) 2034 } 2035 2036 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 2037 t.Fatalf("len(world) != 4, = %d", len(world)) 2038 } 2039 2040 if fu := v.GetString("fu"); fu != "" { 2041 t.Fatalf("fu != \"\", = %s", fu) 2042 } 2043 2044 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 2045 t.Fatal(err) 2046 } 2047 2048 if pop := v.GetInt("hello.pop"); pop != 45000 { 2049 t.Fatalf("pop != 45000, = %d", pop) 2050 } 2051 2052 if world := v.GetStringSlice("hello.world"); len(world) != 0 { 2053 t.Fatalf("len(world) != 0, = %d", len(world)) 2054 } 2055 2056 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 2057 t.Fatalf("len(universe) != 2, = %d", len(universe)) 2058 } 2059 2060 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 2061 t.Fatalf("len(ints) != 2, = %d", len(ints)) 2062 } 2063 2064 if fu := v.GetString("fu"); fu != "bar" { 2065 t.Fatalf("fu != \"bar\", = %s", fu) 2066 } 2067 } 2068 2069 func TestMergeConfigMap(t *testing.T) { 2070 v := New() 2071 v.SetConfigType("yml") 2072 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 2073 t.Fatal(err) 2074 } 2075 2076 assert := func(i int) { 2077 large := v.GetInt64("hello.largenum") 2078 pop := v.GetInt("hello.pop") 2079 if large != 765432101234567 { 2080 t.Fatal("Got large num:", large) 2081 } 2082 2083 if pop != i { 2084 t.Fatal("Got pop:", pop) 2085 } 2086 } 2087 2088 assert(37890) 2089 2090 update := map[string]interface{}{ 2091 "Hello": map[string]interface{}{ 2092 "Pop": 1234, 2093 }, 2094 "World": map[interface{}]interface{}{ 2095 "Rock": 345, 2096 }, 2097 } 2098 2099 if err := v.MergeConfigMap(update); err != nil { 2100 t.Fatal(err) 2101 } 2102 2103 if rock := v.GetInt("world.rock"); rock != 345 { 2104 t.Fatal("Got rock:", rock) 2105 } 2106 2107 assert(1234) 2108 } 2109 2110 func TestUnmarshalingWithAliases(t *testing.T) { 2111 v := New() 2112 v.SetDefault("ID", 1) 2113 v.Set("name", "Steve") 2114 v.Set("lastname", "Owen") 2115 2116 v.RegisterAlias("UserID", "ID") 2117 v.RegisterAlias("Firstname", "name") 2118 v.RegisterAlias("Surname", "lastname") 2119 2120 type config struct { 2121 ID int 2122 FirstName string 2123 Surname string 2124 } 2125 2126 var C config 2127 err := v.Unmarshal(&C) 2128 if err != nil { 2129 t.Fatalf("unable to decode into struct, %v", err) 2130 } 2131 2132 assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C) 2133 } 2134 2135 func TestSetConfigNameClearsFileCache(t *testing.T) { 2136 SetConfigFile("/tmp/config.yaml") 2137 SetConfigName("default") 2138 f, err := v.getConfigFile() 2139 if err == nil { 2140 t.Fatalf("config file cache should have been cleared") 2141 } 2142 assert.Empty(t, f) 2143 } 2144 2145 func TestShadowedNestedValue(t *testing.T) { 2146 config := `name: steve 2147 clothing: 2148 jacket: leather 2149 trousers: denim 2150 pants: 2151 size: large 2152 ` 2153 initConfig("yaml", config) 2154 2155 assert.Equal(t, "steve", GetString("name")) 2156 2157 polyester := "polyester" 2158 SetDefault("clothing.shirt", polyester) 2159 SetDefault("clothing.jacket.price", 100) 2160 2161 assert.Equal(t, "leather", GetString("clothing.jacket")) 2162 assert.Nil(t, Get("clothing.jacket.price")) 2163 assert.Equal(t, polyester, GetString("clothing.shirt")) 2164 2165 clothingSettings := AllSettings()["clothing"].(map[string]interface{}) 2166 assert.Equal(t, "leather", clothingSettings["jacket"]) 2167 assert.Equal(t, polyester, clothingSettings["shirt"]) 2168 } 2169 2170 func TestDotParameter(t *testing.T) { 2171 initJSON() 2172 // shoud take precedence over batters defined in jsonExample 2173 r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`)) 2174 unmarshalReader(r, v.config) 2175 2176 actual := Get("batters.batter") 2177 expected := []interface{}{map[string]interface{}{"type": "Small"}} 2178 assert.Equal(t, expected, actual) 2179 } 2180 2181 func TestCaseInsensitive(t *testing.T) { 2182 for _, config := range []struct { 2183 typ string 2184 content string 2185 }{ 2186 {"yaml", ` 2187 aBcD: 1 2188 eF: 2189 gH: 2 2190 iJk: 3 2191 Lm: 2192 nO: 4 2193 P: 2194 Q: 5 2195 R: 6 2196 `}, 2197 {"json", `{ 2198 "aBcD": 1, 2199 "eF": { 2200 "iJk": 3, 2201 "Lm": { 2202 "P": { 2203 "Q": 5, 2204 "R": 6 2205 }, 2206 "nO": 4 2207 }, 2208 "gH": 2 2209 } 2210 }`}, 2211 {"toml", `aBcD = 1 2212 [eF] 2213 gH = 2 2214 iJk = 3 2215 [eF.Lm] 2216 nO = 4 2217 [eF.Lm.P] 2218 Q = 5 2219 R = 6 2220 `}, 2221 } { 2222 doTestCaseInsensitive(t, config.typ, config.content) 2223 } 2224 } 2225 2226 func TestCaseInsensitiveSet(t *testing.T) { 2227 Reset() 2228 m1 := map[string]interface{}{ 2229 "Foo": 32, 2230 "Bar": map[interface{}]interface{}{ 2231 "ABc": "A", 2232 "cDE": "B", 2233 }, 2234 } 2235 2236 m2 := map[string]interface{}{ 2237 "Foo": 52, 2238 "Bar": map[interface{}]interface{}{ 2239 "bCd": "A", 2240 "eFG": "B", 2241 }, 2242 } 2243 2244 Set("Given1", m1) 2245 Set("Number1", 42) 2246 2247 SetDefault("Given2", m2) 2248 SetDefault("Number2", 52) 2249 2250 // Verify SetDefault 2251 if v := Get("number2"); v != 52 { 2252 t.Fatalf("Expected 52 got %q", v) 2253 } 2254 2255 if v := Get("given2.foo"); v != 52 { 2256 t.Fatalf("Expected 52 got %q", v) 2257 } 2258 2259 if v := Get("given2.bar.bcd"); v != "A" { 2260 t.Fatalf("Expected A got %q", v) 2261 } 2262 2263 if _, ok := m2["Foo"]; !ok { 2264 t.Fatal("Input map changed") 2265 } 2266 2267 // Verify Set 2268 if v := Get("number1"); v != 42 { 2269 t.Fatalf("Expected 42 got %q", v) 2270 } 2271 2272 if v := Get("given1.foo"); v != 32 { 2273 t.Fatalf("Expected 32 got %q", v) 2274 } 2275 2276 if v := Get("given1.bar.abc"); v != "A" { 2277 t.Fatalf("Expected A got %q", v) 2278 } 2279 2280 if _, ok := m1["Foo"]; !ok { 2281 t.Fatal("Input map changed") 2282 } 2283 } 2284 2285 func TestParseNested(t *testing.T) { 2286 type duration struct { 2287 Delay time.Duration 2288 } 2289 2290 type item struct { 2291 Name string 2292 Delay time.Duration 2293 Nested duration 2294 } 2295 2296 config := `[[parent]] 2297 delay="100ms" 2298 [parent.nested] 2299 delay="200ms" 2300 ` 2301 initConfig("toml", config) 2302 2303 var items []item 2304 err := v.UnmarshalKey("parent", &items) 2305 if err != nil { 2306 t.Fatalf("unable to decode into struct, %v", err) 2307 } 2308 2309 assert.Equal(t, 1, len(items)) 2310 assert.Equal(t, 100*time.Millisecond, items[0].Delay) 2311 assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) 2312 } 2313 2314 func doTestCaseInsensitive(t *testing.T, typ, config string) { 2315 initConfig(typ, config) 2316 Set("RfD", true) 2317 assert.Equal(t, true, Get("rfd")) 2318 assert.Equal(t, true, Get("rFD")) 2319 assert.Equal(t, 1, cast.ToInt(Get("abcd"))) 2320 assert.Equal(t, 1, cast.ToInt(Get("Abcd"))) 2321 assert.Equal(t, 2, cast.ToInt(Get("ef.gh"))) 2322 assert.Equal(t, 3, cast.ToInt(Get("ef.ijk"))) 2323 assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no"))) 2324 assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q"))) 2325 } 2326 2327 func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) { 2328 watchDir, err := ioutil.TempDir("", "") 2329 require.Nil(t, err) 2330 configFile := path.Join(watchDir, "config.yaml") 2331 err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0o640) 2332 require.Nil(t, err) 2333 cleanup := func() { 2334 os.RemoveAll(watchDir) 2335 } 2336 v := New() 2337 v.SetConfigFile(configFile) 2338 err = v.ReadInConfig() 2339 require.Nil(t, err) 2340 require.Equal(t, "bar", v.Get("foo")) 2341 return v, configFile, cleanup 2342 } 2343 2344 func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func()) { 2345 watchDir, err := ioutil.TempDir("", "") 2346 require.Nil(t, err) 2347 dataDir1 := path.Join(watchDir, "data1") 2348 err = os.Mkdir(dataDir1, 0o777) 2349 require.Nil(t, err) 2350 realConfigFile := path.Join(dataDir1, "config.yaml") 2351 t.Logf("Real config file location: %s\n", realConfigFile) 2352 err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0o640) 2353 require.Nil(t, err) 2354 cleanup := func() { 2355 os.RemoveAll(watchDir) 2356 } 2357 // now, symlink the tm `data1` dir to `data` in the baseDir 2358 os.Symlink(dataDir1, path.Join(watchDir, "data")) 2359 // and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml` 2360 configFile := path.Join(watchDir, "config.yaml") 2361 os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile) 2362 t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml")) 2363 // init Viper 2364 v := New() 2365 v.SetConfigFile(configFile) 2366 err = v.ReadInConfig() 2367 require.Nil(t, err) 2368 require.Equal(t, "bar", v.Get("foo")) 2369 return v, watchDir, configFile, cleanup 2370 } 2371 2372 func TestWatchFile(t *testing.T) { 2373 if runtime.GOOS == "linux" { 2374 // TODO(bep) FIX ME 2375 t.Skip("Skip test on Linux ...") 2376 } 2377 2378 t.Run("file content changed", func(t *testing.T) { 2379 // given a `config.yaml` file being watched 2380 v, configFile, cleanup := newViperWithConfigFile(t) 2381 defer cleanup() 2382 _, err := os.Stat(configFile) 2383 require.NoError(t, err) 2384 t.Logf("test config file: %s\n", configFile) 2385 wg := sync.WaitGroup{} 2386 wg.Add(1) 2387 var wgDoneOnce sync.Once // OnConfigChange is called twice on Windows 2388 v.OnConfigChange(func(in fsnotify.Event) { 2389 t.Logf("config file changed") 2390 wgDoneOnce.Do(func() { 2391 wg.Done() 2392 }) 2393 }) 2394 v.WatchConfig() 2395 // when overwriting the file and waiting for the custom change notification handler to be triggered 2396 err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0o640) 2397 wg.Wait() 2398 // then the config value should have changed 2399 require.Nil(t, err) 2400 assert.Equal(t, "baz", v.Get("foo")) 2401 }) 2402 2403 t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) { 2404 // skip if not executed on Linux 2405 if runtime.GOOS != "linux" { 2406 t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...") 2407 } 2408 v, watchDir, _, _ := newViperWithSymlinkedConfigFile(t) 2409 // defer cleanup() 2410 wg := sync.WaitGroup{} 2411 v.WatchConfig() 2412 v.OnConfigChange(func(in fsnotify.Event) { 2413 t.Logf("config file changed") 2414 wg.Done() 2415 }) 2416 wg.Add(1) 2417 // when link to another `config.yaml` file 2418 dataDir2 := path.Join(watchDir, "data2") 2419 err := os.Mkdir(dataDir2, 0o777) 2420 require.Nil(t, err) 2421 configFile2 := path.Join(dataDir2, "config.yaml") 2422 err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0o640) 2423 require.Nil(t, err) 2424 // change the symlink using the `ln -sfn` command 2425 err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run() 2426 require.Nil(t, err) 2427 wg.Wait() 2428 // then 2429 require.Nil(t, err) 2430 assert.Equal(t, "baz", v.Get("foo")) 2431 }) 2432 } 2433 2434 func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) { 2435 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 2436 flags.String("foo.bar", "cobra_flag", "") 2437 2438 v := New() 2439 assert.NoError(t, v.BindPFlags(flags)) 2440 2441 config := &struct { 2442 Foo struct { 2443 Bar string 2444 } 2445 }{} 2446 2447 assert.NoError(t, v.Unmarshal(config)) 2448 assert.Equal(t, "cobra_flag", config.Foo.Bar) 2449 } 2450 2451 // var yamlExampleWithDot = []byte(`Hacker: true 2452 // name: steve 2453 // hobbies: 2454 // - skateboarding 2455 // - snowboarding 2456 // - go 2457 // clothing: 2458 // jacket: leather 2459 // trousers: denim 2460 // pants: 2461 // size: large 2462 // age: 35 2463 // eyes : brown 2464 // beard: true 2465 // emails: 2466 // steve@hacker.com: 2467 // created: 01/02/03 2468 // active: true 2469 // `) 2470 2471 func TestKeyDelimiter(t *testing.T) { 2472 v := NewWithOptions(KeyDelimiter("::")) 2473 v.SetConfigType("yaml") 2474 r := strings.NewReader(string(yamlExampleWithDot)) 2475 2476 err := v.unmarshalReader(r, v.config) 2477 require.NoError(t, err) 2478 2479 values := map[string]interface{}{ 2480 "image": map[string]interface{}{ 2481 "repository": "someImage", 2482 "tag": "1.0.0", 2483 }, 2484 "ingress": map[string]interface{}{ 2485 "annotations": map[string]interface{}{ 2486 "traefik.frontend.rule.type": "PathPrefix", 2487 "traefik.ingress.kubernetes.io/ssl-redirect": "true", 2488 }, 2489 }, 2490 } 2491 2492 v.SetDefault("charts::values", values) 2493 2494 assert.Equal(t, "leather", v.GetString("clothing::jacket")) 2495 assert.Equal(t, "01/02/03", v.GetString("emails::steve@hacker.com::created")) 2496 2497 type config struct { 2498 Charts struct { 2499 Values map[string]interface{} 2500 } 2501 } 2502 2503 expected := config{ 2504 Charts: struct { 2505 Values map[string]interface{} 2506 }{ 2507 Values: values, 2508 }, 2509 } 2510 2511 var actual config 2512 2513 assert.NoError(t, v.Unmarshal(&actual)) 2514 2515 assert.Equal(t, expected, actual) 2516 } 2517 2518 var yamlDeepNestedSlices = []byte(`TV: 2519 - title: "The expanse" 2520 seasons: 2521 - first_released: "December 14, 2015" 2522 episodes: 2523 - title: "Dulcinea" 2524 air_date: "December 14, 2015" 2525 - title: "The Big Empty" 2526 air_date: "December 15, 2015" 2527 - title: "Remember the Cant" 2528 air_date: "December 22, 2015" 2529 - first_released: "February 1, 2017" 2530 episodes: 2531 - title: "Safe" 2532 air_date: "February 1, 2017" 2533 - title: "Doors & Corners" 2534 air_date: "February 1, 2017" 2535 - title: "Static" 2536 air_date: "February 8, 2017" 2537 episodes: 2538 - ["Dulcinea", "The Big Empty", "Remember the Cant"] 2539 - ["Safe", "Doors & Corners", "Static"] 2540 `) 2541 2542 func TestSliceIndexAccess(t *testing.T) { 2543 v.SetConfigType("yaml") 2544 r := strings.NewReader(string(yamlDeepNestedSlices)) 2545 2546 err := v.unmarshalReader(r, v.config) 2547 require.NoError(t, err) 2548 2549 assert.Equal(t, "The expanse", v.GetString("tv.0.title")) 2550 assert.Equal(t, "February 1, 2017", v.GetString("tv.0.seasons.1.first_released")) 2551 assert.Equal(t, "Static", v.GetString("tv.0.seasons.1.episodes.2.title")) 2552 assert.Equal(t, "December 15, 2015", v.GetString("tv.0.seasons.0.episodes.1.air_date")) 2553 2554 // Test for index out of bounds 2555 assert.Equal(t, "", v.GetString("tv.0.seasons.2.first_released")) 2556 2557 // Accessing multidimensional arrays 2558 assert.Equal(t, "Static", v.GetString("tv.0.episodes.1.2")) 2559 } 2560 2561 func BenchmarkGetBool(b *testing.B) { 2562 key := "BenchmarkGetBool" 2563 v = New() 2564 v.Set(key, true) 2565 2566 for i := 0; i < b.N; i++ { 2567 if !v.GetBool(key) { 2568 b.Fatal("GetBool returned false") 2569 } 2570 } 2571 } 2572 2573 func BenchmarkGet(b *testing.B) { 2574 key := "BenchmarkGet" 2575 v = New() 2576 v.Set(key, true) 2577 2578 for i := 0; i < b.N; i++ { 2579 if !v.Get(key).(bool) { 2580 b.Fatal("Get returned false") 2581 } 2582 } 2583 } 2584 2585 // BenchmarkGetBoolFromMap is the "perfect result" for the above. 2586 func BenchmarkGetBoolFromMap(b *testing.B) { 2587 m := make(map[string]bool) 2588 key := "BenchmarkGetBool" 2589 m[key] = true 2590 2591 for i := 0; i < b.N; i++ { 2592 if !m[key] { 2593 b.Fatal("Map value was false") 2594 } 2595 } 2596 } 2597 2598 // Skip some tests on Windows that kept failing when Windows was added to the CI as a target. 2599 func skipWindows(t *testing.T) { 2600 if runtime.GOOS == "windows" { 2601 t.Skip("Skip test on Windows") 2602 } 2603 }