2014-04-04 17:21:59 -04:00
|
|
|
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
|
|
|
//
|
|
|
|
// Use of this source code is governed by an MIT-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package viper
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2014-10-09 15:48:55 -04:00
|
|
|
"fmt"
|
2016-09-27 17:04:25 +02:00
|
|
|
"io"
|
2015-08-01 19:37:27 -05:00
|
|
|
"io/ioutil"
|
2014-09-27 14:03:00 -07:00
|
|
|
"os"
|
2015-08-01 19:37:27 -05:00
|
|
|
"path"
|
|
|
|
"reflect"
|
2014-09-27 14:00:51 -07:00
|
|
|
"sort"
|
2015-03-06 11:21:17 -08:00
|
|
|
"strings"
|
2014-04-04 17:21:59 -04:00
|
|
|
"testing"
|
2014-09-27 14:00:51 -07:00
|
|
|
"time"
|
2014-04-04 17:21:59 -04:00
|
|
|
|
2014-10-09 15:48:55 -04:00
|
|
|
"github.com/spf13/pflag"
|
2014-04-04 17:21:59 -04:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
)
|
|
|
|
|
|
|
|
var yamlExample = []byte(`Hacker: true
|
|
|
|
name: steve
|
|
|
|
hobbies:
|
|
|
|
- skateboarding
|
|
|
|
- snowboarding
|
|
|
|
- go
|
2014-05-29 16:48:24 -04:00
|
|
|
clothing:
|
|
|
|
jacket: leather
|
|
|
|
trousers: denim
|
2015-05-27 18:15:02 +03:00
|
|
|
pants:
|
|
|
|
size: large
|
2014-08-05 07:35:21 -04:00
|
|
|
age: 35
|
2014-10-09 16:39:24 -04:00
|
|
|
eyes : brown
|
2014-08-05 07:35:21 -04:00
|
|
|
beard: true
|
|
|
|
`)
|
2014-04-04 17:21:59 -04:00
|
|
|
|
2015-12-29 23:11:39 -07:00
|
|
|
var yamlExampleWithExtras = []byte(`Existing: true
|
|
|
|
Bogus: true
|
|
|
|
`)
|
|
|
|
|
|
|
|
type testUnmarshalExtra struct {
|
|
|
|
Existing bool
|
|
|
|
}
|
|
|
|
|
2014-04-04 17:21:59 -04:00
|
|
|
var tomlExample = []byte(`
|
|
|
|
title = "TOML Example"
|
|
|
|
|
|
|
|
[owner]
|
|
|
|
organization = "MongoDB"
|
2014-04-05 01:19:39 -04:00
|
|
|
Bio = "MongoDB Chief Developer Advocate & Hacker at Large"
|
2014-04-04 17:21:59 -04:00
|
|
|
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" }
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}`)
|
|
|
|
|
2015-12-11 23:51:11 +01:00
|
|
|
var hclExample = []byte(`
|
|
|
|
id = "0001"
|
|
|
|
type = "donut"
|
|
|
|
name = "Cake"
|
|
|
|
ppu = 0.55
|
2015-12-13 21:37:32 +01:00
|
|
|
foos {
|
|
|
|
foo {
|
|
|
|
key = 1
|
|
|
|
}
|
|
|
|
foo {
|
|
|
|
key = 2
|
|
|
|
}
|
|
|
|
foo {
|
|
|
|
key = 3
|
|
|
|
}
|
|
|
|
foo {
|
|
|
|
key = 4
|
|
|
|
}
|
|
|
|
}`)
|
2015-12-11 23:51:11 +01:00
|
|
|
|
2015-04-14 13:15:02 -05:00
|
|
|
var propertiesExample = []byte(`
|
|
|
|
p_id: 0001
|
|
|
|
p_type: donut
|
|
|
|
p_name: Cake
|
|
|
|
p_ppu: 0.55
|
|
|
|
p_batters.batter.type: Regular
|
|
|
|
`)
|
|
|
|
|
2014-10-27 15:32:46 -04:00
|
|
|
var remoteExample = []byte(`{
|
|
|
|
"id":"0002",
|
|
|
|
"type":"cronut",
|
|
|
|
"newkey":"remote"
|
|
|
|
}`)
|
|
|
|
|
2014-12-22 18:31:11 -05:00
|
|
|
func initConfigs() {
|
|
|
|
Reset()
|
2016-09-27 17:04:25 +02:00
|
|
|
var r io.Reader
|
2014-12-22 18:31:11 -05:00
|
|
|
SetConfigType("yaml")
|
2016-09-27 17:04:25 +02:00
|
|
|
r = bytes.NewReader(yamlExample)
|
2015-08-23 23:40:56 -04:00
|
|
|
unmarshalReader(r, v.config)
|
2014-12-22 18:31:11 -05:00
|
|
|
|
|
|
|
SetConfigType("json")
|
|
|
|
r = bytes.NewReader(jsonExample)
|
2015-08-23 23:40:56 -04:00
|
|
|
unmarshalReader(r, v.config)
|
2014-12-22 18:31:11 -05:00
|
|
|
|
2015-12-12 00:27:07 +01:00
|
|
|
SetConfigType("hcl")
|
|
|
|
r = bytes.NewReader(hclExample)
|
|
|
|
unmarshalReader(r, v.config)
|
|
|
|
|
2015-04-14 13:15:02 -05:00
|
|
|
SetConfigType("properties")
|
|
|
|
r = bytes.NewReader(propertiesExample)
|
2015-08-23 23:40:56 -04:00
|
|
|
unmarshalReader(r, v.config)
|
2015-04-14 13:15:02 -05:00
|
|
|
|
2014-12-22 18:31:11 -05:00
|
|
|
SetConfigType("toml")
|
|
|
|
r = bytes.NewReader(tomlExample)
|
2015-08-23 23:40:56 -04:00
|
|
|
unmarshalReader(r, v.config)
|
2014-12-22 18:31:11 -05:00
|
|
|
|
|
|
|
SetConfigType("json")
|
|
|
|
remote := bytes.NewReader(remoteExample)
|
2015-08-23 23:40:56 -04:00
|
|
|
unmarshalReader(remote, v.kvstore)
|
2014-12-22 18:31:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func initYAML() {
|
|
|
|
Reset()
|
|
|
|
SetConfigType("yaml")
|
|
|
|
r := bytes.NewReader(yamlExample)
|
|
|
|
|
2015-08-23 23:40:56 -04:00
|
|
|
unmarshalReader(r, v.config)
|
2014-12-22 18:31:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func initJSON() {
|
|
|
|
Reset()
|
|
|
|
SetConfigType("json")
|
|
|
|
r := bytes.NewReader(jsonExample)
|
|
|
|
|
2015-08-23 23:40:56 -04:00
|
|
|
unmarshalReader(r, v.config)
|
2014-12-22 18:31:11 -05:00
|
|
|
}
|
|
|
|
|
2015-04-14 13:15:02 -05:00
|
|
|
func initProperties() {
|
|
|
|
Reset()
|
|
|
|
SetConfigType("properties")
|
|
|
|
r := bytes.NewReader(propertiesExample)
|
|
|
|
|
2015-08-23 23:40:56 -04:00
|
|
|
unmarshalReader(r, v.config)
|
2015-04-14 13:15:02 -05:00
|
|
|
}
|
|
|
|
|
2014-12-22 18:31:11 -05:00
|
|
|
func initTOML() {
|
|
|
|
Reset()
|
|
|
|
SetConfigType("toml")
|
|
|
|
r := bytes.NewReader(tomlExample)
|
|
|
|
|
2015-08-23 23:40:56 -04:00
|
|
|
unmarshalReader(r, v.config)
|
2014-12-22 18:31:11 -05:00
|
|
|
}
|
|
|
|
|
2015-12-11 23:51:11 +01:00
|
|
|
func initHcl() {
|
|
|
|
Reset()
|
|
|
|
SetConfigType("hcl")
|
|
|
|
r := bytes.NewReader(hclExample)
|
|
|
|
|
|
|
|
unmarshalReader(r, v.config)
|
|
|
|
}
|
|
|
|
|
2015-08-01 19:37:27 -05:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-09 15:48:55 -04:00
|
|
|
//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)
|
|
|
|
}
|
|
|
|
|
2014-04-04 17:21:59 -04:00
|
|
|
func TestBasics(t *testing.T) {
|
|
|
|
SetConfigFile("/tmp/config.yaml")
|
2014-12-05 03:55:51 +01:00
|
|
|
assert.Equal(t, "/tmp/config.yaml", v.getConfigFile())
|
2014-04-04 17:21:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestDefault(t *testing.T) {
|
|
|
|
SetDefault("age", 45)
|
|
|
|
assert.Equal(t, 45, Get("age"))
|
2015-10-13 18:31:32 -04:00
|
|
|
|
|
|
|
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"))
|
2014-04-04 17:21:59 -04:00
|
|
|
}
|
|
|
|
|
2015-08-23 23:40:56 -04:00
|
|
|
func TestUnmarshalling(t *testing.T) {
|
2014-04-04 17:21:59 -04:00
|
|
|
SetConfigType("yaml")
|
|
|
|
r := bytes.NewReader(yamlExample)
|
|
|
|
|
2015-08-23 23:40:56 -04:00
|
|
|
unmarshalReader(r, v.config)
|
2014-04-04 17:21:59 -04:00
|
|
|
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"))
|
Fix: Get() and IsSet() work correctly on nested values
Rewriting of find(), which now checks for in-depth values,
and ignores shadowed ones.
* When looking for "foo.bar":
- before: if "foo" was defined in some map, "foo.*" was looked for
only in this map
- now: "foo.*" is looked for in all maps, the first one with a value is
used (even if "foo" was defined elsewhere, in a higher-priority map).
* Also, find() tests that the requested key is not shadowed somewhere
on its path.
e.g., {"foo.bar": 1} in the config map shadows "foo.bar.baz" in
the defaults map
* Lastly, if in the config map, config["foo"]["bar"] and config["foo.bar"]
coexist, the latter value is used.
(this should not be necessary for other maps, since by construction
their keys should not contain dots)
=> Get() and IsSet() corrected and simplified, since all the logic
about value retrieval has been put in find()
+ README.md modified accordingly:
In Section “Accessing nested keys”, to reflect the corrected behavior of find():
- paths searched for at all levels, not only the one of the first sub-key;
- paths may be shadowed by a shorter, higher priority, path.
+ tests modified accordingly
2016-09-27 17:11:17 +02:00
|
|
|
assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{"size": "large"}}, Get("clothing"))
|
2014-04-04 17:21:59 -04:00
|
|
|
assert.Equal(t, 35, Get("age"))
|
|
|
|
}
|
|
|
|
|
2015-12-29 23:11:39 -07:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-04 17:21:59 -04:00
|
|
|
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"))
|
|
|
|
}
|
|
|
|
|
2014-08-05 07:35:21 -04:00
|
|
|
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"))
|
|
|
|
}
|
|
|
|
|
2014-05-08 16:40:14 +02:00
|
|
|
func TestYML(t *testing.T) {
|
2014-12-22 18:31:11 -05:00
|
|
|
initYAML()
|
2014-05-08 16:40:14 +02:00
|
|
|
assert.Equal(t, "steve", Get("name"))
|
|
|
|
}
|
|
|
|
|
2014-04-04 17:21:59 -04:00
|
|
|
func TestJSON(t *testing.T) {
|
2014-12-22 18:31:11 -05:00
|
|
|
initJSON()
|
2014-04-04 17:21:59 -04:00
|
|
|
assert.Equal(t, "0001", Get("id"))
|
|
|
|
}
|
|
|
|
|
2015-04-14 13:15:02 -05:00
|
|
|
func TestProperties(t *testing.T) {
|
|
|
|
initProperties()
|
|
|
|
assert.Equal(t, "0001", Get("p_id"))
|
|
|
|
}
|
|
|
|
|
2014-04-04 17:21:59 -04:00
|
|
|
func TestTOML(t *testing.T) {
|
2014-12-22 18:31:11 -05:00
|
|
|
initTOML()
|
2014-04-04 17:21:59 -04:00
|
|
|
assert.Equal(t, "TOML Example", Get("title"))
|
|
|
|
}
|
|
|
|
|
2015-12-11 23:51:11 +01:00
|
|
|
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"))
|
|
|
|
}
|
|
|
|
|
2014-10-27 15:32:46 -04:00
|
|
|
func TestRemotePrecedence(t *testing.T) {
|
2014-12-22 18:31:11 -05:00
|
|
|
initJSON()
|
|
|
|
|
2014-10-27 15:32:46 -04:00
|
|
|
remote := bytes.NewReader(remoteExample)
|
|
|
|
assert.Equal(t, "0001", Get("id"))
|
2015-08-23 23:40:56 -04:00
|
|
|
unmarshalReader(remote, v.kvstore)
|
2014-10-27 15:32:46 -04:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2014-09-27 14:03:00 -07:00
|
|
|
func TestEnv(t *testing.T) {
|
2014-12-22 18:31:11 -05:00
|
|
|
initJSON()
|
|
|
|
|
2014-09-27 14:03:00 -07:00
|
|
|
BindEnv("id")
|
|
|
|
BindEnv("f", "FOOD")
|
|
|
|
|
|
|
|
os.Setenv("ID", "13")
|
|
|
|
os.Setenv("FOOD", "apple")
|
2014-09-27 14:01:11 -07:00
|
|
|
os.Setenv("NAME", "crunk")
|
2014-09-27 14:03:00 -07:00
|
|
|
|
|
|
|
assert.Equal(t, "13", Get("id"))
|
|
|
|
assert.Equal(t, "apple", Get("f"))
|
2014-09-27 14:01:11 -07:00
|
|
|
assert.Equal(t, "Cake", Get("name"))
|
|
|
|
|
|
|
|
AutomaticEnv()
|
|
|
|
|
|
|
|
assert.Equal(t, "crunk", Get("name"))
|
2015-03-06 11:21:17 -08:00
|
|
|
|
2014-09-27 14:03:00 -07:00
|
|
|
}
|
|
|
|
|
2014-12-22 18:31:11 -05:00
|
|
|
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"))
|
|
|
|
}
|
|
|
|
|
2015-02-16 23:32:10 -05:00
|
|
|
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"))
|
|
|
|
}
|
|
|
|
|
2015-03-06 11:21:17 -08:00
|
|
|
func TestSetEnvReplacer(t *testing.T) {
|
|
|
|
Reset()
|
|
|
|
|
|
|
|
AutomaticEnv()
|
|
|
|
os.Setenv("REFRESH_INTERVAL", "30s")
|
|
|
|
|
|
|
|
replacer := strings.NewReplacer("-", "_")
|
|
|
|
SetEnvKeyReplacer(replacer)
|
|
|
|
|
|
|
|
assert.Equal(t, "30s", Get("refresh-interval"))
|
|
|
|
}
|
|
|
|
|
2014-09-27 14:00:51 -07:00
|
|
|
func TestAllKeys(t *testing.T) {
|
2014-12-22 18:31:11 -05:00
|
|
|
initConfigs()
|
|
|
|
|
AllKeys() includes all keys / AllSettings() includes overridden nested values
* Function AllKeys() now returns all keys holding a value (for nested values,
the nested key is the full path, i.e., a sequence of dot-separated keys).
Previously, returned only depth-1 keys, as well as flags and environment
variables: this is more generic and may be used widely.
Besides, it takes into account shadowed keys (key ignored if shadowed by
a path at a higher-priority level).
* Function AllSettings() now returns nested maps for all keys holding a value,
as specified by AllKeys().
The value stored in the map is the one with highest priority, as returned
by the Get() function (taking into account aliases, environment variables,
flags, etc.).
This fixes Unmarshal(): it fills in correct values for nested configuration
elements overridden by flags or env variables.
+ tests fixed accordingly
+ test added to TestShadowedNestedValue(), to test Unmarshalling of shadowed keys
2016-07-16 22:20:50 +02:00
|
|
|
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"}
|
2014-09-27 14:00:51 -07:00
|
|
|
dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
|
AllKeys() includes all keys / AllSettings() includes overridden nested values
* Function AllKeys() now returns all keys holding a value (for nested values,
the nested key is the full path, i.e., a sequence of dot-separated keys).
Previously, returned only depth-1 keys, as well as flags and environment
variables: this is more generic and may be used widely.
Besides, it takes into account shadowed keys (key ignored if shadowed by
a path at a higher-priority level).
* Function AllSettings() now returns nested maps for all keys holding a value,
as specified by AllKeys().
The value stored in the map is the one with highest priority, as returned
by the Get() function (taking into account aliases, environment variables,
flags, etc.).
This fixes Unmarshal(): it fills in correct values for nested configuration
elements overridden by flags or env variables.
+ tests fixed accordingly
+ test added to TestShadowedNestedValue(), to test Unmarshalling of shadowed keys
2016-07-16 22:20:50 +02:00
|
|
|
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}}}}}
|
2014-09-27 14:00:51 -07:00
|
|
|
|
|
|
|
var allkeys sort.StringSlice
|
|
|
|
allkeys = AllKeys()
|
|
|
|
allkeys.Sort()
|
|
|
|
ks.Sort()
|
|
|
|
|
|
|
|
assert.Equal(t, ks, allkeys)
|
|
|
|
assert.Equal(t, all, AllSettings())
|
|
|
|
}
|
|
|
|
|
2014-04-05 01:19:39 -04:00
|
|
|
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")
|
2014-04-04 17:21:59 -04:00
|
|
|
}
|
2014-06-26 17:58:55 -04:00
|
|
|
|
2015-08-23 23:40:56 -04:00
|
|
|
func TestUnmarshal(t *testing.T) {
|
2014-06-26 17:58:55 -04:00
|
|
|
SetDefault("port", 1313)
|
|
|
|
Set("name", "Steve")
|
2016-09-24 02:20:44 +03:00
|
|
|
Set("duration", "1s1ms")
|
2014-06-26 17:58:55 -04:00
|
|
|
|
|
|
|
type config struct {
|
2016-09-24 02:20:44 +03:00
|
|
|
Port int
|
|
|
|
Name string
|
|
|
|
Duration time.Duration
|
2014-06-26 17:58:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var C config
|
|
|
|
|
2015-08-23 23:40:56 -04:00
|
|
|
err := Unmarshal(&C)
|
2014-06-26 17:58:55 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to decode into struct, %v", err)
|
|
|
|
}
|
|
|
|
|
2016-09-27 15:08:18 +02:00
|
|
|
assert.Equal(t, &config{Name: "Steve", Port: 1313, Duration: time.Second + time.Millisecond}, &C)
|
2014-06-26 17:58:55 -04:00
|
|
|
|
|
|
|
Set("port", 1234)
|
2015-08-23 23:40:56 -04:00
|
|
|
err = Unmarshal(&C)
|
2014-06-26 17:58:55 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to decode into struct, %v", err)
|
|
|
|
}
|
2016-09-27 15:08:18 +02:00
|
|
|
assert.Equal(t, &config{Name: "Steve", Port: 1234, Duration: time.Second + time.Millisecond}, &C)
|
2014-09-27 14:03:00 -07:00
|
|
|
}
|
2014-10-09 15:48:55 -04:00
|
|
|
|
2015-04-01 21:38:54 -04:00
|
|
|
func TestBindPFlags(t *testing.T) {
|
2016-09-27 15:46:53 +02:00
|
|
|
v := New() // create independent Viper object
|
2015-04-01 21:38:54 -04:00
|
|
|
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",
|
|
|
|
}
|
|
|
|
|
2016-09-20 10:17:41 +02:00
|
|
|
for name := range testValues {
|
2015-04-01 21:38:54 -04:00
|
|
|
testValues[name] = flagSet.String(name, "", "test")
|
|
|
|
}
|
|
|
|
|
2016-09-27 15:46:53 +02:00
|
|
|
err := v.BindPFlags(flagSet)
|
2015-04-01 21:38:54 -04:00
|
|
|
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 {
|
2016-09-27 15:46:53 +02:00
|
|
|
assert.Equal(t, expected, v.Get(name))
|
2015-04-01 21:38:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-10-09 15:48:55 -04:00
|
|
|
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"))
|
|
|
|
|
|
|
|
}
|
2014-10-09 16:39:24 -04:00
|
|
|
|
|
|
|
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"))
|
|
|
|
|
|
|
|
}
|
2015-02-28 16:03:22 -05:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2015-05-01 15:15:24 -04:00
|
|
|
|
|
|
|
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",
|
2015-05-11 23:36:42 +02:00
|
|
|
"Bio": "MongoDB Chief Developer Advocate & Hacker at Large",
|
2015-05-01 15:15:24 -04:00
|
|
|
"dob": dob,
|
|
|
|
},
|
2015-05-11 23:36:42 +02:00
|
|
|
"owner.Bio": "MongoDB Chief Developer Advocate & Hacker at Large",
|
2015-05-01 15:15:24 -04:00
|
|
|
"type": "donut",
|
|
|
|
"id": "0001",
|
|
|
|
"name": "Cake",
|
|
|
|
"hacker": true,
|
|
|
|
"ppu": 0.55,
|
Fix: Get() and IsSet() work correctly on nested values
Rewriting of find(), which now checks for in-depth values,
and ignores shadowed ones.
* When looking for "foo.bar":
- before: if "foo" was defined in some map, "foo.*" was looked for
only in this map
- now: "foo.*" is looked for in all maps, the first one with a value is
used (even if "foo" was defined elsewhere, in a higher-priority map).
* Also, find() tests that the requested key is not shadowed somewhere
on its path.
e.g., {"foo.bar": 1} in the config map shadows "foo.bar.baz" in
the defaults map
* Lastly, if in the config map, config["foo"]["bar"] and config["foo.bar"]
coexist, the latter value is used.
(this should not be necessary for other maps, since by construction
their keys should not contain dots)
=> Get() and IsSet() corrected and simplified, since all the logic
about value retrieval has been put in find()
+ README.md modified accordingly:
In Section “Accessing nested keys”, to reflect the corrected behavior of find():
- paths searched for at all levels, not only the one of the first sub-key;
- paths may be shadowed by a shorter, higher priority, path.
+ tests modified accordingly
2016-09-27 17:11:17 +02:00
|
|
|
"clothing": map[string]interface{}{
|
2015-05-01 15:15:24 -04:00
|
|
|
"jacket": "leather",
|
|
|
|
"trousers": "denim",
|
2015-05-27 18:15:02 +03:00
|
|
|
"pants": map[interface{}]interface{}{
|
|
|
|
"size": "large",
|
|
|
|
},
|
2015-05-01 15:15:24 -04:00
|
|
|
},
|
2015-05-27 18:15:02 +03:00
|
|
|
"clothing.jacket": "leather",
|
|
|
|
"clothing.pants.size": "large",
|
|
|
|
"clothing.trousers": "denim",
|
|
|
|
"owner.dob": dob,
|
|
|
|
"beard": true,
|
2015-12-13 21:37:32 +01:00
|
|
|
"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,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2015-05-01 15:15:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
for key, expectedValue := range expected {
|
|
|
|
|
|
|
|
assert.Equal(t, expectedValue, v.Get(key))
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2015-05-08 17:13:33 +08:00
|
|
|
|
|
|
|
func TestReadBufConfig(t *testing.T) {
|
|
|
|
v := New()
|
|
|
|
v.SetConfigType("yaml")
|
2015-05-14 17:40:59 +08:00
|
|
|
v.ReadConfig(bytes.NewBuffer(yamlExample))
|
2015-05-08 17:13:33 +08:00
|
|
|
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"))
|
Fix: Get() and IsSet() work correctly on nested values
Rewriting of find(), which now checks for in-depth values,
and ignores shadowed ones.
* When looking for "foo.bar":
- before: if "foo" was defined in some map, "foo.*" was looked for
only in this map
- now: "foo.*" is looked for in all maps, the first one with a value is
used (even if "foo" was defined elsewhere, in a higher-priority map).
* Also, find() tests that the requested key is not shadowed somewhere
on its path.
e.g., {"foo.bar": 1} in the config map shadows "foo.bar.baz" in
the defaults map
* Lastly, if in the config map, config["foo"]["bar"] and config["foo.bar"]
coexist, the latter value is used.
(this should not be necessary for other maps, since by construction
their keys should not contain dots)
=> Get() and IsSet() corrected and simplified, since all the logic
about value retrieval has been put in find()
+ README.md modified accordingly:
In Section “Accessing nested keys”, to reflect the corrected behavior of find():
- paths searched for at all levels, not only the one of the first sub-key;
- paths may be shadowed by a shorter, higher priority, path.
+ tests modified accordingly
2016-09-27 17:11:17 +02:00
|
|
|
assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{"size": "large"}}, v.Get("clothing"))
|
2015-05-08 17:13:33 +08:00
|
|
|
assert.Equal(t, 35, v.Get("age"))
|
|
|
|
}
|
2015-08-01 19:37:27 -05:00
|
|
|
|
2015-11-29 17:16:21 -06:00
|
|
|
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"))
|
|
|
|
}
|
|
|
|
|
2015-08-01 19:37:27 -05:00
|
|
|
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`))
|
|
|
|
}
|
2015-12-24 19:44:44 +08:00
|
|
|
|
|
|
|
func TestSub(t *testing.T) {
|
|
|
|
v := New()
|
|
|
|
v.SetConfigType("yaml")
|
|
|
|
v.ReadConfig(bytes.NewBuffer(yamlExample))
|
|
|
|
|
2015-12-25 12:29:33 +08:00
|
|
|
subv := v.Sub("clothing")
|
|
|
|
assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("pants.size"))
|
|
|
|
|
|
|
|
subv = v.Sub("clothing.pants")
|
2015-12-24 19:44:44 +08:00
|
|
|
assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("size"))
|
|
|
|
|
|
|
|
subv = v.Sub("clothing.pants.size")
|
2016-09-27 15:08:18 +02:00
|
|
|
assert.Equal(t, (*Viper)(nil), subv)
|
2016-10-05 18:22:39 -05:00
|
|
|
|
|
|
|
subv = v.Sub("missing.key")
|
2016-09-27 15:08:18 +02:00
|
|
|
assert.Equal(t, (*Viper)(nil), subv)
|
2015-12-24 19:44:44 +08:00
|
|
|
}
|
|
|
|
|
2015-11-12 14:20:40 -06:00
|
|
|
var yamlMergeExampleTgt = []byte(`
|
|
|
|
hello:
|
|
|
|
pop: 37890
|
2016-08-05 15:16:55 +08:00
|
|
|
lagrenum: 765432101234567
|
2015-11-12 14:20:40 -06:00
|
|
|
world:
|
|
|
|
- us
|
|
|
|
- uk
|
|
|
|
- fr
|
|
|
|
- de
|
|
|
|
`)
|
|
|
|
|
|
|
|
var yamlMergeExampleSrc = []byte(`
|
|
|
|
hello:
|
|
|
|
pop: 45000
|
2016-08-05 15:16:55 +08:00
|
|
|
lagrenum: 7654321001234567
|
2015-11-12 14:20:40 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2016-08-05 15:16:55 +08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2015-11-12 14:20:40 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2016-08-05 15:16:55 +08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2015-11-12 14:20:40 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2016-01-20 22:15:43 +01:00
|
|
|
|
|
|
|
func TestUnmarshalingWithAliases(t *testing.T) {
|
AllKeys() includes all keys / AllSettings() includes overridden nested values
* Function AllKeys() now returns all keys holding a value (for nested values,
the nested key is the full path, i.e., a sequence of dot-separated keys).
Previously, returned only depth-1 keys, as well as flags and environment
variables: this is more generic and may be used widely.
Besides, it takes into account shadowed keys (key ignored if shadowed by
a path at a higher-priority level).
* Function AllSettings() now returns nested maps for all keys holding a value,
as specified by AllKeys().
The value stored in the map is the one with highest priority, as returned
by the Get() function (taking into account aliases, environment variables,
flags, etc.).
This fixes Unmarshal(): it fills in correct values for nested configuration
elements overridden by flags or env variables.
+ tests fixed accordingly
+ test added to TestShadowedNestedValue(), to test Unmarshalling of shadowed keys
2016-07-16 22:20:50 +02:00
|
|
|
v := New()
|
|
|
|
v.SetDefault("ID", 1)
|
|
|
|
v.Set("name", "Steve")
|
|
|
|
v.Set("lastname", "Owen")
|
2016-01-20 22:15:43 +01:00
|
|
|
|
AllKeys() includes all keys / AllSettings() includes overridden nested values
* Function AllKeys() now returns all keys holding a value (for nested values,
the nested key is the full path, i.e., a sequence of dot-separated keys).
Previously, returned only depth-1 keys, as well as flags and environment
variables: this is more generic and may be used widely.
Besides, it takes into account shadowed keys (key ignored if shadowed by
a path at a higher-priority level).
* Function AllSettings() now returns nested maps for all keys holding a value,
as specified by AllKeys().
The value stored in the map is the one with highest priority, as returned
by the Get() function (taking into account aliases, environment variables,
flags, etc.).
This fixes Unmarshal(): it fills in correct values for nested configuration
elements overridden by flags or env variables.
+ tests fixed accordingly
+ test added to TestShadowedNestedValue(), to test Unmarshalling of shadowed keys
2016-07-16 22:20:50 +02:00
|
|
|
v.RegisterAlias("UserID", "ID")
|
|
|
|
v.RegisterAlias("Firstname", "name")
|
|
|
|
v.RegisterAlias("Surname", "lastname")
|
2016-01-20 22:15:43 +01:00
|
|
|
|
|
|
|
type config struct {
|
2016-09-27 15:08:18 +02:00
|
|
|
ID int
|
2016-01-20 22:15:43 +01:00
|
|
|
FirstName string
|
2015-12-29 23:11:39 -07:00
|
|
|
Surname string
|
2016-01-20 22:15:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var C config
|
AllKeys() includes all keys / AllSettings() includes overridden nested values
* Function AllKeys() now returns all keys holding a value (for nested values,
the nested key is the full path, i.e., a sequence of dot-separated keys).
Previously, returned only depth-1 keys, as well as flags and environment
variables: this is more generic and may be used widely.
Besides, it takes into account shadowed keys (key ignored if shadowed by
a path at a higher-priority level).
* Function AllSettings() now returns nested maps for all keys holding a value,
as specified by AllKeys().
The value stored in the map is the one with highest priority, as returned
by the Get() function (taking into account aliases, environment variables,
flags, etc.).
This fixes Unmarshal(): it fills in correct values for nested configuration
elements overridden by flags or env variables.
+ tests fixed accordingly
+ test added to TestShadowedNestedValue(), to test Unmarshalling of shadowed keys
2016-07-16 22:20:50 +02:00
|
|
|
err := v.Unmarshal(&C)
|
2016-01-20 22:15:43 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to decode into struct, %v", err)
|
|
|
|
}
|
|
|
|
|
2016-09-27 15:08:18 +02:00
|
|
|
assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C)
|
2015-12-29 23:11:39 -07:00
|
|
|
}
|
2016-08-05 09:18:19 +02:00
|
|
|
|
|
|
|
func TestSetConfigNameClearsFileCache(t *testing.T) {
|
|
|
|
SetConfigFile("/tmp/config.yaml")
|
|
|
|
SetConfigName("default")
|
|
|
|
assert.Empty(t, v.getConfigFile())
|
|
|
|
}
|
2016-09-19 19:37:56 +02:00
|
|
|
|
|
|
|
func TestShadowedNestedValue(t *testing.T) {
|
|
|
|
polyester := "polyester"
|
|
|
|
initYAML()
|
|
|
|
SetDefault("clothing.shirt", polyester)
|
2016-09-27 15:09:09 +02:00
|
|
|
SetDefault("clothing.jacket.price", 100)
|
2016-09-19 19:37:56 +02:00
|
|
|
|
2016-09-27 15:08:18 +02:00
|
|
|
assert.Equal(t, "leather", GetString("clothing.jacket"))
|
2016-09-27 15:09:09 +02:00
|
|
|
assert.Nil(t, Get("clothing.jacket.price"))
|
2016-09-27 15:08:18 +02:00
|
|
|
assert.Equal(t, polyester, GetString("clothing.shirt"))
|
AllKeys() includes all keys / AllSettings() includes overridden nested values
* Function AllKeys() now returns all keys holding a value (for nested values,
the nested key is the full path, i.e., a sequence of dot-separated keys).
Previously, returned only depth-1 keys, as well as flags and environment
variables: this is more generic and may be used widely.
Besides, it takes into account shadowed keys (key ignored if shadowed by
a path at a higher-priority level).
* Function AllSettings() now returns nested maps for all keys holding a value,
as specified by AllKeys().
The value stored in the map is the one with highest priority, as returned
by the Get() function (taking into account aliases, environment variables,
flags, etc.).
This fixes Unmarshal(): it fills in correct values for nested configuration
elements overridden by flags or env variables.
+ tests fixed accordingly
+ test added to TestShadowedNestedValue(), to test Unmarshalling of shadowed keys
2016-07-16 22:20:50 +02:00
|
|
|
|
|
|
|
clothingSettings := AllSettings()["clothing"].(map[string]interface{})
|
|
|
|
assert.Equal(t, "leather", clothingSettings["jacket"])
|
|
|
|
assert.Equal(t, polyester, clothingSettings["shirt"])
|
2016-09-19 19:37:56 +02:00
|
|
|
}
|
2016-09-26 10:02:50 +02:00
|
|
|
|
2016-09-29 15:52:01 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2016-09-26 17:04:02 +02:00
|
|
|
func TestGetBool(t *testing.T) {
|
|
|
|
key := "BooleanKey"
|
|
|
|
v = New()
|
|
|
|
v.Set(key, true)
|
|
|
|
if !v.GetBool(key) {
|
|
|
|
t.Fatal("GetBool returned false")
|
|
|
|
}
|
|
|
|
if v.GetBool("NotFound") {
|
|
|
|
t.Fatal("GetBool returned true")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-26 10:02:50 +02:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|