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  })