github.com/maresnic/mr-kong@v1.0.0/kong_test.go (about) 1 package kong_test 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "strings" 8 "testing" 9 10 "github.com/alecthomas/assert/v2" 11 "github.com/alecthomas/repr" 12 13 "github.com/maresnic/mr-kong" 14 ) 15 16 func mustNew(t *testing.T, cli interface{}, options ...kong.Option) *kong.Kong { 17 t.Helper() 18 options = append([]kong.Option{ 19 kong.Name("test"), 20 kong.Exit(func(int) { 21 t.Helper() 22 t.Fatalf("unexpected exit()") 23 }), 24 }, options...) 25 parser, err := kong.New(cli, options...) 26 assert.NoError(t, err) 27 return parser 28 } 29 30 func TestPositionalArguments(t *testing.T) { 31 var cli struct { 32 User struct { 33 Create struct { 34 ID int `kong:"arg"` 35 First string `kong:"arg"` 36 Last string `kong:"arg"` 37 } `kong:"cmd"` 38 } `kong:"cmd"` 39 } 40 p := mustNew(t, &cli) 41 ctx, err := p.Parse([]string{"user", "create", "10", "Alec", "Thomas"}) 42 assert.NoError(t, err) 43 assert.Equal(t, "user create <id> <first> <last>", ctx.Command()) 44 t.Run("Missing", func(t *testing.T) { 45 _, err := p.Parse([]string{"user", "create", "10"}) 46 assert.Error(t, err) 47 }) 48 } 49 50 func TestBranchingArgument(t *testing.T) { 51 /* 52 app user create <id> <first> <last> 53 app user <id> delete 54 app user <id> rename <to> 55 56 */ 57 var cli struct { 58 User struct { 59 Create struct { 60 ID string `kong:"arg"` 61 First string `kong:"arg"` 62 Last string `kong:"arg"` 63 } `kong:"cmd"` 64 65 // Branching argument. 66 ID struct { 67 ID int `kong:"arg"` 68 Flag int 69 Delete struct{} `kong:"cmd"` 70 Rename struct { 71 To string 72 } `kong:"cmd"` 73 } `kong:"arg"` 74 } `kong:"cmd,help='User management.'"` 75 } 76 p := mustNew(t, &cli) 77 ctx, err := p.Parse([]string{"user", "10", "delete"}) 78 assert.NoError(t, err) 79 assert.Equal(t, 10, cli.User.ID.ID) 80 assert.Equal(t, "user <id> delete", ctx.Command()) 81 t.Run("Missing", func(t *testing.T) { 82 _, err = p.Parse([]string{"user"}) 83 assert.Error(t, err) 84 }) 85 } 86 87 func TestResetWithDefaults(t *testing.T) { 88 var cli struct { 89 Flag string 90 FlagWithDefault string `kong:"default='default'"` 91 } 92 cli.Flag = "BLAH" 93 cli.FlagWithDefault = "BLAH" 94 parser := mustNew(t, &cli) 95 _, err := parser.Parse([]string{}) 96 assert.NoError(t, err) 97 assert.Equal(t, "", cli.Flag) 98 assert.Equal(t, "default", cli.FlagWithDefault) 99 } 100 101 func TestFlagSlice(t *testing.T) { 102 var cli struct { 103 Slice []int 104 } 105 parser := mustNew(t, &cli) 106 _, err := parser.Parse([]string{"--slice=1,2,3"}) 107 assert.NoError(t, err) 108 assert.Equal(t, []int{1, 2, 3}, cli.Slice) 109 } 110 111 func TestFlagSliceWithSeparator(t *testing.T) { 112 var cli struct { 113 Slice []string 114 } 115 parser := mustNew(t, &cli) 116 _, err := parser.Parse([]string{`--slice=a\,b,c`}) 117 assert.NoError(t, err) 118 assert.Equal(t, []string{"a,b", "c"}, cli.Slice) 119 } 120 121 func TestArgSlice(t *testing.T) { 122 var cli struct { 123 Slice []int `arg` 124 Flag bool 125 } 126 parser := mustNew(t, &cli) 127 _, err := parser.Parse([]string{"1", "2", "3", "--flag"}) 128 assert.NoError(t, err) 129 assert.Equal(t, []int{1, 2, 3}, cli.Slice) 130 assert.Equal(t, true, cli.Flag) 131 } 132 133 func TestArgSliceWithSeparator(t *testing.T) { 134 var cli struct { 135 Slice []string `arg` 136 Flag bool 137 } 138 parser := mustNew(t, &cli) 139 _, err := parser.Parse([]string{"a,b", "c", "--flag"}) 140 assert.NoError(t, err) 141 assert.Equal(t, []string{"a,b", "c"}, cli.Slice) 142 assert.Equal(t, true, cli.Flag) 143 } 144 145 func TestUnsupportedFieldErrors(t *testing.T) { 146 var cli struct { 147 Keys struct{} 148 } 149 _, err := kong.New(&cli) 150 assert.Error(t, err) 151 } 152 153 func TestMatchingArgField(t *testing.T) { 154 var cli struct { 155 ID struct { 156 NotID int `kong:"arg"` 157 } `kong:"arg"` 158 } 159 160 _, err := kong.New(&cli) 161 assert.Error(t, err) 162 } 163 164 func TestCantMixPositionalAndBranches(t *testing.T) { 165 var cli struct { 166 Arg string `kong:"arg"` 167 Command struct { 168 } `kong:"cmd"` 169 } 170 _, err := kong.New(&cli) 171 assert.Error(t, err) 172 } 173 174 func TestPropagatedFlags(t *testing.T) { 175 var cli struct { 176 Flag1 string 177 Command1 struct { 178 Flag2 bool 179 Command2 struct{} `kong:"cmd"` 180 } `kong:"cmd"` 181 } 182 183 parser := mustNew(t, &cli) 184 _, err := parser.Parse([]string{"command-1", "command-2", "--flag-2", "--flag-1=moo"}) 185 assert.NoError(t, err) 186 assert.Equal(t, "moo", cli.Flag1) 187 assert.Equal(t, true, cli.Command1.Flag2) 188 } 189 190 func TestRequiredFlag(t *testing.T) { 191 var cli struct { 192 Flag string `kong:"required"` 193 } 194 195 parser := mustNew(t, &cli) 196 _, err := parser.Parse([]string{}) 197 assert.Error(t, err) 198 } 199 200 func TestOptionalArg(t *testing.T) { 201 var cli struct { 202 Arg string `kong:"arg,optional"` 203 } 204 205 parser := mustNew(t, &cli) 206 _, err := parser.Parse([]string{}) 207 assert.NoError(t, err) 208 } 209 210 func TestOptionalArgWithDefault(t *testing.T) { 211 var cli struct { 212 Arg string `kong:"arg,optional,default='moo'"` 213 } 214 215 parser := mustNew(t, &cli) 216 _, err := parser.Parse([]string{}) 217 assert.NoError(t, err) 218 assert.Equal(t, "moo", cli.Arg) 219 } 220 221 func TestArgWithDefaultIsOptional(t *testing.T) { 222 var cli struct { 223 Arg string `kong:"arg,default='moo'"` 224 } 225 226 parser := mustNew(t, &cli) 227 _, err := parser.Parse([]string{}) 228 assert.NoError(t, err) 229 assert.Equal(t, "moo", cli.Arg) 230 } 231 232 func TestRequiredArg(t *testing.T) { 233 var cli struct { 234 Arg string `kong:"arg"` 235 } 236 237 parser := mustNew(t, &cli) 238 _, err := parser.Parse([]string{}) 239 assert.Error(t, err) 240 } 241 242 func TestInvalidRequiredAfterOptional(t *testing.T) { 243 var cli struct { 244 ID int `kong:"arg,optional"` 245 Name string `kong:"arg"` 246 } 247 248 _, err := kong.New(&cli) 249 assert.Error(t, err) 250 } 251 252 func TestOptionalStructArg(t *testing.T) { 253 var cli struct { 254 Name struct { 255 Name string `kong:"arg,optional"` 256 Enabled bool 257 } `kong:"arg,optional"` 258 } 259 260 parser := mustNew(t, &cli) 261 262 t.Run("WithFlag", func(t *testing.T) { 263 _, err := parser.Parse([]string{"gak", "--enabled"}) 264 assert.NoError(t, err) 265 assert.Equal(t, "gak", cli.Name.Name) 266 assert.Equal(t, true, cli.Name.Enabled) 267 }) 268 269 t.Run("WithoutFlag", func(t *testing.T) { 270 _, err := parser.Parse([]string{"gak"}) 271 assert.NoError(t, err) 272 assert.Equal(t, "gak", cli.Name.Name) 273 }) 274 275 t.Run("WithNothing", func(t *testing.T) { 276 _, err := parser.Parse([]string{}) 277 assert.NoError(t, err) 278 }) 279 } 280 281 func TestMixedRequiredArgs(t *testing.T) { 282 var cli struct { 283 Name string `kong:"arg"` 284 ID int `kong:"arg,optional"` 285 } 286 287 parser := mustNew(t, &cli) 288 289 t.Run("SingleRequired", func(t *testing.T) { 290 _, err := parser.Parse([]string{"gak", "5"}) 291 assert.NoError(t, err) 292 assert.Equal(t, "gak", cli.Name) 293 assert.Equal(t, 5, cli.ID) 294 }) 295 296 t.Run("ExtraOptional", func(t *testing.T) { 297 _, err := parser.Parse([]string{"gak"}) 298 assert.NoError(t, err) 299 assert.Equal(t, "gak", cli.Name) 300 }) 301 } 302 303 func TestInvalidDefaultErrors(t *testing.T) { 304 var cli struct { 305 Flag int `kong:"default='foo'"` 306 } 307 p := mustNew(t, &cli) 308 _, err := p.Parse(nil) 309 assert.Error(t, err) 310 } 311 312 func TestCommandMissingTagIsInvalid(t *testing.T) { 313 var cli struct { 314 One struct{} 315 } 316 _, err := kong.New(&cli) 317 assert.Error(t, err) 318 } 319 320 func TestDuplicateFlag(t *testing.T) { 321 var cli struct { 322 Flag bool 323 Cmd struct { 324 Flag bool 325 } `kong:"cmd"` 326 } 327 _, err := kong.New(&cli) 328 assert.Error(t, err) 329 } 330 331 func TestDuplicateFlagOnPeerCommandIsOkay(t *testing.T) { 332 var cli struct { 333 Cmd1 struct { 334 Flag bool 335 } `kong:"cmd"` 336 Cmd2 struct { 337 Flag bool 338 } `kong:"cmd"` 339 } 340 _, err := kong.New(&cli) 341 assert.NoError(t, err) 342 } 343 344 func TestTraceErrorPartiallySucceeds(t *testing.T) { 345 var cli struct { 346 One struct { 347 Two struct { 348 } `kong:"cmd"` 349 } `kong:"cmd"` 350 } 351 p := mustNew(t, &cli) 352 ctx, err := kong.Trace(p, []string{"one", "bad"}) 353 assert.NoError(t, err) 354 assert.Error(t, ctx.Error) 355 assert.Equal(t, "one", ctx.Command()) 356 } 357 358 type commandWithNegatableFlag struct { 359 Flag bool `kong:"default='true',negatable"` 360 ran bool 361 } 362 363 func (c *commandWithNegatableFlag) Run() error { 364 c.ran = true 365 return nil 366 } 367 368 func TestNegatableFlag(t *testing.T) { 369 tests := []struct { 370 name string 371 args []string 372 expected bool 373 }{ 374 { 375 name: "no flag", 376 args: []string{"cmd"}, 377 expected: true, 378 }, 379 { 380 name: "boolean flag", 381 args: []string{"cmd", "--flag"}, 382 expected: true, 383 }, 384 { 385 name: "inverted boolean flag", 386 args: []string{"cmd", "--flag=false"}, 387 expected: false, 388 }, 389 { 390 name: "negated boolean flag", 391 args: []string{"cmd", "--no-flag"}, 392 expected: false, 393 }, 394 { 395 name: "inverted negated boolean flag", 396 args: []string{"cmd", "--no-flag=false"}, 397 expected: true, 398 }, 399 } 400 for _, tt := range tests { 401 tt := tt 402 t.Run(tt.name, func(t *testing.T) { 403 var cli struct { 404 Cmd commandWithNegatableFlag `kong:"cmd"` 405 } 406 407 p := mustNew(t, &cli) 408 kctx, err := p.Parse(tt.args) 409 assert.NoError(t, err) 410 assert.Equal(t, tt.expected, cli.Cmd.Flag) 411 412 err = kctx.Run() 413 assert.NoError(t, err) 414 assert.Equal(t, tt.expected, cli.Cmd.Flag) 415 assert.True(t, cli.Cmd.ran) 416 }) 417 } 418 } 419 420 func TestExistingNoFlag(t *testing.T) { 421 var cli struct { 422 Cmd struct { 423 Flag bool `kong:"default='true'"` 424 NoFlag string 425 } `kong:"cmd"` 426 } 427 428 p := mustNew(t, &cli) 429 _, err := p.Parse([]string{"cmd", "--no-flag=none"}) 430 assert.NoError(t, err) 431 assert.Equal(t, true, cli.Cmd.Flag) 432 assert.Equal(t, "none", cli.Cmd.NoFlag) 433 } 434 435 func TestInvalidNegatedNonBool(t *testing.T) { 436 var cli struct { 437 Cmd struct { 438 Flag string `kong:"negatable"` 439 } `kong:"cmd"` 440 } 441 442 _, err := kong.New(&cli) 443 assert.Error(t, err) 444 } 445 446 type hookContext struct { 447 cmd bool 448 values []string 449 } 450 451 type hookValue string 452 453 func (h *hookValue) BeforeApply(ctx *hookContext) error { 454 ctx.values = append(ctx.values, "before:"+string(*h)) 455 return nil 456 } 457 458 func (h *hookValue) AfterApply(ctx *hookContext) error { 459 ctx.values = append(ctx.values, "after:"+string(*h)) 460 return nil 461 } 462 463 type hookCmd struct { 464 Two hookValue `kong:"arg,optional"` 465 Three hookValue 466 } 467 468 func (h *hookCmd) BeforeApply(ctx *hookContext) error { 469 ctx.cmd = true 470 return nil 471 } 472 473 func (h *hookCmd) AfterApply(ctx *hookContext) error { 474 ctx.cmd = true 475 return nil 476 } 477 478 func TestHooks(t *testing.T) { 479 var tests = []struct { 480 name string 481 input string 482 values hookContext 483 }{ 484 {"Command", "one", hookContext{true, nil}}, 485 {"Arg", "one two", hookContext{true, []string{"before:", "after:two"}}}, 486 {"Flag", "one --three=THREE", hookContext{true, []string{"before:", "after:THREE"}}}, 487 {"ArgAndFlag", "one two --three=THREE", hookContext{true, []string{"before:", "before:", "after:two", "after:THREE"}}}, 488 } 489 490 var cli struct { 491 One hookCmd `cmd:""` 492 } 493 494 ctx := &hookContext{} 495 p := mustNew(t, &cli, kong.Bind(ctx)) 496 497 for _, test := range tests { 498 test := test 499 *ctx = hookContext{} 500 cli.One = hookCmd{} 501 t.Run(test.name, func(t *testing.T) { 502 _, err := p.Parse(strings.Split(test.input, " ")) 503 assert.NoError(t, err) 504 assert.Equal(t, &test.values, ctx) 505 }) 506 } 507 } 508 509 func TestShort(t *testing.T) { 510 var cli struct { 511 Bool bool `short:"b"` 512 String string `short:"s"` 513 } 514 app := mustNew(t, &cli) 515 _, err := app.Parse([]string{"-b", "-shello"}) 516 assert.NoError(t, err) 517 assert.True(t, cli.Bool) 518 assert.Equal(t, "hello", cli.String) 519 } 520 521 func TestAlias(t *testing.T) { 522 var cli struct { 523 String string `aliases:"str"` 524 } 525 app := mustNew(t, &cli) 526 _, err := app.Parse([]string{"--str", "hello"}) 527 assert.NoError(t, err) 528 assert.Equal(t, "hello", cli.String) 529 } 530 531 func TestDuplicateFlagChoosesLast(t *testing.T) { 532 var cli struct { 533 Flag int 534 } 535 536 _, err := mustNew(t, &cli).Parse([]string{"--flag=1", "--flag=2"}) 537 assert.NoError(t, err) 538 assert.Equal(t, 2, cli.Flag) 539 } 540 541 func TestDuplicateSliceAccumulates(t *testing.T) { 542 var cli struct { 543 Flag []int 544 } 545 546 args := []string{"--flag=1,2", "--flag=3,4"} 547 _, err := mustNew(t, &cli).Parse(args) 548 assert.NoError(t, err) 549 assert.Equal(t, []int{1, 2, 3, 4}, cli.Flag) 550 } 551 552 func TestMapFlag(t *testing.T) { 553 var cli struct { 554 Set map[string]int 555 } 556 _, err := mustNew(t, &cli).Parse([]string{"--set", "a=10", "--set", "b=20"}) 557 assert.NoError(t, err) 558 assert.Equal(t, map[string]int{"a": 10, "b": 20}, cli.Set) 559 } 560 561 func TestMapFlagWithSliceValue(t *testing.T) { 562 var cli struct { 563 Set map[string][]int 564 } 565 _, err := mustNew(t, &cli).Parse([]string{"--set", "a=1,2", "--set", "b=3"}) 566 assert.NoError(t, err) 567 assert.Equal(t, map[string][]int{"a": {1, 2}, "b": {3}}, cli.Set) 568 } 569 570 type embeddedFlags struct { 571 Embedded string 572 } 573 574 func TestEmbeddedStruct(t *testing.T) { 575 var cli struct { 576 embeddedFlags 577 NotEmbedded string 578 } 579 580 _, err := mustNew(t, &cli).Parse([]string{"--embedded=moo", "--not-embedded=foo"}) 581 assert.NoError(t, err) 582 assert.Equal(t, "moo", cli.Embedded) 583 assert.Equal(t, "foo", cli.NotEmbedded) 584 } 585 586 func TestSliceWithDisabledSeparator(t *testing.T) { 587 var cli struct { 588 Flag []string `sep:"none"` 589 } 590 _, err := mustNew(t, &cli).Parse([]string{"--flag=a,b", "--flag=b,c"}) 591 assert.NoError(t, err) 592 assert.Equal(t, []string{"a,b", "b,c"}, cli.Flag) 593 } 594 595 func TestMultilineMessage(t *testing.T) { 596 w := &bytes.Buffer{} 597 var cli struct{} 598 p := mustNew(t, &cli, kong.Writers(w, w)) 599 p.Printf("hello\nworld") 600 assert.Equal(t, "test: hello\n world\n", w.String()) 601 } 602 603 type cmdWithRun struct { 604 Arg string `arg:""` 605 } 606 607 func (c *cmdWithRun) Run(key string) error { 608 c.Arg += key 609 if key == "ERROR" { 610 return fmt.Errorf("ERROR") 611 } 612 return nil 613 } 614 615 type parentCmdWithRun struct { 616 Flag string 617 SubCommand struct { 618 Arg string `arg:""` 619 } `cmd:""` 620 } 621 622 func (p *parentCmdWithRun) Run(key string) error { 623 p.SubCommand.Arg += key 624 return nil 625 } 626 627 type grammarWithRun struct { 628 One cmdWithRun `cmd:""` 629 Two cmdWithRun `cmd:""` 630 Three parentCmdWithRun `cmd:""` 631 } 632 633 func TestRun(t *testing.T) { 634 cli := &grammarWithRun{} 635 p := mustNew(t, cli) 636 637 ctx, err := p.Parse([]string{"one", "two"}) 638 assert.NoError(t, err) 639 err = ctx.Run("hello") 640 assert.NoError(t, err) 641 assert.Equal(t, "twohello", cli.One.Arg) 642 643 ctx, err = p.Parse([]string{"two", "three"}) 644 assert.NoError(t, err) 645 err = ctx.Run("ERROR") 646 assert.Error(t, err) 647 648 ctx, err = p.Parse([]string{"three", "sub-command", "arg"}) 649 assert.NoError(t, err) 650 err = ctx.Run("ping") 651 assert.NoError(t, err) 652 assert.Equal(t, "argping", cli.Three.SubCommand.Arg) 653 } 654 655 type failCmd struct{} 656 657 func (f failCmd) Run() error { 658 return errors.New("this command failed") 659 } 660 661 func TestPassesThroughOriginalCommandError(t *testing.T) { 662 var cli struct { 663 Fail failCmd `kong:"cmd"` 664 } 665 p := mustNew(t, &cli) 666 ctx, _ := p.Parse([]string{"fail"}) 667 err := ctx.Run() 668 assert.Error(t, err) 669 assert.Equal(t, err.Error(), "this command failed") 670 } 671 672 func TestInterpolationIntoModel(t *testing.T) { 673 var cli struct { 674 Flag string `default:"${default_value}" help:"Help, I need ${somebody}" enum:"${enum}"` 675 EnumRef string `enum:"a,b" required:"" help:"One of ${enum}"` 676 EnvRef string `env:"${env}" help:"God ${env}"` 677 } 678 _, err := kong.New(&cli) 679 assert.Error(t, err) 680 p, err := kong.New(&cli, kong.Vars{ 681 "default_value": "Some default value.", 682 "somebody": "chickens!", 683 "enum": "a,b,c,d", 684 "env": "SAVE_THE_QUEEN", 685 }) 686 assert.NoError(t, err) 687 assert.Equal(t, 4, len(p.Model.Flags)) 688 flag := p.Model.Flags[1] 689 flag2 := p.Model.Flags[2] 690 flag3 := p.Model.Flags[3] 691 assert.Equal(t, "Some default value.", flag.Default) 692 assert.Equal(t, "Help, I need chickens!", flag.Help) 693 assert.Equal(t, map[string]bool{"a": true, "b": true, "c": true, "d": true}, flag.EnumMap()) 694 assert.Equal(t, []string{"a", "b", "c", "d"}, flag.EnumSlice()) 695 assert.Equal(t, "One of a,b", flag2.Help) 696 assert.Equal(t, []string{"SAVE_THE_QUEEN"}, flag3.Envs) 697 assert.Equal(t, "God SAVE_THE_QUEEN", flag3.Help) 698 } 699 700 func TestIssue244(t *testing.T) { 701 type Config struct { 702 Project string `short:"p" env:"CI_PROJECT_ID" help:"Environment variable: ${env}"` 703 } 704 w := &strings.Builder{} 705 k := mustNew(t, &Config{}, kong.Exit(func(int) {}), kong.Writers(w, w)) 706 _, err := k.Parse([]string{"--help"}) 707 assert.NoError(t, err) 708 assert.Contains(t, w.String(), `Environment variable: CI_PROJECT_ID`) 709 } 710 711 func TestErrorMissingArgs(t *testing.T) { 712 var cli struct { 713 One string `arg:""` 714 Two string `arg:""` 715 } 716 717 p := mustNew(t, &cli) 718 _, err := p.Parse(nil) 719 assert.Error(t, err) 720 assert.Equal(t, "expected \"<one> <two>\"", err.Error()) 721 } 722 723 func TestBoolOverride(t *testing.T) { 724 var cli struct { 725 Flag bool `default:"true"` 726 } 727 p := mustNew(t, &cli) 728 _, err := p.Parse([]string{"--flag=false"}) 729 assert.NoError(t, err) 730 _, err = p.Parse([]string{"--flag", "false"}) 731 assert.Error(t, err) 732 } 733 734 func TestAnonymousPrefix(t *testing.T) { 735 type Anonymous struct { 736 Flag string 737 } 738 var cli struct { 739 Anonymous `prefix:"anon-"` 740 } 741 p := mustNew(t, &cli) 742 _, err := p.Parse([]string{"--anon-flag=moo"}) 743 assert.NoError(t, err) 744 assert.Equal(t, "moo", cli.Flag) 745 } 746 747 type TestInterface interface { 748 SomeMethod() 749 } 750 751 type TestImpl struct { 752 Flag string 753 } 754 755 func (t *TestImpl) SomeMethod() {} 756 757 func TestEmbedInterface(t *testing.T) { 758 type CLI struct { 759 SomeFlag string 760 TestInterface 761 } 762 cli := &CLI{TestInterface: &TestImpl{}} 763 p := mustNew(t, cli) 764 _, err := p.Parse([]string{"--some-flag=foo", "--flag=yes"}) 765 assert.NoError(t, err) 766 assert.Equal(t, "foo", cli.SomeFlag) 767 assert.Equal(t, "yes", cli.TestInterface.(*TestImpl).Flag) //nolint 768 } 769 770 func TestExcludedField(t *testing.T) { 771 var cli struct { 772 Flag string 773 Excluded string `kong:"-"` 774 } 775 776 p := mustNew(t, &cli) 777 _, err := p.Parse([]string{"--flag=foo"}) 778 assert.NoError(t, err) 779 _, err = p.Parse([]string{"--excluded=foo"}) 780 assert.Error(t, err) 781 } 782 783 func TestUnnamedFieldEmbeds(t *testing.T) { 784 type Embed struct { 785 Flag string 786 } 787 var cli struct { 788 One Embed `prefix:"one-" embed:""` 789 Two Embed `prefix:"two-" embed:""` 790 } 791 buf := &strings.Builder{} 792 p := mustNew(t, &cli, kong.Writers(buf, buf), kong.Exit(func(int) {})) 793 _, err := p.Parse([]string{"--help"}) 794 assert.NoError(t, err) 795 assert.Contains(t, buf.String(), `--one-flag=STRING`) 796 assert.Contains(t, buf.String(), `--two-flag=STRING`) 797 } 798 799 func TestHooksCalledForDefault(t *testing.T) { 800 var cli struct { 801 Flag hookValue `default:"default"` 802 } 803 804 ctx := &hookContext{} 805 _, err := mustNew(t, &cli, kong.Bind(ctx)).Parse(nil) 806 assert.NoError(t, err) 807 assert.Equal(t, "default", string(cli.Flag)) 808 assert.Equal(t, []string{"before:default", "after:default"}, ctx.values) 809 } 810 811 func TestEnum(t *testing.T) { 812 var cli struct { 813 Flag string `enum:"a,b,c" required:""` 814 } 815 _, err := mustNew(t, &cli).Parse([]string{"--flag", "d"}) 816 assert.EqualError(t, err, "--flag must be one of \"a\",\"b\",\"c\" but got \"d\"") 817 } 818 819 func TestEnumMeaningfulOrder(t *testing.T) { 820 var cli struct { 821 Flag string `enum:"first,second,third,fourth,fifth" required:""` 822 } 823 _, err := mustNew(t, &cli).Parse([]string{"--flag", "sixth"}) 824 assert.EqualError(t, err, "--flag must be one of \"first\",\"second\",\"third\",\"fourth\",\"fifth\" but got \"sixth\"") 825 } 826 827 type commandWithHook struct { 828 value string 829 } 830 831 func (c *commandWithHook) AfterApply(cli *cliWithHook) error { 832 c.value = cli.Flag 833 return nil 834 } 835 836 type cliWithHook struct { 837 Flag string 838 Command commandWithHook `cmd:""` 839 } 840 841 func (c *cliWithHook) AfterApply(ctx *kong.Context) error { 842 ctx.Bind(c) 843 return nil 844 } 845 846 func TestParentBindings(t *testing.T) { 847 cli := &cliWithHook{} 848 _, err := mustNew(t, cli).Parse([]string{"command", "--flag=foo"}) 849 assert.NoError(t, err) 850 assert.Equal(t, "foo", cli.Command.value) 851 } 852 853 func TestNumericParamErrors(t *testing.T) { 854 var cli struct { 855 Name string 856 } 857 parser := mustNew(t, &cli) 858 _, err := parser.Parse([]string{"--name", "-10"}) 859 assert.EqualError(t, err, `--name: expected string value but got "-10" (short flag); perhaps try --name="-10"?`) 860 } 861 862 func TestDefaultValueIsHyphen(t *testing.T) { 863 var cli struct { 864 Flag string `default:"-"` 865 } 866 p := mustNew(t, &cli) 867 _, err := p.Parse(nil) 868 assert.NoError(t, err) 869 assert.Equal(t, "-", cli.Flag) 870 } 871 872 func TestDefaultEnumValidated(t *testing.T) { 873 var cli struct { 874 Flag string `default:"invalid" enum:"valid"` 875 } 876 p := mustNew(t, &cli) 877 _, err := p.Parse(nil) 878 assert.EqualError(t, err, "--flag must be one of \"valid\" but got \"invalid\"") 879 } 880 881 func TestEnvarEnumValidated(t *testing.T) { 882 restore := tempEnv(map[string]string{ 883 "FLAG": "invalid", 884 }) 885 defer restore() 886 var cli struct { 887 Flag string `env:"FLAG" required:"" enum:"valid"` 888 } 889 p := mustNew(t, &cli) 890 _, err := p.Parse(nil) 891 assert.EqualError(t, err, "--flag must be one of \"valid\" but got \"invalid\"") 892 } 893 894 func TestXor(t *testing.T) { 895 var cli struct { 896 Hello bool `xor:"another"` 897 One bool `xor:"group"` 898 Two string `xor:"group"` 899 } 900 p := mustNew(t, &cli) 901 _, err := p.Parse([]string{"--hello", "--one", "--two=hi"}) 902 assert.EqualError(t, err, "--one and --two can't be used together") 903 904 p = mustNew(t, &cli) 905 _, err = p.Parse([]string{"--one", "--hello"}) 906 assert.NoError(t, err) 907 } 908 909 func TestXorChild(t *testing.T) { 910 var cli struct { 911 One bool `xor:"group"` 912 Cmd struct { 913 Two string `xor:"group"` 914 Three string `xor:"group"` 915 } `cmd` 916 } 917 p := mustNew(t, &cli) 918 _, err := p.Parse([]string{"--one", "cmd", "--two=hi"}) 919 assert.NoError(t, err) 920 921 p = mustNew(t, &cli) 922 _, err = p.Parse([]string{"--two=hi", "cmd", "--three"}) 923 assert.Error(t, err, "--two and --three can't be used together") 924 } 925 926 func TestMultiXor(t *testing.T) { 927 var cli struct { 928 Hello bool `xor:"one,two"` 929 One bool `xor:"one"` 930 Two string `xor:"two"` 931 } 932 933 p := mustNew(t, &cli) 934 _, err := p.Parse([]string{"--hello", "--one"}) 935 assert.EqualError(t, err, "--hello and --one can't be used together") 936 937 p = mustNew(t, &cli) 938 _, err = p.Parse([]string{"--hello", "--two=foo"}) 939 assert.EqualError(t, err, "--hello and --two can't be used together") 940 } 941 942 func TestXorRequired(t *testing.T) { 943 var cli struct { 944 One bool `xor:"one,two" required:""` 945 Two bool `xor:"one" required:""` 946 Three bool `xor:"two" required:""` 947 Four bool `required:""` 948 } 949 p := mustNew(t, &cli) 950 _, err := p.Parse([]string{"--one"}) 951 assert.EqualError(t, err, "missing flags: --four") 952 953 p = mustNew(t, &cli) 954 _, err = p.Parse([]string{"--two"}) 955 assert.EqualError(t, err, "missing flags: --four, --one or --three") 956 957 p = mustNew(t, &cli) 958 _, err = p.Parse([]string{}) 959 assert.EqualError(t, err, "missing flags: --four, --one or --three, --one or --two") 960 } 961 962 func TestXorRequiredMany(t *testing.T) { 963 var cli struct { 964 One bool `xor:"one" required:""` 965 Two bool `xor:"one" required:""` 966 Three bool `xor:"one" required:""` 967 } 968 p := mustNew(t, &cli) 969 _, err := p.Parse([]string{"--one"}) 970 assert.NoError(t, err) 971 972 p = mustNew(t, &cli) 973 _, err = p.Parse([]string{"--three"}) 974 assert.NoError(t, err) 975 976 p = mustNew(t, &cli) 977 _, err = p.Parse([]string{}) 978 assert.EqualError(t, err, "missing flags: --one or --two or --three") 979 } 980 981 func TestEnumSequence(t *testing.T) { 982 var cli struct { 983 State []string `enum:"a,b,c" default:"a"` 984 } 985 p := mustNew(t, &cli) 986 _, err := p.Parse(nil) 987 assert.NoError(t, err) 988 assert.Equal(t, []string{"a"}, cli.State) 989 } 990 991 func TestIssue40EnumAcrossCommands(t *testing.T) { 992 var cli struct { 993 One struct { 994 OneArg string `arg:"" required:""` 995 } `cmd:""` 996 Two struct { 997 TwoArg string `arg:"" enum:"a,b,c" required:"" env:"FOO"` 998 } `cmd:""` 999 Three struct { 1000 ThreeArg string `arg:"" optional:"" default:"a" enum:"a,b,c"` 1001 } `cmd:""` 1002 } 1003 1004 p := mustNew(t, &cli) 1005 _, err := p.Parse([]string{"one", "two"}) 1006 assert.NoError(t, err) 1007 _, err = p.Parse([]string{"two", "d"}) 1008 assert.Error(t, err) 1009 _, err = p.Parse([]string{"three", "d"}) 1010 assert.Error(t, err) 1011 _, err = p.Parse([]string{"three", "c"}) 1012 assert.NoError(t, err) 1013 } 1014 1015 func TestIssue179(t *testing.T) { 1016 type A struct { 1017 Enum string `required:"" enum:"1,2"` 1018 } 1019 1020 type B struct{} 1021 1022 var root struct { 1023 A A `cmd` 1024 B B `cmd` 1025 } 1026 1027 p := mustNew(t, &root) 1028 _, err := p.Parse([]string{"b"}) 1029 assert.NoError(t, err) 1030 } 1031 1032 func TestIssue153(t *testing.T) { 1033 type LsCmd struct { 1034 Paths []string `arg required name:"path" help:"Paths to list." env:"CMD_PATHS"` 1035 } 1036 1037 var cli struct { 1038 Debug bool `help:"Enable debug mode."` 1039 1040 Ls LsCmd `cmd help:"List paths."` 1041 } 1042 1043 p, revert := newEnvParser(t, &cli, envMap{ 1044 "CMD_PATHS": "hello", 1045 }) 1046 defer revert() 1047 _, err := p.Parse([]string{"ls"}) 1048 assert.NoError(t, err) 1049 assert.Equal(t, []string{"hello"}, cli.Ls.Paths) 1050 } 1051 1052 func TestEnumArg(t *testing.T) { 1053 var cli struct { 1054 Nested struct { 1055 One string `arg:"" enum:"a,b,c" required:""` 1056 Two string `arg:""` 1057 } `cmd:""` 1058 } 1059 p := mustNew(t, &cli) 1060 _, err := p.Parse([]string{"nested", "a", "b"}) 1061 assert.NoError(t, err) 1062 assert.Equal(t, "a", cli.Nested.One) 1063 assert.Equal(t, "b", cli.Nested.Two) 1064 } 1065 1066 func TestDefaultCommand(t *testing.T) { 1067 var cli struct { 1068 One struct{} `cmd:"" default:"1"` 1069 Two struct{} `cmd:""` 1070 } 1071 p := mustNew(t, &cli) 1072 ctx, err := p.Parse([]string{}) 1073 assert.NoError(t, err) 1074 assert.Equal(t, "one", ctx.Command()) 1075 } 1076 1077 func TestMultipleDefaultCommands(t *testing.T) { 1078 var cli struct { 1079 One struct{} `cmd:"" default:"1"` 1080 Two struct{} `cmd:"" default:"1"` 1081 } 1082 _, err := kong.New(&cli) 1083 assert.EqualError(t, err, "<anonymous struct>.Two: can't have more than one default command under <command>") 1084 } 1085 1086 func TestDefaultCommandWithSubCommand(t *testing.T) { 1087 var cli struct { 1088 One struct { 1089 Two struct{} `cmd:""` 1090 } `cmd:"" default:"1"` 1091 } 1092 _, err := kong.New(&cli) 1093 assert.EqualError(t, err, "<anonymous struct>.One: default command one <command> must not have subcommands or arguments") 1094 } 1095 1096 func TestDefaultCommandWithAllowedSubCommand(t *testing.T) { 1097 var cli struct { 1098 One struct { 1099 Two struct{} `cmd:""` 1100 } `cmd:"" default:"withargs"` 1101 } 1102 p := mustNew(t, &cli) 1103 ctx, err := p.Parse([]string{"two"}) 1104 assert.NoError(t, err) 1105 assert.Equal(t, "one two", ctx.Command()) 1106 } 1107 1108 func TestDefaultCommandWithArgument(t *testing.T) { 1109 var cli struct { 1110 One struct { 1111 Arg string `arg:""` 1112 } `cmd:"" default:"1"` 1113 } 1114 _, err := kong.New(&cli) 1115 assert.EqualError(t, err, "<anonymous struct>.One: default command one <arg> must not have subcommands or arguments") 1116 } 1117 1118 func TestDefaultCommandWithAllowedArgument(t *testing.T) { 1119 var cli struct { 1120 One struct { 1121 Arg string `arg:""` 1122 Flag string 1123 } `cmd:"" default:"withargs"` 1124 } 1125 p := mustNew(t, &cli) 1126 _, err := p.Parse([]string{"arg", "--flag=value"}) 1127 assert.NoError(t, err) 1128 assert.Equal(t, "arg", cli.One.Arg) 1129 assert.Equal(t, "value", cli.One.Flag) 1130 } 1131 1132 func TestDefaultCommandWithBranchingArgument(t *testing.T) { 1133 var cli struct { 1134 One struct { 1135 Two struct { 1136 Two string `arg:""` 1137 } `arg:""` 1138 } `cmd:"" default:"1"` 1139 } 1140 _, err := kong.New(&cli) 1141 assert.EqualError(t, err, "<anonymous struct>.One: default command one <command> must not have subcommands or arguments") 1142 } 1143 1144 func TestDefaultCommandWithAllowedBranchingArgument(t *testing.T) { 1145 var cli struct { 1146 One struct { 1147 Two struct { 1148 Two string `arg:""` 1149 Flag string 1150 } `arg:""` 1151 } `cmd:"" default:"withargs"` 1152 } 1153 p := mustNew(t, &cli) 1154 _, err := p.Parse([]string{"arg", "--flag=value"}) 1155 assert.NoError(t, err) 1156 assert.Equal(t, "arg", cli.One.Two.Two) 1157 assert.Equal(t, "value", cli.One.Two.Flag) 1158 } 1159 1160 func TestDefaultCommandPrecedence(t *testing.T) { 1161 var cli struct { 1162 Two struct { 1163 Arg string `arg:""` 1164 Flag bool 1165 } `cmd:"" default:"withargs"` 1166 One struct{} `cmd:""` 1167 } 1168 p := mustNew(t, &cli) 1169 1170 // A named command should take precedence over a default command with arg 1171 ctx, err := p.Parse([]string{"one"}) 1172 assert.NoError(t, err) 1173 assert.Equal(t, "one", ctx.Command()) 1174 1175 // An explicitly named command with arg should parse, even if labeled default:"witharg" 1176 ctx, err = p.Parse([]string{"two", "arg"}) 1177 assert.NoError(t, err) 1178 assert.Equal(t, "two <arg>", ctx.Command()) 1179 1180 // An arg to a default command that does not match another command should select the default 1181 ctx, err = p.Parse([]string{"arg"}) 1182 assert.NoError(t, err) 1183 assert.Equal(t, "two <arg>", ctx.Command()) 1184 1185 // A flag on a default command should not be valid on a sibling command 1186 _, err = p.Parse([]string{"one", "--flag"}) 1187 assert.EqualError(t, err, "unknown flag --flag") 1188 } 1189 1190 func TestLoneHpyhen(t *testing.T) { 1191 var cli struct { 1192 Flag string 1193 Arg string `arg:"" optional:""` 1194 } 1195 p := mustNew(t, &cli) 1196 1197 _, err := p.Parse([]string{"-"}) 1198 assert.NoError(t, err) 1199 assert.Equal(t, "-", cli.Arg) 1200 1201 _, err = p.Parse([]string{"--flag", "-"}) 1202 assert.NoError(t, err) 1203 assert.Equal(t, "-", cli.Flag) 1204 } 1205 1206 func TestPlugins(t *testing.T) { 1207 var pluginOne struct { 1208 One string 1209 } 1210 var pluginTwo struct { 1211 Two string 1212 } 1213 var cli struct { 1214 Base string 1215 kong.Plugins 1216 } 1217 cli.Plugins = kong.Plugins{&pluginOne, &pluginTwo} 1218 1219 p := mustNew(t, &cli) 1220 _, err := p.Parse([]string{"--base=base", "--one=one", "--two=two"}) 1221 assert.NoError(t, err) 1222 assert.Equal(t, "base", cli.Base) 1223 assert.Equal(t, "one", pluginOne.One) 1224 assert.Equal(t, "two", pluginTwo.Two) 1225 } 1226 1227 type validateCmd struct{} 1228 1229 func (v *validateCmd) Validate() error { return errors.New("cmd error") } 1230 1231 type validateCli struct { 1232 Cmd validateCmd `cmd:""` 1233 } 1234 1235 func (v *validateCli) Validate() error { return errors.New("app error") } 1236 1237 type validateFlag string 1238 1239 func (v *validateFlag) Validate() error { return errors.New("flag error") } 1240 1241 func TestValidateApp(t *testing.T) { 1242 cli := validateCli{} 1243 p := mustNew(t, &cli) 1244 _, err := p.Parse([]string{}) 1245 assert.EqualError(t, err, "app error") 1246 } 1247 1248 func TestValidateCmd(t *testing.T) { 1249 cli := struct { 1250 Cmd validateCmd `cmd:""` 1251 }{} 1252 p := mustNew(t, &cli) 1253 _, err := p.Parse([]string{"cmd"}) 1254 assert.EqualError(t, err, "cmd: cmd error") 1255 } 1256 1257 func TestValidateFlag(t *testing.T) { 1258 cli := struct { 1259 Flag validateFlag 1260 }{} 1261 p := mustNew(t, &cli) 1262 _, err := p.Parse([]string{"--flag=one"}) 1263 assert.EqualError(t, err, "--flag: flag error") 1264 } 1265 1266 func TestValidateArg(t *testing.T) { 1267 cli := struct { 1268 Arg validateFlag `arg:""` 1269 }{} 1270 p := mustNew(t, &cli) 1271 _, err := p.Parse([]string{"one"}) 1272 assert.EqualError(t, err, "<arg>: flag error") 1273 } 1274 1275 func TestPointers(t *testing.T) { 1276 cli := struct { 1277 Mapped *mappedValue 1278 JSON *jsonUnmarshalerValue 1279 }{} 1280 p := mustNew(t, &cli) 1281 _, err := p.Parse([]string{"--mapped=mapped", "--json=\"foo\""}) 1282 assert.NoError(t, err) 1283 assert.NotZero(t, cli.Mapped) 1284 assert.Equal(t, "mapped", cli.Mapped.decoded) 1285 assert.NotZero(t, cli.JSON) 1286 assert.Equal(t, "FOO", string(*cli.JSON)) 1287 } 1288 1289 type dynamicCommand struct { 1290 Flag string 1291 1292 ran bool 1293 } 1294 1295 func (d *dynamicCommand) Run() error { 1296 d.ran = true 1297 return nil 1298 } 1299 1300 func TestDynamicCommands(t *testing.T) { 1301 cli := struct { 1302 One struct{} `cmd:"one"` 1303 }{} 1304 help := &strings.Builder{} 1305 two := &dynamicCommand{} 1306 three := &dynamicCommand{} 1307 p := mustNew(t, &cli, 1308 kong.DynamicCommand("two", "", "", &two), 1309 kong.DynamicCommand("three", "", "", three, "hidden"), 1310 kong.Writers(help, help), 1311 kong.Exit(func(int) {})) 1312 kctx, err := p.Parse([]string{"two", "--flag=flag"}) 1313 assert.NoError(t, err) 1314 assert.Equal(t, "flag", two.Flag) 1315 assert.False(t, two.ran) 1316 err = kctx.Run() 1317 assert.NoError(t, err) 1318 assert.True(t, two.ran) 1319 1320 _, err = p.Parse([]string{"--help"}) 1321 assert.EqualError(t, err, `expected one of "one", "two"`) 1322 assert.NotContains(t, help.String(), "three", help.String()) 1323 } 1324 1325 func TestDuplicateShortflags(t *testing.T) { 1326 cli := struct { 1327 Flag1 bool `short:"t"` 1328 Flag2 bool `short:"t"` 1329 }{} 1330 _, err := kong.New(&cli) 1331 assert.EqualError(t, err, "<anonymous struct>.Flag2: duplicate short flag -t") 1332 } 1333 1334 func TestDuplicateAliases(t *testing.T) { 1335 cli1 := struct { 1336 Flag1 string `aliases:"flag"` 1337 Flag2 string `aliases:"flag"` 1338 }{} 1339 _, err := kong.New(&cli1) 1340 assert.EqualError(t, err, "<anonymous struct>.Flag2: duplicate flag --flag") 1341 } 1342 1343 func TestSubCommandAliases(t *testing.T) { 1344 type SubC struct { 1345 Flag1 string `aliases:"flag"` 1346 } 1347 1348 cli1 := struct { 1349 Sub1 SubC `cmd:"sub1"` 1350 Sub2 SubC `cmd:"sub2"` 1351 }{} 1352 1353 _, err := kong.New(&cli1) 1354 assert.NoError(t, err, "dupe aliases shouldn't error if they're in separate sub commands") 1355 } 1356 1357 func TestDuplicateAliasLong(t *testing.T) { 1358 cli2 := struct { 1359 Flag string `` 1360 Flag2 string `aliases:"flag"` // duplicates Flag 1361 }{} 1362 _, err := kong.New(&cli2) 1363 assert.EqualError(t, err, "<anonymous struct>.Flag2: duplicate flag --flag") 1364 } 1365 1366 func TestDuplicateNestedShortFlags(t *testing.T) { 1367 cli := struct { 1368 Flag1 bool `short:"t"` 1369 Cmd struct { 1370 Flag2 bool `short:"t"` 1371 } `cmd:""` 1372 }{} 1373 _, err := kong.New(&cli) 1374 assert.EqualError(t, err, "<anonymous struct>.Flag2: duplicate short flag -t") 1375 } 1376 1377 func TestHydratePointerCommandsAndEmbeds(t *testing.T) { 1378 type cmd struct { 1379 Flag bool 1380 } 1381 1382 type embed struct { 1383 Embed bool 1384 } 1385 1386 var cli struct { 1387 Cmd *cmd `cmd:""` 1388 Embed *embed `embed:""` 1389 } 1390 1391 k := mustNew(t, &cli) 1392 _, err := k.Parse([]string{"--embed", "cmd", "--flag"}) 1393 assert.NoError(t, err) 1394 assert.Equal(t, &cmd{Flag: true}, cli.Cmd) 1395 assert.Equal(t, &embed{Embed: true}, cli.Embed) 1396 } 1397 1398 //nolint:revive 1399 type testIgnoreFields struct { 1400 Foo struct { 1401 Bar bool 1402 Sub struct { 1403 SubFlag1 bool `kong:"name=subflag1"` 1404 XXX_SubFlag2 bool `kong:"name=subflag2"` //nolint:stylecheck 1405 } `kong:"cmd"` 1406 } `kong:"cmd"` 1407 XXX_Baz struct { //nolint:stylecheck 1408 Boo bool 1409 } `kong:"cmd,name=baz"` 1410 } 1411 1412 func TestIgnoreRegex(t *testing.T) { 1413 cli := testIgnoreFields{} 1414 1415 k, err := kong.New(&cli, kong.IgnoreFields(`.*\.XXX_.+`)) 1416 assert.NoError(t, err) 1417 1418 _, err = k.Parse([]string{"foo", "sub"}) 1419 assert.NoError(t, err) 1420 1421 _, err = k.Parse([]string{"foo", "sub", "--subflag1"}) 1422 assert.NoError(t, err) 1423 1424 _, err = k.Parse([]string{"foo", "sub", "--subflag2"}) 1425 assert.Error(t, err) 1426 assert.Contains(t, err.Error(), "unknown flag --subflag2") 1427 1428 _, err = k.Parse([]string{"baz"}) 1429 assert.Error(t, err) 1430 assert.Contains(t, err.Error(), "unexpected argument baz") 1431 } 1432 1433 // Verify that passing a nil regex will work 1434 func TestIgnoreRegexEmpty(t *testing.T) { 1435 cli := testIgnoreFields{} 1436 1437 _, err := kong.New(&cli, kong.IgnoreFields("")) 1438 assert.Error(t, err) 1439 assert.Contains(t, "regex input cannot be empty", err.Error()) 1440 } 1441 1442 type optionWithErr struct{} 1443 1444 func (o *optionWithErr) Apply(k *kong.Kong) error { 1445 return errors.New("option returned err") 1446 } 1447 1448 func TestOptionReturnsErr(t *testing.T) { 1449 cli := struct { 1450 Test bool 1451 }{} 1452 1453 optWithError := &optionWithErr{} 1454 1455 _, err := kong.New(cli, optWithError) 1456 assert.Error(t, err) 1457 assert.Equal(t, "option returned err", err.Error()) 1458 } 1459 1460 func TestEnumValidation(t *testing.T) { 1461 tests := []struct { 1462 name string 1463 cli interface{} 1464 fail bool 1465 }{ 1466 { 1467 "Arg", 1468 &struct { 1469 Enum string `arg:"" enum:"one,two"` 1470 }{}, 1471 false, 1472 }, 1473 { 1474 "RequiredArg", 1475 &struct { 1476 Enum string `required:"" arg:"" enum:"one,two"` 1477 }{}, 1478 false, 1479 }, 1480 { 1481 "OptionalArg", 1482 &struct { 1483 Enum string `optional:"" arg:"" enum:"one,two"` 1484 }{}, 1485 true, 1486 }, 1487 { 1488 "RepeatedArgs", 1489 &struct { 1490 Enum []string `arg:"" enum:"one,two"` 1491 }{}, 1492 false, 1493 }, 1494 { 1495 "RequiredRepeatedArgs", 1496 &struct { 1497 Enum []string `required:"" arg:"" enum:"one,two"` 1498 }{}, 1499 false, 1500 }, 1501 { 1502 "OptionalRepeatedArgs", 1503 &struct { 1504 Enum []string `optional:"" arg:"" enum:"one,two"` 1505 }{}, 1506 false, 1507 }, 1508 { 1509 "EnumWithEmptyDefault", 1510 &struct { 1511 Flag string `enum:"one,two," default:""` 1512 }{}, 1513 false, 1514 }, 1515 } 1516 for _, test := range tests { 1517 test := test 1518 t.Run(test.name, func(t *testing.T) { 1519 _, err := kong.New(test.cli) 1520 if test.fail { 1521 assert.Error(t, err, repr.String(test.cli)) 1522 } else { 1523 assert.NoError(t, err, repr.String(test.cli)) 1524 } 1525 }) 1526 } 1527 } 1528 1529 func TestPassthroughCmd(t *testing.T) { 1530 tests := []struct { 1531 name string 1532 args []string 1533 flag string 1534 cmdArgs []string 1535 }{ 1536 { 1537 "Simple", 1538 []string{"--flag", "foobar", "command", "something"}, 1539 "foobar", 1540 []string{"something"}, 1541 }, 1542 { 1543 "DashDash", 1544 []string{"--flag", "foobar", "command", "--", "something"}, 1545 "foobar", 1546 []string{"--", "something"}, 1547 }, 1548 { 1549 "Flag", 1550 []string{"command", "--flag", "foobar"}, 1551 "", 1552 []string{"--flag", "foobar"}, 1553 }, 1554 { 1555 "FlagAndFlag", 1556 []string{"--flag", "foobar", "command", "--flag", "foobar"}, 1557 "foobar", 1558 []string{"--flag", "foobar"}, 1559 }, 1560 { 1561 "NoArgs", 1562 []string{"--flag", "foobar", "command"}, 1563 "foobar", 1564 []string(nil), 1565 }, 1566 } 1567 for _, test := range tests { 1568 test := test 1569 t.Run(test.name, func(t *testing.T) { 1570 var cli struct { 1571 Flag string 1572 Command struct { 1573 Args []string `arg:"" optional:""` 1574 } `cmd:"" passthrough:""` 1575 } 1576 p := mustNew(t, &cli) 1577 _, err := p.Parse(test.args) 1578 assert.NoError(t, err) 1579 assert.Equal(t, test.flag, cli.Flag) 1580 assert.Equal(t, test.cmdArgs, cli.Command.Args) 1581 }) 1582 } 1583 } 1584 1585 func TestPassthroughCmdOnlyArgs(t *testing.T) { 1586 var cli struct { 1587 Command struct { 1588 Flag string 1589 Args []string `arg:"" optional:""` 1590 } `cmd:"" passthrough:""` 1591 } 1592 _, err := kong.New(&cli) 1593 assert.EqualError(t, err, "<anonymous struct>.Command: passthrough command command [<args> ...] [flags] must not have subcommands or flags") 1594 } 1595 1596 func TestPassthroughCmdOnlyStringArgs(t *testing.T) { 1597 var cli struct { 1598 Command struct { 1599 Args []int `arg:"" optional:""` 1600 } `cmd:"" passthrough:""` 1601 } 1602 _, err := kong.New(&cli) 1603 assert.EqualError(t, err, "<anonymous struct>.Command: passthrough command command [<args> ...] must contain exactly one positional argument of []string type") 1604 } 1605 1606 func TestHelpShouldStillWork(t *testing.T) { 1607 type CLI struct { 1608 Dir string `type:"existingdir" default:"missing-dir"` 1609 File string `type:"existingfile" default:"testdata/missing.txt"` 1610 } 1611 var cli CLI 1612 w := &strings.Builder{} 1613 k := mustNew(t, &cli, kong.Writers(w, w)) 1614 rc := -1 // init nonzero to help assert help hook was called 1615 k.Exit = func(i int) { 1616 rc = i 1617 } 1618 _, err := k.Parse([]string{"--help"}) 1619 t.Log(w.String()) 1620 // checking return code validates the help hook was called 1621 assert.Zero(t, rc) 1622 // allow for error propagation from other validation (only for the 1623 // sake of this test, due to the exit function not actually exiting the 1624 // program; errors will not propagate in the real world). 1625 assert.Error(t, err) 1626 } 1627 1628 func TestVersionFlagShouldStillWork(t *testing.T) { 1629 type CLI struct { 1630 Dir string `type:"existingdir" default:"missing-dir"` 1631 File string `type:"existingfile" default:"testdata/missing.txt"` 1632 Version kong.VersionFlag 1633 } 1634 var cli CLI 1635 w := &strings.Builder{} 1636 k := mustNew(t, &cli, kong.Writers(w, w)) 1637 rc := -1 // init nonzero to help assert help hook was called 1638 k.Exit = func(i int) { 1639 rc = i 1640 } 1641 _, err := k.Parse([]string{"--version"}) 1642 t.Log(w.String()) 1643 // checking return code validates the help hook was called 1644 assert.Zero(t, rc) 1645 // allow for error propagation from other validation (only for the 1646 // sake of this test, due to the exit function not actually exiting the 1647 // program; errors will not propagate in the real world). 1648 assert.Error(t, err) 1649 } 1650 1651 func TestSliceDecoderHelpfulErrorMsg(t *testing.T) { 1652 tests := []struct { 1653 name string 1654 cli interface{} 1655 args []string 1656 err string 1657 }{ 1658 { 1659 "DefaultRune", 1660 &struct { 1661 Stuff []string 1662 }{}, 1663 []string{"--stuff"}, 1664 `--stuff: missing value, expecting "<arg>,..."`, 1665 }, 1666 { 1667 "SpecifiedRune", 1668 &struct { 1669 Stuff []string `sep:","` 1670 }{}, 1671 []string{"--stuff"}, 1672 `--stuff: missing value, expecting "<arg>,..."`, 1673 }, 1674 { 1675 "SpaceRune", 1676 &struct { 1677 Stuff []string `sep:" "` 1678 }{}, 1679 []string{"--stuff"}, 1680 `--stuff: missing value, expecting "<arg> ..."`, 1681 }, 1682 { 1683 "OtherRune", 1684 &struct { 1685 Stuff []string `sep:"_"` 1686 }{}, 1687 []string{"--stuff"}, 1688 `--stuff: missing value, expecting "<arg>_..."`, 1689 }, 1690 } 1691 for _, test := range tests { 1692 test := test 1693 t.Run(test.name, func(t *testing.T) { 1694 p := mustNew(t, test.cli) 1695 _, err := p.Parse(test.args) 1696 assert.EqualError(t, err, test.err) 1697 }) 1698 } 1699 } 1700 1701 func TestMapDecoderHelpfulErrorMsg(t *testing.T) { 1702 tests := []struct { 1703 name string 1704 cli interface{} 1705 args []string 1706 expected string 1707 }{ 1708 { 1709 "DefaultRune", 1710 &struct { 1711 Stuff map[string]int 1712 }{}, 1713 []string{"--stuff"}, 1714 `--stuff: missing value, expecting "<key>=<value>;..."`, 1715 }, 1716 { 1717 "SpecifiedRune", 1718 &struct { 1719 Stuff map[string]int `mapsep:";"` 1720 }{}, 1721 []string{"--stuff"}, 1722 `--stuff: missing value, expecting "<key>=<value>;..."`, 1723 }, 1724 { 1725 "SpaceRune", 1726 &struct { 1727 Stuff map[string]int `mapsep:" "` 1728 }{}, 1729 []string{"--stuff"}, 1730 `--stuff: missing value, expecting "<key>=<value> ..."`, 1731 }, 1732 { 1733 "OtherRune", 1734 &struct { 1735 Stuff map[string]int `mapsep:","` 1736 }{}, 1737 []string{"--stuff"}, 1738 `--stuff: missing value, expecting "<key>=<value>,..."`, 1739 }, 1740 } 1741 for _, test := range tests { 1742 test := test 1743 t.Run(test.name, func(t *testing.T) { 1744 p := mustNew(t, test.cli) 1745 _, err := p.Parse(test.args) 1746 assert.EqualError(t, err, test.expected) 1747 }) 1748 } 1749 } 1750 1751 func TestDuplicateName(t *testing.T) { 1752 var cli struct { 1753 DupA struct{} `cmd:"" name:"duplicate"` 1754 DupB struct{} `cmd:"" name:"duplicate"` 1755 } 1756 _, err := kong.New(&cli) 1757 assert.Error(t, err) 1758 } 1759 1760 func TestDuplicateChildName(t *testing.T) { 1761 var cli struct { 1762 A struct { 1763 DupA struct{} `cmd:"" name:"duplicate"` 1764 DupB struct{} `cmd:"" name:"duplicate"` 1765 } `cmd:""` 1766 B struct{} `cmd:""` 1767 } 1768 _, err := kong.New(&cli) 1769 assert.Error(t, err) 1770 } 1771 1772 func TestChildNameCanBeDuplicated(t *testing.T) { 1773 var cli struct { 1774 A struct { 1775 A struct{} `cmd:"" name:"duplicateA"` 1776 B struct{} `cmd:"" name:"duplicateB"` 1777 } `cmd:"" name:"duplicateA"` 1778 B struct{} `cmd:"" name:"duplicateB"` 1779 } 1780 mustNew(t, &cli) 1781 } 1782 1783 func TestCumulativeArgumentLast(t *testing.T) { 1784 var cli struct { 1785 Arg1 string `arg:""` 1786 Arg2 []string `arg:""` 1787 } 1788 _, err := kong.New(&cli) 1789 assert.NoError(t, err) 1790 } 1791 1792 func TestCumulativeArgumentNotLast(t *testing.T) { 1793 var cli struct { 1794 Arg2 []string `arg:""` 1795 Arg1 string `arg:""` 1796 } 1797 _, err := kong.New(&cli) 1798 assert.Error(t, err) 1799 } 1800 1801 func TestStringPointer(t *testing.T) { 1802 var cli struct { 1803 Foo *string 1804 } 1805 k, err := kong.New(&cli) 1806 assert.NoError(t, err) 1807 assert.NotZero(t, k) 1808 ctx, err := k.Parse([]string{"--foo", "wtf"}) 1809 assert.NoError(t, err) 1810 assert.NotZero(t, ctx) 1811 assert.NotZero(t, cli.Foo) 1812 assert.Equal(t, "wtf", *cli.Foo) 1813 } 1814 1815 func TestStringPointerNoValue(t *testing.T) { 1816 var cli struct { 1817 Foo *string 1818 } 1819 k, err := kong.New(&cli) 1820 assert.NoError(t, err) 1821 assert.NotZero(t, k) 1822 ctx, err := k.Parse([]string{}) 1823 assert.NoError(t, err) 1824 assert.NotZero(t, ctx) 1825 assert.Zero(t, cli.Foo) 1826 } 1827 1828 func TestStringPointerDefault(t *testing.T) { 1829 var cli struct { 1830 Foo *string `default:"stuff"` 1831 } 1832 k, err := kong.New(&cli) 1833 assert.NoError(t, err) 1834 assert.NotZero(t, k) 1835 ctx, err := k.Parse([]string{}) 1836 assert.NoError(t, err) 1837 assert.NotZero(t, ctx) 1838 assert.NotZero(t, cli.Foo) 1839 assert.Equal(t, "stuff", *cli.Foo) 1840 } 1841 1842 func TestStringPointerAliasNoValue(t *testing.T) { 1843 type Foo string 1844 var cli struct { 1845 F *Foo 1846 } 1847 k, err := kong.New(&cli) 1848 assert.NoError(t, err) 1849 assert.NotZero(t, k) 1850 ctx, err := k.Parse([]string{}) 1851 assert.NoError(t, err) 1852 assert.NotZero(t, ctx) 1853 assert.Zero(t, cli.F) 1854 } 1855 1856 func TestStringPointerAlias(t *testing.T) { 1857 type Foo string 1858 var cli struct { 1859 F *Foo 1860 } 1861 k, err := kong.New(&cli) 1862 assert.NoError(t, err) 1863 assert.NotZero(t, k) 1864 ctx, err := k.Parse([]string{"--f=value"}) 1865 assert.NoError(t, err) 1866 assert.NotZero(t, ctx) 1867 assert.NotZero(t, cli.F) 1868 assert.Equal(t, Foo("value"), *cli.F) 1869 } 1870 1871 func TestStringPointerEmptyValue(t *testing.T) { 1872 var cli struct { 1873 F *string 1874 G *string 1875 } 1876 k, err := kong.New(&cli) 1877 assert.NoError(t, err) 1878 assert.NotZero(t, k) 1879 ctx, err := k.Parse([]string{"--f", "", "--g="}) 1880 assert.NoError(t, err) 1881 assert.NotZero(t, ctx) 1882 assert.NotZero(t, cli.F) 1883 assert.NotZero(t, cli.G) 1884 assert.Equal(t, "", *cli.F) 1885 assert.Equal(t, "", *cli.G) 1886 } 1887 1888 func TestIntPtr(t *testing.T) { 1889 var cli struct { 1890 F *int 1891 G *int 1892 } 1893 k, err := kong.New(&cli) 1894 assert.NoError(t, err) 1895 assert.NotZero(t, k) 1896 ctx, err := k.Parse([]string{"--f=6"}) 1897 assert.NoError(t, err) 1898 assert.NotZero(t, ctx) 1899 assert.NotZero(t, cli.F) 1900 assert.Zero(t, cli.G) 1901 assert.Equal(t, 6, *cli.F) 1902 } 1903 1904 func TestBoolPtr(t *testing.T) { 1905 var cli struct { 1906 X *bool 1907 } 1908 k, err := kong.New(&cli) 1909 assert.NoError(t, err) 1910 assert.NotZero(t, k) 1911 ctx, err := k.Parse([]string{"--x"}) 1912 assert.NoError(t, err) 1913 assert.NotZero(t, ctx) 1914 assert.NotZero(t, cli.X) 1915 assert.Equal(t, true, *cli.X) 1916 } 1917 1918 func TestBoolPtrFalse(t *testing.T) { 1919 var cli struct { 1920 X *bool 1921 } 1922 k, err := kong.New(&cli) 1923 assert.NoError(t, err) 1924 assert.NotZero(t, k) 1925 ctx, err := k.Parse([]string{"--x=false"}) 1926 assert.NoError(t, err) 1927 assert.NotZero(t, ctx) 1928 assert.NotZero(t, cli.X) 1929 assert.Equal(t, false, *cli.X) 1930 } 1931 1932 func TestBoolPtrNegated(t *testing.T) { 1933 var cli struct { 1934 X *bool `negatable:""` 1935 } 1936 k, err := kong.New(&cli) 1937 assert.NoError(t, err) 1938 assert.NotZero(t, k) 1939 ctx, err := k.Parse([]string{"--no-x"}) 1940 assert.NoError(t, err) 1941 assert.NotZero(t, ctx) 1942 assert.NotZero(t, cli.X) 1943 assert.Equal(t, false, *cli.X) 1944 } 1945 1946 func TestNilNegatableBoolPtr(t *testing.T) { 1947 var cli struct { 1948 X *bool `negatable:""` 1949 } 1950 k, err := kong.New(&cli) 1951 assert.NoError(t, err) 1952 assert.NotZero(t, k) 1953 ctx, err := k.Parse([]string{}) 1954 assert.NoError(t, err) 1955 assert.NotZero(t, ctx) 1956 assert.Zero(t, cli.X) 1957 } 1958 1959 func TestBoolPtrNil(t *testing.T) { 1960 var cli struct { 1961 X *bool 1962 } 1963 k, err := kong.New(&cli) 1964 assert.NoError(t, err) 1965 assert.NotZero(t, k) 1966 ctx, err := k.Parse([]string{}) 1967 assert.NoError(t, err) 1968 assert.NotZero(t, ctx) 1969 assert.Zero(t, cli.X) 1970 } 1971 1972 func TestUnsupportedPtr(t *testing.T) { 1973 type Foo struct { 1974 x int //nolint 1975 y int //nolint 1976 } 1977 1978 var cli struct { 1979 F *Foo 1980 } 1981 k, err := kong.New(&cli) 1982 assert.NoError(t, err) 1983 assert.NotZero(t, k) 1984 ctx, err := k.Parse([]string{"--f=whatever"}) 1985 assert.Zero(t, ctx) 1986 assert.Error(t, err) 1987 assert.Equal(t, "--f: cannot find mapper for kong_test.Foo", err.Error()) 1988 } 1989 1990 func TestEnumPtr(t *testing.T) { 1991 var cli struct { 1992 X *string `enum:"A,B,C" default:"C"` 1993 } 1994 k, err := kong.New(&cli) 1995 assert.NoError(t, err) 1996 assert.NotZero(t, k) 1997 ctx, err := k.Parse([]string{"--x=A"}) 1998 assert.NoError(t, err) 1999 assert.NotZero(t, ctx) 2000 assert.NotZero(t, cli.X) 2001 assert.Equal(t, "A", *cli.X) 2002 } 2003 2004 func TestEnumPtrOmitted(t *testing.T) { 2005 var cli struct { 2006 X *string `enum:"A,B,C" default:"C"` 2007 } 2008 k, err := kong.New(&cli) 2009 assert.NoError(t, err) 2010 assert.NotZero(t, k) 2011 ctx, err := k.Parse([]string{}) 2012 assert.NoError(t, err) 2013 assert.NotZero(t, ctx) 2014 assert.NotZero(t, cli.X) 2015 assert.Equal(t, "C", *cli.X) 2016 } 2017 2018 func TestEnumPtrOmittedNoDefault(t *testing.T) { 2019 var cli struct { 2020 X *string `enum:"A,B,C"` 2021 } 2022 k, err := kong.New(&cli) 2023 assert.NoError(t, err) 2024 assert.NotZero(t, k) 2025 ctx, err := k.Parse([]string{}) 2026 assert.NoError(t, err) 2027 assert.NotZero(t, ctx) 2028 assert.Zero(t, cli.X) 2029 }