github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/libs/cli/setup_test.go (about) 1 package cli 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "strconv" 11 "strings" 12 "testing" 13 14 "github.com/spf13/cobra" 15 "github.com/spf13/viper" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func TestSetupEnv(t *testing.T) { 21 ctx, cancel := context.WithCancel(context.Background()) 22 defer cancel() 23 24 cases := []struct { 25 args []string 26 env map[string]string 27 expected string 28 }{ 29 {nil, nil, ""}, 30 {[]string{"--foobar", "bang!"}, nil, "bang!"}, 31 // make sure reset is good 32 {nil, nil, ""}, 33 // test both variants of the prefix 34 {nil, map[string]string{"DEMO_FOOBAR": "good"}, "good"}, 35 {nil, map[string]string{"DEMOFOOBAR": "silly"}, "silly"}, 36 // and that cli overrides env... 37 {[]string{"--foobar", "important"}, 38 map[string]string{"DEMO_FOOBAR": "ignored"}, "important"}, 39 } 40 41 for idx, tc := range cases { 42 i := strconv.Itoa(idx) 43 // test command that store value of foobar in local variable 44 var foo string 45 demo := &cobra.Command{ 46 Use: "demo", 47 RunE: func(cmd *cobra.Command, args []string) error { 48 foo = viper.GetString("foobar") 49 return nil 50 }, 51 } 52 demo.Flags().String("foobar", "", "Some test value from config") 53 cmd := PrepareBaseCmd(demo, "DEMO", "/qwerty/asdfgh") // some missing dir.. 54 55 viper.Reset() 56 args := append([]string{cmd.Use}, tc.args...) 57 err := RunWithArgs(ctx, cmd, args, tc.env) 58 require.NoError(t, err, i) 59 assert.Equal(t, tc.expected, foo, i) 60 } 61 } 62 63 // writeConfigVals writes a toml file with the given values. 64 // It returns an error if writing was impossible. 65 func writeConfigVals(dir string, vals map[string]string) error { 66 lines := make([]string, 0, len(vals)) 67 for k, v := range vals { 68 lines = append(lines, fmt.Sprintf("%s = %q", k, v)) 69 } 70 data := strings.Join(lines, "\n") 71 cfile := filepath.Join(dir, "config.toml") 72 return os.WriteFile(cfile, []byte(data), 0600) 73 } 74 75 func TestSetupConfig(t *testing.T) { 76 ctx, cancel := context.WithCancel(context.Background()) 77 defer cancel() 78 79 // we pre-create two config files we can refer to in the rest of 80 // the test cases. 81 cval1 := "fubble" 82 conf1 := t.TempDir() 83 err := writeConfigVals(conf1, map[string]string{"boo": cval1}) 84 require.NoError(t, err) 85 86 cases := []struct { 87 args []string 88 env map[string]string 89 expected string 90 expectedTwo string 91 }{ 92 {nil, nil, "", ""}, 93 // setting on the command line 94 {[]string{"--boo", "haha"}, nil, "haha", ""}, 95 {[]string{"--two-words", "rocks"}, nil, "", "rocks"}, 96 {[]string{"--home", conf1}, nil, cval1, ""}, 97 // test both variants of the prefix 98 {nil, map[string]string{"RD_BOO": "bang"}, "bang", ""}, 99 {nil, map[string]string{"RD_TWO_WORDS": "fly"}, "", "fly"}, 100 {nil, map[string]string{"RDTWO_WORDS": "fly"}, "", "fly"}, 101 {nil, map[string]string{"RD_HOME": conf1}, cval1, ""}, 102 {nil, map[string]string{"RDHOME": conf1}, cval1, ""}, 103 } 104 105 for idx, tc := range cases { 106 i := strconv.Itoa(idx) 107 // test command that store value of foobar in local variable 108 var foo, two string 109 boo := &cobra.Command{ 110 Use: "reader", 111 RunE: func(cmd *cobra.Command, args []string) error { 112 foo = viper.GetString("boo") 113 two = viper.GetString("two-words") 114 return nil 115 }, 116 } 117 boo.Flags().String("boo", "", "Some test value from config") 118 boo.Flags().String("two-words", "", "Check out env handling -") 119 cmd := PrepareBaseCmd(boo, "RD", "/qwerty/asdfgh") // some missing dir... 120 121 viper.Reset() 122 args := append([]string{cmd.Use}, tc.args...) 123 err := RunWithArgs(ctx, cmd, args, tc.env) 124 require.NoError(t, err, i) 125 assert.Equal(t, tc.expected, foo, i) 126 assert.Equal(t, tc.expectedTwo, two, i) 127 } 128 } 129 130 type DemoConfig struct { 131 Name string `mapstructure:"name"` 132 Age int `mapstructure:"age"` 133 Unused int `mapstructure:"unused"` 134 } 135 136 func TestSetupUnmarshal(t *testing.T) { 137 ctx, cancel := context.WithCancel(context.Background()) 138 defer cancel() 139 140 // we pre-create two config files we can refer to in the rest of 141 // the test cases. 142 cval1, cval2 := "someone", "else" 143 conf1 := t.TempDir() 144 err := writeConfigVals(conf1, map[string]string{"name": cval1}) 145 require.NoError(t, err) 146 // even with some ignored fields, should be no problem 147 conf2 := t.TempDir() 148 err = writeConfigVals(conf2, map[string]string{"name": cval2, "foo": "bar"}) 149 require.NoError(t, err) 150 151 // unused is not declared on a flag and remains from base 152 base := DemoConfig{ 153 Name: "default", 154 Age: 42, 155 Unused: -7, 156 } 157 c := func(name string, age int) DemoConfig { 158 r := base 159 // anything set on the flags as a default is used over 160 // the default config object 161 r.Name = "from-flag" 162 if name != "" { 163 r.Name = name 164 } 165 if age != 0 { 166 r.Age = age 167 } 168 return r 169 } 170 171 cases := []struct { 172 args []string 173 env map[string]string 174 expected DemoConfig 175 }{ 176 {nil, nil, c("", 0)}, 177 // setting on the command line 178 {[]string{"--name", "haha"}, nil, c("haha", 0)}, 179 {[]string{"--home", conf1}, nil, c(cval1, 0)}, 180 // test both variants of the prefix 181 {nil, map[string]string{"MR_AGE": "56"}, c("", 56)}, 182 {nil, map[string]string{"MR_HOME": conf1}, c(cval1, 0)}, 183 {[]string{"--age", "17"}, map[string]string{"MRHOME": conf2}, c(cval2, 17)}, 184 } 185 186 for idx, tc := range cases { 187 i := strconv.Itoa(idx) 188 // test command that store value of foobar in local variable 189 cfg := base 190 marsh := &cobra.Command{ 191 Use: "marsh", 192 RunE: func(cmd *cobra.Command, args []string) error { 193 return viper.Unmarshal(&cfg) 194 }, 195 } 196 marsh.Flags().String("name", "from-flag", "Some test value from config") 197 // if we want a flag to use the proper default, then copy it 198 // from the default config here 199 marsh.Flags().Int("age", base.Age, "Some test value from config") 200 cmd := PrepareBaseCmd(marsh, "MR", "/qwerty/asdfgh") // some missing dir... 201 202 viper.Reset() 203 args := append([]string{cmd.Use}, tc.args...) 204 err := RunWithArgs(ctx, cmd, args, tc.env) 205 require.NoError(t, err, i) 206 assert.Equal(t, tc.expected, cfg, i) 207 } 208 } 209 210 func TestSetupTrace(t *testing.T) { 211 ctx, cancel := context.WithCancel(context.Background()) 212 defer cancel() 213 214 cases := []struct { 215 args []string 216 env map[string]string 217 long bool 218 expected string 219 }{ 220 {nil, nil, false, "trace flag = false"}, 221 {[]string{"--trace"}, nil, true, "trace flag = true"}, 222 {[]string{"--no-such-flag"}, nil, false, "unknown flag: --no-such-flag"}, 223 {nil, map[string]string{"DBG_TRACE": "true"}, true, "trace flag = true"}, 224 } 225 226 for idx, tc := range cases { 227 i := strconv.Itoa(idx) 228 // test command that store value of foobar in local variable 229 trace := &cobra.Command{ 230 Use: "trace", 231 RunE: func(cmd *cobra.Command, args []string) error { 232 return fmt.Errorf("trace flag = %t", viper.GetBool(TraceFlag)) 233 }, 234 } 235 cmd := PrepareBaseCmd(trace, "DBG", "/qwerty/asdfgh") // some missing dir.. 236 237 viper.Reset() 238 args := append([]string{cmd.Use}, tc.args...) 239 stdout, stderr, err := runCaptureWithArgs(ctx, cmd, args, tc.env) 240 require.Error(t, err, i) 241 require.Equal(t, "", stdout, i) 242 require.NotEqual(t, "", stderr, i) 243 msg := strings.Split(stderr, "\n") 244 desired := fmt.Sprintf("ERROR: %s", tc.expected) 245 assert.Equal(t, desired, msg[0], i, msg) 246 if tc.long && assert.True(t, len(msg) > 2, i) { 247 // the next line starts the stack trace... 248 assert.Contains(t, stderr, "TestSetupTrace", i) 249 assert.Contains(t, stderr, "setup_test.go", i) 250 } 251 } 252 } 253 254 // runCaptureWithArgs executes the given command with the specified command 255 // line args and environmental variables set. It returns string fields 256 // representing output written to stdout and stderr, additionally any error 257 // from cmd.Execute() is also returned 258 func runCaptureWithArgs(ctx context.Context, cmd *cobra.Command, args []string, env map[string]string) (stdout, stderr string, err error) { 259 oldout, olderr := os.Stdout, os.Stderr // keep backup of the real stdout 260 rOut, wOut, _ := os.Pipe() 261 rErr, wErr, _ := os.Pipe() 262 os.Stdout, os.Stderr = wOut, wErr 263 defer func() { 264 os.Stdout, os.Stderr = oldout, olderr // restoring the real stdout 265 }() 266 267 // copy the output in a separate goroutine so printing can't block indefinitely 268 copyStd := func(reader *os.File) *(chan string) { 269 stdC := make(chan string) 270 go func() { 271 var buf bytes.Buffer 272 // io.Copy will end when we call reader.Close() below 273 io.Copy(&buf, reader) //nolint:errcheck //ignore error 274 select { 275 case <-cmd.Context().Done(): 276 case stdC <- buf.String(): 277 } 278 }() 279 return &stdC 280 } 281 outC := copyStd(rOut) 282 errC := copyStd(rErr) 283 284 // now run the command 285 err = RunWithArgs(ctx, cmd, args, env) 286 287 // and grab the stdout to return 288 wOut.Close() 289 wErr.Close() 290 stdout = <-*outC 291 stderr = <-*errC 292 return stdout, stderr, err 293 }