package cobra import ( "bytes" "fmt" "os" "reflect" "strings" "testing" "github.com/spf13/pflag" ) // test to ensure hidden commands run as intended func TestHiddenCommandExecutes(t *testing.T) { // ensure that outs does not already equal what the command will be setting it // to, if it did this test would not actually be testing anything... if outs == "hidden" { t.Errorf("outs should NOT EQUAL hidden") } cmdHidden.Execute() // upon running the command, the value of outs should now be 'hidden' if outs != "hidden" { t.Errorf("Hidden command failed to run!") } } // test to ensure hidden commands do not show up in usage/help text func TestHiddenCommandIsHidden(t *testing.T) { if cmdHidden.IsAvailableCommand() { t.Errorf("Hidden command found!") } } func TestStripFlags(t *testing.T) { tests := []struct { input []string output []string }{ { []string{"foo", "bar"}, []string{"foo", "bar"}, }, { []string{"foo", "--bar", "-b"}, []string{"foo"}, }, { []string{"-b", "foo", "--bar", "bar"}, []string{}, }, { []string{"-i10", "echo"}, []string{"echo"}, }, { []string{"-i=10", "echo"}, []string{"echo"}, }, { []string{"--int=100", "echo"}, []string{"echo"}, }, { []string{"-ib", "echo", "-bfoo", "baz"}, []string{"echo", "baz"}, }, { []string{"-i=baz", "bar", "-i", "foo", "blah"}, []string{"bar", "blah"}, }, { []string{"--int=baz", "-bbar", "-i", "foo", "blah"}, []string{"blah"}, }, { []string{"--cat", "bar", "-i", "foo", "blah"}, []string{"bar", "blah"}, }, { []string{"-c", "bar", "-i", "foo", "blah"}, []string{"bar", "blah"}, }, { []string{"--persist", "bar"}, []string{"bar"}, }, { []string{"-p", "bar"}, []string{"bar"}, }, } cmdPrint := &Command{ Use: "print [string to print]", Short: "Print anything to the screen", Long: `an utterly useless command for testing.`, Run: func(cmd *Command, args []string) { tp = args }, } var flagi int var flagstr string var flagbool bool cmdPrint.PersistentFlags().BoolVarP(&flagbool, "persist", "p", false, "help for persistent one") cmdPrint.Flags().IntVarP(&flagi, "int", "i", 345, "help message for flag int") cmdPrint.Flags().StringVarP(&flagstr, "bar", "b", "bar", "help message for flag string") cmdPrint.Flags().BoolVarP(&flagbool, "cat", "c", false, "help message for flag bool") for _, test := range tests { output := stripFlags(test.input, cmdPrint) if !reflect.DeepEqual(test.output, output) { t.Errorf("expected: %v, got: %v", test.output, output) } } } func TestDisableFlagParsing(t *testing.T) { targs := []string{} cmdPrint := &Command{ DisableFlagParsing: true, Run: func(cmd *Command, args []string) { targs = args }, } args := []string{"cmd", "-v", "-race", "-file", "foo.go"} cmdPrint.SetArgs(args) err := cmdPrint.Execute() if err != nil { t.Error(err) } if !reflect.DeepEqual(args, targs) { t.Errorf("expected: %v, got: %v", args, targs) } } func TestInitHelpFlagMergesFlags(t *testing.T) { usage := "custom flag" baseCmd := Command{Use: "testcmd"} baseCmd.PersistentFlags().Bool("help", false, usage) cmd := Command{Use: "do"} baseCmd.AddCommand(&cmd) cmd.InitDefaultHelpFlag() actual := cmd.Flags().Lookup("help").Usage if actual != usage { t.Fatalf("Expected the help flag from the base command with usage '%s', but got the default with usage '%s'", usage, actual) } } func TestCommandsAreSorted(t *testing.T) { EnableCommandSorting = true originalNames := []string{"middle", "zlast", "afirst"} expectedNames := []string{"afirst", "middle", "zlast"} var tmpCommand = &Command{Use: "tmp"} for _, name := range originalNames { tmpCommand.AddCommand(&Command{Use: name}) } for i, c := range tmpCommand.Commands() { if expectedNames[i] != c.Name() { t.Errorf("expected: %s, got: %s", expectedNames[i], c.Name()) } } EnableCommandSorting = true } func TestEnableCommandSortingIsDisabled(t *testing.T) { EnableCommandSorting = false originalNames := []string{"middle", "zlast", "afirst"} var tmpCommand = &Command{Use: "tmp"} for _, name := range originalNames { tmpCommand.AddCommand(&Command{Use: name}) } for i, c := range tmpCommand.Commands() { if originalNames[i] != c.Name() { t.Errorf("expected: %s, got: %s", originalNames[i], c.Name()) } } EnableCommandSorting = true } func TestSetOutput(t *testing.T) { cmd := &Command{} cmd.SetOutput(nil) if out := cmd.OutOrStdout(); out != os.Stdout { t.Fatalf("expected setting output to nil to revert back to stdout, got %v", out) } } func TestFlagErrorFunc(t *testing.T) { cmd := &Command{ Use: "print", RunE: func(cmd *Command, args []string) error { return nil }, } expectedFmt := "This is expected: %s" cmd.SetFlagErrorFunc(func(c *Command, err error) error { return fmt.Errorf(expectedFmt, err) }) cmd.SetArgs([]string{"--bogus-flag"}) cmd.SetOutput(new(bytes.Buffer)) err := cmd.Execute() expected := fmt.Sprintf(expectedFmt, "unknown flag: --bogus-flag") if err.Error() != expected { t.Errorf("expected %v, got %v", expected, err.Error()) } } // TestSortedFlags checks, // if cmd.LocalFlags() is unsorted when cmd.Flags().SortFlags set to false. // Related to https://github.com/spf13/cobra/issues/404. func TestSortedFlags(t *testing.T) { cmd := &Command{} cmd.Flags().SortFlags = false names := []string{"C", "B", "A", "D"} for _, name := range names { cmd.Flags().Bool(name, false, "") } i := 0 cmd.LocalFlags().VisitAll(func(f *pflag.Flag) { if i == len(names) { return } if isStringInStringSlice(f.Name, names) { if names[i] != f.Name { t.Errorf("Incorrect order. Expected %v, got %v", names[i], f.Name) } i++ } }) } // contains checks, if s is in ss. func isStringInStringSlice(s string, ss []string) bool { for _, v := range ss { if v == s { return true } } return false } // TestHelpFlagInHelp checks, // if '--help' flag is shown in help for child (executing `parent help child`), // that has no other flags. // Related to https://github.com/spf13/cobra/issues/302. func TestHelpFlagInHelp(t *testing.T) { output := new(bytes.Buffer) parent := &Command{Use: "parent", Run: func(*Command, []string) {}} parent.SetOutput(output) child := &Command{Use: "child", Run: func(*Command, []string) {}} parent.AddCommand(child) parent.SetArgs([]string{"help", "child"}) err := parent.Execute() if err != nil { t.Fatal(err) } if !strings.Contains(output.String(), "[flags]") { t.Errorf("\nExpecting to contain: %v\nGot: %v", "[flags]", output.String()) } } // TestMergeCommandLineToFlags checks, // if pflag.CommandLine is correctly merged to c.Flags() after first call // of c.mergePersistentFlags. // Related to https://github.com/spf13/cobra/issues/443. func TestMergeCommandLineToFlags(t *testing.T) { pflag.Bool("boolflag", false, "") c := &Command{Use: "c", Run: func(*Command, []string) {}} c.mergePersistentFlags() if c.Flags().Lookup("boolflag") == nil { t.Fatal("Expecting to have flag from CommandLine in c.Flags()") } // Reset pflag.CommandLine flagset. pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) } // TestUseDeprecatedFlags checks, // if cobra.Execute() prints a message, if a deprecated flag is used. // Related to https://github.com/spf13/cobra/issues/463. func TestUseDeprecatedFlags(t *testing.T) { c := &Command{Use: "c", Run: func(*Command, []string) {}} output := new(bytes.Buffer) c.SetOutput(output) c.Flags().BoolP("deprecated", "d", false, "deprecated flag") c.Flags().MarkDeprecated("deprecated", "This flag is deprecated") c.SetArgs([]string{"c", "-d"}) if err := c.Execute(); err != nil { t.Error("Unexpected error:", err) } if !strings.Contains(output.String(), "This flag is deprecated") { t.Errorf("Expected to contain deprecated message, but got %q", output.String()) } } // TestSetHelpCommand checks, if SetHelpCommand works correctly. func TestSetHelpCommand(t *testing.T) { c := &Command{Use: "c", Run: func(*Command, []string) {}} output := new(bytes.Buffer) c.SetOutput(output) c.SetArgs([]string{"help"}) // Help will not be shown, if c has no subcommands. c.AddCommand(&Command{ Use: "empty", Run: func(cmd *Command, args []string) {}, }) correctMessage := "WORKS" c.SetHelpCommand(&Command{ Use: "help [command]", Short: "Help about any command", Long: `Help provides help for any command in the application. Simply type ` + c.Name() + ` help [path to command] for full details.`, Run: func(c *Command, args []string) { c.Print(correctMessage) }, }) if err := c.Execute(); err != nil { t.Error("Unexpected error:", err) } if output.String() != correctMessage { t.Errorf("Expected to contain %q message, but got %q", correctMessage, output.String()) } } func TestTraverseWithParentFlags(t *testing.T) { cmd := &Command{ Use: "do", TraverseChildren: true, } cmd.Flags().String("foo", "", "foo things") cmd.Flags().BoolP("goo", "g", false, "foo things") sub := &Command{Use: "next"} sub.Flags().String("add", "", "add things") cmd.AddCommand(sub) c, args, err := cmd.Traverse([]string{"-g", "--foo", "ok", "next", "--add"}) if err != nil { t.Fatalf("Expected no error: %s", err) } if len(args) != 1 && args[0] != "--add" { t.Fatalf("wrong args %s", args) } if c.Name() != sub.Name() { t.Fatalf("wrong command %q expected %q", c.Name(), sub.Name()) } } func TestTraverseNoParentFlags(t *testing.T) { cmd := &Command{ Use: "do", TraverseChildren: true, } cmd.Flags().String("foo", "", "foo things") sub := &Command{Use: "next"} sub.Flags().String("add", "", "add things") cmd.AddCommand(sub) c, args, err := cmd.Traverse([]string{"next"}) if err != nil { t.Fatalf("Expected no error: %s", err) } if len(args) != 0 { t.Fatalf("wrong args %s", args) } if c.Name() != sub.Name() { t.Fatalf("wrong command %q expected %q", c.Name(), sub.Name()) } } func TestTraverseWithBadParentFlags(t *testing.T) { cmd := &Command{ Use: "do", TraverseChildren: true, } sub := &Command{Use: "next"} sub.Flags().String("add", "", "add things") cmd.AddCommand(sub) expected := "got unknown flag: --add" c, _, err := cmd.Traverse([]string{"--add", "ok", "next"}) if err == nil || strings.Contains(err.Error(), expected) { t.Fatalf("Expected error %s got %s", expected, err) } if c != nil { t.Fatalf("Expected nil command") } } func TestTraverseWithBadChildFlag(t *testing.T) { cmd := &Command{ Use: "do", TraverseChildren: true, } cmd.Flags().String("foo", "", "foo things") sub := &Command{Use: "next"} cmd.AddCommand(sub) // Expect no error because the last commands args shouldn't be parsed in // Traverse c, args, err := cmd.Traverse([]string{"next", "--add"}) if err != nil { t.Fatalf("Expected no error: %s", err) } if len(args) != 1 && args[0] != "--add" { t.Fatalf("wrong args %s", args) } if c.Name() != sub.Name() { t.Fatalf("wrong command %q expected %q", c.Name(), sub.Name()) } } func TestRequiredFlags(t *testing.T) { c := &Command{Use: "c", Run: func(*Command, []string) {}} output := new(bytes.Buffer) c.SetOutput(output) c.Flags().String("foo1", "", "required foo1") c.MarkFlagRequired("foo1") c.Flags().String("foo2", "", "required foo2") c.MarkFlagRequired("foo2") c.Flags().String("bar", "", "optional bar") expected := fmt.Sprintf("Required flag(s) %q, %q have/has not been set", "foo1", "foo2") if err := c.Execute(); err != nil { if err.Error() != expected { t.Errorf("expected %v, got %v", expected, err.Error()) } } } func TestPersistentRequiredFlags(t *testing.T) { parent := &Command{Use: "parent", Run: func(*Command, []string) {}} output := new(bytes.Buffer) parent.SetOutput(output) parent.PersistentFlags().String("foo1", "", "required foo1") parent.MarkPersistentFlagRequired("foo1") parent.PersistentFlags().String("foo2", "", "required foo2") parent.MarkPersistentFlagRequired("foo2") parent.Flags().String("foo3", "", "optional foo3") child := &Command{Use: "child", Run: func(*Command, []string) {}} child.Flags().String("bar1", "", "required bar1") child.MarkFlagRequired("bar1") child.Flags().String("bar2", "", "required bar2") child.MarkFlagRequired("bar2") child.Flags().String("bar3", "", "optional bar3") parent.AddCommand(child) parent.SetArgs([]string{"child"}) expected := fmt.Sprintf("Required flag(s) %q, %q, %q, %q have/has not been set", "bar1", "bar2", "foo1", "foo2") if err := parent.Execute(); err != nil { if err.Error() != expected { t.Errorf("expected %v, got %v", expected, err.Error()) } } } // TestUpdateName checks if c.Name() updates on changed c.Use. // Related to https://github.com/spf13/cobra/pull/422#discussion_r143918343. func TestUpdateName(t *testing.T) { c := &Command{Use: "name xyz"} originalName := c.Name() c.Use = "changedName abc" if originalName == c.Name() || c.Name() != "changedName" { t.Error("c.Name() should be updated on changed c.Use") } }