-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
In function TestNestedOverrides(), in file overrides_test.go More involved complement to TestOverrides() in viper_test.go, as suggested in Issue #168 (#168)
- Loading branch information
1 parent
86b56e8
commit f2dc934
Showing
1 changed file
with
153 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package viper | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
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 | ||
deepCheckValue(assert, v, []string{"tom", "size"}, 4) // value should still be reachable | ||
v = override(assert, "tom.size", 4, "tom.age", 10) | ||
assert.Equal(4, v.Get("tom.size")) | ||
deepCheckValue(assert, v, []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.Nil(v.Get("tom.size")) // "tom.size" should not exist anymore | ||
assert.Equal(10, v.Get("tom.age")) // new value should be there | ||
deepCheckValue(assert, v, []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, []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, []string{"tom", "age"}, []int{30, 40, 50, 60}) | ||
} | ||
} | ||
|
||
type layer int | ||
|
||
const ( | ||
defaultLayer layer = iota + 1 | ||
overrideLayer | ||
) | ||
|
||
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, 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, secondKeys, secondValue) | ||
|
||
return v | ||
} | ||
|
||
// deepCheckValue checks that all given keys correspond to a valid path in the | ||
// configuration map, and that the final value equals the one given | ||
func deepCheckValue(assert *assert.Assertions, v *Viper, keys []string, value interface{}) { | ||
var m map[string]interface{} | ||
var ok bool | ||
if assert == nil || v == nil || | ||
len(keys) == 0 || len(keys[0]) == 0 { | ||
return | ||
} | ||
|
||
ms := keys[0] | ||
val := v.Get(keys[0]) | ||
err := false | ||
for i := 1; i < len(keys); i++ { | ||
// deep scan of the map to get the final value | ||
m, ok = val.(map[string]interface{}) | ||
if err = !assert.True(ok, fmt.Sprintf("%s is not a map[string]interface{}", ms)); err { | ||
break | ||
} | ||
val = m[keys[i]] | ||
ms = ms + "[\"" + keys[i] + "\"]" | ||
} | ||
if !err { | ||
assert.Equal(value, val) | ||
} | ||
} |