| package cobra |
| |
| import ( |
| "bytes" |
| "fmt" |
| "os" |
| "os/exec" |
| "regexp" |
| "strings" |
| "testing" |
| ) |
| |
| func checkOmit(t *testing.T, found, unexpected string) { |
| if strings.Contains(found, unexpected) { |
| t.Errorf("Got: %q\nBut should not have!\n", unexpected) |
| } |
| } |
| |
| func check(t *testing.T, found, expected string) { |
| if !strings.Contains(found, expected) { |
| t.Errorf("Expecting to contain: \n %q\nGot:\n %q\n", expected, found) |
| } |
| } |
| |
| func checkNumOccurrences(t *testing.T, found, expected string, expectedOccurrences int) { |
| numOccurrences := strings.Count(found, expected) |
| if numOccurrences != expectedOccurrences { |
| t.Errorf("Expecting to contain %d occurrences of: \n %q\nGot %d:\n %q\n", expectedOccurrences, expected, numOccurrences, found) |
| } |
| } |
| |
| func checkRegex(t *testing.T, found, pattern string) { |
| matched, err := regexp.MatchString(pattern, found) |
| if err != nil { |
| t.Errorf("Error thrown performing MatchString: \n %s\n", err) |
| } |
| if !matched { |
| t.Errorf("Expecting to match: \n %q\nGot:\n %q\n", pattern, found) |
| } |
| } |
| |
| func runShellCheck(s string) error { |
| cmd := exec.Command("shellcheck", "-s", "bash", "-", "-e", |
| "SC2034", // PREFIX appears unused. Verify it or export it. |
| ) |
| cmd.Stderr = os.Stderr |
| cmd.Stdout = os.Stdout |
| |
| stdin, err := cmd.StdinPipe() |
| if err != nil { |
| return err |
| } |
| go func() { |
| _, err := stdin.Write([]byte(s)) |
| CheckErr(err) |
| |
| stdin.Close() |
| }() |
| |
| return cmd.Run() |
| } |
| |
| // World worst custom function, just keep telling you to enter hello! |
| const bashCompletionFunc = `__root_custom_func() { |
| COMPREPLY=( "hello" ) |
| } |
| ` |
| |
| func TestBashCompletions(t *testing.T) { |
| rootCmd := &Command{ |
| Use: "root", |
| ArgAliases: []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"}, |
| ValidArgs: []string{"pod", "node", "service", "replicationcontroller"}, |
| BashCompletionFunction: bashCompletionFunc, |
| Run: emptyRun, |
| } |
| rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot") |
| assertNoErr(t, rootCmd.MarkFlagRequired("introot")) |
| |
| // Filename. |
| rootCmd.Flags().String("filename", "", "Enter a filename") |
| assertNoErr(t, rootCmd.MarkFlagFilename("filename", "json", "yaml", "yml")) |
| |
| // Persistent filename. |
| rootCmd.PersistentFlags().String("persistent-filename", "", "Enter a filename") |
| assertNoErr(t, rootCmd.MarkPersistentFlagFilename("persistent-filename")) |
| assertNoErr(t, rootCmd.MarkPersistentFlagRequired("persistent-filename")) |
| |
| // Filename extensions. |
| rootCmd.Flags().String("filename-ext", "", "Enter a filename (extension limited)") |
| assertNoErr(t, rootCmd.MarkFlagFilename("filename-ext")) |
| rootCmd.Flags().String("custom", "", "Enter a filename (extension limited)") |
| assertNoErr(t, rootCmd.MarkFlagCustom("custom", "__complete_custom")) |
| |
| // Subdirectories in a given directory. |
| rootCmd.Flags().String("theme", "", "theme to use (located in /themes/THEMENAME/)") |
| assertNoErr(t, rootCmd.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"})) |
| |
| // For two word flags check |
| rootCmd.Flags().StringP("two", "t", "", "this is two word flags") |
| rootCmd.Flags().BoolP("two-w-default", "T", false, "this is not two word flags") |
| |
| echoCmd := &Command{ |
| Use: "echo [string to echo]", |
| Aliases: []string{"say"}, |
| Short: "Echo anything to the screen", |
| Long: "an utterly useless command for testing.", |
| Example: "Just run cobra-test echo", |
| Run: emptyRun, |
| } |
| |
| echoCmd.Flags().String("filename", "", "Enter a filename") |
| assertNoErr(t, echoCmd.MarkFlagFilename("filename", "json", "yaml", "yml")) |
| echoCmd.Flags().String("config", "", "config to use (located in /config/PROFILE/)") |
| assertNoErr(t, echoCmd.Flags().SetAnnotation("config", BashCompSubdirsInDir, []string{"config"})) |
| |
| printCmd := &Command{ |
| Use: "print [string to print]", |
| Args: MinimumNArgs(1), |
| Short: "Print anything to the screen", |
| Long: "an absolutely utterly useless command for testing.", |
| Run: emptyRun, |
| } |
| |
| deprecatedCmd := &Command{ |
| Use: "deprecated [can't do anything here]", |
| Args: NoArgs, |
| Short: "A command which is deprecated", |
| Long: "an absolutely utterly useless command for testing deprecation!.", |
| Deprecated: "Please use echo instead", |
| Run: emptyRun, |
| } |
| |
| colonCmd := &Command{ |
| Use: "cmd:colon", |
| Run: emptyRun, |
| } |
| |
| timesCmd := &Command{ |
| Use: "times [# times] [string to echo]", |
| SuggestFor: []string{"counts"}, |
| Args: OnlyValidArgs, |
| ValidArgs: []string{"one", "two", "three", "four"}, |
| Short: "Echo anything to the screen more times", |
| Long: "a slightly useless command for testing.", |
| Run: emptyRun, |
| } |
| |
| echoCmd.AddCommand(timesCmd) |
| rootCmd.AddCommand(echoCmd, printCmd, deprecatedCmd, colonCmd) |
| |
| buf := new(bytes.Buffer) |
| assertNoErr(t, rootCmd.GenBashCompletion(buf)) |
| output := buf.String() |
| |
| check(t, output, "_root") |
| check(t, output, "_root_echo") |
| check(t, output, "_root_echo_times") |
| check(t, output, "_root_print") |
| check(t, output, "_root_cmd__colon") |
| |
| // check for required flags |
| check(t, output, `must_have_one_flag+=("--introot=")`) |
| check(t, output, `must_have_one_flag+=("--persistent-filename=")`) |
| // check for custom completion function with both qualified and unqualified name |
| checkNumOccurrences(t, output, `__custom_func`, 2) // 1. check existence, 2. invoke |
| checkNumOccurrences(t, output, `__root_custom_func`, 3) // 1. check existence, 2. invoke, 3. actual definition |
| // check for custom completion function body |
| check(t, output, `COMPREPLY=( "hello" )`) |
| // check for required nouns |
| check(t, output, `must_have_one_noun+=("pod")`) |
| // check for noun aliases |
| check(t, output, `noun_aliases+=("pods")`) |
| check(t, output, `noun_aliases+=("rc")`) |
| checkOmit(t, output, `must_have_one_noun+=("pods")`) |
| // check for filename extension flags |
| check(t, output, `flags_completion+=("_filedir")`) |
| // check for filename extension flags |
| check(t, output, `must_have_one_noun+=("three")`) |
| // check for filename extension flags |
| check(t, output, fmt.Sprintf(`flags_completion+=("__%s_handle_filename_extension_flag json|yaml|yml")`, rootCmd.Name())) |
| // check for filename extension flags in a subcommand |
| checkRegex(t, output, fmt.Sprintf(`_root_echo\(\)\n{[^}]*flags_completion\+=\("__%s_handle_filename_extension_flag json\|yaml\|yml"\)`, rootCmd.Name())) |
| // check for custom flags |
| check(t, output, `flags_completion+=("__complete_custom")`) |
| // check for subdirs_in_dir flags |
| check(t, output, fmt.Sprintf(`flags_completion+=("__%s_handle_subdirs_in_dir_flag themes")`, rootCmd.Name())) |
| // check for subdirs_in_dir flags in a subcommand |
| checkRegex(t, output, fmt.Sprintf(`_root_echo\(\)\n{[^}]*flags_completion\+=\("__%s_handle_subdirs_in_dir_flag config"\)`, rootCmd.Name())) |
| |
| // check two word flags |
| check(t, output, `two_word_flags+=("--two")`) |
| check(t, output, `two_word_flags+=("-t")`) |
| checkOmit(t, output, `two_word_flags+=("--two-w-default")`) |
| checkOmit(t, output, `two_word_flags+=("-T")`) |
| |
| // check local nonpersistent flag |
| check(t, output, `local_nonpersistent_flags+=("--two")`) |
| check(t, output, `local_nonpersistent_flags+=("--two=")`) |
| check(t, output, `local_nonpersistent_flags+=("-t")`) |
| check(t, output, `local_nonpersistent_flags+=("--two-w-default")`) |
| check(t, output, `local_nonpersistent_flags+=("-T")`) |
| |
| checkOmit(t, output, deprecatedCmd.Name()) |
| |
| // If available, run shellcheck against the script. |
| if err := exec.Command("which", "shellcheck").Run(); err != nil { |
| return |
| } |
| if err := runShellCheck(output); err != nil { |
| t.Fatalf("shellcheck failed: %v", err) |
| } |
| } |
| |
| func TestBashCompletionHiddenFlag(t *testing.T) { |
| c := &Command{Use: "c", Run: emptyRun} |
| |
| const flagName = "hiddenFlag" |
| c.Flags().Bool(flagName, false, "") |
| assertNoErr(t, c.Flags().MarkHidden(flagName)) |
| |
| buf := new(bytes.Buffer) |
| assertNoErr(t, c.GenBashCompletion(buf)) |
| output := buf.String() |
| |
| if strings.Contains(output, flagName) { |
| t.Errorf("Expected completion to not include %q flag: Got %v", flagName, output) |
| } |
| } |
| |
| func TestBashCompletionDeprecatedFlag(t *testing.T) { |
| c := &Command{Use: "c", Run: emptyRun} |
| |
| const flagName = "deprecated-flag" |
| c.Flags().Bool(flagName, false, "") |
| assertNoErr(t, c.Flags().MarkDeprecated(flagName, "use --not-deprecated instead")) |
| |
| buf := new(bytes.Buffer) |
| assertNoErr(t, c.GenBashCompletion(buf)) |
| output := buf.String() |
| |
| if strings.Contains(output, flagName) { |
| t.Errorf("expected completion to not include %q flag: Got %v", flagName, output) |
| } |
| } |
| |
| func TestBashCompletionTraverseChildren(t *testing.T) { |
| c := &Command{Use: "c", Run: emptyRun, TraverseChildren: true} |
| |
| c.Flags().StringP("string-flag", "s", "", "string flag") |
| c.Flags().BoolP("bool-flag", "b", false, "bool flag") |
| |
| buf := new(bytes.Buffer) |
| assertNoErr(t, c.GenBashCompletion(buf)) |
| output := buf.String() |
| |
| // check that local nonpersistent flag are not set since we have TraverseChildren set to true |
| checkOmit(t, output, `local_nonpersistent_flags+=("--string-flag")`) |
| checkOmit(t, output, `local_nonpersistent_flags+=("--string-flag=")`) |
| checkOmit(t, output, `local_nonpersistent_flags+=("-s")`) |
| checkOmit(t, output, `local_nonpersistent_flags+=("--bool-flag")`) |
| checkOmit(t, output, `local_nonpersistent_flags+=("-b")`) |
| } |