1package kingpin 2 3import ( 4 "sort" 5 "strings" 6 7 "github.com/stretchr/testify/assert" 8 9 "testing" 10) 11 12func parseAndExecute(app *Application, context *ParseContext) (string, error) { 13 if err := parse(context, app); err != nil { 14 return "", err 15 } 16 17 selected, err := app.setValues(context) 18 if err != nil { 19 return "", err 20 } 21 22 return app.execute(context, selected) 23} 24 25func complete(t *testing.T, app *Application, args ...string) []string { 26 context, err := app.ParseContext(args) 27 assert.NoError(t, err) 28 if err != nil { 29 return nil 30 } 31 32 completions := app.completionOptions(context) 33 sort.Strings(completions) 34 35 return completions 36} 37 38func TestNestedCommands(t *testing.T) { 39 app := New("app", "") 40 sub1 := app.Command("sub1", "") 41 sub1.Flag("sub1", "") 42 subsub1 := sub1.Command("sub1sub1", "") 43 subsub1.Command("sub1sub1end", "") 44 45 sub2 := app.Command("sub2", "") 46 sub2.Flag("sub2", "") 47 sub2.Command("sub2sub1", "") 48 49 context := tokenize([]string{"sub1", "sub1sub1", "sub1sub1end"}, false) 50 selected, err := parseAndExecute(app, context) 51 assert.NoError(t, err) 52 assert.True(t, context.EOL()) 53 assert.Equal(t, "sub1 sub1sub1 sub1sub1end", selected) 54} 55 56func TestNestedCommandsWithArgs(t *testing.T) { 57 app := New("app", "") 58 cmd := app.Command("a", "").Command("b", "") 59 a := cmd.Arg("a", "").String() 60 b := cmd.Arg("b", "").String() 61 context := tokenize([]string{"a", "b", "c", "d"}, false) 62 selected, err := parseAndExecute(app, context) 63 assert.NoError(t, err) 64 assert.True(t, context.EOL()) 65 assert.Equal(t, "a b", selected) 66 assert.Equal(t, "c", *a) 67 assert.Equal(t, "d", *b) 68} 69 70func TestNestedCommandsWithFlags(t *testing.T) { 71 app := New("app", "") 72 cmd := app.Command("a", "").Command("b", "") 73 a := cmd.Flag("aaa", "").Short('a').String() 74 b := cmd.Flag("bbb", "").Short('b').String() 75 err := app.init() 76 assert.NoError(t, err) 77 context := tokenize(strings.Split("a b --aaa x -b x", " "), false) 78 selected, err := parseAndExecute(app, context) 79 assert.NoError(t, err) 80 assert.True(t, context.EOL()) 81 assert.Equal(t, "a b", selected) 82 assert.Equal(t, "x", *a) 83 assert.Equal(t, "x", *b) 84} 85 86func TestNestedCommandWithMergedFlags(t *testing.T) { 87 app := New("app", "") 88 cmd0 := app.Command("a", "") 89 cmd0f0 := cmd0.Flag("aflag", "").Bool() 90 // cmd1 := app.Command("b", "") 91 // cmd1f0 := cmd0.Flag("bflag", "").Bool() 92 cmd00 := cmd0.Command("aa", "") 93 cmd00f0 := cmd00.Flag("aaflag", "").Bool() 94 err := app.init() 95 assert.NoError(t, err) 96 context := tokenize(strings.Split("a aa --aflag --aaflag", " "), false) 97 selected, err := parseAndExecute(app, context) 98 assert.NoError(t, err) 99 assert.True(t, *cmd0f0) 100 assert.True(t, *cmd00f0) 101 assert.Equal(t, "a aa", selected) 102} 103 104func TestNestedCommandWithDuplicateFlagErrors(t *testing.T) { 105 app := New("app", "") 106 app.Flag("test", "").Bool() 107 app.Command("cmd0", "").Flag("test", "").Bool() 108 err := app.init() 109 assert.Error(t, err) 110} 111 112func TestNestedCommandWithArgAndMergedFlags(t *testing.T) { 113 app := New("app", "") 114 cmd0 := app.Command("a", "") 115 cmd0f0 := cmd0.Flag("aflag", "").Bool() 116 // cmd1 := app.Command("b", "") 117 // cmd1f0 := cmd0.Flag("bflag", "").Bool() 118 cmd00 := cmd0.Command("aa", "") 119 cmd00a0 := cmd00.Arg("arg", "").String() 120 cmd00f0 := cmd00.Flag("aaflag", "").Bool() 121 err := app.init() 122 assert.NoError(t, err) 123 context := tokenize(strings.Split("a aa hello --aflag --aaflag", " "), false) 124 selected, err := parseAndExecute(app, context) 125 assert.NoError(t, err) 126 assert.True(t, *cmd0f0) 127 assert.True(t, *cmd00f0) 128 assert.Equal(t, "a aa", selected) 129 assert.Equal(t, "hello", *cmd00a0) 130} 131 132func TestDefaultSubcommandEOL(t *testing.T) { 133 app := newTestApp() 134 c0 := app.Command("c0", "").Default() 135 c0.Command("c01", "").Default() 136 c0.Command("c02", "") 137 138 cmd, err := app.Parse([]string{"c0"}) 139 assert.NoError(t, err) 140 assert.Equal(t, "c0 c01", cmd) 141} 142 143func TestDefaultSubcommandWithArg(t *testing.T) { 144 app := newTestApp() 145 c0 := app.Command("c0", "").Default() 146 c01 := c0.Command("c01", "").Default() 147 c012 := c01.Command("c012", "").Default() 148 a0 := c012.Arg("a0", "").String() 149 c0.Command("c02", "") 150 151 cmd, err := app.Parse([]string{"c0", "hello"}) 152 assert.NoError(t, err) 153 assert.Equal(t, "c0 c01 c012", cmd) 154 assert.Equal(t, "hello", *a0) 155} 156 157func TestDefaultSubcommandWithFlags(t *testing.T) { 158 app := newTestApp() 159 c0 := app.Command("c0", "").Default() 160 _ = c0.Flag("f0", "").Int() 161 c0c1 := c0.Command("c1", "").Default() 162 c0c1f1 := c0c1.Flag("f1", "").Int() 163 selected, err := app.Parse([]string{"--f1=2"}) 164 assert.NoError(t, err) 165 assert.Equal(t, "c0 c1", selected) 166 assert.Equal(t, 2, *c0c1f1) 167 _, err = app.Parse([]string{"--f2"}) 168 assert.Error(t, err) 169} 170 171func TestMultipleDefaultCommands(t *testing.T) { 172 app := newTestApp() 173 app.Command("c0", "").Default() 174 app.Command("c1", "").Default() 175 _, err := app.Parse([]string{}) 176 assert.Error(t, err) 177} 178 179func TestAliasedCommand(t *testing.T) { 180 app := newTestApp() 181 app.Command("one", "").Alias("two") 182 selected, _ := app.Parse([]string{"one"}) 183 assert.Equal(t, "one", selected) 184 selected, _ = app.Parse([]string{"two"}) 185 assert.Equal(t, "one", selected) 186 // 2 due to "help" and "one" 187 assert.Equal(t, 2, len(app.Model().FlattenedCommands())) 188} 189 190func TestDuplicateAlias(t *testing.T) { 191 app := newTestApp() 192 app.Command("one", "") 193 app.Command("two", "").Alias("one") 194 _, err := app.Parse([]string{"one"}) 195 assert.Error(t, err) 196} 197 198func TestFlagCompletion(t *testing.T) { 199 app := newTestApp() 200 app.Command("one", "") 201 two := app.Command("two", "") 202 two.Flag("flag-1", "") 203 two.Flag("flag-2", "").HintOptions("opt1", "opt2", "opt3") 204 two.Flag("flag-3", "") 205 206 cases := []struct { 207 target cmdMixin 208 flagName string 209 flagValue string 210 expectedFlagMatch bool 211 expectedOptionMatch bool 212 expectedFlags []string 213 }{ 214 { 215 // Test top level flags 216 target: app.cmdMixin, 217 flagName: "", 218 flagValue: "", 219 expectedFlagMatch: false, 220 expectedOptionMatch: false, 221 expectedFlags: []string{"--help"}, 222 }, 223 { 224 // Test no flag passed 225 target: two.cmdMixin, 226 flagName: "", 227 flagValue: "", 228 expectedFlagMatch: false, 229 expectedOptionMatch: false, 230 expectedFlags: []string{"--flag-1", "--flag-2", "--flag-3"}, 231 }, 232 { 233 // Test an incomplete flag. Should still give all options as if the flag wasn't given at all. 234 target: two.cmdMixin, 235 flagName: "flag-", 236 flagValue: "", 237 expectedFlagMatch: false, 238 expectedOptionMatch: false, 239 expectedFlags: []string{"--flag-1", "--flag-2", "--flag-3"}, 240 }, 241 { 242 // Test with a complete flag. Should show available choices for the flag 243 // This flag has no options. No options should be produced. 244 // Should also report an option was matched 245 target: two.cmdMixin, 246 flagName: "flag-1", 247 flagValue: "", 248 expectedFlagMatch: true, 249 expectedOptionMatch: true, 250 expectedFlags: []string(nil), 251 }, 252 { 253 // Test with a complete flag. Should show available choices for the flag 254 target: two.cmdMixin, 255 flagName: "flag-2", 256 flagValue: "", 257 expectedFlagMatch: true, 258 expectedOptionMatch: false, 259 expectedFlags: []string{"opt1", "opt2", "opt3"}, 260 }, 261 { 262 // Test with a complete flag and complete option for that flag. 263 target: two.cmdMixin, 264 flagName: "flag-2", 265 flagValue: "opt1", 266 expectedFlagMatch: true, 267 expectedOptionMatch: true, 268 expectedFlags: []string{"opt1", "opt2", "opt3"}, 269 }, 270 } 271 272 for i, c := range cases { 273 choices, flagMatch, optionMatch := c.target.FlagCompletion(c.flagName, c.flagValue) 274 assert.Equal(t, c.expectedFlags, choices, "Test case %d: expectedFlags != actual flags", i+1) 275 assert.Equal(t, c.expectedFlagMatch, flagMatch, "Test case %d: expectedFlagMatch != flagMatch", i+1) 276 assert.Equal(t, c.expectedOptionMatch, optionMatch, "Test case %d: expectedOptionMatch != optionMatch", i+1) 277 } 278 279} 280 281func TestCmdCompletion(t *testing.T) { 282 app := newTestApp() 283 app.Command("one", "") 284 two := app.Command("two", "") 285 two.Command("sub1", "") 286 two.Command("sub2", "") 287 288 assert.Equal(t, []string{"help", "one", "two"}, complete(t, app)) 289 assert.Equal(t, []string{"sub1", "sub2"}, complete(t, app, "two")) 290} 291 292func TestHiddenCmdCompletion(t *testing.T) { 293 app := newTestApp() 294 295 // top level visible & hidden cmds, with no sub-cmds 296 app.Command("visible1", "") 297 app.Command("hidden1", "").Hidden() 298 299 // visible cmd with visible & hidden sub-cmds 300 visible2 := app.Command("visible2", "") 301 visible2.Command("visible2-visible", "") 302 visible2.Command("visible2-hidden", "").Hidden() 303 304 // hidden cmd with visible & hidden sub-cmds 305 hidden2 := app.Command("hidden2", "").Hidden() 306 hidden2.Command("hidden2-visible", "") 307 hidden2.Command("hidden2-hidden", "").Hidden() 308 309 // Only top level visible cmds should show 310 assert.Equal(t, []string{"help", "visible1", "visible2"}, complete(t, app)) 311 312 // Only visible sub-cmds should show 313 assert.Equal(t, []string{"visible2-visible"}, complete(t, app, "visible2")) 314 315 // Hidden commands should still complete visible sub-cmds 316 assert.Equal(t, []string{"hidden2-visible"}, complete(t, app, "hidden2")) 317} 318 319func TestDefaultCmdCompletion(t *testing.T) { 320 app := newTestApp() 321 322 cmd1 := app.Command("cmd1", "") 323 324 cmd1Sub1 := cmd1.Command("cmd1-sub1", "") 325 cmd1Sub1.Arg("cmd1-sub1-arg1", "").HintOptions("cmd1-arg1").String() 326 327 cmd2 := app.Command("cmd2", "").Default() 328 329 cmd2.Command("cmd2-sub1", "") 330 331 cmd2Sub2 := cmd2.Command("cmd2-sub2", "").Default() 332 333 cmd2Sub2Sub1 := cmd2Sub2.Command("cmd2-sub2-sub1", "").Default() 334 cmd2Sub2Sub1.Arg("cmd2-sub2-sub1-arg1", "").HintOptions("cmd2-sub2-sub1-arg1").String() 335 cmd2Sub2Sub1.Arg("cmd2-sub2-sub1-arg2", "").HintOptions("cmd2-sub2-sub1-arg2").String() 336 337 // Without args, should get: 338 // - root cmds (including implicit "help") 339 // - thread of default cmds 340 // - first arg hints for the final default cmd 341 assert.Equal(t, []string{"cmd1", "cmd2", "cmd2-sub1", "cmd2-sub2", "cmd2-sub2-sub1", "cmd2-sub2-sub1-arg1", "help"}, complete(t, app)) 342 343 // With a non-default cmd already listed, should get: 344 // - sub cmds of that arg 345 assert.Equal(t, []string{"cmd1-sub1"}, complete(t, app, "cmd1")) 346 347 // With an explicit default cmd listed, should get: 348 // - default child-cmds 349 // - first arg hints for the final default cmd 350 assert.Equal(t, []string{"cmd2-sub1", "cmd2-sub2", "cmd2-sub2-sub1", "cmd2-sub2-sub1-arg1"}, complete(t, app, "cmd2")) 351 352 // Args should be completed when all preceding cmds are explicit, and when 353 // any of them are implicit (not listed). Check this by trying all possible 354 // combinations of choosing/excluding the three levels of cmds. This tests 355 // root-level default, middle default, and end default. 356 for i := 0; i < 8; i++ { 357 var cmdline []string 358 359 if i&1 != 0 { 360 cmdline = append(cmdline, "cmd2") 361 } 362 if i&2 != 0 { 363 cmdline = append(cmdline, "cmd2-sub2") 364 } 365 if i&4 != 0 { 366 cmdline = append(cmdline, "cmd2-sub2-sub1") 367 } 368 369 assert.Contains(t, complete(t, app, cmdline...), "cmd2-sub2-sub1-arg1", "with cmdline: %v", cmdline) 370 } 371 372 // With both args of a default sub cmd, should get no completions 373 assert.Empty(t, complete(t, app, "arg1", "arg2")) 374} 375