1package cli 2 3import ( 4 "bytes" 5 "errors" 6 "flag" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "reflect" 12 "strings" 13 "testing" 14) 15 16var ( 17 lastExitCode = 0 18 fakeOsExiter = func(rc int) { 19 lastExitCode = rc 20 } 21 fakeErrWriter = &bytes.Buffer{} 22) 23 24func init() { 25 OsExiter = fakeOsExiter 26 ErrWriter = fakeErrWriter 27} 28 29type opCounts struct { 30 Total, BashComplete, OnUsageError, Before, CommandNotFound, Action, After, SubCommand int 31} 32 33func ExampleApp_Run() { 34 // set args for examples sake 35 os.Args = []string{"greet", "--name", "Jeremy"} 36 37 app := NewApp() 38 app.Name = "greet" 39 app.Flags = []Flag{ 40 StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, 41 } 42 app.Action = func(c *Context) error { 43 fmt.Printf("Hello %v\n", c.String("name")) 44 return nil 45 } 46 app.UsageText = "app [first_arg] [second_arg]" 47 app.Author = "Harrison" 48 app.Email = "harrison@lolwut.com" 49 app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} 50 _ = app.Run(os.Args) 51 // Output: 52 // Hello Jeremy 53} 54 55func ExampleApp_Run_subcommand() { 56 // set args for examples sake 57 os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} 58 app := NewApp() 59 app.Name = "say" 60 app.Commands = []Command{ 61 { 62 Name: "hello", 63 Aliases: []string{"hi"}, 64 Usage: "use it to see a description", 65 Description: "This is how we describe hello the function", 66 Subcommands: []Command{ 67 { 68 Name: "english", 69 Aliases: []string{"en"}, 70 Usage: "sends a greeting in english", 71 Description: "greets someone in english", 72 Flags: []Flag{ 73 StringFlag{ 74 Name: "name", 75 Value: "Bob", 76 Usage: "Name of the person to greet", 77 }, 78 }, 79 Action: func(c *Context) error { 80 fmt.Println("Hello,", c.String("name")) 81 return nil 82 }, 83 }, 84 }, 85 }, 86 } 87 88 _ = app.Run(os.Args) 89 // Output: 90 // Hello, Jeremy 91} 92 93func ExampleApp_Run_appHelp() { 94 // set args for examples sake 95 os.Args = []string{"greet", "help"} 96 97 app := NewApp() 98 app.Name = "greet" 99 app.Version = "0.1.0" 100 app.Description = "This is how we describe greet the app" 101 app.Authors = []Author{ 102 {Name: "Harrison", Email: "harrison@lolwut.com"}, 103 {Name: "Oliver Allen", Email: "oliver@toyshop.com"}, 104 } 105 app.Flags = []Flag{ 106 StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, 107 } 108 app.Commands = []Command{ 109 { 110 Name: "describeit", 111 Aliases: []string{"d"}, 112 Usage: "use it to see a description", 113 Description: "This is how we describe describeit the function", 114 Action: func(c *Context) error { 115 fmt.Printf("i like to describe things") 116 return nil 117 }, 118 }, 119 } 120 _ = app.Run(os.Args) 121 // Output: 122 // NAME: 123 // greet - A new cli application 124 // 125 // USAGE: 126 // greet [global options] command [command options] [arguments...] 127 // 128 // VERSION: 129 // 0.1.0 130 // 131 // DESCRIPTION: 132 // This is how we describe greet the app 133 // 134 // AUTHORS: 135 // Harrison <harrison@lolwut.com> 136 // Oliver Allen <oliver@toyshop.com> 137 // 138 // COMMANDS: 139 // describeit, d use it to see a description 140 // help, h Shows a list of commands or help for one command 141 // 142 // GLOBAL OPTIONS: 143 // --name value a name to say (default: "bob") 144 // --help, -h show help 145 // --version, -v print the version 146} 147 148func ExampleApp_Run_commandHelp() { 149 // set args for examples sake 150 os.Args = []string{"greet", "h", "describeit"} 151 152 app := NewApp() 153 app.Name = "greet" 154 app.Flags = []Flag{ 155 StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, 156 } 157 app.Commands = []Command{ 158 { 159 Name: "describeit", 160 Aliases: []string{"d"}, 161 Usage: "use it to see a description", 162 Description: "This is how we describe describeit the function", 163 Action: func(c *Context) error { 164 fmt.Printf("i like to describe things") 165 return nil 166 }, 167 }, 168 } 169 _ = app.Run(os.Args) 170 // Output: 171 // NAME: 172 // greet describeit - use it to see a description 173 // 174 // USAGE: 175 // greet describeit [arguments...] 176 // 177 // DESCRIPTION: 178 // This is how we describe describeit the function 179} 180 181func ExampleApp_Run_noAction() { 182 app := App{} 183 app.Name = "greet" 184 _ = app.Run([]string{"greet"}) 185 // Output: 186 // NAME: 187 // greet 188 // 189 // USAGE: 190 // [global options] command [command options] [arguments...] 191 // 192 // COMMANDS: 193 // help, h Shows a list of commands or help for one command 194 // 195 // GLOBAL OPTIONS: 196 // --help, -h show help 197} 198 199func ExampleApp_Run_subcommandNoAction() { 200 app := App{} 201 app.Name = "greet" 202 app.Commands = []Command{ 203 { 204 Name: "describeit", 205 Aliases: []string{"d"}, 206 Usage: "use it to see a description", 207 Description: "This is how we describe describeit the function", 208 }, 209 } 210 _ = app.Run([]string{"greet", "describeit"}) 211 // Output: 212 // NAME: 213 // describeit - use it to see a description 214 // 215 // USAGE: 216 // describeit [arguments...] 217 // 218 // DESCRIPTION: 219 // This is how we describe describeit the function 220 221} 222 223func ExampleApp_Run_bashComplete_withShortFlag() { 224 os.Args = []string{"greet", "-", "--generate-bash-completion"} 225 226 app := NewApp() 227 app.Name = "greet" 228 app.EnableBashCompletion = true 229 app.Flags = []Flag{ 230 IntFlag{ 231 Name: "other,o", 232 }, 233 StringFlag{ 234 Name: "xyz,x", 235 }, 236 } 237 238 _ = app.Run(os.Args) 239 // Output: 240 // --other 241 // -o 242 // --xyz 243 // -x 244 // --help 245 // -h 246} 247 248func ExampleApp_Run_bashComplete_withLongFlag() { 249 os.Args = []string{"greet", "--s", "--generate-bash-completion"} 250 251 app := NewApp() 252 app.Name = "greet" 253 app.EnableBashCompletion = true 254 app.Flags = []Flag{ 255 IntFlag{ 256 Name: "other,o", 257 }, 258 StringFlag{ 259 Name: "xyz,x", 260 }, 261 StringFlag{ 262 Name: "some-flag,s", 263 }, 264 StringFlag{ 265 Name: "similar-flag", 266 }, 267 } 268 269 _ = app.Run(os.Args) 270 // Output: 271 // --some-flag 272 // --similar-flag 273} 274func ExampleApp_Run_bashComplete_withMultipleLongFlag() { 275 os.Args = []string{"greet", "--st", "--generate-bash-completion"} 276 277 app := NewApp() 278 app.Name = "greet" 279 app.EnableBashCompletion = true 280 app.Flags = []Flag{ 281 IntFlag{ 282 Name: "int-flag,i", 283 }, 284 StringFlag{ 285 Name: "string,s", 286 }, 287 StringFlag{ 288 Name: "string-flag-2", 289 }, 290 StringFlag{ 291 Name: "similar-flag", 292 }, 293 StringFlag{ 294 Name: "some-flag", 295 }, 296 } 297 298 _ = app.Run(os.Args) 299 // Output: 300 // --string 301 // --string-flag-2 302} 303 304func ExampleApp_Run_bashComplete() { 305 // set args for examples sake 306 os.Args = []string{"greet", "--generate-bash-completion"} 307 308 app := NewApp() 309 app.Name = "greet" 310 app.EnableBashCompletion = true 311 app.Commands = []Command{ 312 { 313 Name: "describeit", 314 Aliases: []string{"d"}, 315 Usage: "use it to see a description", 316 Description: "This is how we describe describeit the function", 317 Action: func(c *Context) error { 318 fmt.Printf("i like to describe things") 319 return nil 320 }, 321 }, { 322 Name: "next", 323 Usage: "next example", 324 Description: "more stuff to see when generating bash completion", 325 Action: func(c *Context) error { 326 fmt.Printf("the next example") 327 return nil 328 }, 329 }, 330 } 331 332 _ = app.Run(os.Args) 333 // Output: 334 // describeit 335 // d 336 // next 337 // help 338 // h 339} 340 341func ExampleApp_Run_zshComplete() { 342 // set args for examples sake 343 os.Args = []string{"greet", "--generate-bash-completion"} 344 _ = os.Setenv("_CLI_ZSH_AUTOCOMPLETE_HACK", "1") 345 346 app := NewApp() 347 app.Name = "greet" 348 app.EnableBashCompletion = true 349 app.Commands = []Command{ 350 { 351 Name: "describeit", 352 Aliases: []string{"d"}, 353 Usage: "use it to see a description", 354 Description: "This is how we describe describeit the function", 355 Action: func(c *Context) error { 356 fmt.Printf("i like to describe things") 357 return nil 358 }, 359 }, { 360 Name: "next", 361 Usage: "next example", 362 Description: "more stuff to see when generating bash completion", 363 Action: func(c *Context) error { 364 fmt.Printf("the next example") 365 return nil 366 }, 367 }, 368 } 369 370 _ = app.Run(os.Args) 371 // Output: 372 // describeit:use it to see a description 373 // d:use it to see a description 374 // next:next example 375 // help:Shows a list of commands or help for one command 376 // h:Shows a list of commands or help for one command 377} 378 379func TestApp_Run(t *testing.T) { 380 s := "" 381 382 app := NewApp() 383 app.Action = func(c *Context) error { 384 s = s + c.Args().First() 385 return nil 386 } 387 388 err := app.Run([]string{"command", "foo"}) 389 expect(t, err, nil) 390 err = app.Run([]string{"command", "bar"}) 391 expect(t, err, nil) 392 expect(t, s, "foobar") 393} 394 395var commandAppTests = []struct { 396 name string 397 expected bool 398}{ 399 {"foobar", true}, 400 {"batbaz", true}, 401 {"b", true}, 402 {"f", true}, 403 {"bat", false}, 404 {"nothing", false}, 405} 406 407func TestApp_Command(t *testing.T) { 408 app := NewApp() 409 fooCommand := Command{Name: "foobar", Aliases: []string{"f"}} 410 batCommand := Command{Name: "batbaz", Aliases: []string{"b"}} 411 app.Commands = []Command{ 412 fooCommand, 413 batCommand, 414 } 415 416 for _, test := range commandAppTests { 417 expect(t, app.Command(test.name) != nil, test.expected) 418 } 419} 420 421func TestApp_Setup_defaultsWriter(t *testing.T) { 422 app := &App{} 423 app.Setup() 424 expect(t, app.Writer, os.Stdout) 425} 426 427func TestApp_CommandWithArgBeforeFlags(t *testing.T) { 428 var parsedOption, firstArg string 429 430 app := NewApp() 431 command := Command{ 432 Name: "cmd", 433 Flags: []Flag{ 434 StringFlag{Name: "option", Value: "", Usage: "some option"}, 435 }, 436 Action: func(c *Context) error { 437 parsedOption = c.String("option") 438 firstArg = c.Args().First() 439 return nil 440 }, 441 } 442 app.Commands = []Command{command} 443 444 _ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) 445 446 expect(t, parsedOption, "my-option") 447 expect(t, firstArg, "my-arg") 448} 449 450func TestApp_CommandWithArgBeforeBoolFlags(t *testing.T) { 451 var parsedOption, parsedSecondOption, firstArg string 452 var parsedBool, parsedSecondBool bool 453 454 app := NewApp() 455 command := Command{ 456 Name: "cmd", 457 Flags: []Flag{ 458 StringFlag{Name: "option", Value: "", Usage: "some option"}, 459 StringFlag{Name: "secondOption", Value: "", Usage: "another option"}, 460 BoolFlag{Name: "boolflag", Usage: "some bool"}, 461 BoolFlag{Name: "b", Usage: "another bool"}, 462 }, 463 Action: func(c *Context) error { 464 parsedOption = c.String("option") 465 parsedSecondOption = c.String("secondOption") 466 parsedBool = c.Bool("boolflag") 467 parsedSecondBool = c.Bool("b") 468 firstArg = c.Args().First() 469 return nil 470 }, 471 } 472 app.Commands = []Command{command} 473 474 _ = app.Run([]string{"", "cmd", "my-arg", "--boolflag", "--option", "my-option", "-b", "--secondOption", "fancy-option"}) 475 476 expect(t, parsedOption, "my-option") 477 expect(t, parsedSecondOption, "fancy-option") 478 expect(t, parsedBool, true) 479 expect(t, parsedSecondBool, true) 480 expect(t, firstArg, "my-arg") 481} 482 483func TestApp_RunAsSubcommandParseFlags(t *testing.T) { 484 var context *Context 485 486 a := NewApp() 487 a.Commands = []Command{ 488 { 489 Name: "foo", 490 Action: func(c *Context) error { 491 context = c 492 return nil 493 }, 494 Flags: []Flag{ 495 StringFlag{ 496 Name: "lang", 497 Value: "english", 498 Usage: "language for the greeting", 499 }, 500 }, 501 Before: func(_ *Context) error { return nil }, 502 }, 503 } 504 _ = a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) 505 506 expect(t, context.Args().Get(0), "abcd") 507 expect(t, context.String("lang"), "spanish") 508} 509 510func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) { 511 a := App{ 512 Flags: []Flag{ 513 StringFlag{Name: "--foo"}, 514 }, 515 Writer: bytes.NewBufferString(""), 516 } 517 518 set := flag.NewFlagSet("", flag.ContinueOnError) 519 _ = set.Parse([]string{"", "---foo"}) 520 c := &Context{flagSet: set} 521 522 err := a.RunAsSubcommand(c) 523 524 expect(t, err, errors.New("bad flag syntax: ---foo")) 525} 526 527func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { 528 var parsedOption string 529 var args []string 530 531 app := NewApp() 532 command := Command{ 533 Name: "cmd", 534 Flags: []Flag{ 535 StringFlag{Name: "option", Value: "", Usage: "some option"}, 536 }, 537 Action: func(c *Context) error { 538 parsedOption = c.String("option") 539 args = c.Args() 540 return nil 541 }, 542 } 543 app.Commands = []Command{command} 544 545 _ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"}) 546 547 expect(t, parsedOption, "my-option") 548 expect(t, args[0], "my-arg") 549 expect(t, args[1], "--") 550 expect(t, args[2], "--notARealFlag") 551} 552 553func TestApp_CommandWithDash(t *testing.T) { 554 var args []string 555 556 app := NewApp() 557 command := Command{ 558 Name: "cmd", 559 Action: func(c *Context) error { 560 args = c.Args() 561 return nil 562 }, 563 } 564 app.Commands = []Command{command} 565 566 _ = app.Run([]string{"", "cmd", "my-arg", "-"}) 567 568 expect(t, args[0], "my-arg") 569 expect(t, args[1], "-") 570} 571 572func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { 573 var args []string 574 575 app := NewApp() 576 command := Command{ 577 Name: "cmd", 578 Action: func(c *Context) error { 579 args = c.Args() 580 return nil 581 }, 582 } 583 app.Commands = []Command{command} 584 585 _ = app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) 586 587 expect(t, args[0], "my-arg") 588 expect(t, args[1], "--") 589 expect(t, args[2], "notAFlagAtAll") 590} 591 592func TestApp_VisibleCommands(t *testing.T) { 593 app := NewApp() 594 app.Commands = []Command{ 595 { 596 Name: "frob", 597 HelpName: "foo frob", 598 Action: func(_ *Context) error { return nil }, 599 }, 600 { 601 Name: "frib", 602 HelpName: "foo frib", 603 Hidden: true, 604 Action: func(_ *Context) error { return nil }, 605 }, 606 } 607 608 app.Setup() 609 expected := []Command{ 610 app.Commands[0], 611 app.Commands[2], // help 612 } 613 actual := app.VisibleCommands() 614 expect(t, len(expected), len(actual)) 615 for i, actualCommand := range actual { 616 expectedCommand := expected[i] 617 618 if expectedCommand.Action != nil { 619 // comparing func addresses is OK! 620 expect(t, fmt.Sprintf("%p", expectedCommand.Action), fmt.Sprintf("%p", actualCommand.Action)) 621 } 622 623 // nil out funcs, as they cannot be compared 624 // (https://github.com/golang/go/issues/8554) 625 expectedCommand.Action = nil 626 actualCommand.Action = nil 627 628 if !reflect.DeepEqual(expectedCommand, actualCommand) { 629 t.Errorf("expected\n%#v\n!=\n%#v", expectedCommand, actualCommand) 630 } 631 } 632} 633 634func TestApp_UseShortOptionHandling(t *testing.T) { 635 var one, two bool 636 var name string 637 expected := "expectedName" 638 639 app := NewApp() 640 app.UseShortOptionHandling = true 641 app.Flags = []Flag{ 642 BoolFlag{Name: "one, o"}, 643 BoolFlag{Name: "two, t"}, 644 StringFlag{Name: "name, n"}, 645 } 646 app.Action = func(c *Context) error { 647 one = c.Bool("one") 648 two = c.Bool("two") 649 name = c.String("name") 650 return nil 651 } 652 653 app.Run([]string{"", "-on", expected}) 654 expect(t, one, true) 655 expect(t, two, false) 656 expect(t, name, expected) 657} 658 659func TestApp_UseShortOptionHandling_missing_value(t *testing.T) { 660 app := NewApp() 661 app.UseShortOptionHandling = true 662 app.Flags = []Flag{ 663 StringFlag{Name: "name, n"}, 664 } 665 666 err := app.Run([]string{"", "-n"}) 667 expect(t, err, errors.New("flag needs an argument: -n")) 668} 669 670func TestApp_UseShortOptionHandlingCommand(t *testing.T) { 671 var one, two bool 672 var name string 673 expected := "expectedName" 674 675 app := NewApp() 676 app.UseShortOptionHandling = true 677 command := Command{ 678 Name: "cmd", 679 Flags: []Flag{ 680 BoolFlag{Name: "one, o"}, 681 BoolFlag{Name: "two, t"}, 682 StringFlag{Name: "name, n"}, 683 }, 684 Action: func(c *Context) error { 685 one = c.Bool("one") 686 two = c.Bool("two") 687 name = c.String("name") 688 return nil 689 }, 690 } 691 app.Commands = []Command{command} 692 693 app.Run([]string{"", "cmd", "-on", expected}) 694 expect(t, one, true) 695 expect(t, two, false) 696 expect(t, name, expected) 697} 698 699func TestApp_UseShortOptionHandlingCommand_missing_value(t *testing.T) { 700 app := NewApp() 701 app.UseShortOptionHandling = true 702 command := Command{ 703 Name: "cmd", 704 Flags: []Flag{ 705 StringFlag{Name: "name, n"}, 706 }, 707 } 708 app.Commands = []Command{command} 709 710 err := app.Run([]string{"", "cmd", "-n"}) 711 expect(t, err, errors.New("flag needs an argument: -n")) 712} 713 714func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) { 715 var one, two bool 716 var name string 717 expected := "expectedName" 718 719 app := NewApp() 720 app.UseShortOptionHandling = true 721 command := Command{ 722 Name: "cmd", 723 } 724 subCommand := Command{ 725 Name: "sub", 726 Flags: []Flag{ 727 BoolFlag{Name: "one, o"}, 728 BoolFlag{Name: "two, t"}, 729 StringFlag{Name: "name, n"}, 730 }, 731 Action: func(c *Context) error { 732 one = c.Bool("one") 733 two = c.Bool("two") 734 name = c.String("name") 735 return nil 736 }, 737 } 738 command.Subcommands = []Command{subCommand} 739 app.Commands = []Command{command} 740 741 err := app.Run([]string{"", "cmd", "sub", "-on", expected}) 742 expect(t, err, nil) 743 expect(t, one, true) 744 expect(t, two, false) 745 expect(t, name, expected) 746} 747 748func TestApp_UseShortOptionHandlingSubCommand_missing_value(t *testing.T) { 749 app := NewApp() 750 app.UseShortOptionHandling = true 751 command := Command{ 752 Name: "cmd", 753 } 754 subCommand := Command{ 755 Name: "sub", 756 Flags: []Flag{ 757 StringFlag{Name: "name, n"}, 758 }, 759 } 760 command.Subcommands = []Command{subCommand} 761 app.Commands = []Command{command} 762 763 err := app.Run([]string{"", "cmd", "sub", "-n"}) 764 expect(t, err, errors.New("flag needs an argument: -n")) 765} 766 767func TestApp_Float64Flag(t *testing.T) { 768 var meters float64 769 770 app := NewApp() 771 app.Flags = []Flag{ 772 Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, 773 } 774 app.Action = func(c *Context) error { 775 meters = c.Float64("height") 776 return nil 777 } 778 779 _ = app.Run([]string{"", "--height", "1.93"}) 780 expect(t, meters, 1.93) 781} 782 783func TestApp_ParseSliceFlags(t *testing.T) { 784 var parsedIntSlice []int 785 var parsedStringSlice []string 786 787 app := NewApp() 788 command := Command{ 789 Name: "cmd", 790 Flags: []Flag{ 791 IntSliceFlag{Name: "p", Value: &IntSlice{}, Usage: "set one or more ip addr"}, 792 StringSliceFlag{Name: "ip", Value: &StringSlice{}, Usage: "set one or more ports to open"}, 793 }, 794 Action: func(c *Context) error { 795 parsedIntSlice = c.IntSlice("p") 796 parsedStringSlice = c.StringSlice("ip") 797 return nil 798 }, 799 } 800 app.Commands = []Command{command} 801 802 _ = app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"}) 803 804 IntsEquals := func(a, b []int) bool { 805 if len(a) != len(b) { 806 return false 807 } 808 for i, v := range a { 809 if v != b[i] { 810 return false 811 } 812 } 813 return true 814 } 815 816 StrsEquals := func(a, b []string) bool { 817 if len(a) != len(b) { 818 return false 819 } 820 for i, v := range a { 821 if v != b[i] { 822 return false 823 } 824 } 825 return true 826 } 827 var expectedIntSlice = []int{22, 80} 828 var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"} 829 830 if !IntsEquals(parsedIntSlice, expectedIntSlice) { 831 t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice) 832 } 833 834 if !StrsEquals(parsedStringSlice, expectedStringSlice) { 835 t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice) 836 } 837} 838 839func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { 840 var parsedIntSlice []int 841 var parsedStringSlice []string 842 843 app := NewApp() 844 command := Command{ 845 Name: "cmd", 846 Flags: []Flag{ 847 IntSliceFlag{Name: "a", Usage: "set numbers"}, 848 StringSliceFlag{Name: "str", Usage: "set strings"}, 849 }, 850 Action: func(c *Context) error { 851 parsedIntSlice = c.IntSlice("a") 852 parsedStringSlice = c.StringSlice("str") 853 return nil 854 }, 855 } 856 app.Commands = []Command{command} 857 858 _ = app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"}) 859 860 var expectedIntSlice = []int{2} 861 var expectedStringSlice = []string{"A"} 862 863 if parsedIntSlice[0] != expectedIntSlice[0] { 864 t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0]) 865 } 866 867 if parsedStringSlice[0] != expectedStringSlice[0] { 868 t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0]) 869 } 870} 871 872func TestApp_DefaultStdout(t *testing.T) { 873 app := NewApp() 874 875 if app.Writer != os.Stdout { 876 t.Error("Default output writer not set.") 877 } 878} 879 880type mockWriter struct { 881 written []byte 882} 883 884func (fw *mockWriter) Write(p []byte) (n int, err error) { 885 if fw.written == nil { 886 fw.written = p 887 } else { 888 fw.written = append(fw.written, p...) 889 } 890 891 return len(p), nil 892} 893 894func (fw *mockWriter) GetWritten() (b []byte) { 895 return fw.written 896} 897 898func TestApp_SetStdout(t *testing.T) { 899 w := &mockWriter{} 900 901 app := NewApp() 902 app.Name = "test" 903 app.Writer = w 904 905 err := app.Run([]string{"help"}) 906 907 if err != nil { 908 t.Fatalf("Run error: %s", err) 909 } 910 911 if len(w.written) == 0 { 912 t.Error("App did not write output to desired writer.") 913 } 914} 915 916func TestApp_BeforeFunc(t *testing.T) { 917 counts := &opCounts{} 918 beforeError := fmt.Errorf("fail") 919 var err error 920 921 app := NewApp() 922 923 app.Before = func(c *Context) error { 924 counts.Total++ 925 counts.Before = counts.Total 926 s := c.String("opt") 927 if s == "fail" { 928 return beforeError 929 } 930 931 return nil 932 } 933 934 app.Commands = []Command{ 935 { 936 Name: "sub", 937 Action: func(c *Context) error { 938 counts.Total++ 939 counts.SubCommand = counts.Total 940 return nil 941 }, 942 }, 943 } 944 945 app.Flags = []Flag{ 946 StringFlag{Name: "opt"}, 947 } 948 949 // run with the Before() func succeeding 950 err = app.Run([]string{"command", "--opt", "succeed", "sub"}) 951 952 if err != nil { 953 t.Fatalf("Run error: %s", err) 954 } 955 956 if counts.Before != 1 { 957 t.Errorf("Before() not executed when expected") 958 } 959 960 if counts.SubCommand != 2 { 961 t.Errorf("Subcommand not executed when expected") 962 } 963 964 // reset 965 counts = &opCounts{} 966 967 // run with the Before() func failing 968 err = app.Run([]string{"command", "--opt", "fail", "sub"}) 969 970 // should be the same error produced by the Before func 971 if err != beforeError { 972 t.Errorf("Run error expected, but not received") 973 } 974 975 if counts.Before != 1 { 976 t.Errorf("Before() not executed when expected") 977 } 978 979 if counts.SubCommand != 0 { 980 t.Errorf("Subcommand executed when NOT expected") 981 } 982 983 // reset 984 counts = &opCounts{} 985 986 afterError := errors.New("fail again") 987 app.After = func(_ *Context) error { 988 return afterError 989 } 990 991 // run with the Before() func failing, wrapped by After() 992 err = app.Run([]string{"command", "--opt", "fail", "sub"}) 993 994 // should be the same error produced by the Before func 995 if _, ok := err.(MultiError); !ok { 996 t.Errorf("MultiError expected, but not received") 997 } 998 999 if counts.Before != 1 { 1000 t.Errorf("Before() not executed when expected") 1001 } 1002 1003 if counts.SubCommand != 0 { 1004 t.Errorf("Subcommand executed when NOT expected") 1005 } 1006} 1007 1008func TestApp_AfterFunc(t *testing.T) { 1009 counts := &opCounts{} 1010 afterError := fmt.Errorf("fail") 1011 var err error 1012 1013 app := NewApp() 1014 1015 app.After = func(c *Context) error { 1016 counts.Total++ 1017 counts.After = counts.Total 1018 s := c.String("opt") 1019 if s == "fail" { 1020 return afterError 1021 } 1022 1023 return nil 1024 } 1025 1026 app.Commands = []Command{ 1027 { 1028 Name: "sub", 1029 Action: func(c *Context) error { 1030 counts.Total++ 1031 counts.SubCommand = counts.Total 1032 return nil 1033 }, 1034 }, 1035 } 1036 1037 app.Flags = []Flag{ 1038 StringFlag{Name: "opt"}, 1039 } 1040 1041 // run with the After() func succeeding 1042 err = app.Run([]string{"command", "--opt", "succeed", "sub"}) 1043 1044 if err != nil { 1045 t.Fatalf("Run error: %s", err) 1046 } 1047 1048 if counts.After != 2 { 1049 t.Errorf("After() not executed when expected") 1050 } 1051 1052 if counts.SubCommand != 1 { 1053 t.Errorf("Subcommand not executed when expected") 1054 } 1055 1056 // reset 1057 counts = &opCounts{} 1058 1059 // run with the Before() func failing 1060 err = app.Run([]string{"command", "--opt", "fail", "sub"}) 1061 1062 // should be the same error produced by the Before func 1063 if err != afterError { 1064 t.Errorf("Run error expected, but not received") 1065 } 1066 1067 if counts.After != 2 { 1068 t.Errorf("After() not executed when expected") 1069 } 1070 1071 if counts.SubCommand != 1 { 1072 t.Errorf("Subcommand not executed when expected") 1073 } 1074} 1075 1076func TestAppNoHelpFlag(t *testing.T) { 1077 oldFlag := HelpFlag 1078 defer func() { 1079 HelpFlag = oldFlag 1080 }() 1081 1082 HelpFlag = BoolFlag{} 1083 1084 app := NewApp() 1085 app.Writer = ioutil.Discard 1086 err := app.Run([]string{"test", "-h"}) 1087 1088 if err != flag.ErrHelp { 1089 t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err) 1090 } 1091} 1092 1093func TestRequiredFlagAppRunBehavior(t *testing.T) { 1094 tdata := []struct { 1095 testCase string 1096 appFlags []Flag 1097 appRunInput []string 1098 appCommands []Command 1099 expectedAnError bool 1100 }{ 1101 // assertion: empty input, when a required flag is present, errors 1102 { 1103 testCase: "error_case_empty_input_with_required_flag_on_app", 1104 appRunInput: []string{"myCLI"}, 1105 appFlags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, 1106 expectedAnError: true, 1107 }, 1108 { 1109 testCase: "error_case_empty_input_with_required_flag_on_command", 1110 appRunInput: []string{"myCLI", "myCommand"}, 1111 appCommands: []Command{{ 1112 Name: "myCommand", 1113 Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, 1114 }}, 1115 expectedAnError: true, 1116 }, 1117 { 1118 testCase: "error_case_empty_input_with_required_flag_on_subcommand", 1119 appRunInput: []string{"myCLI", "myCommand", "mySubCommand"}, 1120 appCommands: []Command{{ 1121 Name: "myCommand", 1122 Subcommands: []Command{{ 1123 Name: "mySubCommand", 1124 Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, 1125 }}, 1126 }}, 1127 expectedAnError: true, 1128 }, 1129 // assertion: inputing --help, when a required flag is present, does not error 1130 { 1131 testCase: "valid_case_help_input_with_required_flag_on_app", 1132 appRunInput: []string{"myCLI", "--help"}, 1133 appFlags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, 1134 }, 1135 { 1136 testCase: "valid_case_help_input_with_required_flag_on_command", 1137 appRunInput: []string{"myCLI", "myCommand", "--help"}, 1138 appCommands: []Command{{ 1139 Name: "myCommand", 1140 Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, 1141 }}, 1142 }, 1143 { 1144 testCase: "valid_case_help_input_with_required_flag_on_subcommand", 1145 appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--help"}, 1146 appCommands: []Command{{ 1147 Name: "myCommand", 1148 Subcommands: []Command{{ 1149 Name: "mySubCommand", 1150 Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, 1151 }}, 1152 }}, 1153 }, 1154 // assertion: giving optional input, when a required flag is present, errors 1155 { 1156 testCase: "error_case_optional_input_with_required_flag_on_app", 1157 appRunInput: []string{"myCLI", "--optional", "cats"}, 1158 appFlags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}}, 1159 expectedAnError: true, 1160 }, 1161 { 1162 testCase: "error_case_optional_input_with_required_flag_on_command", 1163 appRunInput: []string{"myCLI", "myCommand", "--optional", "cats"}, 1164 appCommands: []Command{{ 1165 Name: "myCommand", 1166 Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}}, 1167 }}, 1168 expectedAnError: true, 1169 }, 1170 { 1171 testCase: "error_case_optional_input_with_required_flag_on_subcommand", 1172 appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--optional", "cats"}, 1173 appCommands: []Command{{ 1174 Name: "myCommand", 1175 Subcommands: []Command{{ 1176 Name: "mySubCommand", 1177 Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}}, 1178 }}, 1179 }}, 1180 expectedAnError: true, 1181 }, 1182 // assertion: when a required flag is present, inputting that required flag does not error 1183 { 1184 testCase: "valid_case_required_flag_input_on_app", 1185 appRunInput: []string{"myCLI", "--requiredFlag", "cats"}, 1186 appFlags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, 1187 }, 1188 { 1189 testCase: "valid_case_required_flag_input_on_command", 1190 appRunInput: []string{"myCLI", "myCommand", "--requiredFlag", "cats"}, 1191 appCommands: []Command{{ 1192 Name: "myCommand", 1193 Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, 1194 }}, 1195 }, 1196 { 1197 testCase: "valid_case_required_flag_input_on_subcommand", 1198 appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--requiredFlag", "cats"}, 1199 appCommands: []Command{{ 1200 Name: "myCommand", 1201 Subcommands: []Command{{ 1202 Name: "mySubCommand", 1203 Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, 1204 }}, 1205 }}, 1206 }, 1207 } 1208 for _, test := range tdata { 1209 t.Run(test.testCase, func(t *testing.T) { 1210 // setup 1211 app := NewApp() 1212 app.Flags = test.appFlags 1213 app.Commands = test.appCommands 1214 1215 // logic under test 1216 err := app.Run(test.appRunInput) 1217 1218 // assertions 1219 if test.expectedAnError && err == nil { 1220 t.Errorf("expected an error, but there was none") 1221 } 1222 if _, ok := err.(requiredFlagsErr); test.expectedAnError && !ok { 1223 t.Errorf("expected a requiredFlagsErr, but got: %s", err) 1224 } 1225 if !test.expectedAnError && err != nil { 1226 t.Errorf("did not expected an error, but there was one: %s", err) 1227 } 1228 }) 1229 } 1230} 1231 1232func TestAppHelpPrinter(t *testing.T) { 1233 oldPrinter := HelpPrinter 1234 defer func() { 1235 HelpPrinter = oldPrinter 1236 }() 1237 1238 var wasCalled = false 1239 HelpPrinter = func(w io.Writer, template string, data interface{}) { 1240 wasCalled = true 1241 } 1242 1243 app := NewApp() 1244 _ = app.Run([]string{"-h"}) 1245 1246 if wasCalled == false { 1247 t.Errorf("Help printer expected to be called, but was not") 1248 } 1249} 1250 1251func TestApp_VersionPrinter(t *testing.T) { 1252 oldPrinter := VersionPrinter 1253 defer func() { 1254 VersionPrinter = oldPrinter 1255 }() 1256 1257 var wasCalled = false 1258 VersionPrinter = func(c *Context) { 1259 wasCalled = true 1260 } 1261 1262 app := NewApp() 1263 ctx := NewContext(app, nil, nil) 1264 ShowVersion(ctx) 1265 1266 if wasCalled == false { 1267 t.Errorf("Version printer expected to be called, but was not") 1268 } 1269} 1270 1271func TestApp_CommandNotFound(t *testing.T) { 1272 counts := &opCounts{} 1273 app := NewApp() 1274 1275 app.CommandNotFound = func(c *Context, command string) { 1276 counts.Total++ 1277 counts.CommandNotFound = counts.Total 1278 } 1279 1280 app.Commands = []Command{ 1281 { 1282 Name: "bar", 1283 Action: func(c *Context) error { 1284 counts.Total++ 1285 counts.SubCommand = counts.Total 1286 return nil 1287 }, 1288 }, 1289 } 1290 1291 _ = app.Run([]string{"command", "foo"}) 1292 1293 expect(t, counts.CommandNotFound, 1) 1294 expect(t, counts.SubCommand, 0) 1295 expect(t, counts.Total, 1) 1296} 1297 1298func TestApp_OrderOfOperations(t *testing.T) { 1299 counts := &opCounts{} 1300 1301 resetCounts := func() { counts = &opCounts{} } 1302 1303 app := NewApp() 1304 app.EnableBashCompletion = true 1305 app.BashComplete = func(c *Context) { 1306 counts.Total++ 1307 counts.BashComplete = counts.Total 1308 } 1309 1310 app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { 1311 counts.Total++ 1312 counts.OnUsageError = counts.Total 1313 return errors.New("hay OnUsageError") 1314 } 1315 1316 beforeNoError := func(c *Context) error { 1317 counts.Total++ 1318 counts.Before = counts.Total 1319 return nil 1320 } 1321 1322 beforeError := func(c *Context) error { 1323 counts.Total++ 1324 counts.Before = counts.Total 1325 return errors.New("hay Before") 1326 } 1327 1328 app.Before = beforeNoError 1329 app.CommandNotFound = func(c *Context, command string) { 1330 counts.Total++ 1331 counts.CommandNotFound = counts.Total 1332 } 1333 1334 afterNoError := func(c *Context) error { 1335 counts.Total++ 1336 counts.After = counts.Total 1337 return nil 1338 } 1339 1340 afterError := func(c *Context) error { 1341 counts.Total++ 1342 counts.After = counts.Total 1343 return errors.New("hay After") 1344 } 1345 1346 app.After = afterNoError 1347 app.Commands = []Command{ 1348 { 1349 Name: "bar", 1350 Action: func(c *Context) error { 1351 counts.Total++ 1352 counts.SubCommand = counts.Total 1353 return nil 1354 }, 1355 }, 1356 } 1357 1358 app.Action = func(c *Context) error { 1359 counts.Total++ 1360 counts.Action = counts.Total 1361 return nil 1362 } 1363 1364 _ = app.Run([]string{"command", "--nope"}) 1365 expect(t, counts.OnUsageError, 1) 1366 expect(t, counts.Total, 1) 1367 1368 resetCounts() 1369 1370 _ = app.Run([]string{"command", "--generate-bash-completion"}) 1371 expect(t, counts.BashComplete, 1) 1372 expect(t, counts.Total, 1) 1373 1374 resetCounts() 1375 1376 oldOnUsageError := app.OnUsageError 1377 app.OnUsageError = nil 1378 _ = app.Run([]string{"command", "--nope"}) 1379 expect(t, counts.Total, 0) 1380 app.OnUsageError = oldOnUsageError 1381 1382 resetCounts() 1383 1384 _ = app.Run([]string{"command", "foo"}) 1385 expect(t, counts.OnUsageError, 0) 1386 expect(t, counts.Before, 1) 1387 expect(t, counts.CommandNotFound, 0) 1388 expect(t, counts.Action, 2) 1389 expect(t, counts.After, 3) 1390 expect(t, counts.Total, 3) 1391 1392 resetCounts() 1393 1394 app.Before = beforeError 1395 _ = app.Run([]string{"command", "bar"}) 1396 expect(t, counts.OnUsageError, 0) 1397 expect(t, counts.Before, 1) 1398 expect(t, counts.After, 2) 1399 expect(t, counts.Total, 2) 1400 app.Before = beforeNoError 1401 1402 resetCounts() 1403 1404 app.After = nil 1405 _ = app.Run([]string{"command", "bar"}) 1406 expect(t, counts.OnUsageError, 0) 1407 expect(t, counts.Before, 1) 1408 expect(t, counts.SubCommand, 2) 1409 expect(t, counts.Total, 2) 1410 app.After = afterNoError 1411 1412 resetCounts() 1413 1414 app.After = afterError 1415 err := app.Run([]string{"command", "bar"}) 1416 if err == nil { 1417 t.Fatalf("expected a non-nil error") 1418 } 1419 expect(t, counts.OnUsageError, 0) 1420 expect(t, counts.Before, 1) 1421 expect(t, counts.SubCommand, 2) 1422 expect(t, counts.After, 3) 1423 expect(t, counts.Total, 3) 1424 app.After = afterNoError 1425 1426 resetCounts() 1427 1428 oldCommands := app.Commands 1429 app.Commands = nil 1430 _ = app.Run([]string{"command"}) 1431 expect(t, counts.OnUsageError, 0) 1432 expect(t, counts.Before, 1) 1433 expect(t, counts.Action, 2) 1434 expect(t, counts.After, 3) 1435 expect(t, counts.Total, 3) 1436 app.Commands = oldCommands 1437} 1438 1439func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { 1440 var subcommandHelpTopics = [][]string{ 1441 {"command", "foo", "--help"}, 1442 {"command", "foo", "-h"}, 1443 {"command", "foo", "help"}, 1444 } 1445 1446 for _, flagSet := range subcommandHelpTopics { 1447 t.Logf("==> checking with flags %v", flagSet) 1448 1449 app := NewApp() 1450 buf := new(bytes.Buffer) 1451 app.Writer = buf 1452 1453 subCmdBar := Command{ 1454 Name: "bar", 1455 Usage: "does bar things", 1456 } 1457 subCmdBaz := Command{ 1458 Name: "baz", 1459 Usage: "does baz things", 1460 } 1461 cmd := Command{ 1462 Name: "foo", 1463 Description: "descriptive wall of text about how it does foo things", 1464 Subcommands: []Command{subCmdBar, subCmdBaz}, 1465 Action: func(c *Context) error { return nil }, 1466 } 1467 1468 app.Commands = []Command{cmd} 1469 err := app.Run(flagSet) 1470 1471 if err != nil { 1472 t.Error(err) 1473 } 1474 1475 output := buf.String() 1476 t.Logf("output: %q\n", buf.Bytes()) 1477 1478 if strings.Contains(output, "No help topic for") { 1479 t.Errorf("expect a help topic, got none: \n%q", output) 1480 } 1481 1482 for _, shouldContain := range []string{ 1483 cmd.Name, cmd.Description, 1484 subCmdBar.Name, subCmdBar.Usage, 1485 subCmdBaz.Name, subCmdBaz.Usage, 1486 } { 1487 if !strings.Contains(output, shouldContain) { 1488 t.Errorf("want help to contain %q, did not: \n%q", shouldContain, output) 1489 } 1490 } 1491 } 1492} 1493 1494func TestApp_Run_SubcommandFullPath(t *testing.T) { 1495 app := NewApp() 1496 buf := new(bytes.Buffer) 1497 app.Writer = buf 1498 app.Name = "command" 1499 subCmd := Command{ 1500 Name: "bar", 1501 Usage: "does bar things", 1502 } 1503 cmd := Command{ 1504 Name: "foo", 1505 Description: "foo commands", 1506 Subcommands: []Command{subCmd}, 1507 } 1508 app.Commands = []Command{cmd} 1509 1510 err := app.Run([]string{"command", "foo", "bar", "--help"}) 1511 if err != nil { 1512 t.Error(err) 1513 } 1514 1515 output := buf.String() 1516 if !strings.Contains(output, "command foo bar - does bar things") { 1517 t.Errorf("expected full path to subcommand: %s", output) 1518 } 1519 if !strings.Contains(output, "command foo bar [arguments...]") { 1520 t.Errorf("expected full path to subcommand: %s", output) 1521 } 1522} 1523 1524func TestApp_Run_SubcommandHelpName(t *testing.T) { 1525 app := NewApp() 1526 buf := new(bytes.Buffer) 1527 app.Writer = buf 1528 app.Name = "command" 1529 subCmd := Command{ 1530 Name: "bar", 1531 HelpName: "custom", 1532 Usage: "does bar things", 1533 } 1534 cmd := Command{ 1535 Name: "foo", 1536 Description: "foo commands", 1537 Subcommands: []Command{subCmd}, 1538 } 1539 app.Commands = []Command{cmd} 1540 1541 err := app.Run([]string{"command", "foo", "bar", "--help"}) 1542 if err != nil { 1543 t.Error(err) 1544 } 1545 1546 output := buf.String() 1547 if !strings.Contains(output, "custom - does bar things") { 1548 t.Errorf("expected HelpName for subcommand: %s", output) 1549 } 1550 if !strings.Contains(output, "custom [arguments...]") { 1551 t.Errorf("expected HelpName to subcommand: %s", output) 1552 } 1553} 1554 1555func TestApp_Run_CommandHelpName(t *testing.T) { 1556 app := NewApp() 1557 buf := new(bytes.Buffer) 1558 app.Writer = buf 1559 app.Name = "command" 1560 subCmd := Command{ 1561 Name: "bar", 1562 Usage: "does bar things", 1563 } 1564 cmd := Command{ 1565 Name: "foo", 1566 HelpName: "custom", 1567 Description: "foo commands", 1568 Subcommands: []Command{subCmd}, 1569 } 1570 app.Commands = []Command{cmd} 1571 1572 err := app.Run([]string{"command", "foo", "bar", "--help"}) 1573 if err != nil { 1574 t.Error(err) 1575 } 1576 1577 output := buf.String() 1578 if !strings.Contains(output, "command foo bar - does bar things") { 1579 t.Errorf("expected full path to subcommand: %s", output) 1580 } 1581 if !strings.Contains(output, "command foo bar [arguments...]") { 1582 t.Errorf("expected full path to subcommand: %s", output) 1583 } 1584} 1585 1586func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { 1587 app := NewApp() 1588 buf := new(bytes.Buffer) 1589 app.Writer = buf 1590 app.Name = "base" 1591 subCmd := Command{ 1592 Name: "bar", 1593 HelpName: "custom", 1594 Usage: "does bar things", 1595 } 1596 cmd := Command{ 1597 Name: "foo", 1598 Description: "foo commands", 1599 Subcommands: []Command{subCmd}, 1600 } 1601 app.Commands = []Command{cmd} 1602 1603 err := app.Run([]string{"command", "foo", "--help"}) 1604 if err != nil { 1605 t.Error(err) 1606 } 1607 1608 output := buf.String() 1609 if !strings.Contains(output, "base foo - foo commands") { 1610 t.Errorf("expected full path to subcommand: %s", output) 1611 } 1612 if !strings.Contains(output, "base foo command [command options] [arguments...]") { 1613 t.Errorf("expected full path to subcommand: %s", output) 1614 } 1615} 1616 1617func TestApp_Run_Help(t *testing.T) { 1618 var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}} 1619 1620 for _, args := range helpArguments { 1621 buf := new(bytes.Buffer) 1622 1623 t.Logf("==> checking with arguments %v", args) 1624 1625 app := NewApp() 1626 app.Name = "boom" 1627 app.Usage = "make an explosive entrance" 1628 app.Writer = buf 1629 app.Action = func(c *Context) error { 1630 buf.WriteString("boom I say!") 1631 return nil 1632 } 1633 1634 err := app.Run(args) 1635 if err != nil { 1636 t.Error(err) 1637 } 1638 1639 output := buf.String() 1640 t.Logf("output: %q\n", buf.Bytes()) 1641 1642 if !strings.Contains(output, "boom - make an explosive entrance") { 1643 t.Errorf("want help to contain %q, did not: \n%q", "boom - make an explosive entrance", output) 1644 } 1645 } 1646} 1647 1648func TestApp_Run_Version(t *testing.T) { 1649 var versionArguments = [][]string{{"boom", "--version"}, {"boom", "-v"}} 1650 1651 for _, args := range versionArguments { 1652 buf := new(bytes.Buffer) 1653 1654 t.Logf("==> checking with arguments %v", args) 1655 1656 app := NewApp() 1657 app.Name = "boom" 1658 app.Usage = "make an explosive entrance" 1659 app.Version = "0.1.0" 1660 app.Writer = buf 1661 app.Action = func(c *Context) error { 1662 buf.WriteString("boom I say!") 1663 return nil 1664 } 1665 1666 err := app.Run(args) 1667 if err != nil { 1668 t.Error(err) 1669 } 1670 1671 output := buf.String() 1672 t.Logf("output: %q\n", buf.Bytes()) 1673 1674 if !strings.Contains(output, "0.1.0") { 1675 t.Errorf("want version to contain %q, did not: \n%q", "0.1.0", output) 1676 } 1677 } 1678} 1679 1680func TestApp_Run_Categories(t *testing.T) { 1681 app := NewApp() 1682 app.Name = "categories" 1683 app.HideHelp = true 1684 app.Commands = []Command{ 1685 { 1686 Name: "command1", 1687 Category: "1", 1688 }, 1689 { 1690 Name: "command2", 1691 Category: "1", 1692 }, 1693 { 1694 Name: "command3", 1695 Category: "2", 1696 }, 1697 } 1698 buf := new(bytes.Buffer) 1699 app.Writer = buf 1700 1701 _ = app.Run([]string{"categories"}) 1702 1703 expect := CommandCategories{ 1704 &CommandCategory{ 1705 Name: "1", 1706 Commands: []Command{ 1707 app.Commands[0], 1708 app.Commands[1], 1709 }, 1710 }, 1711 &CommandCategory{ 1712 Name: "2", 1713 Commands: []Command{ 1714 app.Commands[2], 1715 }, 1716 }, 1717 } 1718 if !reflect.DeepEqual(app.Categories(), expect) { 1719 t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect) 1720 } 1721 1722 output := buf.String() 1723 t.Logf("output: %q\n", buf.Bytes()) 1724 1725 if !strings.Contains(output, "1:\n command1") { 1726 t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n command1", output) 1727 } 1728} 1729 1730func TestApp_VisibleCategories(t *testing.T) { 1731 app := NewApp() 1732 app.Name = "visible-categories" 1733 app.HideHelp = true 1734 app.Commands = []Command{ 1735 { 1736 Name: "command1", 1737 Category: "1", 1738 HelpName: "foo command1", 1739 Hidden: true, 1740 }, 1741 { 1742 Name: "command2", 1743 Category: "2", 1744 HelpName: "foo command2", 1745 }, 1746 { 1747 Name: "command3", 1748 Category: "3", 1749 HelpName: "foo command3", 1750 }, 1751 } 1752 1753 expected := []*CommandCategory{ 1754 { 1755 Name: "2", 1756 Commands: []Command{ 1757 app.Commands[1], 1758 }, 1759 }, 1760 { 1761 Name: "3", 1762 Commands: []Command{ 1763 app.Commands[2], 1764 }, 1765 }, 1766 } 1767 1768 app.Setup() 1769 expect(t, expected, app.VisibleCategories()) 1770 1771 app = NewApp() 1772 app.Name = "visible-categories" 1773 app.HideHelp = true 1774 app.Commands = []Command{ 1775 { 1776 Name: "command1", 1777 Category: "1", 1778 HelpName: "foo command1", 1779 Hidden: true, 1780 }, 1781 { 1782 Name: "command2", 1783 Category: "2", 1784 HelpName: "foo command2", 1785 Hidden: true, 1786 }, 1787 { 1788 Name: "command3", 1789 Category: "3", 1790 HelpName: "foo command3", 1791 }, 1792 } 1793 1794 expected = []*CommandCategory{ 1795 { 1796 Name: "3", 1797 Commands: []Command{ 1798 app.Commands[2], 1799 }, 1800 }, 1801 } 1802 1803 app.Setup() 1804 expect(t, expected, app.VisibleCategories()) 1805 1806 app = NewApp() 1807 app.Name = "visible-categories" 1808 app.HideHelp = true 1809 app.Commands = []Command{ 1810 { 1811 Name: "command1", 1812 Category: "1", 1813 HelpName: "foo command1", 1814 Hidden: true, 1815 }, 1816 { 1817 Name: "command2", 1818 Category: "2", 1819 HelpName: "foo command2", 1820 Hidden: true, 1821 }, 1822 { 1823 Name: "command3", 1824 Category: "3", 1825 HelpName: "foo command3", 1826 Hidden: true, 1827 }, 1828 } 1829 1830 expected = []*CommandCategory{} 1831 1832 app.Setup() 1833 expect(t, expected, app.VisibleCategories()) 1834} 1835 1836func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { 1837 app := NewApp() 1838 app.Action = func(c *Context) error { return nil } 1839 app.Before = func(c *Context) error { return fmt.Errorf("before error") } 1840 app.After = func(c *Context) error { return fmt.Errorf("after error") } 1841 1842 err := app.Run([]string{"foo"}) 1843 if err == nil { 1844 t.Fatalf("expected to receive error from Run, got none") 1845 } 1846 1847 if !strings.Contains(err.Error(), "before error") { 1848 t.Errorf("expected text of error from Before method, but got none in \"%v\"", err) 1849 } 1850 if !strings.Contains(err.Error(), "after error") { 1851 t.Errorf("expected text of error from After method, but got none in \"%v\"", err) 1852 } 1853} 1854 1855func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { 1856 app := NewApp() 1857 app.Commands = []Command{ 1858 { 1859 Subcommands: []Command{ 1860 { 1861 Name: "sub", 1862 }, 1863 }, 1864 Name: "bar", 1865 Before: func(c *Context) error { return fmt.Errorf("before error") }, 1866 After: func(c *Context) error { return fmt.Errorf("after error") }, 1867 }, 1868 } 1869 1870 err := app.Run([]string{"foo", "bar"}) 1871 if err == nil { 1872 t.Fatalf("expected to receive error from Run, got none") 1873 } 1874 1875 if !strings.Contains(err.Error(), "before error") { 1876 t.Errorf("expected text of error from Before method, but got none in \"%v\"", err) 1877 } 1878 if !strings.Contains(err.Error(), "after error") { 1879 t.Errorf("expected text of error from After method, but got none in \"%v\"", err) 1880 } 1881} 1882 1883func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { 1884 app := NewApp() 1885 app.Flags = []Flag{ 1886 IntFlag{Name: "flag"}, 1887 } 1888 app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { 1889 if isSubcommand { 1890 t.Errorf("Expect no subcommand") 1891 } 1892 if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { 1893 t.Errorf("Expect an invalid value error, but got \"%v\"", err) 1894 } 1895 return errors.New("intercepted: " + err.Error()) 1896 } 1897 app.Commands = []Command{ 1898 { 1899 Name: "bar", 1900 }, 1901 } 1902 1903 err := app.Run([]string{"foo", "--flag=wrong"}) 1904 if err == nil { 1905 t.Fatalf("expected to receive error from Run, got none") 1906 } 1907 1908 if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { 1909 t.Errorf("Expect an intercepted error, but got \"%v\"", err) 1910 } 1911} 1912 1913func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { 1914 app := NewApp() 1915 app.Flags = []Flag{ 1916 IntFlag{Name: "flag"}, 1917 } 1918 app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { 1919 if isSubcommand { 1920 t.Errorf("Expect subcommand") 1921 } 1922 if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { 1923 t.Errorf("Expect an invalid value error, but got \"%v\"", err) 1924 } 1925 return errors.New("intercepted: " + err.Error()) 1926 } 1927 app.Commands = []Command{ 1928 { 1929 Name: "bar", 1930 }, 1931 } 1932 1933 err := app.Run([]string{"foo", "--flag=wrong", "bar"}) 1934 if err == nil { 1935 t.Fatalf("expected to receive error from Run, got none") 1936 } 1937 1938 if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { 1939 t.Errorf("Expect an intercepted error, but got \"%v\"", err) 1940 } 1941} 1942 1943// A custom flag that conforms to the relevant interfaces, but has none of the 1944// fields that the other flag types do. 1945type customBoolFlag struct { 1946 Nombre string 1947} 1948 1949// Don't use the normal FlagStringer 1950func (c *customBoolFlag) String() string { 1951 return "***" + c.Nombre + "***" 1952} 1953 1954func (c *customBoolFlag) GetName() string { 1955 return c.Nombre 1956} 1957 1958func (c *customBoolFlag) TakesValue() bool { 1959 return false 1960} 1961 1962func (c *customBoolFlag) GetValue() string { 1963 return "value" 1964} 1965 1966func (c *customBoolFlag) GetUsage() string { 1967 return "usage" 1968} 1969 1970func (c *customBoolFlag) Apply(set *flag.FlagSet) { 1971 set.String(c.Nombre, c.Nombre, "") 1972} 1973 1974func TestCustomFlagsUnused(t *testing.T) { 1975 app := NewApp() 1976 app.Flags = []Flag{&customBoolFlag{"custom"}} 1977 1978 err := app.Run([]string{"foo"}) 1979 if err != nil { 1980 t.Errorf("Run returned unexpected error: %v", err) 1981 } 1982} 1983 1984func TestCustomFlagsUsed(t *testing.T) { 1985 app := NewApp() 1986 app.Flags = []Flag{&customBoolFlag{"custom"}} 1987 1988 err := app.Run([]string{"foo", "--custom=bar"}) 1989 if err != nil { 1990 t.Errorf("Run returned unexpected error: %v", err) 1991 } 1992} 1993 1994func TestCustomHelpVersionFlags(t *testing.T) { 1995 app := NewApp() 1996 1997 // Be sure to reset the global flags 1998 defer func(helpFlag Flag, versionFlag Flag) { 1999 HelpFlag = helpFlag 2000 VersionFlag = versionFlag 2001 }(HelpFlag, VersionFlag) 2002 2003 HelpFlag = &customBoolFlag{"help-custom"} 2004 VersionFlag = &customBoolFlag{"version-custom"} 2005 2006 err := app.Run([]string{"foo", "--help-custom=bar"}) 2007 if err != nil { 2008 t.Errorf("Run returned unexpected error: %v", err) 2009 } 2010} 2011 2012func TestHandleAction_WithNonFuncAction(t *testing.T) { 2013 app := NewApp() 2014 app.Action = 42 2015 fs, err := flagSet(app.Name, app.Flags) 2016 if err != nil { 2017 t.Errorf("error creating FlagSet: %s", err) 2018 } 2019 err = HandleAction(app.Action, NewContext(app, fs, nil)) 2020 2021 if err == nil { 2022 t.Fatalf("expected to receive error from Run, got none") 2023 } 2024 2025 exitErr, ok := err.(*ExitError) 2026 2027 if !ok { 2028 t.Fatalf("expected to receive a *ExitError") 2029 } 2030 2031 if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type.") { 2032 t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) 2033 } 2034 2035 if exitErr.ExitCode() != 2 { 2036 t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) 2037 } 2038} 2039 2040func TestHandleAction_WithInvalidFuncSignature(t *testing.T) { 2041 app := NewApp() 2042 app.Action = func() string { return "" } 2043 fs, err := flagSet(app.Name, app.Flags) 2044 if err != nil { 2045 t.Errorf("error creating FlagSet: %s", err) 2046 } 2047 err = HandleAction(app.Action, NewContext(app, fs, nil)) 2048 2049 if err == nil { 2050 t.Fatalf("expected to receive error from Run, got none") 2051 } 2052 2053 exitErr, ok := err.(*ExitError) 2054 2055 if !ok { 2056 t.Fatalf("expected to receive a *ExitError") 2057 } 2058 2059 if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { 2060 t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) 2061 } 2062 2063 if exitErr.ExitCode() != 2 { 2064 t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) 2065 } 2066} 2067 2068func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) { 2069 app := NewApp() 2070 app.Action = func(_ *Context) (int, error) { return 0, nil } 2071 fs, err := flagSet(app.Name, app.Flags) 2072 if err != nil { 2073 t.Errorf("error creating FlagSet: %s", err) 2074 } 2075 err = HandleAction(app.Action, NewContext(app, fs, nil)) 2076 2077 if err == nil { 2078 t.Fatalf("expected to receive error from Run, got none") 2079 } 2080 2081 exitErr, ok := err.(*ExitError) 2082 2083 if !ok { 2084 t.Fatalf("expected to receive a *ExitError") 2085 } 2086 2087 if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { 2088 t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error()) 2089 } 2090 2091 if exitErr.ExitCode() != 2 { 2092 t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) 2093 } 2094} 2095 2096func TestHandleExitCoder_Default(t *testing.T) { 2097 app := NewApp() 2098 fs, err := flagSet(app.Name, app.Flags) 2099 if err != nil { 2100 t.Errorf("error creating FlagSet: %s", err) 2101 } 2102 2103 ctx := NewContext(app, fs, nil) 2104 app.handleExitCoder(ctx, NewExitError("Default Behavior Error", 42)) 2105 2106 output := fakeErrWriter.String() 2107 if !strings.Contains(output, "Default") { 2108 t.Fatalf("Expected Default Behavior from Error Handler but got: %s", output) 2109 } 2110} 2111 2112func TestHandleExitCoder_Custom(t *testing.T) { 2113 app := NewApp() 2114 fs, err := flagSet(app.Name, app.Flags) 2115 if err != nil { 2116 t.Errorf("error creating FlagSet: %s", err) 2117 } 2118 2119 app.ExitErrHandler = func(_ *Context, _ error) { 2120 _, _ = fmt.Fprintln(ErrWriter, "I'm a Custom error handler, I print what I want!") 2121 } 2122 2123 ctx := NewContext(app, fs, nil) 2124 app.handleExitCoder(ctx, NewExitError("Default Behavior Error", 42)) 2125 2126 output := fakeErrWriter.String() 2127 if !strings.Contains(output, "Custom") { 2128 t.Fatalf("Expected Custom Behavior from Error Handler but got: %s", output) 2129 } 2130} 2131 2132func TestHandleAction_WithUnknownPanic(t *testing.T) { 2133 defer func() { refute(t, recover(), nil) }() 2134 2135 var fn ActionFunc 2136 2137 app := NewApp() 2138 app.Action = func(ctx *Context) error { 2139 _ = fn(ctx) 2140 return nil 2141 } 2142 fs, err := flagSet(app.Name, app.Flags) 2143 if err != nil { 2144 t.Errorf("error creating FlagSet: %s", err) 2145 } 2146 _ = HandleAction(app.Action, NewContext(app, fs, nil)) 2147} 2148 2149func TestShellCompletionForIncompleteFlags(t *testing.T) { 2150 app := NewApp() 2151 app.Flags = []Flag{ 2152 IntFlag{ 2153 Name: "test-completion", 2154 }, 2155 } 2156 app.EnableBashCompletion = true 2157 app.BashComplete = func(ctx *Context) { 2158 for _, command := range ctx.App.Commands { 2159 if command.Hidden { 2160 continue 2161 } 2162 2163 for _, name := range command.Names() { 2164 _, _ = fmt.Fprintln(ctx.App.Writer, name) 2165 } 2166 } 2167 2168 for _, f := range ctx.App.Flags { 2169 for _, name := range strings.Split(f.GetName(), ",") { 2170 if name == BashCompletionFlag.GetName() { 2171 continue 2172 } 2173 2174 switch name = strings.TrimSpace(name); len(name) { 2175 case 0: 2176 case 1: 2177 _, _ = fmt.Fprintln(ctx.App.Writer, "-"+name) 2178 default: 2179 _, _ = fmt.Fprintln(ctx.App.Writer, "--"+name) 2180 } 2181 } 2182 } 2183 } 2184 app.Action = func(ctx *Context) error { 2185 return fmt.Errorf("should not get here") 2186 } 2187 err := app.Run([]string{"", "--test-completion", "--" + BashCompletionFlag.GetName()}) 2188 if err != nil { 2189 t.Errorf("app should not return an error: %s", err) 2190 } 2191} 2192 2193func TestHandleActionActuallyWorksWithActions(t *testing.T) { 2194 var f ActionFunc 2195 called := false 2196 f = func(c *Context) error { 2197 called = true 2198 return nil 2199 } 2200 2201 err := HandleAction(f, nil) 2202 2203 if err != nil { 2204 t.Errorf("Should not have errored: %v", err) 2205 } 2206 2207 if !called { 2208 t.Errorf("Function was not called") 2209 } 2210} 2211 2212func TestWhenExitSubCommandWithCodeThenAppQuitUnexpectedly(t *testing.T) { 2213 testCode := 104 2214 2215 app := NewApp() 2216 app.Commands = []Command{ 2217 Command{ 2218 Name: "cmd", 2219 Subcommands: []Command{ 2220 Command{ 2221 Name: "subcmd", 2222 Action: func(c *Context) error { 2223 return NewExitError("exit error", testCode) 2224 }, 2225 }, 2226 }, 2227 }, 2228 } 2229 2230 // set user function as ExitErrHandler 2231 var exitCodeFromExitErrHandler int 2232 app.ExitErrHandler = func(c *Context, err error) { 2233 if exitErr, ok := err.(ExitCoder); ok { 2234 t.Log(exitErr) 2235 exitCodeFromExitErrHandler = exitErr.ExitCode() 2236 } 2237 } 2238 2239 // keep and restore original OsExiter 2240 origExiter := OsExiter 2241 defer func() { 2242 OsExiter = origExiter 2243 }() 2244 2245 // set user function as OsExiter 2246 var exitCodeFromOsExiter int 2247 OsExiter = func(exitCode int) { 2248 exitCodeFromOsExiter = exitCode 2249 } 2250 2251 app.Run([]string{ 2252 "myapp", 2253 "cmd", 2254 "subcmd", 2255 }) 2256 2257 if exitCodeFromOsExiter != 0 { 2258 t.Errorf("exitCodeFromOsExiter should not change, but its value is %v", exitCodeFromOsExiter) 2259 } 2260 2261 if exitCodeFromExitErrHandler != testCode { 2262 t.Errorf("exitCodeFromOsExiter valeu should be %v, but its value is %v", testCode, exitCodeFromExitErrHandler) 2263 } 2264} 2265