github.com/grafana/pyroscope@v1.18.0/pkg/cfg/cfg_test.go (about) 1 package cfg 2 3 import ( 4 "flag" 5 "os" 6 "testing" 7 "time" 8 9 "github.com/grafana/dskit/flagext" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 ) 13 14 func TestParse(t *testing.T) { 15 yamlSource := dYAML([]byte(` 16 server: 17 port: 2000 18 timeout: 60h 19 tls: 20 key: YAML 21 `), true) 22 23 fs := flag.NewFlagSet(t.Name(), flag.PanicOnError) 24 flagSource := dFlags(fs, []string{"-verbose", "-server.port=21"}) 25 26 data := Data{} 27 err := Unmarshal(&data, 28 Defaults(fs), 29 yamlSource, 30 flagSource, 31 ) 32 require.NoError(t, err) 33 34 assert.Equal(t, Data{ 35 Verbose: true, // flag 36 Server: Server{ 37 Port: 21, // flag 38 Timeout: 60 * time.Hour, // defaults 39 }, 40 TLS: TLS{ 41 Cert: "DEFAULTCERT", // defaults 42 Key: "YAML", // yaml 43 }, 44 }, data) 45 } 46 47 func TestParseWithInvalidYAML(t *testing.T) { 48 yamlSource := dYAML([]byte(` 49 servers: 50 ports: 2000 51 timeoutz: 60h 52 tls: 53 keey: YAML 54 `), true) 55 56 fs := flag.NewFlagSet(t.Name(), flag.PanicOnError) 57 flagSource := dFlags(fs, []string{"-verbose", "-server.port=21"}) 58 59 data := Data{} 60 err := Unmarshal(&data, 61 Defaults(fs), 62 yamlSource, 63 flagSource, 64 ) 65 require.Error(t, err) 66 require.Equal(t, err.Error(), "yaml: unmarshal errors:\n line 2: field servers not found in type cfg.Data\n line 6: field keey not found in type cfg.TLS") 67 } 68 69 func TestDefaultUnmarshal(t *testing.T) { 70 testContext := func(yamlString string, args []string) TestConfigWrapper { 71 file, err := os.CreateTemp("", "config.yaml") 72 defer func() { 73 os.Remove(file.Name()) 74 }() 75 require.NoError(t, err) 76 77 _, err = file.WriteString(yamlString) 78 require.NoError(t, err) 79 80 configFileArgs := []string{"-config.file", file.Name()} 81 if args == nil { 82 args = configFileArgs 83 } else { 84 args = append(args, configFileArgs...) 85 } 86 87 var config TestConfigWrapper 88 flags := flag.NewFlagSet(t.Name(), flag.PanicOnError) 89 err = DefaultUnmarshal(&config, args, flags) 90 require.NoError(t, err) 91 92 return config 93 } 94 t.Run("with an empty config file and no command line args, defaults are used", func(t *testing.T) { 95 configFileString := `--- 96 required: foo` 97 config := testContext(configFileString, nil) 98 99 assert.Equal(t, "Jerry", config.Name) 100 assert.Equal(t, true, config.Role.Sings) 101 assert.Equal(t, "guitar", config.Role.Instrument) 102 }) 103 104 t.Run("values provided in config file take precedence over defaults", func(t *testing.T) { 105 configFileString := `--- 106 required: foo 107 name: Phil` 108 config := testContext(configFileString, nil) 109 110 assert.Equal(t, "Phil", config.Name) 111 assert.Equal(t, true, config.Role.Sings) 112 }) 113 114 t.Run("partial structs can be provided in the config file, with defaults filling zeros", func(t *testing.T) { 115 configFileString := `--- 116 required: foo 117 name: Phil 118 role: 119 instrument: bass` 120 config := testContext(configFileString, nil) 121 122 assert.Equal(t, "Phil", config.Name) 123 assert.Equal(t, "bass", config.Role.Instrument) 124 //zero value overridden by default 125 assert.Equal(t, true, config.Role.Sings) 126 }) 127 128 t.Run("values can be explicitly zeroed out in config file", func(t *testing.T) { 129 configFileString := `--- 130 required: foo 131 name: Mickey 132 role: 133 sings: false 134 instrument: drums` 135 config := testContext(configFileString, nil) 136 137 assert.Equal(t, "Mickey", config.Name) 138 assert.Equal(t, "drums", config.Role.Instrument) 139 assert.Equal(t, false, config.Role.Sings) 140 }) 141 142 t.Run("values passed by command line take precedence", func(t *testing.T) { 143 configFileString := `--- 144 name: Mickey 145 role: 146 sings: false` 147 148 args := []string{"-name", "Bob", "-role.sings=true", "-role.instrument", "piano"} 149 config := testContext(configFileString, args) 150 151 assert.Equal(t, "Bob", config.Name) 152 assert.Equal(t, true, config.Role.Sings) 153 assert.Equal(t, "piano", config.Role.Instrument) 154 }) 155 } 156 157 type TestConfigWrapper struct { 158 TestConfig `yaml:",inline"` 159 ConfigFile string 160 } 161 162 func (c *TestConfigWrapper) RegisterFlags(f *flag.FlagSet) { 163 f.StringVar(&c.ConfigFile, "config.file", "", "yaml file to load") 164 c.TestConfig.RegisterFlags(f) 165 } 166 167 func (c *TestConfigWrapper) Clone() flagext.Registerer { 168 return func(c TestConfigWrapper) *TestConfigWrapper { 169 return &c 170 }(*c) 171 } 172 173 type TestConfig struct { 174 //Add a parameter that will always be there, as the yaml parser exhibits 175 //weird behavior when a config file is completely empty 176 Required string `yaml:"required"` 177 Name string `yaml:"name"` 178 Role Role `yaml:"role"` 179 } 180 181 func (c *TestConfig) RegisterFlags(f *flag.FlagSet) { 182 f.StringVar(&c.Name, "name", "Jerry", "Favorite band member") 183 c.Role.RegisterFlags(f) 184 } 185 186 type Role struct { 187 Sings bool `yaml:"sings"` 188 Instrument string `yaml:"instrument"` 189 } 190 191 func (c *Role) RegisterFlags(f *flag.FlagSet) { 192 f.BoolVar(&c.Sings, "role.sings", true, "Do they sing?") 193 f.StringVar(&c.Instrument, "role.instrument", "guitar", "What instrument do they play?") 194 }