2015-03-16 22:31:03 +03:00
|
|
|
package cobra
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2018-02-09 00:34:46 +03:00
|
|
|
"fmt"
|
2015-03-16 22:31:03 +03:00
|
|
|
"os"
|
2016-04-02 23:45:01 +03:00
|
|
|
"os/exec"
|
2018-02-28 08:38:38 +03:00
|
|
|
"regexp"
|
2015-03-16 22:31:03 +03:00
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
2015-04-08 00:38:22 +03:00
|
|
|
func checkOmit(t *testing.T, found, unexpected string) {
|
|
|
|
if strings.Contains(found, unexpected) {
|
2017-10-31 21:58:37 +03:00
|
|
|
t.Errorf("Got: %q\nBut should not have!\n", unexpected)
|
2015-04-08 00:38:22 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-16 22:31:03 +03:00
|
|
|
func check(t *testing.T, found, expected string) {
|
|
|
|
if !strings.Contains(found, expected) {
|
2017-10-31 21:58:37 +03:00
|
|
|
t.Errorf("Expecting to contain: \n %q\nGot:\n %q\n", expected, found)
|
2015-03-16 22:31:03 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-21 19:12:02 +03:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-28 08:38:38 +03:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-02 23:45:01 +03:00
|
|
|
func runShellCheck(s string) error {
|
|
|
|
excluded := []string{
|
|
|
|
"SC2034", // PREFIX appears unused. Verify it or export it.
|
|
|
|
}
|
|
|
|
cmd := exec.Command("shellcheck", "-s", "bash", "-", "-e", strings.Join(excluded, ","))
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
|
|
|
|
stdin, err := cmd.StdinPipe()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
go func() {
|
|
|
|
stdin.Write([]byte(s))
|
2017-10-31 21:58:37 +03:00
|
|
|
stdin.Close()
|
2016-04-02 23:45:01 +03:00
|
|
|
}()
|
|
|
|
|
|
|
|
return cmd.Run()
|
|
|
|
}
|
|
|
|
|
2015-03-16 22:31:03 +03:00
|
|
|
// World worst custom function, just keep telling you to enter hello!
|
2018-08-21 19:12:02 +03:00
|
|
|
const bashCompletionFunc = `__root_custom_func() {
|
2017-10-31 21:58:37 +03:00
|
|
|
COMPREPLY=( "hello" )
|
2015-03-16 22:31:03 +03:00
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
func TestBashCompletions(t *testing.T) {
|
2017-10-31 21:58:37 +03:00
|
|
|
rootCmd := &Command{
|
|
|
|
Use: "root",
|
|
|
|
ArgAliases: []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"},
|
|
|
|
ValidArgs: []string{"pod", "node", "service", "replicationcontroller"},
|
|
|
|
BashCompletionFunction: bashCompletionFunc,
|
2019-01-09 03:34:09 +03:00
|
|
|
Run: emptyRun,
|
2017-10-31 21:58:37 +03:00
|
|
|
}
|
|
|
|
rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
|
|
|
|
rootCmd.MarkFlagRequired("introot")
|
|
|
|
|
|
|
|
// Filename.
|
|
|
|
rootCmd.Flags().String("filename", "", "Enter a filename")
|
|
|
|
rootCmd.MarkFlagFilename("filename", "json", "yaml", "yml")
|
|
|
|
|
|
|
|
// Persistent filename.
|
|
|
|
rootCmd.PersistentFlags().String("persistent-filename", "", "Enter a filename")
|
|
|
|
rootCmd.MarkPersistentFlagFilename("persistent-filename")
|
|
|
|
rootCmd.MarkPersistentFlagRequired("persistent-filename")
|
|
|
|
|
|
|
|
// Filename extensions.
|
|
|
|
rootCmd.Flags().String("filename-ext", "", "Enter a filename (extension limited)")
|
|
|
|
rootCmd.MarkFlagFilename("filename-ext")
|
|
|
|
rootCmd.Flags().String("custom", "", "Enter a filename (extension limited)")
|
|
|
|
rootCmd.MarkFlagCustom("custom", "__complete_custom")
|
|
|
|
|
|
|
|
// Subdirectories in a given directory.
|
|
|
|
rootCmd.Flags().String("theme", "", "theme to use (located in /themes/THEMENAME/)")
|
|
|
|
rootCmd.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"})
|
|
|
|
|
2019-03-11 15:55:09 +03:00
|
|
|
// 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")
|
|
|
|
|
2017-10-31 21:58:37 +03:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2018-02-28 08:38:38 +03:00
|
|
|
echoCmd.Flags().String("filename", "", "Enter a filename")
|
|
|
|
echoCmd.MarkFlagFilename("filename", "json", "yaml", "yml")
|
|
|
|
echoCmd.Flags().String("config", "", "config to use (located in /config/PROFILE/)")
|
|
|
|
echoCmd.Flags().SetAnnotation("config", BashCompSubdirsInDir, []string{"config"})
|
|
|
|
|
2017-10-31 21:58:37 +03:00
|
|
|
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)
|
|
|
|
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")
|
2015-03-16 22:31:03 +03:00
|
|
|
|
|
|
|
// check for required flags
|
2017-10-31 21:58:37 +03:00
|
|
|
check(t, output, `must_have_one_flag+=("--introot=")`)
|
|
|
|
check(t, output, `must_have_one_flag+=("--persistent-filename=")`)
|
2018-08-21 19:12:02 +03:00
|
|
|
// 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
|
2017-10-31 21:58:37 +03:00
|
|
|
check(t, output, `COMPREPLY=( "hello" )`)
|
2015-03-16 22:31:03 +03:00
|
|
|
// check for required nouns
|
2017-10-31 21:58:37 +03:00
|
|
|
check(t, output, `must_have_one_noun+=("pod")`)
|
2016-03-25 18:05:56 +03:00
|
|
|
// check for noun aliases
|
2017-10-31 21:58:37 +03:00
|
|
|
check(t, output, `noun_aliases+=("pods")`)
|
|
|
|
check(t, output, `noun_aliases+=("rc")`)
|
|
|
|
checkOmit(t, output, `must_have_one_noun+=("pods")`)
|
2015-06-22 21:28:16 +03:00
|
|
|
// check for filename extension flags
|
2017-10-31 21:58:37 +03:00
|
|
|
check(t, output, `flags_completion+=("_filedir")`)
|
2015-06-22 21:28:16 +03:00
|
|
|
// check for filename extension flags
|
2017-10-31 21:58:37 +03:00
|
|
|
check(t, output, `must_have_one_noun+=("three")`)
|
2017-09-01 18:16:37 +03:00
|
|
|
// check for filename extension flags
|
2018-02-09 00:34:46 +03:00
|
|
|
check(t, output, fmt.Sprintf(`flags_completion+=("__%s_handle_filename_extension_flag json|yaml|yml")`, rootCmd.Name()))
|
2018-02-28 08:38:38 +03:00
|
|
|
// 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()))
|
2016-03-20 23:05:18 +03:00
|
|
|
// check for custom flags
|
2017-10-31 21:58:37 +03:00
|
|
|
check(t, output, `flags_completion+=("__complete_custom")`)
|
2015-08-09 22:30:58 +03:00
|
|
|
// check for subdirs_in_dir flags
|
2018-02-09 00:34:46 +03:00
|
|
|
check(t, output, fmt.Sprintf(`flags_completion+=("__%s_handle_subdirs_in_dir_flag themes")`, rootCmd.Name()))
|
2018-02-28 08:38:38 +03:00
|
|
|
// 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()))
|
2015-04-08 00:38:22 +03:00
|
|
|
|
2019-03-11 15:55:09 +03:00
|
|
|
// 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")`)
|
|
|
|
|
2020-09-09 18:34:51 +03:00
|
|
|
// 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")`)
|
|
|
|
|
2017-10-31 21:58:37 +03:00
|
|
|
checkOmit(t, output, deprecatedCmd.Name())
|
2016-04-02 23:45:01 +03:00
|
|
|
|
2017-10-31 21:58:37 +03:00
|
|
|
// If available, run shellcheck against the script.
|
2016-04-02 23:45:01 +03:00
|
|
|
if err := exec.Command("which", "shellcheck").Run(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2017-10-31 21:58:37 +03:00
|
|
|
if err := runShellCheck(output); err != nil {
|
2016-04-02 23:45:01 +03:00
|
|
|
t.Fatalf("shellcheck failed: %v", err)
|
|
|
|
}
|
2015-03-16 22:31:03 +03:00
|
|
|
}
|
2016-08-03 00:49:33 +03:00
|
|
|
|
|
|
|
func TestBashCompletionHiddenFlag(t *testing.T) {
|
2017-10-31 21:58:37 +03:00
|
|
|
c := &Command{Use: "c", Run: emptyRun}
|
2016-08-03 00:49:33 +03:00
|
|
|
|
2017-10-31 21:58:37 +03:00
|
|
|
const flagName = "hiddenFlag"
|
|
|
|
c.Flags().Bool(flagName, false, "")
|
|
|
|
c.Flags().MarkHidden(flagName)
|
2016-08-03 00:49:33 +03:00
|
|
|
|
2017-10-31 21:58:37 +03:00
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
c.GenBashCompletion(buf)
|
|
|
|
output := buf.String()
|
2016-08-03 00:49:33 +03:00
|
|
|
|
2017-10-31 21:58:37 +03:00
|
|
|
if strings.Contains(output, flagName) {
|
|
|
|
t.Errorf("Expected completion to not include %q flag: Got %v", flagName, output)
|
2016-08-03 00:49:33 +03:00
|
|
|
}
|
|
|
|
}
|
2016-08-03 01:01:33 +03:00
|
|
|
|
|
|
|
func TestBashCompletionDeprecatedFlag(t *testing.T) {
|
2017-10-31 21:58:37 +03:00
|
|
|
c := &Command{Use: "c", Run: emptyRun}
|
2017-05-14 15:57:11 +03:00
|
|
|
|
2017-10-31 21:58:37 +03:00
|
|
|
const flagName = "deprecated-flag"
|
|
|
|
c.Flags().Bool(flagName, false, "")
|
|
|
|
c.Flags().MarkDeprecated(flagName, "use --not-deprecated instead")
|
2017-05-14 15:57:11 +03:00
|
|
|
|
2017-06-05 20:32:33 +03:00
|
|
|
buf := new(bytes.Buffer)
|
2017-10-31 21:58:37 +03:00
|
|
|
c.GenBashCompletion(buf)
|
|
|
|
output := buf.String()
|
2017-05-14 15:57:11 +03:00
|
|
|
|
2017-10-31 21:58:37 +03:00
|
|
|
if strings.Contains(output, flagName) {
|
|
|
|
t.Errorf("expected completion to not include %q flag: Got %v", flagName, output)
|
2017-05-14 15:57:11 +03:00
|
|
|
}
|
|
|
|
}
|
2020-09-09 18:34:51 +03:00
|
|
|
|
|
|
|
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)
|
|
|
|
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")`)
|
|
|
|
}
|