github.com/hukeping/viper@v0.0.0-20151110042204-e37b56e207dd/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 "fmt" 11 "io/ioutil" 12 "os" 13 "path" 14 "reflect" 15 "sort" 16 "strings" 17 "testing" 18 "time" 19 20 "github.com/spf13/pflag" 21 "github.com/stretchr/testify/assert" 22 ) 23 24 var yamlExample = []byte(`Hacker: true 25 name: steve 26 hobbies: 27 - skateboarding 28 - snowboarding 29 - go 30 clothing: 31 jacket: leather 32 trousers: denim 33 pants: 34 size: large 35 age: 35 36 eyes : brown 37 beard: true 38 `) 39 40 var tomlExample = []byte(` 41 title = "TOML Example" 42 43 [owner] 44 organization = "MongoDB" 45 Bio = "MongoDB Chief Developer Advocate & Hacker at Large" 46 dob = 1979-05-27T07:32:00Z # First class dates? Why not?`) 47 48 var jsonExample = []byte(`{ 49 "id": "0001", 50 "type": "donut", 51 "name": "Cake", 52 "ppu": 0.55, 53 "batters": { 54 "batter": [ 55 { "type": "Regular" }, 56 { "type": "Chocolate" }, 57 { "type": "Blueberry" }, 58 { "type": "Devil's Food" } 59 ] 60 } 61 }`) 62 63 var propertiesExample = []byte(` 64 p_id: 0001 65 p_type: donut 66 p_name: Cake 67 p_ppu: 0.55 68 p_batters.batter.type: Regular 69 `) 70 71 var remoteExample = []byte(`{ 72 "id":"0002", 73 "type":"cronut", 74 "newkey":"remote" 75 }`) 76 77 func initConfigs() { 78 Reset() 79 SetConfigType("yaml") 80 r := bytes.NewReader(yamlExample) 81 unmarshalReader(r, v.config) 82 83 SetConfigType("json") 84 r = bytes.NewReader(jsonExample) 85 unmarshalReader(r, v.config) 86 87 SetConfigType("properties") 88 r = bytes.NewReader(propertiesExample) 89 unmarshalReader(r, v.config) 90 91 SetConfigType("toml") 92 r = bytes.NewReader(tomlExample) 93 unmarshalReader(r, v.config) 94 95 SetConfigType("json") 96 remote := bytes.NewReader(remoteExample) 97 unmarshalReader(remote, v.kvstore) 98 } 99 100 func initYAML() { 101 Reset() 102 SetConfigType("yaml") 103 r := bytes.NewReader(yamlExample) 104 105 unmarshalReader(r, v.config) 106 } 107 108 func initJSON() { 109 Reset() 110 SetConfigType("json") 111 r := bytes.NewReader(jsonExample) 112 113 unmarshalReader(r, v.config) 114 } 115 116 func initProperties() { 117 Reset() 118 SetConfigType("properties") 119 r := bytes.NewReader(propertiesExample) 120 121 unmarshalReader(r, v.config) 122 } 123 124 func initTOML() { 125 Reset() 126 SetConfigType("toml") 127 r := bytes.NewReader(tomlExample) 128 129 unmarshalReader(r, v.config) 130 } 131 132 // make directories for testing 133 func initDirs(t *testing.T) (string, string, func()) { 134 135 var ( 136 testDirs = []string{`a a`, `b`, `c\c`, `D_`} 137 config = `improbable` 138 ) 139 140 root, err := ioutil.TempDir("", "") 141 142 cleanup := true 143 defer func() { 144 if cleanup { 145 os.Chdir("..") 146 os.RemoveAll(root) 147 } 148 }() 149 150 assert.Nil(t, err) 151 152 err = os.Chdir(root) 153 assert.Nil(t, err) 154 155 for _, dir := range testDirs { 156 err = os.Mkdir(dir, 0750) 157 assert.Nil(t, err) 158 159 err = ioutil.WriteFile( 160 path.Join(dir, config+".toml"), 161 []byte("key = \"value is "+dir+"\"\n"), 162 0640) 163 assert.Nil(t, err) 164 } 165 166 cleanup = false 167 return root, config, func() { 168 os.Chdir("..") 169 os.RemoveAll(root) 170 } 171 } 172 173 //stubs for PFlag Values 174 type stringValue string 175 176 func newStringValue(val string, p *string) *stringValue { 177 *p = val 178 return (*stringValue)(p) 179 } 180 181 func (s *stringValue) Set(val string) error { 182 *s = stringValue(val) 183 return nil 184 } 185 186 func (s *stringValue) Type() string { 187 return "string" 188 } 189 190 func (s *stringValue) String() string { 191 return fmt.Sprintf("%s", *s) 192 } 193 194 func TestBasics(t *testing.T) { 195 SetConfigFile("/tmp/config.yaml") 196 assert.Equal(t, "/tmp/config.yaml", v.getConfigFile()) 197 } 198 199 func TestDefault(t *testing.T) { 200 SetDefault("age", 45) 201 assert.Equal(t, 45, Get("age")) 202 203 SetDefault("clothing.jacket", "slacks") 204 assert.Equal(t, "slacks", Get("clothing.jacket")) 205 206 SetConfigType("yaml") 207 err := ReadConfig(bytes.NewBuffer(yamlExample)) 208 209 assert.NoError(t, err) 210 assert.Equal(t, "leather", Get("clothing.jacket")) 211 } 212 213 func TestUnmarshalling(t *testing.T) { 214 SetConfigType("yaml") 215 r := bytes.NewReader(yamlExample) 216 217 unmarshalReader(r, v.config) 218 assert.True(t, InConfig("name")) 219 assert.False(t, InConfig("state")) 220 assert.Equal(t, "steve", Get("name")) 221 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies")) 222 assert.Equal(t, map[interface{}]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{"size": "large"}}, Get("clothing")) 223 assert.Equal(t, 35, Get("age")) 224 } 225 226 func TestOverrides(t *testing.T) { 227 Set("age", 40) 228 assert.Equal(t, 40, Get("age")) 229 } 230 231 func TestDefaultPost(t *testing.T) { 232 assert.NotEqual(t, "NYC", Get("state")) 233 SetDefault("state", "NYC") 234 assert.Equal(t, "NYC", Get("state")) 235 } 236 237 func TestAliases(t *testing.T) { 238 RegisterAlias("years", "age") 239 assert.Equal(t, 40, Get("years")) 240 Set("years", 45) 241 assert.Equal(t, 45, Get("age")) 242 } 243 244 func TestAliasInConfigFile(t *testing.T) { 245 // the config file specifies "beard". If we make this an alias for 246 // "hasbeard", we still want the old config file to work with beard. 247 RegisterAlias("beard", "hasbeard") 248 assert.Equal(t, true, Get("hasbeard")) 249 Set("hasbeard", false) 250 assert.Equal(t, false, Get("beard")) 251 } 252 253 func TestYML(t *testing.T) { 254 initYAML() 255 assert.Equal(t, "steve", Get("name")) 256 } 257 258 func TestJSON(t *testing.T) { 259 initJSON() 260 assert.Equal(t, "0001", Get("id")) 261 } 262 263 func TestProperties(t *testing.T) { 264 initProperties() 265 assert.Equal(t, "0001", Get("p_id")) 266 } 267 268 func TestTOML(t *testing.T) { 269 initTOML() 270 assert.Equal(t, "TOML Example", Get("title")) 271 } 272 273 func TestRemotePrecedence(t *testing.T) { 274 initJSON() 275 276 remote := bytes.NewReader(remoteExample) 277 assert.Equal(t, "0001", Get("id")) 278 unmarshalReader(remote, v.kvstore) 279 assert.Equal(t, "0001", Get("id")) 280 assert.NotEqual(t, "cronut", Get("type")) 281 assert.Equal(t, "remote", Get("newkey")) 282 Set("newkey", "newvalue") 283 assert.NotEqual(t, "remote", Get("newkey")) 284 assert.Equal(t, "newvalue", Get("newkey")) 285 Set("newkey", "remote") 286 } 287 288 func TestEnv(t *testing.T) { 289 initJSON() 290 291 BindEnv("id") 292 BindEnv("f", "FOOD") 293 294 os.Setenv("ID", "13") 295 os.Setenv("FOOD", "apple") 296 os.Setenv("NAME", "crunk") 297 298 assert.Equal(t, "13", Get("id")) 299 assert.Equal(t, "apple", Get("f")) 300 assert.Equal(t, "Cake", Get("name")) 301 302 AutomaticEnv() 303 304 assert.Equal(t, "crunk", Get("name")) 305 306 } 307 308 func TestEnvPrefix(t *testing.T) { 309 initJSON() 310 311 SetEnvPrefix("foo") // will be uppercased automatically 312 BindEnv("id") 313 BindEnv("f", "FOOD") // not using prefix 314 315 os.Setenv("FOO_ID", "13") 316 os.Setenv("FOOD", "apple") 317 os.Setenv("FOO_NAME", "crunk") 318 319 assert.Equal(t, "13", Get("id")) 320 assert.Equal(t, "apple", Get("f")) 321 assert.Equal(t, "Cake", Get("name")) 322 323 AutomaticEnv() 324 325 assert.Equal(t, "crunk", Get("name")) 326 } 327 328 func TestAutoEnv(t *testing.T) { 329 Reset() 330 331 AutomaticEnv() 332 os.Setenv("FOO_BAR", "13") 333 assert.Equal(t, "13", Get("foo_bar")) 334 } 335 336 func TestAutoEnvWithPrefix(t *testing.T) { 337 Reset() 338 339 AutomaticEnv() 340 SetEnvPrefix("Baz") 341 os.Setenv("BAZ_BAR", "13") 342 assert.Equal(t, "13", Get("bar")) 343 } 344 345 func TestSetEnvReplacer(t *testing.T) { 346 Reset() 347 348 AutomaticEnv() 349 os.Setenv("REFRESH_INTERVAL", "30s") 350 351 replacer := strings.NewReplacer("-", "_") 352 SetEnvKeyReplacer(replacer) 353 354 assert.Equal(t, "30s", Get("refresh-interval")) 355 } 356 357 func TestAllKeys(t *testing.T) { 358 initConfigs() 359 360 ks := sort.StringSlice{"title", "newkey", "owner", "name", "beard", "ppu", "batters", "hobbies", "clothing", "age", "hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name"} 361 dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 362 all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "Bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[interface{}]interface{}{"trousers": "denim", "jacket": "leather", "pants": map[interface{}]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters.batter.type": "Regular", "p_type": "donut"} 363 364 var allkeys sort.StringSlice 365 allkeys = AllKeys() 366 allkeys.Sort() 367 ks.Sort() 368 369 assert.Equal(t, ks, allkeys) 370 assert.Equal(t, all, AllSettings()) 371 } 372 373 func TestCaseInSensitive(t *testing.T) { 374 assert.Equal(t, true, Get("hacker")) 375 Set("Title", "Checking Case") 376 assert.Equal(t, "Checking Case", Get("tItle")) 377 } 378 379 func TestAliasesOfAliases(t *testing.T) { 380 RegisterAlias("Foo", "Bar") 381 RegisterAlias("Bar", "Title") 382 assert.Equal(t, "Checking Case", Get("FOO")) 383 } 384 385 func TestRecursiveAliases(t *testing.T) { 386 RegisterAlias("Baz", "Roo") 387 RegisterAlias("Roo", "baz") 388 } 389 390 func TestUnmarshal(t *testing.T) { 391 SetDefault("port", 1313) 392 Set("name", "Steve") 393 394 type config struct { 395 Port int 396 Name string 397 } 398 399 var C config 400 401 err := Unmarshal(&C) 402 if err != nil { 403 t.Fatalf("unable to decode into struct, %v", err) 404 } 405 406 assert.Equal(t, &C, &config{Name: "Steve", Port: 1313}) 407 408 Set("port", 1234) 409 err = Unmarshal(&C) 410 if err != nil { 411 t.Fatalf("unable to decode into struct, %v", err) 412 } 413 assert.Equal(t, &C, &config{Name: "Steve", Port: 1234}) 414 } 415 416 func TestBindPFlags(t *testing.T) { 417 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 418 419 var testValues = map[string]*string{ 420 "host": nil, 421 "port": nil, 422 "endpoint": nil, 423 } 424 425 var mutatedTestValues = map[string]string{ 426 "host": "localhost", 427 "port": "6060", 428 "endpoint": "/public", 429 } 430 431 for name, _ := range testValues { 432 testValues[name] = flagSet.String(name, "", "test") 433 } 434 435 err := BindPFlags(flagSet) 436 if err != nil { 437 t.Fatalf("error binding flag set, %v", err) 438 } 439 440 flagSet.VisitAll(func(flag *pflag.Flag) { 441 flag.Value.Set(mutatedTestValues[flag.Name]) 442 flag.Changed = true 443 }) 444 445 for name, expected := range mutatedTestValues { 446 assert.Equal(t, Get(name), expected) 447 } 448 449 } 450 451 func TestBindPFlag(t *testing.T) { 452 var testString = "testing" 453 var testValue = newStringValue(testString, &testString) 454 455 flag := &pflag.Flag{ 456 Name: "testflag", 457 Value: testValue, 458 Changed: false, 459 } 460 461 BindPFlag("testvalue", flag) 462 463 assert.Equal(t, testString, Get("testvalue")) 464 465 flag.Value.Set("testing_mutate") 466 flag.Changed = true //hack for pflag usage 467 468 assert.Equal(t, "testing_mutate", Get("testvalue")) 469 470 } 471 472 func TestBoundCaseSensitivity(t *testing.T) { 473 474 assert.Equal(t, "brown", Get("eyes")) 475 476 BindEnv("eYEs", "TURTLE_EYES") 477 os.Setenv("TURTLE_EYES", "blue") 478 479 assert.Equal(t, "blue", Get("eyes")) 480 481 var testString = "green" 482 var testValue = newStringValue(testString, &testString) 483 484 flag := &pflag.Flag{ 485 Name: "eyeballs", 486 Value: testValue, 487 Changed: true, 488 } 489 490 BindPFlag("eYEs", flag) 491 assert.Equal(t, "green", Get("eyes")) 492 493 } 494 495 func TestSizeInBytes(t *testing.T) { 496 input := map[string]uint{ 497 "": 0, 498 "b": 0, 499 "12 bytes": 0, 500 "200000000000gb": 0, 501 "12 b": 12, 502 "43 MB": 43 * (1 << 20), 503 "10mb": 10 * (1 << 20), 504 "1gb": 1 << 30, 505 } 506 507 for str, expected := range input { 508 assert.Equal(t, expected, parseSizeInBytes(str), str) 509 } 510 } 511 512 func TestFindsNestedKeys(t *testing.T) { 513 initConfigs() 514 dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 515 516 Set("super", map[string]interface{}{ 517 "deep": map[string]interface{}{ 518 "nested": "value", 519 }, 520 }) 521 522 expected := map[string]interface{}{ 523 "super": map[string]interface{}{ 524 "deep": map[string]interface{}{ 525 "nested": "value", 526 }, 527 }, 528 "super.deep": map[string]interface{}{ 529 "nested": "value", 530 }, 531 "super.deep.nested": "value", 532 "owner.organization": "MongoDB", 533 "batters.batter": []interface{}{ 534 map[string]interface{}{ 535 "type": "Regular", 536 }, 537 map[string]interface{}{ 538 "type": "Chocolate", 539 }, 540 map[string]interface{}{ 541 "type": "Blueberry", 542 }, 543 map[string]interface{}{ 544 "type": "Devil's Food", 545 }, 546 }, 547 "hobbies": []interface{}{ 548 "skateboarding", "snowboarding", "go", 549 }, 550 "title": "TOML Example", 551 "newkey": "remote", 552 "batters": map[string]interface{}{ 553 "batter": []interface{}{ 554 map[string]interface{}{ 555 "type": "Regular", 556 }, 557 map[string]interface{}{ 558 "type": "Chocolate", 559 }, map[string]interface{}{ 560 "type": "Blueberry", 561 }, map[string]interface{}{ 562 "type": "Devil's Food", 563 }, 564 }, 565 }, 566 "eyes": "brown", 567 "age": 35, 568 "owner": map[string]interface{}{ 569 "organization": "MongoDB", 570 "Bio": "MongoDB Chief Developer Advocate & Hacker at Large", 571 "dob": dob, 572 }, 573 "owner.Bio": "MongoDB Chief Developer Advocate & Hacker at Large", 574 "type": "donut", 575 "id": "0001", 576 "name": "Cake", 577 "hacker": true, 578 "ppu": 0.55, 579 "clothing": map[interface{}]interface{}{ 580 "jacket": "leather", 581 "trousers": "denim", 582 "pants": map[interface{}]interface{}{ 583 "size": "large", 584 }, 585 }, 586 "clothing.jacket": "leather", 587 "clothing.pants.size": "large", 588 "clothing.trousers": "denim", 589 "owner.dob": dob, 590 "beard": true, 591 } 592 593 for key, expectedValue := range expected { 594 595 assert.Equal(t, expectedValue, v.Get(key)) 596 } 597 598 } 599 600 func TestReadBufConfig(t *testing.T) { 601 v := New() 602 v.SetConfigType("yaml") 603 v.ReadConfig(bytes.NewBuffer(yamlExample)) 604 t.Log(v.AllKeys()) 605 606 assert.True(t, v.InConfig("name")) 607 assert.False(t, v.InConfig("state")) 608 assert.Equal(t, "steve", v.Get("name")) 609 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies")) 610 assert.Equal(t, map[interface{}]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{"size": "large"}}, v.Get("clothing")) 611 assert.Equal(t, 35, v.Get("age")) 612 } 613 614 func TestDirsSearch(t *testing.T) { 615 616 root, config, cleanup := initDirs(t) 617 defer cleanup() 618 619 v := New() 620 v.SetConfigName(config) 621 v.SetDefault(`key`, `default`) 622 623 entries, err := ioutil.ReadDir(root) 624 for _, e := range entries { 625 if e.IsDir() { 626 v.AddConfigPath(e.Name()) 627 } 628 } 629 630 err = v.ReadInConfig() 631 assert.Nil(t, err) 632 633 assert.Equal(t, `value is `+path.Base(v.configPaths[0]), v.GetString(`key`)) 634 } 635 636 func TestWrongDirsSearchNotFound(t *testing.T) { 637 638 _, config, cleanup := initDirs(t) 639 defer cleanup() 640 641 v := New() 642 v.SetConfigName(config) 643 v.SetDefault(`key`, `default`) 644 645 v.AddConfigPath(`whattayoutalkingbout`) 646 v.AddConfigPath(`thispathaintthere`) 647 648 err := v.ReadInConfig() 649 assert.Equal(t, reflect.TypeOf(UnsupportedConfigError("")), reflect.TypeOf(err)) 650 651 // Even though config did not load and the error might have 652 // been ignored by the client, the default still loads 653 assert.Equal(t, `default`, v.GetString(`key`)) 654 }