// 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/ioutil" "os" "path" "reflect" "sort" "strings" "testing" "time" "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 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 batter { type = "Regular" } batter { type = "Chocolate" } batter { type = "Blueberry" } batter { type = "Devil's Food" }`) 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() SetConfigType("yaml") r := bytes.NewReader(yamlExample) unmarshalReader(r, v.config) SetConfigType("json") r = bytes.NewReader(jsonExample) 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) SetConfigType("hcl") r = bytes.NewReader(hclExample) unmarshalReader(r, v.config) } func initYAML() { Reset() SetConfigType("yaml") r := bytes.NewReader(yamlExample) unmarshalReader(r, v.config) } 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") assert.Equal(t, "/tmp/config.yaml", v.getConfigFile()) } 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 TestUnmarshalling(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[interface{}]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{"size": "large"}}, Get("clothing")) assert.Equal(t, 35, Get("age")) } 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 TestHCLList(t *testing.T) { initHcl() batters := []map[string]interface{}{ map[string]interface{}{ "type": "Regular", }, map[string]interface{}{ "type": "Chocolate", }, map[string]interface{}{ "type": "Blueberry", }, map[string]interface{}{ "type": "Devil's Food", }, } assert.Equal(t, batters, Get("batter")) } 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")) AutomaticEnv() assert.Equal(t, "crunk", 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")) AutomaticEnv() assert.Equal(t, "crunk", 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 TestSetEnvReplacer(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", "name", "beard", "ppu", "batters", "hobbies", "clothing", "age", "hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name"} 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[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"} var allkeys sort.StringSlice allkeys = AllKeys() allkeys.Sort() ks.Sort() assert.Equal(t, ks, allkeys) assert.Equal(t, all, AllSettings()) } func TestCaseInSensitive(t *testing.T) { assert.Equal(t, true, Get("hacker")) Set("Title", "Checking Case") assert.Equal(t, "Checking Case", Get("tItle")) } func TestAliasesOfAliases(t *testing.T) { 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") type config struct { Port int Name string } var C config err := Unmarshal(&C) if err != nil { t.Fatalf("unable to decode into struct, %v", err) } assert.Equal(t, &C, &config{Name: "Steve", Port: 1313}) Set("port", 1234) err = Unmarshal(&C) if err != nil { t.Fatalf("unable to decode into struct, %v", err) } assert.Equal(t, &C, &config{Name: "Steve", Port: 1234}) } func TestBindPFlags(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") } err := 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, Get(name), expected) } } 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[interface{}]interface{}{ "jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{ "size": "large", }, }, "clothing.jacket": "leather", "clothing.pants.size": "large", "clothing.trousers": "denim", "owner.dob": dob, "beard": true, } 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[interface{}]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{"size": "large"}}, v.Get("clothing")) assert.Equal(t, 35, v.Get("age")) } 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(UnsupportedConfigError("")), 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`)) }