bash_completions_test.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. package cobra
  2. import (
  3. "bytes"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "regexp"
  8. "strings"
  9. "testing"
  10. )
  11. func checkOmit(t *testing.T, found, unexpected string) {
  12. if strings.Contains(found, unexpected) {
  13. t.Errorf("Got: %q\nBut should not have!\n", unexpected)
  14. }
  15. }
  16. func check(t *testing.T, found, expected string) {
  17. if !strings.Contains(found, expected) {
  18. t.Errorf("Expecting to contain: \n %q\nGot:\n %q\n", expected, found)
  19. }
  20. }
  21. func checkRegex(t *testing.T, found, pattern string) {
  22. matched, err := regexp.MatchString(pattern, found)
  23. if err != nil {
  24. t.Errorf("Error thrown performing MatchString: \n %s\n", err)
  25. }
  26. if !matched {
  27. t.Errorf("Expecting to match: \n %q\nGot:\n %q\n", pattern, found)
  28. }
  29. }
  30. func runShellCheck(s string) error {
  31. excluded := []string{
  32. "SC2034", // PREFIX appears unused. Verify it or export it.
  33. }
  34. cmd := exec.Command("shellcheck", "-s", "bash", "-", "-e", strings.Join(excluded, ","))
  35. cmd.Stderr = os.Stderr
  36. cmd.Stdout = os.Stdout
  37. stdin, err := cmd.StdinPipe()
  38. if err != nil {
  39. return err
  40. }
  41. go func() {
  42. stdin.Write([]byte(s))
  43. stdin.Close()
  44. }()
  45. return cmd.Run()
  46. }
  47. // World worst custom function, just keep telling you to enter hello!
  48. const bashCompletionFunc = `__custom_func() {
  49. COMPREPLY=( "hello" )
  50. }
  51. `
  52. func TestBashCompletions(t *testing.T) {
  53. rootCmd := &Command{
  54. Use: "root",
  55. ArgAliases: []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"},
  56. ValidArgs: []string{"pod", "node", "service", "replicationcontroller"},
  57. BashCompletionFunction: bashCompletionFunc,
  58. Run: emptyRun,
  59. }
  60. rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
  61. rootCmd.MarkFlagRequired("introot")
  62. // Filename.
  63. rootCmd.Flags().String("filename", "", "Enter a filename")
  64. rootCmd.MarkFlagFilename("filename", "json", "yaml", "yml")
  65. // Persistent filename.
  66. rootCmd.PersistentFlags().String("persistent-filename", "", "Enter a filename")
  67. rootCmd.MarkPersistentFlagFilename("persistent-filename")
  68. rootCmd.MarkPersistentFlagRequired("persistent-filename")
  69. // Filename extensions.
  70. rootCmd.Flags().String("filename-ext", "", "Enter a filename (extension limited)")
  71. rootCmd.MarkFlagFilename("filename-ext")
  72. rootCmd.Flags().String("custom", "", "Enter a filename (extension limited)")
  73. rootCmd.MarkFlagCustom("custom", "__complete_custom")
  74. // Subdirectories in a given directory.
  75. rootCmd.Flags().String("theme", "", "theme to use (located in /themes/THEMENAME/)")
  76. rootCmd.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"})
  77. echoCmd := &Command{
  78. Use: "echo [string to echo]",
  79. Aliases: []string{"say"},
  80. Short: "Echo anything to the screen",
  81. Long: "an utterly useless command for testing.",
  82. Example: "Just run cobra-test echo",
  83. Run: emptyRun,
  84. }
  85. echoCmd.Flags().String("filename", "", "Enter a filename")
  86. echoCmd.MarkFlagFilename("filename", "json", "yaml", "yml")
  87. echoCmd.Flags().String("config", "", "config to use (located in /config/PROFILE/)")
  88. echoCmd.Flags().SetAnnotation("config", BashCompSubdirsInDir, []string{"config"})
  89. printCmd := &Command{
  90. Use: "print [string to print]",
  91. Args: MinimumNArgs(1),
  92. Short: "Print anything to the screen",
  93. Long: "an absolutely utterly useless command for testing.",
  94. Run: emptyRun,
  95. }
  96. deprecatedCmd := &Command{
  97. Use: "deprecated [can't do anything here]",
  98. Args: NoArgs,
  99. Short: "A command which is deprecated",
  100. Long: "an absolutely utterly useless command for testing deprecation!.",
  101. Deprecated: "Please use echo instead",
  102. Run: emptyRun,
  103. }
  104. colonCmd := &Command{
  105. Use: "cmd:colon",
  106. Run: emptyRun,
  107. }
  108. timesCmd := &Command{
  109. Use: "times [# times] [string to echo]",
  110. SuggestFor: []string{"counts"},
  111. Args: OnlyValidArgs,
  112. ValidArgs: []string{"one", "two", "three", "four"},
  113. Short: "Echo anything to the screen more times",
  114. Long: "a slightly useless command for testing.",
  115. Run: emptyRun,
  116. }
  117. echoCmd.AddCommand(timesCmd)
  118. rootCmd.AddCommand(echoCmd, printCmd, deprecatedCmd, colonCmd)
  119. buf := new(bytes.Buffer)
  120. rootCmd.GenBashCompletion(buf)
  121. output := buf.String()
  122. check(t, output, "_root")
  123. check(t, output, "_root_echo")
  124. check(t, output, "_root_echo_times")
  125. check(t, output, "_root_print")
  126. check(t, output, "_root_cmd__colon")
  127. // check for required flags
  128. check(t, output, `must_have_one_flag+=("--introot=")`)
  129. check(t, output, `must_have_one_flag+=("--persistent-filename=")`)
  130. // check for custom completion function
  131. check(t, output, `COMPREPLY=( "hello" )`)
  132. // check for required nouns
  133. check(t, output, `must_have_one_noun+=("pod")`)
  134. // check for noun aliases
  135. check(t, output, `noun_aliases+=("pods")`)
  136. check(t, output, `noun_aliases+=("rc")`)
  137. checkOmit(t, output, `must_have_one_noun+=("pods")`)
  138. // check for filename extension flags
  139. check(t, output, `flags_completion+=("_filedir")`)
  140. // check for filename extension flags
  141. check(t, output, `must_have_one_noun+=("three")`)
  142. // check for filename extension flags
  143. check(t, output, fmt.Sprintf(`flags_completion+=("__%s_handle_filename_extension_flag json|yaml|yml")`, rootCmd.Name()))
  144. // check for filename extension flags in a subcommand
  145. checkRegex(t, output, fmt.Sprintf(`_root_echo\(\)\n{[^}]*flags_completion\+=\("__%s_handle_filename_extension_flag json\|yaml\|yml"\)`, rootCmd.Name()))
  146. // check for custom flags
  147. check(t, output, `flags_completion+=("__complete_custom")`)
  148. // check for subdirs_in_dir flags
  149. check(t, output, fmt.Sprintf(`flags_completion+=("__%s_handle_subdirs_in_dir_flag themes")`, rootCmd.Name()))
  150. // check for subdirs_in_dir flags in a subcommand
  151. checkRegex(t, output, fmt.Sprintf(`_root_echo\(\)\n{[^}]*flags_completion\+=\("__%s_handle_subdirs_in_dir_flag config"\)`, rootCmd.Name()))
  152. checkOmit(t, output, deprecatedCmd.Name())
  153. // If available, run shellcheck against the script.
  154. if err := exec.Command("which", "shellcheck").Run(); err != nil {
  155. return
  156. }
  157. if err := runShellCheck(output); err != nil {
  158. t.Fatalf("shellcheck failed: %v", err)
  159. }
  160. }
  161. func TestBashCompletionHiddenFlag(t *testing.T) {
  162. c := &Command{Use: "c", Run: emptyRun}
  163. const flagName = "hiddenFlag"
  164. c.Flags().Bool(flagName, false, "")
  165. c.Flags().MarkHidden(flagName)
  166. buf := new(bytes.Buffer)
  167. c.GenBashCompletion(buf)
  168. output := buf.String()
  169. if strings.Contains(output, flagName) {
  170. t.Errorf("Expected completion to not include %q flag: Got %v", flagName, output)
  171. }
  172. }
  173. func TestBashCompletionDeprecatedFlag(t *testing.T) {
  174. c := &Command{Use: "c", Run: emptyRun}
  175. const flagName = "deprecated-flag"
  176. c.Flags().Bool(flagName, false, "")
  177. c.Flags().MarkDeprecated(flagName, "use --not-deprecated instead")
  178. buf := new(bytes.Buffer)
  179. c.GenBashCompletion(buf)
  180. output := buf.String()
  181. if strings.Contains(output, flagName) {
  182. t.Errorf("expected completion to not include %q flag: Got %v", flagName, output)
  183. }
  184. }