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  }