// 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" "os" "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 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) marshalReader(r, v.config) SetConfigType("json") r = bytes.NewReader(jsonExample) marshalReader(r, v.config) SetConfigType("properties") r = bytes.NewReader(propertiesExample) marshalReader(r, v.config) SetConfigType("toml") r = bytes.NewReader(tomlExample) marshalReader(r, v.config) SetConfigType("json") remote := bytes.NewReader(remoteExample) marshalReader(remote, v.kvstore) } func initYAML() { Reset() SetConfigType("yaml") r := bytes.NewReader(yamlExample) marshalReader(r, v.config) } func initJSON() { Reset() SetConfigType("json") r := bytes.NewReader(jsonExample) marshalReader(r, v.config) } func initProperties() { Reset() SetConfigType("properties") r := bytes.NewReader(propertiesExample) marshalReader(r, v.config) } func initTOML() { Reset() SetConfigType("toml") r := bytes.NewReader(tomlExample) marshalReader(r, v.config) } //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")) } func TestMarshalling(t *testing.T) { SetConfigType("yaml") r := bytes.NewReader(yamlExample) marshalReader(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 TestAliasNestedInConfigFile(t *testing.T) { // the config file specifies "clothing.jacket". If we make this an alias for // "clothing-jacket", we still want the old config file to work with "clothing.jacket". RegisterAlias("clothing.jacket", "clothing-jacket") assert.Equal(t, "leather", Get("clothing-jacket")) Set("clothing-jacket", "nylon") assert.Equal(t, "nylon", Get("clothing.jacket")) } 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 TestRemotePrecedence(t *testing.T) { initJSON() remote := bytes.NewReader(remoteExample) assert.Equal(t, "0001", Get("id")) marshalReader(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 TestMarshal(t *testing.T) { SetDefault("port", 1313) Set("name", "Steve") type config struct { Port int Name string } var C config err := Marshal(&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 = Marshal(&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")) }