spf13--cobra/command_test.go
Tim Peoples 94eba18f8c Add CalledAs method to Command (w/ tests)
The `CalledAs` method returns the name of the command or alias that
invoked the command -- as long as the command was actually invoked.
Otherwise, it returns the empty string.

The opens up possibilies for commands to behave differently based on
which alias invoked the command (in the same vein as Linux programs
which adjust their behavior based on the value of argv[0]).
2017-11-05 11:13:35 -08:00

589 lines
15 KiB
Go

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 TestTraverseWithTwoSubcommands(t *testing.T) {
cmd := &Command{
Use: "do",
TraverseChildren: true,
}
sub := &Command{
Use: "sub",
TraverseChildren: true,
}
cmd.AddCommand(sub)
subsub := &Command{
Use: "subsub",
}
sub.AddCommand(subsub)
c, _, err := cmd.Traverse([]string{"sub", "subsub"})
if err != nil {
t.Fatalf("Expected no error: %s", err)
}
if c.Name() != subsub.Name() {
t.Fatalf("wrong command %q expected %q", c.Name(), subsub.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")
}
}
type calledAsTestcase struct {
args []string
call string
want string
epm bool
tc bool
}
func (tc *calledAsTestcase) test(t *testing.T) {
defer func(ov bool) { EnablePrefixMatching = ov }(EnablePrefixMatching)
EnablePrefixMatching = tc.epm
var called *Command
run := func(c *Command, _ []string) { t.Logf("called: %q", c.Name()); called = c }
parent := &Command{Use: "parent", Run: run}
child1 := &Command{Use: "child1", Run: run, Aliases: []string{"this"}}
child2 := &Command{Use: "child2", Run: run, Aliases: []string{"that"}}
parent.AddCommand(child1)
parent.AddCommand(child2)
parent.SetArgs(tc.args)
output := new(bytes.Buffer)
parent.SetOutput(output)
parent.Execute()
if called == nil {
if tc.call != "" {
t.Errorf("missing expected call to command: %s", tc.call)
}
return
}
if called.Name() != tc.call {
t.Errorf("called command == %q; Wanted %q", called.Name(), tc.call)
} else if got := called.CalledAs(); got != tc.want {
t.Errorf("%s.CalledAs() == %q; Wanted: %q", tc.call, got, tc.want)
}
}
func TestCalledAs(t *testing.T) {
tests := map[string]calledAsTestcase{
"find/no-args": {nil, "parent", "parent", false, false},
"find/real-name": {[]string{"child1"}, "child1", "child1", false, false},
"find/full-alias": {[]string{"that"}, "child2", "that", false, false},
"find/part-no-prefix": {[]string{"thi"}, "", "", false, false},
"find/part-alias": {[]string{"thi"}, "child1", "this", true, false},
"find/conflict": {[]string{"th"}, "", "", true, false},
"traverse/no-args": {nil, "parent", "parent", false, true},
"traverse/real-name": {[]string{"child1"}, "child1", "child1", false, true},
"traverse/full-alias": {[]string{"that"}, "child2", "that", false, true},
"traverse/part-no-prefix": {[]string{"thi"}, "", "", false, true},
"traverse/part-alias": {[]string{"thi"}, "child1", "this", true, true},
"traverse/conflict": {[]string{"th"}, "", "", true, true},
}
for name, tc := range tests {
t.Run(name, tc.test)
}
}