code.cestus.io/tools/fabricator@v0.4.3/pkg/ff/ffpflag/pflag_test.go (about) 1 package ffpflag_test 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "reflect" 8 "strings" 9 "testing" 10 11 "code.cestus.io/tools/fabricator/pkg/ff" 12 "code.cestus.io/tools/fabricator/pkg/ff/ffpflag" 13 "code.cestus.io/tools/fabricator/pkg/ff/fftoml" 14 "code.cestus.io/tools/fabricator/pkg/ff/ffyaml" 15 "github.com/spf13/pflag" 16 ) 17 18 type Vars struct { 19 S string 20 B bool 21 F float32 22 I []int32 23 Args []string 24 25 ParseError error 26 WantParseErrorIs error 27 WantParseErrorString string 28 } 29 30 func Pair() (*pflag.FlagSet, *Vars) { 31 fs := pflag.NewFlagSet("ffpflag_test", pflag.ContinueOnError) 32 33 var v Vars 34 fs.StringVarP(&v.S, "string", "s", "", "a string value") 35 fs.BoolVarP(&v.B, "bool", "b", false, "a bool value") 36 fs.Float32Var(&v.F, "f", 0., "a float32 value") 37 fs.Int32SliceVarP(&v.I, "int32", "i", nil, "collection of int32 (repeatable)") 38 39 return fs, &v 40 } 41 42 func Compare(want, have *Vars) error { 43 // Normalize args. 44 if want.Args == nil { 45 want.Args = []string{} 46 } 47 if have.Args == nil { 48 have.Args = []string{} 49 } 50 51 if want.WantParseErrorIs != nil || want.WantParseErrorString != "" { 52 if want.WantParseErrorIs != nil && have.ParseError == nil { 53 return fmt.Errorf("want error (%v), have none", want.WantParseErrorIs) 54 } 55 56 if want.WantParseErrorString != "" && have.ParseError == nil { 57 return fmt.Errorf("want error (%q), have none", want.WantParseErrorString) 58 } 59 60 if want.WantParseErrorIs == nil && want.WantParseErrorString == "" && have.ParseError != nil { 61 return fmt.Errorf("want clean parse, have error (%v)", have.ParseError) 62 } 63 64 if want.WantParseErrorIs != nil && have.ParseError != nil && !errors.Is(have.ParseError, want.WantParseErrorIs) { 65 return fmt.Errorf("want wrapped error (%#+v), have error (%#+v)", want.WantParseErrorIs, have.ParseError) 66 } 67 68 if want.WantParseErrorString != "" && have.ParseError != nil && !strings.Contains(have.ParseError.Error(), want.WantParseErrorString) { 69 return fmt.Errorf("want error string (%q), have error (%v)", want.WantParseErrorString, have.ParseError) 70 } 71 72 return nil 73 } else { 74 if have.ParseError != nil { 75 return fmt.Errorf("want no parse error, have error: %v", have.ParseError) 76 } 77 } 78 79 if want.S != have.S { 80 return fmt.Errorf("var S: want %q, have %q", want.S, have.S) 81 } 82 if want.B != have.B { 83 return fmt.Errorf("var B: want %v, have %v", want.B, have.B) 84 } 85 if want.F != have.F { 86 return fmt.Errorf("var F: want %v, have %v", want.F, have.F) 87 } 88 if !reflect.DeepEqual(want.I, have.I) { 89 return fmt.Errorf("var I: want %v, have %v", want.I, have.I) 90 } 91 if !reflect.DeepEqual(want.Args, have.Args) { 92 return fmt.Errorf("var Args: want %v, have %v", want.Args, have.Args) 93 } 94 95 return nil 96 } 97 98 func TestBasics(t *testing.T) { 99 t.Parallel() 100 101 for _, testcase := range []struct { 102 name string 103 env map[string]string 104 file string 105 args []string 106 opts []ff.Option 107 want Vars 108 }{ 109 { 110 name: "empty", 111 args: []string{}, 112 want: Vars{}, 113 }, 114 { 115 name: "long flags", 116 args: []string{"--string=foo", "--bool=true", "--int32=123", "--int32=456"}, 117 want: Vars{S: "foo", B: true, I: []int32{123, 456}}, 118 }, 119 { 120 name: "short and long flags", 121 args: []string{"--string=foo", "-s", "bar", "-b", "--int32=1", "-i", "2"}, 122 want: Vars{S: "bar", B: true, I: []int32{1, 2}}, 123 }, 124 { 125 name: "flags interspersed with args", 126 args: []string{"hello world", "--string=foo", "-b", "another", "argument", "-i", "1"}, 127 want: Vars{S: "foo", B: true, I: []int32{1}, Args: []string{"hello world", "another", "argument"}}, 128 }, 129 { 130 name: "args after delimiter", 131 args: []string{"--string=foo", "--", "--string=bar"}, 132 want: Vars{S: "foo", Args: []string{"--string=bar"}}, 133 }, 134 { 135 name: "config file with mixed prefixes", 136 file: "testdata/1.conf", 137 opts: []ff.Option{ff.WithConfigFileParser(ff.PlainParser)}, 138 want: Vars{S: "B", F: 1.23, I: []int32{1, 2, 3, 4}}, 139 }, 140 { 141 name: "env vars", 142 env: map[string]string{"PF_STRING": "hello", "PF_F": "0.123"}, 143 opts: []ff.Option{ff.WithEnvVarPrefix("PF")}, 144 want: Vars{S: "hello", F: 0.123}, 145 }, 146 { 147 name: "JSON config file", 148 file: "testdata/2.json", 149 opts: []ff.Option{ff.WithConfigFileParser(ff.JSONParser)}, 150 want: Vars{S: "hello", I: []int32{1, 2, 3}}, 151 }, 152 { 153 name: "TOML config file", 154 file: "testdata/3.toml", 155 opts: []ff.Option{ff.WithConfigFileParser(fftoml.Parser)}, 156 want: Vars{B: true, I: []int32{50, 100, 150, 0}}, 157 }, 158 { 159 name: "YAML config file", 160 file: "testdata/4.yaml", 161 opts: []ff.Option{ff.WithConfigFileParser(ffyaml.Parser)}, 162 want: Vars{S: "c", F: 123.4, I: []int32{10, 11, 12}}, 163 }, 164 } { 165 t.Run(testcase.name, func(t *testing.T) { 166 if testcase.file != "" { 167 testcase.opts = append(testcase.opts, ff.WithConfigFile(testcase.file)) 168 } 169 170 if len(testcase.env) > 0 { 171 for k, v := range testcase.env { 172 defer os.Setenv(k, os.Getenv(k)) 173 os.Setenv(k, v) 174 } 175 } 176 177 fs, vars := Pair() 178 vars.ParseError = ff.Parse(ffpflag.NewFlagSet(fs), testcase.args, testcase.opts...) 179 vars.Args = fs.Args() 180 if err := Compare(&testcase.want, vars); err != nil { 181 t.Fatal(err) 182 } 183 }) 184 } 185 }