From 07c785e294e70494ae6016d59749a71af3f74920 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Thu, 21 Jun 2018 10:30:20 -0400 Subject: MM-10730 Added support for empty environment variables to viper (#8973) --- Gopkg.lock | 4 +- Gopkg.toml | 2 +- utils/config_test.go | 25 + vendor/github.com/mattermost/viper/flags_test.go | 65 + .../github.com/mattermost/viper/overrides_test.go | 173 +++ .../github.com/mattermost/viper/remote/remote.go | 105 ++ vendor/github.com/mattermost/viper/util_test.go | 54 + vendor/github.com/mattermost/viper/viper.go | 18 +- vendor/github.com/mattermost/viper/viper_test.go | 1421 ++++++++++++++++++++ 9 files changed, 1855 insertions(+), 12 deletions(-) create mode 100644 vendor/github.com/mattermost/viper/flags_test.go create mode 100644 vendor/github.com/mattermost/viper/overrides_test.go create mode 100644 vendor/github.com/mattermost/viper/remote/remote.go create mode 100644 vendor/github.com/mattermost/viper/util_test.go create mode 100644 vendor/github.com/mattermost/viper/viper_test.go diff --git a/Gopkg.lock b/Gopkg.lock index d386cd2c6..625948673 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -473,10 +473,10 @@ version = "v1.0.1" [[projects]] - branch = "env-settings" + branch = "mattermost" name = "github.com/mattermost/viper" packages = ["."] - revision = "4f5003aa93559718c866d86fbc795439079484f5" + revision = "1b00ce64485c7a2449f239de3cbc9040a9c46aa0" source = "https://github.com/mattermost/viper" [[projects]] diff --git a/Gopkg.toml b/Gopkg.toml index d4e7192c7..968f6b782 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -48,7 +48,7 @@ [[constraint]] name = "github.com/mattermost/viper" source = "https://github.com/mattermost/viper" - branch = "env-settings" + branch = "mattermost" # Keep back because of breaking API changes [[constraint]] diff --git a/utils/config_test.go b/utils/config_test.go index 278f24251..80ab05ffe 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -335,6 +335,9 @@ func TestConfigFromEnviroVars(t *testing.T) { "TeamSettings": { "SiteName": "Mattermost", "CustomBrandText": "" + }, + "SupportSettings": { + "TermsOfServiceLink": "https://about.mattermost.com/default-terms/" } }` @@ -447,6 +450,28 @@ func TestConfigFromEnviroVars(t *testing.T) { } } }) + + t.Run("empty string setting", func(t *testing.T) { + os.Setenv("MM_SUPPORTSETTINGS_TERMSOFSERVICELINK", "") + defer os.Unsetenv("MM_SUPPORTSETTINGS_TERMSOFSERVICELINK") + + cfg, envCfg, err := ReadConfig(strings.NewReader(config), true) + require.Nil(t, err) + + if *cfg.SupportSettings.TermsOfServiceLink != "" { + t.Fatal("Couldn't read empty TermsOfServiceLink from environment var") + } + + if supportSettings, ok := envCfg["SupportSettings"]; !ok { + t.Fatal("SupportSettings is missing from envConfig") + } else if supportSettingsAsMap, ok := supportSettings.(map[string]interface{}); !ok { + t.Fatal("SupportSettings is not a map in envConfig") + } else { + if termsOfServiceLinkInEnv, ok := supportSettingsAsMap["TermsOfServiceLink"].(bool); !ok || !termsOfServiceLinkInEnv { + t.Fatal("TermsOfServiceLink should be in envConfig") + } + } + }) } func TestValidateLocales(t *testing.T) { diff --git a/vendor/github.com/mattermost/viper/flags_test.go b/vendor/github.com/mattermost/viper/flags_test.go new file mode 100644 index 000000000..0b976b605 --- /dev/null +++ b/vendor/github.com/mattermost/viper/flags_test.go @@ -0,0 +1,65 @@ +package viper + +import ( + "testing" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" +) + +func TestBindFlagValueSet(t *testing.T) { + flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) + + var testValues = map[string]*string{ + "host": nil, + "port": nil, + "endpoint": nil, + } + + var mutatedTestValues = map[string]string{ + "host": "localhost", + "port": "6060", + "endpoint": "/public", + } + + for name := range testValues { + testValues[name] = flagSet.String(name, "", "test") + } + + flagValueSet := pflagValueSet{flagSet} + + err := BindFlagValues(flagValueSet) + if err != nil { + t.Fatalf("error binding flag set, %v", err) + } + + flagSet.VisitAll(func(flag *pflag.Flag) { + flag.Value.Set(mutatedTestValues[flag.Name]) + flag.Changed = true + }) + + for name, expected := range mutatedTestValues { + assert.Equal(t, Get(name), expected) + } +} + +func TestBindFlagValue(t *testing.T) { + var testString = "testing" + var testValue = newStringValue(testString, &testString) + + flag := &pflag.Flag{ + Name: "testflag", + Value: testValue, + Changed: false, + } + + flagValue := pflagValue{flag} + BindFlagValue("testvalue", flagValue) + + assert.Equal(t, testString, Get("testvalue")) + + flag.Value.Set("testing_mutate") + flag.Changed = true //hack for pflag usage + + assert.Equal(t, "testing_mutate", Get("testvalue")) +} diff --git a/vendor/github.com/mattermost/viper/overrides_test.go b/vendor/github.com/mattermost/viper/overrides_test.go new file mode 100644 index 000000000..dd2aa9b0d --- /dev/null +++ b/vendor/github.com/mattermost/viper/overrides_test.go @@ -0,0 +1,173 @@ +package viper + +import ( + "fmt" + "strings" + "testing" + + "github.com/spf13/cast" + "github.com/stretchr/testify/assert" +) + +type layer int + +const ( + defaultLayer layer = iota + 1 + overrideLayer +) + +func TestNestedOverrides(t *testing.T) { + assert := assert.New(t) + var v *Viper + + // Case 0: value overridden by a value + overrideDefault(assert, "tom", 10, "tom", 20) // "tom" is first given 10 as default value, then overridden by 20 + override(assert, "tom", 10, "tom", 20) // "tom" is first given value 10, then overridden by 20 + overrideDefault(assert, "tom.age", 10, "tom.age", 20) + override(assert, "tom.age", 10, "tom.age", 20) + overrideDefault(assert, "sawyer.tom.age", 10, "sawyer.tom.age", 20) + override(assert, "sawyer.tom.age", 10, "sawyer.tom.age", 20) + + // Case 1: key:value overridden by a value + v = overrideDefault(assert, "tom.age", 10, "tom", "boy") // "tom.age" is first given 10 as default value, then "tom" is overridden by "boy" + assert.Nil(v.Get("tom.age")) // "tom.age" should not exist anymore + v = override(assert, "tom.age", 10, "tom", "boy") + assert.Nil(v.Get("tom.age")) + + // Case 2: value overridden by a key:value + overrideDefault(assert, "tom", "boy", "tom.age", 10) // "tom" is first given "boy" as default value, then "tom" is overridden by map{"age":10} + override(assert, "tom.age", 10, "tom", "boy") + + // Case 3: key:value overridden by a key:value + v = overrideDefault(assert, "tom.size", 4, "tom.age", 10) + assert.Equal(4, v.Get("tom.size")) // value should still be reachable + v = override(assert, "tom.size", 4, "tom.age", 10) + assert.Equal(4, v.Get("tom.size")) + deepCheckValue(assert, v, overrideLayer, []string{"tom", "size"}, 4) + + // Case 4: key:value overridden by a map + v = overrideDefault(assert, "tom.size", 4, "tom", map[string]interface{}{"age": 10}) // "tom.size" is first given "4" as default value, then "tom" is overridden by map{"age":10} + assert.Equal(4, v.Get("tom.size")) // "tom.size" should still be reachable + assert.Equal(10, v.Get("tom.age")) // new value should be there + deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, 10) // new value should be there + v = override(assert, "tom.size", 4, "tom", map[string]interface{}{"age": 10}) + assert.Nil(v.Get("tom.size")) + assert.Equal(10, v.Get("tom.age")) + deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, 10) + + // Case 5: array overridden by a value + overrideDefault(assert, "tom", []int{10, 20}, "tom", 30) + override(assert, "tom", []int{10, 20}, "tom", 30) + overrideDefault(assert, "tom.age", []int{10, 20}, "tom.age", 30) + override(assert, "tom.age", []int{10, 20}, "tom.age", 30) + + // Case 6: array overridden by an array + overrideDefault(assert, "tom", []int{10, 20}, "tom", []int{30, 40}) + override(assert, "tom", []int{10, 20}, "tom", []int{30, 40}) + overrideDefault(assert, "tom.age", []int{10, 20}, "tom.age", []int{30, 40}) + v = override(assert, "tom.age", []int{10, 20}, "tom.age", []int{30, 40}) + // explicit array merge: + s, ok := v.Get("tom.age").([]int) + if assert.True(ok, "tom[\"age\"] is not a slice") { + v.Set("tom.age", append(s, []int{50, 60}...)) + assert.Equal([]int{30, 40, 50, 60}, v.Get("tom.age")) + deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, []int{30, 40, 50, 60}) + } +} + +func overrideDefault(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper { + return overrideFromLayer(defaultLayer, assert, firstPath, firstValue, secondPath, secondValue) +} +func override(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper { + return overrideFromLayer(overrideLayer, assert, firstPath, firstValue, secondPath, secondValue) +} + +// overrideFromLayer performs the sequential override and low-level checks. +// +// First assignment is made on layer l for path firstPath with value firstValue, +// the second one on the override layer (i.e., with the Set() function) +// for path secondPath with value secondValue. +// +// firstPath and secondPath can include an arbitrary number of dots to indicate +// a nested element. +// +// After each assignment, the value is checked, retrieved both by its full path +// and by its key sequence (successive maps). +func overrideFromLayer(l layer, assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper { + v := New() + firstKeys := strings.Split(firstPath, v.keyDelim) + if assert == nil || + len(firstKeys) == 0 || len(firstKeys[0]) == 0 { + return v + } + + // Set and check first value + switch l { + case defaultLayer: + v.SetDefault(firstPath, firstValue) + case overrideLayer: + v.Set(firstPath, firstValue) + default: + return v + } + assert.Equal(firstValue, v.Get(firstPath)) + deepCheckValue(assert, v, l, firstKeys, firstValue) + + // Override and check new value + secondKeys := strings.Split(secondPath, v.keyDelim) + if len(secondKeys) == 0 || len(secondKeys[0]) == 0 { + return v + } + v.Set(secondPath, secondValue) + assert.Equal(secondValue, v.Get(secondPath)) + deepCheckValue(assert, v, overrideLayer, secondKeys, secondValue) + + return v +} + +// deepCheckValue checks that all given keys correspond to a valid path in the +// configuration map of the given layer, and that the final value equals the one given +func deepCheckValue(assert *assert.Assertions, v *Viper, l layer, keys []string, value interface{}) { + if assert == nil || v == nil || + len(keys) == 0 || len(keys[0]) == 0 { + return + } + + // init + var val interface{} + var ms string + switch l { + case defaultLayer: + val = v.defaults + ms = "v.defaults" + case overrideLayer: + val = v.override + ms = "v.override" + } + + // loop through map + var m map[string]interface{} + err := false + for _, k := range keys { + if val == nil { + assert.Fail(fmt.Sprintf("%s is not a map[string]interface{}", ms)) + return + } + + // deep scan of the map to get the final value + switch val.(type) { + case map[interface{}]interface{}: + m = cast.ToStringMap(val) + case map[string]interface{}: + m = val.(map[string]interface{}) + default: + assert.Fail(fmt.Sprintf("%s is not a map[string]interface{}", ms)) + return + } + ms = ms + "[\"" + k + "\"]" + val = m[k] + } + if !err { + assert.Equal(value, val) + } +} diff --git a/vendor/github.com/mattermost/viper/remote/remote.go b/vendor/github.com/mattermost/viper/remote/remote.go new file mode 100644 index 000000000..810d0702e --- /dev/null +++ b/vendor/github.com/mattermost/viper/remote/remote.go @@ -0,0 +1,105 @@ +// Copyright © 2015 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Package remote integrates the remote features of Viper. +package remote + +import ( + "bytes" + "io" + "os" + + "github.com/spf13/viper" + crypt "github.com/xordataexchange/crypt/config" +) + +type remoteConfigProvider struct{} + +func (rc remoteConfigProvider) Get(rp viper.RemoteProvider) (io.Reader, error) { + cm, err := getConfigManager(rp) + if err != nil { + return nil, err + } + b, err := cm.Get(rp.Path()) + if err != nil { + return nil, err + } + return bytes.NewReader(b), nil +} + +func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) { + cm, err := getConfigManager(rp) + if err != nil { + return nil, err + } + resp, err := cm.Get(rp.Path()) + if err != nil { + return nil, err + } + + return bytes.NewReader(resp), nil +} + +func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *viper.RemoteResponse, chan bool) { + cm, err := getConfigManager(rp) + if err != nil { + return nil, nil + } + quit := make(chan bool) + quitwc := make(chan bool) + viperResponsCh := make(chan *viper.RemoteResponse) + cryptoResponseCh := cm.Watch(rp.Path(), quit) + // need this function to convert the Channel response form crypt.Response to viper.Response + go func(cr <-chan *crypt.Response, vr chan<- *viper.RemoteResponse, quitwc <-chan bool, quit chan<- bool) { + for { + select { + case <-quitwc: + quit <- true + return + case resp := <-cr: + vr <- &viper.RemoteResponse{ + Error: resp.Error, + Value: resp.Value, + } + + } + + } + }(cryptoResponseCh, viperResponsCh, quitwc, quit) + + return viperResponsCh, quitwc +} + +func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) { + var cm crypt.ConfigManager + var err error + + if rp.SecretKeyring() != "" { + kr, err := os.Open(rp.SecretKeyring()) + defer kr.Close() + if err != nil { + return nil, err + } + if rp.Provider() == "etcd" { + cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr) + } else { + cm, err = crypt.NewConsulConfigManager([]string{rp.Endpoint()}, kr) + } + } else { + if rp.Provider() == "etcd" { + cm, err = crypt.NewStandardEtcdConfigManager([]string{rp.Endpoint()}) + } else { + cm, err = crypt.NewStandardConsulConfigManager([]string{rp.Endpoint()}) + } + } + if err != nil { + return nil, err + } + return cm, nil +} + +func init() { + viper.RemoteConfig = &remoteConfigProvider{} +} diff --git a/vendor/github.com/mattermost/viper/util_test.go b/vendor/github.com/mattermost/viper/util_test.go new file mode 100644 index 000000000..0af80bb63 --- /dev/null +++ b/vendor/github.com/mattermost/viper/util_test.go @@ -0,0 +1,54 @@ +// Copyright © 2016 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Viper is a application configuration system. +// It believes that applications can be configured a variety of ways +// via flags, ENVIRONMENT variables, configuration files retrieved +// from the file system, or a remote key/value store. + +package viper + +import ( + "reflect" + "testing" +) + +func TestCopyAndInsensitiviseMap(t *testing.T) { + var ( + given = map[string]interface{}{ + "Foo": 32, + "Bar": map[interface{}]interface { + }{ + "ABc": "A", + "cDE": "B"}, + } + expected = map[string]interface{}{ + "foo": 32, + "bar": map[string]interface { + }{ + "abc": "A", + "cde": "B"}, + } + ) + + got := copyAndInsensitiviseMap(given) + + if !reflect.DeepEqual(got, expected) { + t.Fatalf("Got %q\nexpected\n%q", got, expected) + } + + if _, ok := given["foo"]; ok { + t.Fatal("Input map changed") + } + + if _, ok := given["bar"]; ok { + t.Fatal("Input map changed") + } + + m := given["Bar"].(map[interface{}]interface{}) + if _, ok := m["ABc"]; !ok { + t.Fatal("Input map changed") + } +} diff --git a/vendor/github.com/mattermost/viper/viper.go b/vendor/github.com/mattermost/viper/viper.go index b9e165695..bb605594b 100644 --- a/vendor/github.com/mattermost/viper/viper.go +++ b/vendor/github.com/mattermost/viper/viper.go @@ -334,14 +334,14 @@ func (v *Viper) mergeWithEnvPrefix(in string) string { // rewriting keys many things, Ex: Get('someKey') -> some_key // (camel case to snake case for JSON keys perhaps) -// getEnv is a wrapper around os.Getenv which replaces characters in the original +// getEnv is a wrapper around os.LookupEnv which replaces characters in the original // key. This allows env vars which have different keys than the config object // keys. -func (v *Viper) getEnv(key string) string { +func (v *Viper) getEnv(key string) (string, bool) { if v.envKeyReplacer != nil { key = v.envKeyReplacer.Replace(key) } - return os.Getenv(key) + return os.LookupEnv(key) } // ConfigFileUsed returns the file used to populate the config registry. @@ -568,10 +568,10 @@ func (v *Viper) isPathShadowedInFlatMap(path []string, mi interface{}) string { // "foo.bar.baz" in a lower-priority map func (v *Viper) isPathShadowedInAutoEnv(path []string) string { var parentKey string - var val string + var ok bool for i := 1; i < len(path); i++ { parentKey = strings.Join(path[0:i], v.keyDelim) - if val = v.getEnv(v.mergeWithEnvPrefix(parentKey)); val != "" { + if _, ok = v.getEnv(v.mergeWithEnvPrefix(parentKey)); ok { return parentKey } } @@ -934,7 +934,7 @@ func (v *Viper) find(lcaseKey string) interface{} { if v.automaticEnvApplied { // even if it hasn't been registered, if automaticEnv is used, // check any Get request - if val = v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); val != "" { + if val, ok := v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); ok { return val } if nested && v.isPathShadowedInAutoEnv(path) != "" { @@ -943,7 +943,7 @@ func (v *Viper) find(lcaseKey string) interface{} { } envkey, exists := v.env[lcaseKey] if exists { - if val = v.getEnv(envkey); val != "" { + if val, ok := v.getEnv(envkey); ok { return val } } @@ -1682,8 +1682,8 @@ func (v *Viper) EnvSettings() map[string]interface{} { m := map[string]interface{}{} // start from the list of keys, and construct the map one value at a time for _, k := range v.AllKeys() { - value := v.getEnv(v.mergeWithEnvPrefix(k)) - if value == "" { + _, ok := v.getEnv(v.mergeWithEnvPrefix(k)) + if !ok { continue } path := strings.Split(k, v.keyDelim) diff --git a/vendor/github.com/mattermost/viper/viper_test.go b/vendor/github.com/mattermost/viper/viper_test.go new file mode 100644 index 000000000..fbf05f205 --- /dev/null +++ b/vendor/github.com/mattermost/viper/viper_test.go @@ -0,0 +1,1421 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package viper + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "reflect" + "sort" + "strings" + "testing" + "time" + + "github.com/spf13/afero" + "github.com/spf13/cast" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" +) + +var yamlExample = []byte(`Hacker: true +name: steve +hobbies: +- skateboarding +- snowboarding +- go +clothing: + jacket: leather + trousers: denim + pants: + size: large +age: 35 +eyes : brown +beard: true +`) + +var yamlExampleWithExtras = []byte(`Existing: true +Bogus: true +`) + +type testUnmarshalExtra struct { + Existing bool +} + +var tomlExample = []byte(` +title = "TOML Example" + +[owner] +organization = "MongoDB" +Bio = "MongoDB Chief Developer Advocate & Hacker at Large" +dob = 1979-05-27T07:32:00Z # First class dates? Why not?`) + +var jsonExample = []byte(`{ +"id": "0001", +"type": "donut", +"name": "Cake", +"ppu": 0.55, +"batters": { + "batter": [ + { "type": "Regular" }, + { "type": "Chocolate" }, + { "type": "Blueberry" }, + { "type": "Devil's Food" } + ] + } +}`) + +var hclExample = []byte(` +id = "0001" +type = "donut" +name = "Cake" +ppu = 0.55 +foos { + foo { + key = 1 + } + foo { + key = 2 + } + foo { + key = 3 + } + foo { + key = 4 + } +}`) + +var propertiesExample = []byte(` +p_id: 0001 +p_type: donut +p_name: Cake +p_ppu: 0.55 +p_batters.batter.type: Regular +`) + +var remoteExample = []byte(`{ +"id":"0002", +"type":"cronut", +"newkey":"remote" +}`) + +func initConfigs() { + Reset() + var r io.Reader + SetConfigType("yaml") + r = bytes.NewReader(yamlExample) + unmarshalReader(r, v.config) + + SetConfigType("json") + r = bytes.NewReader(jsonExample) + unmarshalReader(r, v.config) + + SetConfigType("hcl") + r = bytes.NewReader(hclExample) + unmarshalReader(r, v.config) + + SetConfigType("properties") + r = bytes.NewReader(propertiesExample) + unmarshalReader(r, v.config) + + SetConfigType("toml") + r = bytes.NewReader(tomlExample) + unmarshalReader(r, v.config) + + SetConfigType("json") + remote := bytes.NewReader(remoteExample) + unmarshalReader(remote, v.kvstore) +} + +func initConfig(typ, config string) { + Reset() + SetConfigType(typ) + r := strings.NewReader(config) + + if err := unmarshalReader(r, v.config); err != nil { + panic(err) + } +} + +func initYAML() { + initConfig("yaml", string(yamlExample)) +} + +func initJSON() { + Reset() + SetConfigType("json") + r := bytes.NewReader(jsonExample) + + unmarshalReader(r, v.config) +} + +func initProperties() { + Reset() + SetConfigType("properties") + r := bytes.NewReader(propertiesExample) + + unmarshalReader(r, v.config) +} + +func initTOML() { + Reset() + SetConfigType("toml") + r := bytes.NewReader(tomlExample) + + unmarshalReader(r, v.config) +} + +func initHcl() { + Reset() + SetConfigType("hcl") + r := bytes.NewReader(hclExample) + + unmarshalReader(r, v.config) +} + +// make directories for testing +func initDirs(t *testing.T) (string, string, func()) { + + var ( + testDirs = []string{`a a`, `b`, `c\c`, `D_`} + config = `improbable` + ) + + root, err := ioutil.TempDir("", "") + + cleanup := true + defer func() { + if cleanup { + os.Chdir("..") + os.RemoveAll(root) + } + }() + + assert.Nil(t, err) + + err = os.Chdir(root) + assert.Nil(t, err) + + for _, dir := range testDirs { + err = os.Mkdir(dir, 0750) + assert.Nil(t, err) + + err = ioutil.WriteFile( + path.Join(dir, config+".toml"), + []byte("key = \"value is "+dir+"\"\n"), + 0640) + assert.Nil(t, err) + } + + cleanup = false + return root, config, func() { + os.Chdir("..") + os.RemoveAll(root) + } +} + +//stubs for PFlag Values +type stringValue string + +func newStringValue(val string, p *string) *stringValue { + *p = val + return (*stringValue)(p) +} + +func (s *stringValue) Set(val string) error { + *s = stringValue(val) + return nil +} + +func (s *stringValue) Type() string { + return "string" +} + +func (s *stringValue) String() string { + return fmt.Sprintf("%s", *s) +} + +func TestBasics(t *testing.T) { + SetConfigFile("/tmp/config.yaml") + filename, err := v.getConfigFile() + assert.Equal(t, "/tmp/config.yaml", filename) + assert.NoError(t, err) +} + +func TestDefault(t *testing.T) { + SetDefault("age", 45) + assert.Equal(t, 45, Get("age")) + + SetDefault("clothing.jacket", "slacks") + assert.Equal(t, "slacks", Get("clothing.jacket")) + + SetConfigType("yaml") + err := ReadConfig(bytes.NewBuffer(yamlExample)) + + assert.NoError(t, err) + assert.Equal(t, "leather", Get("clothing.jacket")) +} + +func TestUnmarshaling(t *testing.T) { + SetConfigType("yaml") + r := bytes.NewReader(yamlExample) + + unmarshalReader(r, v.config) + assert.True(t, InConfig("name")) + assert.False(t, InConfig("state")) + assert.Equal(t, "steve", Get("name")) + assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies")) + assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, Get("clothing")) + assert.Equal(t, 35, Get("age")) +} + +func TestUnmarshalExact(t *testing.T) { + vip := New() + target := &testUnmarshalExtra{} + vip.SetConfigType("yaml") + r := bytes.NewReader(yamlExampleWithExtras) + vip.ReadConfig(r) + err := vip.UnmarshalExact(target) + if err == nil { + t.Fatal("UnmarshalExact should error when populating a struct from a conf that contains unused fields") + } +} + +func TestOverrides(t *testing.T) { + Set("age", 40) + assert.Equal(t, 40, Get("age")) +} + +func TestDefaultPost(t *testing.T) { + assert.NotEqual(t, "NYC", Get("state")) + SetDefault("state", "NYC") + assert.Equal(t, "NYC", Get("state")) +} + +func TestAliases(t *testing.T) { + RegisterAlias("years", "age") + assert.Equal(t, 40, Get("years")) + Set("years", 45) + assert.Equal(t, 45, Get("age")) +} + +func TestAliasInConfigFile(t *testing.T) { + // the config file specifies "beard". If we make this an alias for + // "hasbeard", we still want the old config file to work with beard. + RegisterAlias("beard", "hasbeard") + assert.Equal(t, true, Get("hasbeard")) + Set("hasbeard", false) + assert.Equal(t, false, Get("beard")) +} + +func TestYML(t *testing.T) { + initYAML() + assert.Equal(t, "steve", Get("name")) +} + +func TestJSON(t *testing.T) { + initJSON() + assert.Equal(t, "0001", Get("id")) +} + +func TestProperties(t *testing.T) { + initProperties() + assert.Equal(t, "0001", Get("p_id")) +} + +func TestTOML(t *testing.T) { + initTOML() + assert.Equal(t, "TOML Example", Get("title")) +} + +func TestHCL(t *testing.T) { + initHcl() + assert.Equal(t, "0001", Get("id")) + assert.Equal(t, 0.55, Get("ppu")) + assert.Equal(t, "donut", Get("type")) + assert.Equal(t, "Cake", Get("name")) + Set("id", "0002") + assert.Equal(t, "0002", Get("id")) + assert.NotEqual(t, "cronut", Get("type")) +} + +func TestRemotePrecedence(t *testing.T) { + initJSON() + + remote := bytes.NewReader(remoteExample) + assert.Equal(t, "0001", Get("id")) + unmarshalReader(remote, v.kvstore) + assert.Equal(t, "0001", Get("id")) + assert.NotEqual(t, "cronut", Get("type")) + assert.Equal(t, "remote", Get("newkey")) + Set("newkey", "newvalue") + assert.NotEqual(t, "remote", Get("newkey")) + assert.Equal(t, "newvalue", Get("newkey")) + Set("newkey", "remote") +} + +func TestEnv(t *testing.T) { + initJSON() + + BindEnv("id") + BindEnv("f", "FOOD") + + os.Setenv("ID", "13") + os.Setenv("FOOD", "apple") + os.Setenv("NAME", "crunk") + + assert.Equal(t, "13", Get("id")) + assert.Equal(t, "apple", Get("f")) + assert.Equal(t, "Cake", Get("name")) + + os.Setenv("FOOD", "") + + assert.Equal(t, "", Get("f")) + + AutomaticEnv() + + assert.Equal(t, "crunk", Get("name")) + + os.Setenv("NAME", "") + + assert.Equal(t, "", Get("name")) +} + +func TestEnvPrefix(t *testing.T) { + initJSON() + + SetEnvPrefix("foo") // will be uppercased automatically + BindEnv("id") + BindEnv("f", "FOOD") // not using prefix + + os.Setenv("FOO_ID", "13") + os.Setenv("FOOD", "apple") + os.Setenv("FOO_NAME", "crunk") + + assert.Equal(t, "13", Get("id")) + assert.Equal(t, "apple", Get("f")) + assert.Equal(t, "Cake", Get("name")) + + os.Setenv("FOO_ID", "") + + assert.Equal(t, "", Get("id")) + + AutomaticEnv() + + assert.Equal(t, "crunk", Get("name")) + + os.Setenv("FOO_NAME", "") + + assert.Equal(t, "", Get("name")) +} + +func TestAutoEnv(t *testing.T) { + Reset() + + AutomaticEnv() + os.Setenv("FOO_BAR", "13") + assert.Equal(t, "13", Get("foo_bar")) +} + +func TestAutoEnvWithPrefix(t *testing.T) { + Reset() + + AutomaticEnv() + SetEnvPrefix("Baz") + os.Setenv("BAZ_BAR", "13") + assert.Equal(t, "13", Get("bar")) +} + +func TestSetEnvKeyReplacer(t *testing.T) { + Reset() + + AutomaticEnv() + os.Setenv("REFRESH_INTERVAL", "30s") + + replacer := strings.NewReplacer("-", "_") + SetEnvKeyReplacer(replacer) + + assert.Equal(t, "30s", Get("refresh-interval")) +} + +func TestAllKeys(t *testing.T) { + initConfigs() + + ks := sort.StringSlice{"title", "newkey", "owner.organization", "owner.dob", "owner.bio", "name", "beard", "ppu", "batters.batter", "hobbies", "clothing.jacket", "clothing.trousers", "clothing.pants.size", "age", "hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name", "foos"} + dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") + all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[string]interface{}{"trousers": "denim", "jacket": "leather", "pants": map[string]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters": map[string]interface{}{"batter": map[string]interface{}{"type": "Regular"}}, "p_type": "donut", "foos": []map[string]interface{}{map[string]interface{}{"foo": []map[string]interface{}{map[string]interface{}{"key": 1}, map[string]interface{}{"key": 2}, map[string]interface{}{"key": 3}, map[string]interface{}{"key": 4}}}}} + + var allkeys sort.StringSlice + allkeys = AllKeys() + allkeys.Sort() + ks.Sort() + + assert.Equal(t, ks, allkeys) + assert.Equal(t, all, AllSettings()) +} + +func TestAllKeysWithEnv(t *testing.T) { + v := New() + + // bind and define environment variables (including a nested one) + v.BindEnv("id") + v.BindEnv("foo.bar") + v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + os.Setenv("ID", "13") + os.Setenv("FOO_BAR", "baz") + + expectedKeys := sort.StringSlice{"id", "foo.bar"} + expectedKeys.Sort() + keys := sort.StringSlice(v.AllKeys()) + keys.Sort() + assert.Equal(t, expectedKeys, keys) +} + +func TestAliasesOfAliases(t *testing.T) { + Set("Title", "Checking Case") + RegisterAlias("Foo", "Bar") + RegisterAlias("Bar", "Title") + assert.Equal(t, "Checking Case", Get("FOO")) +} + +func TestRecursiveAliases(t *testing.T) { + RegisterAlias("Baz", "Roo") + RegisterAlias("Roo", "baz") +} + +func TestUnmarshal(t *testing.T) { + SetDefault("port", 1313) + Set("name", "Steve") + Set("duration", "1s1ms") + + type config struct { + Port int + Name string + Duration time.Duration + } + + var C config + + err := Unmarshal(&C) + if err != nil { + t.Fatalf("unable to decode into struct, %v", err) + } + + assert.Equal(t, &config{Name: "Steve", Port: 1313, Duration: time.Second + time.Millisecond}, &C) + + Set("port", 1234) + err = Unmarshal(&C) + if err != nil { + t.Fatalf("unable to decode into struct, %v", err) + } + assert.Equal(t, &config{Name: "Steve", Port: 1234, Duration: time.Second + time.Millisecond}, &C) +} + +func TestBindPFlags(t *testing.T) { + v := New() // create independent Viper object + flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) + + var testValues = map[string]*string{ + "host": nil, + "port": nil, + "endpoint": nil, + } + + var mutatedTestValues = map[string]string{ + "host": "localhost", + "port": "6060", + "endpoint": "/public", + } + + for name := range testValues { + testValues[name] = flagSet.String(name, "", "test") + } + + err := v.BindPFlags(flagSet) + if err != nil { + t.Fatalf("error binding flag set, %v", err) + } + + flagSet.VisitAll(func(flag *pflag.Flag) { + flag.Value.Set(mutatedTestValues[flag.Name]) + flag.Changed = true + }) + + for name, expected := range mutatedTestValues { + assert.Equal(t, expected, v.Get(name)) + } + +} + +func TestBindPFlagsStringSlice(t *testing.T) { + for _, testValue := range []struct { + Expected []string + Value string + }{ + {[]string{}, ""}, + {[]string{"jeden"}, "jeden"}, + {[]string{"dwa", "trzy"}, "dwa,trzy"}, + {[]string{"cztery", "piec , szesc"}, "cztery,\"piec , szesc\""}} { + + for _, changed := range []bool{true, false} { + v := New() // create independent Viper object + flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) + flagSet.StringSlice("stringslice", testValue.Expected, "test") + flagSet.Visit(func(f *pflag.Flag) { + if len(testValue.Value) > 0 { + f.Value.Set(testValue.Value) + f.Changed = changed + } + }) + + err := v.BindPFlags(flagSet) + if err != nil { + t.Fatalf("error binding flag set, %v", err) + } + + type TestStr struct { + StringSlice []string + } + val := &TestStr{} + if err := v.Unmarshal(val); err != nil { + t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) + } + assert.Equal(t, testValue.Expected, val.StringSlice) + } + } +} + +func TestBindPFlag(t *testing.T) { + var testString = "testing" + var testValue = newStringValue(testString, &testString) + + flag := &pflag.Flag{ + Name: "testflag", + Value: testValue, + Changed: false, + } + + BindPFlag("testvalue", flag) + + assert.Equal(t, testString, Get("testvalue")) + + flag.Value.Set("testing_mutate") + flag.Changed = true //hack for pflag usage + + assert.Equal(t, "testing_mutate", Get("testvalue")) + +} + +func TestBoundCaseSensitivity(t *testing.T) { + assert.Equal(t, "brown", Get("eyes")) + + BindEnv("eYEs", "TURTLE_EYES") + os.Setenv("TURTLE_EYES", "blue") + + assert.Equal(t, "blue", Get("eyes")) + + var testString = "green" + var testValue = newStringValue(testString, &testString) + + flag := &pflag.Flag{ + Name: "eyeballs", + Value: testValue, + Changed: true, + } + + BindPFlag("eYEs", flag) + assert.Equal(t, "green", Get("eyes")) + +} + +func TestSizeInBytes(t *testing.T) { + input := map[string]uint{ + "": 0, + "b": 0, + "12 bytes": 0, + "200000000000gb": 0, + "12 b": 12, + "43 MB": 43 * (1 << 20), + "10mb": 10 * (1 << 20), + "1gb": 1 << 30, + } + + for str, expected := range input { + assert.Equal(t, expected, parseSizeInBytes(str), str) + } +} + +func TestFindsNestedKeys(t *testing.T) { + initConfigs() + dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") + + Set("super", map[string]interface{}{ + "deep": map[string]interface{}{ + "nested": "value", + }, + }) + + expected := map[string]interface{}{ + "super": map[string]interface{}{ + "deep": map[string]interface{}{ + "nested": "value", + }, + }, + "super.deep": map[string]interface{}{ + "nested": "value", + }, + "super.deep.nested": "value", + "owner.organization": "MongoDB", + "batters.batter": []interface{}{ + map[string]interface{}{ + "type": "Regular", + }, + map[string]interface{}{ + "type": "Chocolate", + }, + map[string]interface{}{ + "type": "Blueberry", + }, + map[string]interface{}{ + "type": "Devil's Food", + }, + }, + "hobbies": []interface{}{ + "skateboarding", "snowboarding", "go", + }, + "title": "TOML Example", + "newkey": "remote", + "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", + }, + }, + }, + "eyes": "brown", + "age": 35, + "owner": map[string]interface{}{ + "organization": "MongoDB", + "bio": "MongoDB Chief Developer Advocate & Hacker at Large", + "dob": dob, + }, + "owner.bio": "MongoDB Chief Developer Advocate & Hacker at Large", + "type": "donut", + "id": "0001", + "name": "Cake", + "hacker": true, + "ppu": 0.55, + "clothing": map[string]interface{}{ + "jacket": "leather", + "trousers": "denim", + "pants": map[string]interface{}{ + "size": "large", + }, + }, + "clothing.jacket": "leather", + "clothing.pants.size": "large", + "clothing.trousers": "denim", + "owner.dob": dob, + "beard": true, + "foos": []map[string]interface{}{ + map[string]interface{}{ + "foo": []map[string]interface{}{ + map[string]interface{}{ + "key": 1, + }, + map[string]interface{}{ + "key": 2, + }, + map[string]interface{}{ + "key": 3, + }, + map[string]interface{}{ + "key": 4, + }, + }, + }, + }, + } + + for key, expectedValue := range expected { + + assert.Equal(t, expectedValue, v.Get(key)) + } + +} + +func TestReadBufConfig(t *testing.T) { + v := New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(yamlExample)) + t.Log(v.AllKeys()) + + assert.True(t, v.InConfig("name")) + assert.False(t, v.InConfig("state")) + assert.Equal(t, "steve", v.Get("name")) + assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies")) + assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, v.Get("clothing")) + assert.Equal(t, 35, v.Get("age")) +} + +func TestIsSet(t *testing.T) { + v := New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(yamlExample)) + assert.True(t, v.IsSet("clothing.jacket")) + assert.False(t, v.IsSet("clothing.jackets")) + assert.False(t, v.IsSet("helloworld")) + v.Set("helloworld", "fubar") + assert.True(t, v.IsSet("helloworld")) +} + +func TestDirsSearch(t *testing.T) { + + root, config, cleanup := initDirs(t) + defer cleanup() + + v := New() + v.SetConfigName(config) + v.SetDefault(`key`, `default`) + + entries, err := ioutil.ReadDir(root) + for _, e := range entries { + if e.IsDir() { + v.AddConfigPath(e.Name()) + } + } + + err = v.ReadInConfig() + assert.Nil(t, err) + + assert.Equal(t, `value is `+path.Base(v.configPaths[0]), v.GetString(`key`)) +} + +func TestWrongDirsSearchNotFound(t *testing.T) { + + _, config, cleanup := initDirs(t) + defer cleanup() + + v := New() + v.SetConfigName(config) + v.SetDefault(`key`, `default`) + + v.AddConfigPath(`whattayoutalkingbout`) + v.AddConfigPath(`thispathaintthere`) + + err := v.ReadInConfig() + assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) + + // Even though config did not load and the error might have + // been ignored by the client, the default still loads + assert.Equal(t, `default`, v.GetString(`key`)) +} + +func TestWrongDirsSearchNotFoundForMerge(t *testing.T) { + + _, config, cleanup := initDirs(t) + defer cleanup() + + v := New() + v.SetConfigName(config) + v.SetDefault(`key`, `default`) + + v.AddConfigPath(`whattayoutalkingbout`) + v.AddConfigPath(`thispathaintthere`) + + err := v.MergeInConfig() + assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) + + // Even though config did not load and the error might have + // been ignored by the client, the default still loads + assert.Equal(t, `default`, v.GetString(`key`)) +} + +func TestSub(t *testing.T) { + v := New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(yamlExample)) + + subv := v.Sub("clothing") + assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("pants.size")) + + subv = v.Sub("clothing.pants") + assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("size")) + + subv = v.Sub("clothing.pants.size") + assert.Equal(t, (*Viper)(nil), subv) + + subv = v.Sub("missing.key") + assert.Equal(t, (*Viper)(nil), subv) +} + +var hclWriteExpected = []byte(`"foos" = { + "foo" = { + "key" = 1 + } + + "foo" = { + "key" = 2 + } + + "foo" = { + "key" = 3 + } + + "foo" = { + "key" = 4 + } +} + +"id" = "0001" + +"name" = "Cake" + +"ppu" = 0.55 + +"type" = "donut"`) + +func TestWriteConfigHCL(t *testing.T) { + v := New() + fs := afero.NewMemMapFs() + v.SetFs(fs) + v.SetConfigName("c") + v.SetConfigType("hcl") + err := v.ReadConfig(bytes.NewBuffer(hclExample)) + if err != nil { + t.Fatal(err) + } + if err := v.WriteConfigAs("c.hcl"); err != nil { + t.Fatal(err) + } + read, err := afero.ReadFile(fs, "c.hcl") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, hclWriteExpected, read) +} + +var jsonWriteExpected = []byte(`{ + "batters": { + "batter": [ + { + "type": "Regular" + }, + { + "type": "Chocolate" + }, + { + "type": "Blueberry" + }, + { + "type": "Devil's Food" + } + ] + }, + "id": "0001", + "name": "Cake", + "ppu": 0.55, + "type": "donut" +}`) + +func TestWriteConfigJson(t *testing.T) { + v := New() + fs := afero.NewMemMapFs() + v.SetFs(fs) + v.SetConfigName("c") + v.SetConfigType("json") + err := v.ReadConfig(bytes.NewBuffer(jsonExample)) + if err != nil { + t.Fatal(err) + } + if err := v.WriteConfigAs("c.json"); err != nil { + t.Fatal(err) + } + read, err := afero.ReadFile(fs, "c.json") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, jsonWriteExpected, read) +} + +var propertiesWriteExpected = []byte(`p_id = 0001 +p_type = donut +p_name = Cake +p_ppu = 0.55 +p_batters.batter.type = Regular +`) + +func TestWriteConfigProperties(t *testing.T) { + v := New() + fs := afero.NewMemMapFs() + v.SetFs(fs) + v.SetConfigName("c") + v.SetConfigType("properties") + err := v.ReadConfig(bytes.NewBuffer(propertiesExample)) + if err != nil { + t.Fatal(err) + } + if err := v.WriteConfigAs("c.properties"); err != nil { + t.Fatal(err) + } + read, err := afero.ReadFile(fs, "c.properties") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, propertiesWriteExpected, read) +} + +func TestWriteConfigTOML(t *testing.T) { + fs := afero.NewMemMapFs() + v := New() + v.SetFs(fs) + v.SetConfigName("c") + v.SetConfigType("toml") + err := v.ReadConfig(bytes.NewBuffer(tomlExample)) + if err != nil { + t.Fatal(err) + } + if err := v.WriteConfigAs("c.toml"); err != nil { + t.Fatal(err) + } + + // The TOML String method does not order the contents. + // Therefore, we must read the generated file and compare the data. + v2 := New() + v2.SetFs(fs) + v2.SetConfigName("c") + v2.SetConfigType("toml") + v2.SetConfigFile("c.toml") + err = v2.ReadInConfig() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, v.GetString("title"), v2.GetString("title")) + assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio")) + assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob")) + assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization")) +} + +var yamlWriteExpected = []byte(`age: 35 +beard: true +clothing: + jacket: leather + pants: + size: large + trousers: denim +eyes: brown +hacker: true +hobbies: +- skateboarding +- snowboarding +- go +name: steve +`) + +func TestWriteConfigYAML(t *testing.T) { + v := New() + fs := afero.NewMemMapFs() + v.SetFs(fs) + v.SetConfigName("c") + v.SetConfigType("yaml") + err := v.ReadConfig(bytes.NewBuffer(yamlExample)) + if err != nil { + t.Fatal(err) + } + if err := v.WriteConfigAs("c.yaml"); err != nil { + t.Fatal(err) + } + read, err := afero.ReadFile(fs, "c.yaml") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, yamlWriteExpected, read) +} + +var yamlMergeExampleTgt = []byte(` +hello: + pop: 37890 + lagrenum: 765432101234567 + world: + - us + - uk + - fr + - de +`) + +var yamlMergeExampleSrc = []byte(` +hello: + pop: 45000 + lagrenum: 7654321001234567 + universe: + - mw + - ad +fu: bar +`) + +func TestMergeConfig(t *testing.T) { + v := New() + v.SetConfigType("yml") + if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { + t.Fatal(err) + } + + if pop := v.GetInt("hello.pop"); pop != 37890 { + t.Fatalf("pop != 37890, = %d", pop) + } + + if pop := v.GetInt("hello.lagrenum"); pop != 765432101234567 { + t.Fatalf("lagrenum != 765432101234567, = %d", pop) + } + + if pop := v.GetInt64("hello.lagrenum"); pop != int64(765432101234567) { + t.Fatalf("int64 lagrenum != 765432101234567, = %d", pop) + } + + if world := v.GetStringSlice("hello.world"); len(world) != 4 { + t.Fatalf("len(world) != 4, = %d", len(world)) + } + + if fu := v.GetString("fu"); fu != "" { + t.Fatalf("fu != \"\", = %s", fu) + } + + if err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { + t.Fatal(err) + } + + if pop := v.GetInt("hello.pop"); pop != 45000 { + t.Fatalf("pop != 45000, = %d", pop) + } + + if pop := v.GetInt("hello.lagrenum"); pop != 7654321001234567 { + t.Fatalf("lagrenum != 7654321001234567, = %d", pop) + } + + if pop := v.GetInt64("hello.lagrenum"); pop != int64(7654321001234567) { + t.Fatalf("int64 lagrenum != 7654321001234567, = %d", pop) + } + + if world := v.GetStringSlice("hello.world"); len(world) != 4 { + t.Fatalf("len(world) != 4, = %d", len(world)) + } + + if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { + t.Fatalf("len(universe) != 2, = %d", len(universe)) + } + + if fu := v.GetString("fu"); fu != "bar" { + t.Fatalf("fu != \"bar\", = %s", fu) + } +} + +func TestMergeConfigNoMerge(t *testing.T) { + v := New() + v.SetConfigType("yml") + if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { + t.Fatal(err) + } + + if pop := v.GetInt("hello.pop"); pop != 37890 { + t.Fatalf("pop != 37890, = %d", pop) + } + + if world := v.GetStringSlice("hello.world"); len(world) != 4 { + t.Fatalf("len(world) != 4, = %d", len(world)) + } + + if fu := v.GetString("fu"); fu != "" { + t.Fatalf("fu != \"\", = %s", fu) + } + + if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { + t.Fatal(err) + } + + if pop := v.GetInt("hello.pop"); pop != 45000 { + t.Fatalf("pop != 45000, = %d", pop) + } + + if world := v.GetStringSlice("hello.world"); len(world) != 0 { + t.Fatalf("len(world) != 0, = %d", len(world)) + } + + if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { + t.Fatalf("len(universe) != 2, = %d", len(universe)) + } + + if fu := v.GetString("fu"); fu != "bar" { + t.Fatalf("fu != \"bar\", = %s", fu) + } +} + +func TestUnmarshalingWithAliases(t *testing.T) { + v := New() + v.SetDefault("ID", 1) + v.Set("name", "Steve") + v.Set("lastname", "Owen") + + v.RegisterAlias("UserID", "ID") + v.RegisterAlias("Firstname", "name") + v.RegisterAlias("Surname", "lastname") + + type config struct { + ID int + FirstName string + Surname string + } + + var C config + err := v.Unmarshal(&C) + if err != nil { + t.Fatalf("unable to decode into struct, %v", err) + } + + assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C) +} + +func TestSetConfigNameClearsFileCache(t *testing.T) { + SetConfigFile("/tmp/config.yaml") + SetConfigName("default") + f, err := v.getConfigFile() + if err == nil { + t.Fatalf("config file cache should have been cleared") + } + assert.Empty(t, f) +} + +func TestShadowedNestedValue(t *testing.T) { + + config := `name: steve +clothing: + jacket: leather + trousers: denim + pants: + size: large +` + initConfig("yaml", config) + + assert.Equal(t, "steve", GetString("name")) + + polyester := "polyester" + SetDefault("clothing.shirt", polyester) + SetDefault("clothing.jacket.price", 100) + + assert.Equal(t, "leather", GetString("clothing.jacket")) + assert.Nil(t, Get("clothing.jacket.price")) + assert.Equal(t, polyester, GetString("clothing.shirt")) + + clothingSettings := AllSettings()["clothing"].(map[string]interface{}) + assert.Equal(t, "leather", clothingSettings["jacket"]) + assert.Equal(t, polyester, clothingSettings["shirt"]) +} + +func TestDotParameter(t *testing.T) { + initJSON() + // shoud take precedence over batters defined in jsonExample + r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`)) + unmarshalReader(r, v.config) + + actual := Get("batters.batter") + expected := []interface{}{map[string]interface{}{"type": "Small"}} + assert.Equal(t, expected, actual) +} + +func TestCaseInsensitive(t *testing.T) { + for _, config := range []struct { + typ string + content string + }{ + {"yaml", ` +aBcD: 1 +eF: + gH: 2 + iJk: 3 + Lm: + nO: 4 + P: + Q: 5 + R: 6 +`}, + {"json", `{ + "aBcD": 1, + "eF": { + "iJk": 3, + "Lm": { + "P": { + "Q": 5, + "R": 6 + }, + "nO": 4 + }, + "gH": 2 + } +}`}, + {"toml", `aBcD = 1 +[eF] +gH = 2 +iJk = 3 +[eF.Lm] +nO = 4 +[eF.Lm.P] +Q = 5 +R = 6 +`}, + } { + doTestCaseInsensitive(t, config.typ, config.content) + } +} + +func TestCaseInsensitiveSet(t *testing.T) { + Reset() + m1 := map[string]interface{}{ + "Foo": 32, + "Bar": map[interface{}]interface { + }{ + "ABc": "A", + "cDE": "B"}, + } + + m2 := map[string]interface{}{ + "Foo": 52, + "Bar": map[interface{}]interface { + }{ + "bCd": "A", + "eFG": "B"}, + } + + Set("Given1", m1) + Set("Number1", 42) + + SetDefault("Given2", m2) + SetDefault("Number2", 52) + + // Verify SetDefault + if v := Get("number2"); v != 52 { + t.Fatalf("Expected 52 got %q", v) + } + + if v := Get("given2.foo"); v != 52 { + t.Fatalf("Expected 52 got %q", v) + } + + if v := Get("given2.bar.bcd"); v != "A" { + t.Fatalf("Expected A got %q", v) + } + + if _, ok := m2["Foo"]; !ok { + t.Fatal("Input map changed") + } + + // Verify Set + if v := Get("number1"); v != 42 { + t.Fatalf("Expected 42 got %q", v) + } + + if v := Get("given1.foo"); v != 32 { + t.Fatalf("Expected 32 got %q", v) + } + + if v := Get("given1.bar.abc"); v != "A" { + t.Fatalf("Expected A got %q", v) + } + + if _, ok := m1["Foo"]; !ok { + t.Fatal("Input map changed") + } +} + +func TestParseNested(t *testing.T) { + type duration struct { + Delay time.Duration + } + + type item struct { + Name string + Delay time.Duration + Nested duration + } + + config := `[[parent]] + delay="100ms" + [parent.nested] + delay="200ms" +` + initConfig("toml", config) + + var items []item + err := v.UnmarshalKey("parent", &items) + if err != nil { + t.Fatalf("unable to decode into struct, %v", err) + } + + assert.Equal(t, 1, len(items)) + assert.Equal(t, 100*time.Millisecond, items[0].Delay) + assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) +} + +func doTestCaseInsensitive(t *testing.T, typ, config string) { + initConfig(typ, config) + Set("RfD", true) + assert.Equal(t, true, Get("rfd")) + assert.Equal(t, true, Get("rFD")) + assert.Equal(t, 1, cast.ToInt(Get("abcd"))) + assert.Equal(t, 1, cast.ToInt(Get("Abcd"))) + assert.Equal(t, 2, cast.ToInt(Get("ef.gh"))) + assert.Equal(t, 3, cast.ToInt(Get("ef.ijk"))) + assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no"))) + assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q"))) + +} + +func BenchmarkGetBool(b *testing.B) { + key := "BenchmarkGetBool" + v = New() + v.Set(key, true) + + for i := 0; i < b.N; i++ { + if !v.GetBool(key) { + b.Fatal("GetBool returned false") + } + } +} + +func BenchmarkGet(b *testing.B) { + key := "BenchmarkGet" + v = New() + v.Set(key, true) + + for i := 0; i < b.N; i++ { + if !v.Get(key).(bool) { + b.Fatal("Get returned false") + } + } +} + +// This is the "perfect result" for the above. +func BenchmarkGetBoolFromMap(b *testing.B) { + m := make(map[string]bool) + key := "BenchmarkGetBool" + m[key] = true + + for i := 0; i < b.N; i++ { + if !m[key] { + b.Fatal("Map value was false") + } + } +} -- cgit v1.2.3-1-g7c22