github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/cli/command_test.go (about) 1 package cli 2 3 import ( 4 "errors" 5 "os" 6 "time" 7 8 . "github.com/onsi/ginkgo/v2" 9 . "github.com/onsi/gomega" 10 "github.com/spf13/cobra" 11 12 "github.com/pyroscope-io/pyroscope/pkg/util/bytesize" 13 ) 14 15 type SubStruct struct { 16 Bar string `mapstructure:"bar" def:"def-value"` 17 } 18 19 type TestConfig struct { 20 // regular field 21 Foo string `mapstructure:"foo" def:"def-value"` 22 // nested field 23 FooStruct SubStruct `mapstructure:"foo-struct"` 24 // config file path 25 Config string `mapstructure:"config" def:"testdata/test-config.yml"` 26 // lists 27 Foos []string `mapstructure:"foos" def:"def-1,def-2"` 28 // other types 29 Bar int `mapstructure:"bar"` 30 Baz time.Duration `mapstructure:"baz"` 31 FooBytes bytesize.ByteSize `mapstructure:"foo-bytes"` 32 FooDur time.Duration `mapstructure:"foo-dur"` 33 } 34 35 type testConfigFileDoesNotExist struct { 36 Foo string `mapstructure:"foo" def:"def-value"` 37 Config string `mapstructure:"config" def:"testdata/doesntexist"` 38 } 39 40 func run(cfg interface{}, args []string, env map[string]string, cb func(interface{})) (bool, error) { 41 prevValues := make(map[string]string) 42 for k, v := range env { 43 prevValues[k] = os.Getenv(k) 44 os.Setenv(k, v) 45 } 46 defer func() { 47 for k := range env { 48 os.Setenv(k, prevValues[k]) 49 } 50 }() 51 52 ran := false 53 vpr := NewViper("PYROSCOPE") 54 cmd := &cobra.Command{ 55 RunE: CreateCmdRunFn(cfg, vpr, func(cmd *cobra.Command, args []string) error { 56 cb(cfg) 57 ran = true 58 return nil 59 }), 60 } 61 cmd.SetArgs(args) 62 63 PopulateFlagSet(cfg, cmd.Flags(), vpr) 64 65 err := cmd.Execute() 66 return ran, err 67 } 68 69 func runTest(args []string, env map[string]string, cb func(*TestConfig)) { 70 cfg := new(TestConfig) 71 ran, err := run(cfg, args, env, func(v interface{}) { 72 cb(v.(*TestConfig)) 73 }) 74 75 Expect(err).ToNot(HaveOccurred()) 76 Expect(ran).To(BeTrue()) 77 } 78 79 func runErrorTest(args []string, env map[string]string, cb func(err error)) { 80 cfg := new(TestConfig) 81 ran, err := run(cfg, args, env, func(v interface{}) {}) 82 83 Expect(ran).ToNot(BeTrue()) 84 cb(err) 85 } 86 87 // runTest([]string{"--foo arg-value"}, map[string]string{}, func(cfg *TestConfig) { 88 // Expect(cfg.Foo).To(Equal("arg-value")) 89 // }) 90 // runTest([]string{"-foo=arg-value"}, map[string]string{}, func(cfg *TestConfig) { 91 // Expect(cfg.Foo).To(Equal("arg-value")) 92 // }) 93 // runTest([]string{"--foo=arg-value"}, map[string]string{}, func(cfg *TestConfig) { 94 // Expect(cfg.Foo).To(Equal("arg-value")) 95 // }) 96 97 var _ = Describe("CreateCmdRunFn", func() { 98 Context("config file", func() { 99 Context("config file is set via an argument", func() { 100 It("sets value from config file", func() { 101 runTest([]string{"--config", "testdata/clitest.yml"}, map[string]string{}, func(cfg *TestConfig) { 102 Expect(cfg.Foo).To(Equal("config-value")) 103 }) 104 }) 105 }) 106 Context("config file is set via an argument", func() { 107 It("sets value from config file", func() { 108 runTest([]string{}, map[string]string{"PYROSCOPE_CONFIG": "testdata/clitest.yml"}, func(cfg *TestConfig) { 109 Expect(cfg.Foo).To(Equal("config-value")) 110 }) 111 }) 112 }) 113 Context("user config file that doesn't exist", func() { 114 It("returns error", func() { 115 runErrorTest(nil, map[string]string{"PYROSCOPE_CONFIG": "testdata/doesntexist"}, func(err error) { 116 Expect(err).To(HaveOccurred()) 117 Expect(errors.Is(err, os.ErrNotExist)).To(BeTrue()) 118 }) 119 }) 120 }) 121 Context("default config file that doesn't exist", func() { 122 It("does not return errors and sets values from config file", func() { 123 cfg := new(testConfigFileDoesNotExist) 124 ran, err := run(cfg, nil, nil, func(v interface{}) { 125 Expect(v.(*testConfigFileDoesNotExist).Foo).To(Equal("def-value")) 126 }) 127 Expect(err).ToNot(HaveOccurred()) 128 Expect(ran).To(BeTrue()) 129 }) 130 }) 131 Context("config file that doesn't have yaml extension", func() { 132 It("sets value from config file", func() { 133 runTest([]string{}, map[string]string{"PYROSCOPE_CONFIG": "testdata/clitest.non-yml-extension"}, func(cfg *TestConfig) { 134 Expect(cfg.Foo).To(Equal("config-value")) 135 }) 136 }) 137 }) 138 }) 139 Context("configuration sources", func() { 140 Context("with no arguments or env variables or user config", func() { 141 It("sets default value provided via `def` value tag", func() { 142 runTest([]string{}, map[string]string{}, func(cfg *TestConfig) { 143 Expect(cfg.FooStruct.Bar).To(Equal("def-value")) 144 }) 145 }) 146 It("reads values from default config file", func() { 147 runTest([]string{}, map[string]string{}, func(cfg *TestConfig) { 148 Expect(cfg.Foo).To(Equal("value-from-default-config-file")) 149 }) 150 }) 151 }) 152 153 Context("when arguments provided", func() { 154 It("sets value from argument", func() { 155 runTest([]string{"--foo", "arg-value"}, map[string]string{}, func(cfg *TestConfig) { 156 Expect(cfg.Foo).To(Equal("arg-value")) 157 }) 158 }) 159 }) 160 161 Context("when env variables provided", func() { 162 It("sets value from env variable", func() { 163 runTest([]string{""}, map[string]string{"PYROSCOPE_FOO": "env-value"}, func(cfg *TestConfig) { 164 Expect(cfg.Foo).To(Equal("env-value")) 165 }) 166 }) 167 }) 168 169 Context("when config file is provided", func() { 170 It("sets value from config file", func() { 171 runTest([]string{"--config", "testdata/clitest.yml"}, map[string]string{}, func(cfg *TestConfig) { 172 Expect(cfg.Foo).To(Equal("config-value")) 173 }) 174 }) 175 }) 176 177 Context("config precendence", func() { 178 It("arguments are most important", func() { 179 runTest([]string{"--config", "testdata/clitest.yml", "--foo", "arg-value"}, map[string]string{"PYROSCOPE_FOO": "env-value"}, func(cfg *TestConfig) { 180 Expect(cfg.Foo).To(Equal("arg-value")) 181 }) 182 }) 183 It("env variables are second most important", func() { 184 runTest([]string{"--config", "testdata/clitest.yml"}, map[string]string{"PYROSCOPE_FOO": "env-value"}, func(cfg *TestConfig) { 185 Expect(cfg.Foo).To(Equal("env-value")) 186 }) 187 }) 188 }) 189 }) 190 191 Context("substructs", func() { 192 Context("with no arguments or env variables or config", func() { 193 It("sets default value provided via `def` value tag", func() { 194 runTest([]string{}, map[string]string{}, func(cfg *TestConfig) { 195 Expect(cfg.FooStruct.Bar).To(Equal("def-value")) 196 }) 197 }) 198 }) 199 200 Context("when arguments provided", func() { 201 It("sets value from argument", func() { 202 runTest([]string{"--foo-struct.bar", "arg-value"}, map[string]string{}, func(cfg *TestConfig) { 203 Expect(cfg.FooStruct.Bar).To(Equal("arg-value")) 204 }) 205 }) 206 }) 207 208 Context("when env variables provided", func() { 209 It("sets value from env variable", func() { 210 runTest([]string{""}, map[string]string{"PYROSCOPE_FOO_STRUCT_BAR": "env-value"}, func(cfg *TestConfig) { 211 Expect(cfg.FooStruct.Bar).To(Equal("env-value")) 212 }) 213 }) 214 }) 215 216 Context("when config file is provided", func() { 217 It("sets value from config file", func() { 218 runTest([]string{"--config", "testdata/clitest.yml"}, map[string]string{}, func(cfg *TestConfig) { 219 Expect(cfg.FooStruct.Bar).To(Equal("config-value")) 220 }) 221 }) 222 }) 223 }) 224 225 Context("lists", func() { 226 Context("when arguments provided", func() { 227 Context("with no arguments or env variables or config", func() { 228 // TODO: support default values 229 // It("sets default value provided via `def` value tag", func() { 230 // runTest([]string{}, map[string]string{}, func(cfg *TestConfig) { 231 // Expect(cfg.Foos).To(Equal([]string{"def-1", "def-2"})) 232 // }) 233 // }) 234 }) 235 236 Context("when arguments provided", func() { 237 It("sets value from argument", func() { 238 runTest([]string{"--foos", "arg-1,arg-2"}, map[string]string{}, func(cfg *TestConfig) { 239 Expect(cfg.Foos).To(Equal([]string{"arg-1", "arg-2"})) 240 }) 241 }) 242 }) 243 244 Context("when env variables provided", func() { 245 It("sets value from env variable", func() { 246 runTest([]string{""}, map[string]string{"PYROSCOPE_FOOS": "env-1,env-2"}, func(cfg *TestConfig) { 247 Expect(cfg.Foos).To(Equal([]string{"env-1", "env-2"})) 248 }) 249 }) 250 }) 251 252 Context("when config file is provided", func() { 253 It("sets value from config file", func() { 254 runTest([]string{"--config", "testdata/clitest.yml"}, map[string]string{}, func(cfg *TestConfig) { 255 Expect(cfg.Foos).To(Equal([]string{"config-1", "config-2"})) 256 }) 257 }) 258 }) 259 }) 260 }) 261 })