123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292 |
- // 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"
- "encoding/json"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
- "path"
- "path/filepath"
- "reflect"
- "runtime"
- "sort"
- "strings"
- "sync"
- "testing"
- "time"
- "github.com/fsnotify/fsnotify"
- "github.com/mitchellh/mapstructure"
- "github.com/spf13/afero"
- "github.com/spf13/cast"
- "github.com/spf13/pflag"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- 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 dotenvExample = []byte(`
- TITLE_DOTENV="DotEnv Example"
- TYPE_DOTENV=donut
- NAME_DOTENV=Cake`)
- 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"
- }`)
- var iniExample = []byte(`; Package name
- NAME = ini
- ; Package version
- VERSION = v1
- ; Package import path
- IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
- # Information about package author
- # Bio can be written in multiple lines.
- [author]
- NAME = Unknown ; Succeeding comment
- E-MAIL = fake@localhost
- GITHUB = https://github.com/%(NAME)s
- BIO = """Gopher.
- Coding addict.
- Good man.
- """ # Succeeding comment`)
- 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("env")
- r = bytes.NewReader(dotenvExample)
- unmarshalReader(r, v.config)
- SetConfigType("json")
- remote := bytes.NewReader(remoteExample)
- unmarshalReader(remote, v.kvstore)
- SetConfigType("ini")
- r = bytes.NewReader(iniExample)
- unmarshalReader(r, v.config)
- }
- 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 initDotEnv() {
- Reset()
- SetConfigType("env")
- r := bytes.NewReader(dotenvExample)
- unmarshalReader(r, v.config)
- }
- func initHcl() {
- Reset()
- SetConfigType("hcl")
- r := bytes.NewReader(hclExample)
- unmarshalReader(r, v.config)
- }
- func initIni() {
- Reset()
- SetConfigType("ini")
- r := bytes.NewReader(iniExample)
- unmarshalReader(r, v.config)
- }
- // make directories for testing
- func initDirs(t *testing.T) (string, string, func()) {
- var (
- testDirs = []string{`a a`, `b`, `C_`}
- config = `improbable`
- )
- if runtime.GOOS != "windows" {
- testDirs = append(testDirs, `d\d`)
- }
- root, err := ioutil.TempDir("", "")
- require.NoError(t, err, "Failed to create temporary directory")
- cleanup := true
- defer func() {
- if cleanup {
- os.Chdir("..")
- os.RemoveAll(root)
- }
- }()
- assert.Nil(t, err)
- err = os.Chdir(root)
- require.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 string(*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 TestSearchInPath_WithoutConfigTypeSet(t *testing.T) {
- filename := ".dotfilenoext"
- path := "/tmp"
- file := filepath.Join(path, filename)
- SetConfigName(filename)
- AddConfigPath(path)
- _, createErr := v.fs.Create(file)
- defer func() {
- _ = v.fs.Remove(file)
- }()
- assert.NoError(t, createErr)
- _, err := v.getConfigFile()
- // unless config type is set, files without extension
- // are not considered
- assert.Error(t, err)
- }
- func TestSearchInPath(t *testing.T) {
- filename := ".dotfilenoext"
- path := "/tmp"
- file := filepath.Join(path, filename)
- SetConfigName(filename)
- SetConfigType("yaml")
- AddConfigPath(path)
- _, createErr := v.fs.Create(file)
- defer func() {
- _ = v.fs.Remove(file)
- }()
- assert.NoError(t, createErr)
- filename, err := v.getConfigFile()
- assert.Equal(t, file, filename)
- assert.NoError(t, err)
- }
- func TestSearchInPath_FilesOnly(t *testing.T) {
- fs := afero.NewMemMapFs()
- err := fs.Mkdir("/tmp/config", 0777)
- require.NoError(t, err)
- _, err = fs.Create("/tmp/config/config.yaml")
- require.NoError(t, err)
- v := New()
- v.SetFs(fs)
- v.AddConfigPath("/tmp")
- v.AddConfigPath("/tmp/config")
- filename, err := v.getConfigFile()
- assert.Equal(t, "/tmp/config/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 TestDotEnv(t *testing.T) {
- initDotEnv()
- assert.Equal(t, "DotEnv Example", Get("title_dotenv"))
- }
- 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 TestIni(t *testing.T) {
- initIni()
- assert.Equal(t, "ini", Get("default.name"))
- }
- 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 TestEmptyEnv(t *testing.T) {
- initJSON()
- BindEnv("type") // Empty environment variable
- BindEnv("name") // Bound, but not set environment variable
- os.Unsetenv("type")
- os.Unsetenv("TYPE")
- os.Unsetenv("name")
- os.Unsetenv("NAME")
- os.Setenv("TYPE", "")
- assert.Equal(t, "donut", Get("type"))
- assert.Equal(t, "Cake", Get("name"))
- }
- func TestEmptyEnv_Allowed(t *testing.T) {
- initJSON()
- AllowEmptyEnv(true)
- BindEnv("type") // Empty environment variable
- BindEnv("name") // Bound, but not set environment variable
- os.Unsetenv("type")
- os.Unsetenv("TYPE")
- os.Unsetenv("name")
- os.Unsetenv("NAME")
- os.Setenv("TYPE", "")
- assert.Equal(t, "", Get("type"))
- assert.Equal(t, "Cake", 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 TestSetEnvKeyReplacer(t *testing.T) {
- Reset()
- AutomaticEnv()
- os.Setenv("REFRESH_INTERVAL", "30s")
- replacer := strings.NewReplacer("-", "_")
- SetEnvKeyReplacer(replacer)
- assert.Equal(t, "30s", Get("refresh-interval"))
- }
- func TestEnvKeyReplacer(t *testing.T) {
- v := NewWithOptions(EnvKeyReplacer(strings.NewReplacer("-", "_")))
- v.AutomaticEnv()
- _ = os.Setenv("REFRESH_INTERVAL", "30s")
- assert.Equal(t, "30s", v.Get("refresh-interval"))
- }
- func TestAllKeys(t *testing.T) {
- initConfigs()
- ks := sort.StringSlice{
- "title",
- "author.bio",
- "author.e-mail",
- "author.github",
- "author.name",
- "newkey",
- "owner.organization",
- "owner.dob",
- "owner.bio",
- "name",
- "beard",
- "ppu",
- "batters.batter",
- "hobbies",
- "clothing.jacket",
- "clothing.trousers",
- "default.import_path",
- "default.name",
- "default.version",
- "clothing.pants.size",
- "age",
- "hacker",
- "id",
- "type",
- "eyes",
- "p_id",
- "p_ppu",
- "p_batters.batter.type",
- "p_type",
- "p_name",
- "foos",
- "title_dotenv",
- "type_dotenv",
- "name_dotenv",
- }
- 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",
- "author": map[string]interface{}{
- "e-mail": "fake@localhost",
- "github": "https://github.com/Unknown",
- "name": "Unknown",
- "bio": "Gopher.\nCoding addict.\nGood man.\n",
- },
- "ppu": 0.55,
- "eyes": "brown",
- "clothing": map[string]interface{}{
- "trousers": "denim",
- "jacket": "leather",
- "pants": map[string]interface{}{"size": "large"},
- },
- "default": map[string]interface{}{
- "import_path": "gopkg.in/ini.v1",
- "name": "ini",
- "version": "v1",
- },
- "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{}{
- {
- "foo": []map[string]interface{}{
- {"key": 1},
- {"key": 2},
- {"key": 3},
- {"key": 4}},
- },
- },
- "title_dotenv": "DotEnv Example",
- "type_dotenv": "donut",
- "name_dotenv": "Cake",
- }
- allkeys := sort.StringSlice(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")
- Set("modes", []int{1, 2, 3})
- type config struct {
- Port int
- Name string
- Duration time.Duration
- Modes []int
- }
- 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,
- Modes: []int{1, 2, 3},
- },
- &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,
- Modes: []int{1, 2, 3},
- },
- &C,
- )
- }
- func TestUnmarshalWithDecoderOptions(t *testing.T) {
- Set("credentials", "{\"foo\":\"bar\"}")
- opt := DecodeHook(mapstructure.ComposeDecodeHookFunc(
- mapstructure.StringToTimeDurationHookFunc(),
- mapstructure.StringToSliceHookFunc(","),
- // Custom Decode Hook Function
- func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) {
- if rf != reflect.String || rt != reflect.Map {
- return data, nil
- }
- m := map[string]string{}
- raw := data.(string)
- if raw == "" {
- return m, nil
- }
- return m, json.Unmarshal([]byte(raw), &m)
- },
- ))
- type config struct {
- Credentials map[string]string
- }
- var C config
- err := Unmarshal(&C, opt)
- if err != nil {
- t.Fatalf("unable to decode into struct, %v", err)
- }
- assert.Equal(t, &config{
- Credentials: map[string]string{"foo": "bar"},
- }, &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) {
- tests := []struct {
- Expected []string
- Value string
- }{
- {nil, ""},
- {[]string{"jeden"}, "jeden"},
- {[]string{"dwa", "trzy"}, "dwa,trzy"},
- {[]string{"cztery", "piec , szesc"}, "cztery,\"piec , szesc\""},
- }
- v := New() // create independent Viper object
- defaultVal := []string{"default"}
- v.SetDefault("stringslice", defaultVal)
- for _, testValue := range tests {
- flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
- flagSet.StringSlice("stringslice", testValue.Expected, "test")
- for _, changed := range []bool{true, false} {
- flagSet.VisitAll(func(f *pflag.Flag) {
- 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)
- }
- if changed {
- assert.Equal(t, testValue.Expected, val.StringSlice)
- } else {
- assert.Equal(t, defaultVal, val.StringSlice)
- }
- }
- }
- }
- func TestBindPFlagsIntSlice(t *testing.T) {
- tests := []struct {
- Expected []int
- Value string
- }{
- {nil, ""},
- {[]int{1}, "1"},
- {[]int{2, 3}, "2,3"},
- }
- v := New() // create independent Viper object
- defaultVal := []int{0}
- v.SetDefault("intslice", defaultVal)
- for _, testValue := range tests {
- flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
- flagSet.IntSlice("intslice", testValue.Expected, "test")
- for _, changed := range []bool{true, false} {
- flagSet.VisitAll(func(f *pflag.Flag) {
- f.Value.Set(testValue.Value)
- f.Changed = changed
- })
- err := v.BindPFlags(flagSet)
- if err != nil {
- t.Fatalf("error binding flag set, %v", err)
- }
- type TestInt struct {
- IntSlice []int
- }
- val := &TestInt{}
- if err := v.Unmarshal(val); err != nil {
- t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err)
- }
- if changed {
- assert.Equal(t, testValue.Expected, val.IntSlice)
- } else {
- assert.Equal(t, defaultVal, val.IntSlice)
- }
- }
- }
- }
- 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 TestBindPFlagStringToString(t *testing.T) {
- tests := []struct {
- Expected map[string]string
- Value string
- }{
- {map[string]string{}, ""},
- {map[string]string{"yo": "hi"}, "yo=hi"},
- {map[string]string{"yo": "hi", "oh": "hi=there"}, "yo=hi,oh=hi=there"},
- {map[string]string{"yo": ""}, "yo="},
- {map[string]string{"yo": "", "oh": "hi=there"}, "yo=,oh=hi=there"},
- }
- v := New() // create independent Viper object
- defaultVal := map[string]string{}
- v.SetDefault("stringtostring", defaultVal)
- for _, testValue := range tests {
- flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
- flagSet.StringToString("stringtostring", testValue.Expected, "test")
- for _, changed := range []bool{true, false} {
- flagSet.VisitAll(func(f *pflag.Flag) {
- f.Value.Set(testValue.Value)
- f.Changed = changed
- })
- err := v.BindPFlags(flagSet)
- if err != nil {
- t.Fatalf("error binding flag set, %v", err)
- }
- type TestMap struct {
- StringToString map[string]string
- }
- val := &TestMap{}
- if err := v.Unmarshal(val); err != nil {
- t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err)
- }
- if changed {
- assert.Equal(t, testValue.Expected, val.StringToString)
- } else {
- assert.Equal(t, defaultVal, val.StringToString)
- }
- }
- }
- }
- 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_DOTENV": "DotEnv Example",
- "TYPE_DOTENV": "donut",
- "NAME_DOTENV": "Cake",
- "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{}{
- {
- "foo": []map[string]interface{}{
- {
- "key": 1,
- },
- {
- "key": 2,
- },
- {
- "key": 3,
- },
- {
- "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")
- /* config and defaults */
- v.ReadConfig(bytes.NewBuffer(yamlExample))
- v.SetDefault("clothing.shoes", "sneakers")
- assert.True(t, v.IsSet("clothing"))
- assert.True(t, v.IsSet("clothing.jacket"))
- assert.False(t, v.IsSet("clothing.jackets"))
- assert.True(t, v.IsSet("clothing.shoes"))
- /* state change */
- assert.False(t, v.IsSet("helloworld"))
- v.Set("helloworld", "fubar")
- assert.True(t, v.IsSet("helloworld"))
- /* env */
- v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
- v.BindEnv("eyes")
- v.BindEnv("foo")
- v.BindEnv("clothing.hat")
- v.BindEnv("clothing.hats")
- os.Setenv("FOO", "bar")
- os.Setenv("CLOTHING_HAT", "bowler")
- assert.True(t, v.IsSet("eyes")) // in the config file
- assert.True(t, v.IsSet("foo")) // in the environment
- assert.True(t, v.IsSet("clothing.hat")) // in the environment
- assert.False(t, v.IsSet("clothing.hats")) // not defined
- /* flags */
- flagset := pflag.NewFlagSet("testisset", pflag.ContinueOnError)
- flagset.Bool("foobaz", false, "foobaz")
- flagset.Bool("barbaz", false, "barbaz")
- foobaz, barbaz := flagset.Lookup("foobaz"), flagset.Lookup("barbaz")
- v.BindPFlag("foobaz", foobaz)
- v.BindPFlag("barbaz", barbaz)
- barbaz.Value.Set("true")
- barbaz.Changed = true // hack for pflag usage
- assert.False(t, v.IsSet("foobaz"))
- assert.True(t, v.IsSet("barbaz"))
- }
- 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)
- assert.Nil(t, err)
- for _, e := range entries {
- if e.IsDir() {
- v.AddConfigPath(e.Name())
- }
- }
- err = v.ReadInConfig()
- assert.Nil(t, err)
- assert.Equal(t, `value is `+filepath.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"`)
- var jsonWriteExpected = []byte(`{
- "batters": {
- "batter": [
- {
- "type": "Regular"
- },
- {
- "type": "Chocolate"
- },
- {
- "type": "Blueberry"
- },
- {
- "type": "Devil's Food"
- }
- ]
- },
- "id": "0001",
- "name": "Cake",
- "ppu": 0.55,
- "type": "donut"
- }`)
- var propertiesWriteExpected = []byte(`p_id = 0001
- p_type = donut
- p_name = Cake
- p_ppu = 0.55
- p_batters.batter.type = Regular
- `)
- 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 TestWriteConfig(t *testing.T) {
- fs := afero.NewMemMapFs()
- testCases := map[string]struct {
- configName string
- inConfigType string
- outConfigType string
- fileName string
- input []byte
- expectedContent []byte
- }{
- "hcl with file extension": {
- configName: "c",
- inConfigType: "hcl",
- outConfigType: "hcl",
- fileName: "c.hcl",
- input: hclExample,
- expectedContent: hclWriteExpected,
- },
- "hcl without file extension": {
- configName: "c",
- inConfigType: "hcl",
- outConfigType: "hcl",
- fileName: "c",
- input: hclExample,
- expectedContent: hclWriteExpected,
- },
- "hcl with file extension and mismatch type": {
- configName: "c",
- inConfigType: "hcl",
- outConfigType: "json",
- fileName: "c.hcl",
- input: hclExample,
- expectedContent: hclWriteExpected,
- },
- "json with file extension": {
- configName: "c",
- inConfigType: "json",
- outConfigType: "json",
- fileName: "c.json",
- input: jsonExample,
- expectedContent: jsonWriteExpected,
- },
- "json without file extension": {
- configName: "c",
- inConfigType: "json",
- outConfigType: "json",
- fileName: "c",
- input: jsonExample,
- expectedContent: jsonWriteExpected,
- },
- "json with file extension and mismatch type": {
- configName: "c",
- inConfigType: "json",
- outConfigType: "hcl",
- fileName: "c.json",
- input: jsonExample,
- expectedContent: jsonWriteExpected,
- },
- "properties with file extension": {
- configName: "c",
- inConfigType: "properties",
- outConfigType: "properties",
- fileName: "c.properties",
- input: propertiesExample,
- expectedContent: propertiesWriteExpected,
- },
- "properties without file extension": {
- configName: "c",
- inConfigType: "properties",
- outConfigType: "properties",
- fileName: "c",
- input: propertiesExample,
- expectedContent: propertiesWriteExpected,
- },
- "yaml with file extension": {
- configName: "c",
- inConfigType: "yaml",
- outConfigType: "yaml",
- fileName: "c.yaml",
- input: yamlExample,
- expectedContent: yamlWriteExpected,
- },
- "yaml without file extension": {
- configName: "c",
- inConfigType: "yaml",
- outConfigType: "yaml",
- fileName: "c",
- input: yamlExample,
- expectedContent: yamlWriteExpected,
- },
- "yaml with file extension and mismatch type": {
- configName: "c",
- inConfigType: "yaml",
- outConfigType: "json",
- fileName: "c.yaml",
- input: yamlExample,
- expectedContent: yamlWriteExpected,
- },
- }
- for name, tc := range testCases {
- t.Run(name, func(t *testing.T) {
- v := New()
- v.SetFs(fs)
- v.SetConfigName(tc.fileName)
- v.SetConfigType(tc.inConfigType)
- err := v.ReadConfig(bytes.NewBuffer(tc.input))
- if err != nil {
- t.Fatal(err)
- }
- v.SetConfigType(tc.outConfigType)
- if err := v.WriteConfigAs(tc.fileName); err != nil {
- t.Fatal(err)
- }
- read, err := afero.ReadFile(fs, tc.fileName)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, tc.expectedContent, read)
- })
- }
- }
- func TestWriteConfigTOML(t *testing.T) {
- fs := afero.NewMemMapFs()
- testCases := map[string]struct {
- configName string
- configType string
- fileName string
- input []byte
- }{
- "with file extension": {
- configName: "c",
- configType: "toml",
- fileName: "c.toml",
- input: tomlExample,
- },
- "without file extension": {
- configName: "c",
- configType: "toml",
- fileName: "c",
- input: tomlExample,
- },
- }
- for name, tc := range testCases {
- t.Run(name, func(t *testing.T) {
- v := New()
- v.SetFs(fs)
- v.SetConfigName(tc.configName)
- v.SetConfigType(tc.configType)
- err := v.ReadConfig(bytes.NewBuffer(tc.input))
- if err != nil {
- t.Fatal(err)
- }
- if err := v.WriteConfigAs(tc.fileName); 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(tc.configName)
- v2.SetConfigType(tc.configType)
- v2.SetConfigFile(tc.fileName)
- 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"))
- })
- }
- }
- func TestWriteConfigDotEnv(t *testing.T) {
- fs := afero.NewMemMapFs()
- testCases := map[string]struct {
- configName string
- configType string
- fileName string
- input []byte
- }{
- "with file extension": {
- configName: "c",
- configType: "env",
- fileName: "c.env",
- input: dotenvExample,
- },
- "without file extension": {
- configName: "c",
- configType: "env",
- fileName: "c",
- input: dotenvExample,
- },
- }
- for name, tc := range testCases {
- t.Run(name, func(t *testing.T) {
- v := New()
- v.SetFs(fs)
- v.SetConfigName(tc.configName)
- v.SetConfigType(tc.configType)
- err := v.ReadConfig(bytes.NewBuffer(tc.input))
- if err != nil {
- t.Fatal(err)
- }
- if err := v.WriteConfigAs(tc.fileName); 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(tc.configName)
- v2.SetConfigType(tc.configType)
- v2.SetConfigFile(tc.fileName)
- err = v2.ReadInConfig()
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, v.GetString("title_dotenv"), v2.GetString("title_dotenv"))
- assert.Equal(t, v.GetString("type_dotenv"), v2.GetString("type_dotenv"))
- assert.Equal(t, v.GetString("kind_dotenv"), v2.GetString("kind_dotenv"))
- })
- }
- }
- func TestSafeWriteConfig(t *testing.T) {
- v := New()
- fs := afero.NewMemMapFs()
- v.SetFs(fs)
- v.AddConfigPath("/test")
- v.SetConfigName("c")
- v.SetConfigType("yaml")
- require.NoError(t, v.ReadConfig(bytes.NewBuffer(yamlExample)))
- require.NoError(t, v.SafeWriteConfig())
- read, err := afero.ReadFile(fs, "/test/c.yaml")
- require.NoError(t, err)
- assert.Equal(t, yamlWriteExpected, read)
- }
- func TestSafeWriteConfigWithMissingConfigPath(t *testing.T) {
- v := New()
- fs := afero.NewMemMapFs()
- v.SetFs(fs)
- v.SetConfigName("c")
- v.SetConfigType("yaml")
- require.EqualError(t, v.SafeWriteConfig(), "missing configuration for 'configPath'")
- }
- func TestSafeWriteConfigWithExistingFile(t *testing.T) {
- v := New()
- fs := afero.NewMemMapFs()
- fs.Create("/test/c.yaml")
- v.SetFs(fs)
- v.AddConfigPath("/test")
- v.SetConfigName("c")
- v.SetConfigType("yaml")
- err := v.SafeWriteConfig()
- require.Error(t, err)
- _, ok := err.(ConfigFileAlreadyExistsError)
- assert.True(t, ok, "Expected ConfigFileAlreadyExistsError")
- }
- func TestSafeWriteAsConfig(t *testing.T) {
- v := New()
- fs := afero.NewMemMapFs()
- v.SetFs(fs)
- err := v.ReadConfig(bytes.NewBuffer(yamlExample))
- if err != nil {
- t.Fatal(err)
- }
- require.NoError(t, v.SafeWriteConfigAs("/test/c.yaml"))
- if _, err = afero.ReadFile(fs, "/test/c.yaml"); err != nil {
- t.Fatal(err)
- }
- }
- func TestSafeWriteConfigAsWithExistingFile(t *testing.T) {
- v := New()
- fs := afero.NewMemMapFs()
- fs.Create("/test/c.yaml")
- v.SetFs(fs)
- err := v.SafeWriteConfigAs("/test/c.yaml")
- require.Error(t, err)
- _, ok := err.(ConfigFileAlreadyExistsError)
- assert.True(t, ok, "Expected ConfigFileAlreadyExistsError")
- }
- var yamlMergeExampleTgt = []byte(`
- hello:
- pop: 37890
- largenum: 765432101234567
- num2pow63: 9223372036854775808
- world:
- - us
- - uk
- - fr
- - de
- `)
- var yamlMergeExampleSrc = []byte(`
- hello:
- pop: 45000
- largenum: 7654321001234567
- universe:
- - mw
- - ad
- ints:
- - 1
- - 2
- 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.GetInt32("hello.pop"); pop != int32(37890) {
- t.Fatalf("pop != 37890, = %d", pop)
- }
- if pop := v.GetInt64("hello.largenum"); pop != int64(765432101234567) {
- t.Fatalf("int64 largenum != 765432101234567, = %d", pop)
- }
- if pop := v.GetUint("hello.pop"); pop != 37890 {
- t.Fatalf("uint pop != 37890, = %d", pop)
- }
- if pop := v.GetUint32("hello.pop"); pop != 37890 {
- t.Fatalf("uint32 pop != 37890, = %d", pop)
- }
- if pop := v.GetUint64("hello.num2pow63"); pop != 9223372036854775808 {
- t.Fatalf("uint64 num2pow63 != 9223372036854775808, = %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.GetInt32("hello.pop"); pop != int32(45000) {
- t.Fatalf("pop != 45000, = %d", pop)
- }
- if pop := v.GetInt64("hello.largenum"); pop != int64(7654321001234567) {
- t.Fatalf("int64 largenum != 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 ints := v.GetIntSlice("hello.ints"); len(ints) != 2 {
- t.Fatalf("len(ints) != 2, = %d", len(ints))
- }
- 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 ints := v.GetIntSlice("hello.ints"); len(ints) != 2 {
- t.Fatalf("len(ints) != 2, = %d", len(ints))
- }
- if fu := v.GetString("fu"); fu != "bar" {
- t.Fatalf("fu != \"bar\", = %s", fu)
- }
- }
- func TestMergeConfigMap(t *testing.T) {
- v := New()
- v.SetConfigType("yml")
- if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil {
- t.Fatal(err)
- }
- assert := func(i int) {
- large := v.GetInt64("hello.largenum")
- pop := v.GetInt("hello.pop")
- if large != 765432101234567 {
- t.Fatal("Got large num:", large)
- }
- if pop != i {
- t.Fatal("Got pop:", pop)
- }
- }
- assert(37890)
- update := map[string]interface{}{
- "Hello": map[string]interface{}{
- "Pop": 1234,
- },
- "World": map[interface{}]interface{}{
- "Rock": 345,
- },
- }
- if err := v.MergeConfigMap(update); err != nil {
- t.Fatal(err)
- }
- if rock := v.GetInt("world.rock"); rock != 345 {
- t.Fatal("Got rock:", rock)
- }
- assert(1234)
- }
- 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 newViperWithConfigFile(t *testing.T) (*Viper, string, func()) {
- watchDir, err := ioutil.TempDir("", "")
- require.Nil(t, err)
- configFile := path.Join(watchDir, "config.yaml")
- err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0640)
- require.Nil(t, err)
- cleanup := func() {
- os.RemoveAll(watchDir)
- }
- v := New()
- v.SetConfigFile(configFile)
- err = v.ReadInConfig()
- require.Nil(t, err)
- require.Equal(t, "bar", v.Get("foo"))
- return v, configFile, cleanup
- }
- func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func()) {
- watchDir, err := ioutil.TempDir("", "")
- require.Nil(t, err)
- dataDir1 := path.Join(watchDir, "data1")
- err = os.Mkdir(dataDir1, 0777)
- require.Nil(t, err)
- realConfigFile := path.Join(dataDir1, "config.yaml")
- t.Logf("Real config file location: %s\n", realConfigFile)
- err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0640)
- require.Nil(t, err)
- cleanup := func() {
- os.RemoveAll(watchDir)
- }
- // now, symlink the tm `data1` dir to `data` in the baseDir
- os.Symlink(dataDir1, path.Join(watchDir, "data"))
- // and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml`
- configFile := path.Join(watchDir, "config.yaml")
- os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile)
- t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml"))
- // init Viper
- v := New()
- v.SetConfigFile(configFile)
- err = v.ReadInConfig()
- require.Nil(t, err)
- require.Equal(t, "bar", v.Get("foo"))
- return v, watchDir, configFile, cleanup
- }
- func TestWatchFile(t *testing.T) {
- if runtime.GOOS == "linux" {
- // TODO(bep) FIX ME
- t.Skip("Skip test on Linux ...")
- }
- t.Run("file content changed", func(t *testing.T) {
- // given a `config.yaml` file being watched
- v, configFile, cleanup := newViperWithConfigFile(t)
- defer cleanup()
- _, err := os.Stat(configFile)
- require.NoError(t, err)
- t.Logf("test config file: %s\n", configFile)
- wg := sync.WaitGroup{}
- wg.Add(1)
- v.OnConfigChange(func(in fsnotify.Event) {
- t.Logf("config file changed")
- wg.Done()
- })
- v.WatchConfig()
- // when overwriting the file and waiting for the custom change notification handler to be triggered
- err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640)
- wg.Wait()
- // then the config value should have changed
- require.Nil(t, err)
- assert.Equal(t, "baz", v.Get("foo"))
- })
- t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) {
- // skip if not executed on Linux
- if runtime.GOOS != "linux" {
- t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...")
- }
- v, watchDir, _, _ := newViperWithSymlinkedConfigFile(t)
- // defer cleanup()
- wg := sync.WaitGroup{}
- v.WatchConfig()
- v.OnConfigChange(func(in fsnotify.Event) {
- t.Logf("config file changed")
- wg.Done()
- })
- wg.Add(1)
- // when link to another `config.yaml` file
- dataDir2 := path.Join(watchDir, "data2")
- err := os.Mkdir(dataDir2, 0777)
- require.Nil(t, err)
- configFile2 := path.Join(dataDir2, "config.yaml")
- err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0640)
- require.Nil(t, err)
- // change the symlink using the `ln -sfn` command
- err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run()
- require.Nil(t, err)
- wg.Wait()
- // then
- require.Nil(t, err)
- assert.Equal(t, "baz", v.Get("foo"))
- })
- }
- func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) {
- flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
- flags.String("foo.bar", "cobra_flag", "")
- v := New()
- assert.NoError(t, v.BindPFlags(flags))
- config := &struct {
- Foo struct {
- Bar string
- }
- }{}
- assert.NoError(t, v.Unmarshal(config))
- assert.Equal(t, "cobra_flag", config.Foo.Bar)
- }
- var yamlExampleWithDot = []byte(`Hacker: true
- name: steve
- hobbies:
- - skateboarding
- - snowboarding
- - go
- clothing:
- jacket: leather
- trousers: denim
- pants:
- size: large
- age: 35
- eyes : brown
- beard: true
- emails:
- steve@hacker.com:
- created: 01/02/03
- active: true
- `)
- func TestKeyDelimiter(t *testing.T) {
- v := NewWithOptions(KeyDelimiter("::"))
- v.SetConfigType("yaml")
- r := strings.NewReader(string(yamlExampleWithDot))
- err := v.unmarshalReader(r, v.config)
- require.NoError(t, err)
- values := map[string]interface{}{
- "image": map[string]interface{}{
- "repository": "someImage",
- "tag": "1.0.0",
- },
- "ingress": map[string]interface{}{
- "annotations": map[string]interface{}{
- "traefik.frontend.rule.type": "PathPrefix",
- "traefik.ingress.kubernetes.io/ssl-redirect": "true",
- },
- },
- }
- v.SetDefault("charts::values", values)
- assert.Equal(t, "leather", v.GetString("clothing::jacket"))
- assert.Equal(t, "01/02/03", v.GetString("emails::steve@hacker.com::created"))
- type config struct {
- Charts struct {
- Values map[string]interface{}
- }
- }
- expected := config{
- Charts: struct {
- Values map[string]interface{}
- }{
- Values: values,
- },
- }
- var actual config
- assert.NoError(t, v.Unmarshal(&actual))
- assert.Equal(t, expected, actual)
- }
- 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")
- }
- }
- }
- // BenchmarkGetBoolFromMap 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")
- }
- }
- }
|