1package kong_test 2 3import ( 4 "errors" 5 "os" 6 "reflect" 7 "strings" 8 "testing" 9 10 "github.com/stretchr/testify/require" 11 12 "github.com/alecthomas/kong" 13) 14 15type envMap map[string]string 16 17func tempEnv(env envMap) func() { 18 for k, v := range env { 19 os.Setenv(k, v) 20 } 21 22 return func() { 23 for k := range env { 24 os.Unsetenv(k) 25 } 26 } 27} 28 29func newEnvParser(t *testing.T, cli interface{}, env envMap) (*kong.Kong, func()) { 30 t.Helper() 31 restoreEnv := tempEnv(env) 32 parser := mustNew(t, cli) 33 return parser, restoreEnv 34} 35 36func TestEnvarsFlagBasic(t *testing.T) { 37 var cli struct { 38 String string `env:"KONG_STRING"` 39 Slice []int `env:"KONG_SLICE"` 40 } 41 parser, unsetEnvs := newEnvParser(t, &cli, envMap{ 42 "KONG_STRING": "bye", 43 "KONG_SLICE": "5,2,9", 44 }) 45 defer unsetEnvs() 46 47 _, err := parser.Parse([]string{}) 48 require.NoError(t, err) 49 require.Equal(t, "bye", cli.String) 50 require.Equal(t, []int{5, 2, 9}, cli.Slice) 51} 52 53func TestEnvarsFlagOverride(t *testing.T) { 54 var cli struct { 55 Flag string `env:"KONG_FLAG"` 56 } 57 parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_FLAG": "bye"}) 58 defer restoreEnv() 59 60 _, err := parser.Parse([]string{"--flag=hello"}) 61 require.NoError(t, err) 62 require.Equal(t, "hello", cli.Flag) 63} 64 65func TestEnvarsTag(t *testing.T) { 66 var cli struct { 67 Slice []int `env:"KONG_NUMBERS"` 68 } 69 parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_NUMBERS": "5,2,9"}) 70 defer restoreEnv() 71 72 _, err := parser.Parse([]string{}) 73 require.NoError(t, err) 74 require.Equal(t, []int{5, 2, 9}, cli.Slice) 75} 76 77func TestEnvarsWithDefault(t *testing.T) { 78 var cli struct { 79 Flag string `env:"KONG_FLAG" default:"default"` 80 } 81 parser, restoreEnv := newEnvParser(t, &cli, envMap{}) 82 defer restoreEnv() 83 84 _, err := parser.Parse(nil) 85 require.NoError(t, err) 86 require.Equal(t, "default", cli.Flag) 87 88 parser, restoreEnv = newEnvParser(t, &cli, envMap{"KONG_FLAG": "moo"}) 89 defer restoreEnv() 90 _, err = parser.Parse(nil) 91 require.NoError(t, err) 92 require.Equal(t, "moo", cli.Flag) 93} 94 95func TestJSONBasic(t *testing.T) { 96 var cli struct { 97 String string 98 Slice []int 99 SliceWithCommas []string 100 Bool bool 101 } 102 103 json := `{ 104 "string": "", 105 "slice": [5, 8], 106 "bool": true, 107 "slice_with_commas": ["a,b", "c"] 108 }` 109 110 r, err := kong.JSON(strings.NewReader(json)) 111 require.NoError(t, err) 112 113 parser := mustNew(t, &cli, kong.Resolvers(r)) 114 _, err = parser.Parse([]string{}) 115 require.NoError(t, err) 116 require.Equal(t, "", cli.String) 117 require.Equal(t, []int{5, 8}, cli.Slice) 118 require.Equal(t, []string{"a,b", "c"}, cli.SliceWithCommas) 119 require.True(t, cli.Bool) 120} 121 122type testUppercaseMapper struct{} 123 124func (testUppercaseMapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error { 125 var value string 126 err := ctx.Scan.PopValueInto("lowercase", &value) 127 if err != nil { 128 return err 129 } 130 target.SetString(strings.ToUpper(value)) 131 return nil 132} 133 134func TestResolversWithMappers(t *testing.T) { 135 var cli struct { 136 Flag string `env:"KONG_MOO" type:"upper"` 137 } 138 139 restoreEnv := tempEnv(envMap{"KONG_MOO": "meow"}) 140 defer restoreEnv() 141 142 parser := mustNew(t, &cli, 143 kong.NamedMapper("upper", testUppercaseMapper{}), 144 ) 145 _, err := parser.Parse([]string{}) 146 require.NoError(t, err) 147 require.Equal(t, "MEOW", cli.Flag) 148} 149 150func TestResolverWithBool(t *testing.T) { 151 var cli struct { 152 Bool bool 153 } 154 155 var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) { 156 if flag.Name == "bool" { 157 return true, nil 158 } 159 return nil, nil 160 } 161 162 p := mustNew(t, &cli, kong.Resolvers(resolver)) 163 164 _, err := p.Parse(nil) 165 require.NoError(t, err) 166 require.True(t, cli.Bool) 167} 168 169func TestLastResolverWins(t *testing.T) { 170 var cli struct { 171 Int []int 172 } 173 174 var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) { 175 if flag.Name == "int" { 176 return 1, nil 177 } 178 return nil, nil 179 } 180 181 var second kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) { 182 if flag.Name == "int" { 183 return 2, nil 184 } 185 return nil, nil 186 } 187 188 p := mustNew(t, &cli, kong.Resolvers(first, second)) 189 _, err := p.Parse(nil) 190 require.NoError(t, err) 191 require.Equal(t, []int{2}, cli.Int) 192} 193 194func TestResolverSatisfiesRequired(t *testing.T) { 195 // nolint: govet 196 var cli struct { 197 Int int `required` 198 } 199 var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) { 200 if flag.Name == "int" { 201 return 1, nil 202 } 203 return nil, nil 204 } 205 _, err := mustNew(t, &cli, kong.Resolvers(resolver)).Parse(nil) 206 require.NoError(t, err) 207 require.Equal(t, 1, cli.Int) 208} 209 210func TestResolverTriggersHooks(t *testing.T) { 211 ctx := &hookContext{} 212 213 var cli struct { 214 Flag hookValue 215 } 216 217 var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) { 218 if flag.Name == "flag" { 219 return "one", nil 220 } 221 return nil, nil 222 } 223 224 _, err := mustNew(t, &cli, kong.Bind(ctx), kong.Resolvers(first)).Parse(nil) 225 require.NoError(t, err) 226 227 require.Equal(t, "one", string(cli.Flag)) 228 require.Equal(t, []string{"before:", "after:one"}, ctx.values) 229} 230 231type validatingResolver struct { 232 err error 233} 234 235func (v *validatingResolver) Validate(app *kong.Application) error { return v.err } 236func (v *validatingResolver) Resolve(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) { 237 return nil, nil 238} 239 240func TestValidatingResolverErrors(t *testing.T) { 241 resolver := &validatingResolver{err: errors.New("invalid")} 242 var cli struct{} 243 _, err := mustNew(t, &cli, kong.Resolvers(resolver)).Parse(nil) 244 require.EqualError(t, err, "invalid") 245} 246