1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package cobra
16
17 import (
18 "bytes"
19 "fmt"
20 "os"
21 "os/exec"
22 "regexp"
23 "strings"
24 "testing"
25 )
26
27 func checkOmit(t *testing.T, found, unexpected string) {
28 if strings.Contains(found, unexpected) {
29 t.Errorf("Got: %q\nBut should not have!\n", unexpected)
30 }
31 }
32
33 func check(t *testing.T, found, expected string) {
34 if !strings.Contains(found, expected) {
35 t.Errorf("Expecting to contain: \n %q\nGot:\n %q\n", expected, found)
36 }
37 }
38
39 func checkNumOccurrences(t *testing.T, found, expected string, expectedOccurrences int) {
40 numOccurrences := strings.Count(found, expected)
41 if numOccurrences != expectedOccurrences {
42 t.Errorf("Expecting to contain %d occurrences of: \n %q\nGot %d:\n %q\n", expectedOccurrences, expected, numOccurrences, found)
43 }
44 }
45
46 func checkRegex(t *testing.T, found, pattern string) {
47 matched, err := regexp.MatchString(pattern, found)
48 if err != nil {
49 t.Errorf("Error thrown performing MatchString: \n %s\n", err)
50 }
51 if !matched {
52 t.Errorf("Expecting to match: \n %q\nGot:\n %q\n", pattern, found)
53 }
54 }
55
56 func runShellCheck(s string) error {
57 cmd := exec.Command("shellcheck", "-s", "bash", "-", "-e",
58 "SC2034",
59 )
60 cmd.Stderr = os.Stderr
61 cmd.Stdout = os.Stdout
62
63 stdin, err := cmd.StdinPipe()
64 if err != nil {
65 return err
66 }
67 go func() {
68 _, err := stdin.Write([]byte(s))
69 CheckErr(err)
70
71 stdin.Close()
72 }()
73
74 return cmd.Run()
75 }
76
77
78 const bashCompletionFunc = `__root_custom_func() {
79 COMPREPLY=( "hello" )
80 }
81 `
82
83 func TestBashCompletions(t *testing.T) {
84 rootCmd := &Command{
85 Use: "root",
86 ArgAliases: []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"},
87 ValidArgs: []string{"pod", "node", "service", "replicationcontroller"},
88 BashCompletionFunction: bashCompletionFunc,
89 Run: emptyRun,
90 }
91 rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
92 assertNoErr(t, rootCmd.MarkFlagRequired("introot"))
93
94
95 rootCmd.Flags().String("filename", "", "Enter a filename")
96 assertNoErr(t, rootCmd.MarkFlagFilename("filename", "json", "yaml", "yml"))
97
98
99 rootCmd.PersistentFlags().String("persistent-filename", "", "Enter a filename")
100 assertNoErr(t, rootCmd.MarkPersistentFlagFilename("persistent-filename"))
101 assertNoErr(t, rootCmd.MarkPersistentFlagRequired("persistent-filename"))
102
103
104 rootCmd.Flags().String("filename-ext", "", "Enter a filename (extension limited)")
105 assertNoErr(t, rootCmd.MarkFlagFilename("filename-ext"))
106 rootCmd.Flags().String("custom", "", "Enter a filename (extension limited)")
107 assertNoErr(t, rootCmd.MarkFlagCustom("custom", "__complete_custom"))
108
109
110 rootCmd.Flags().String("theme", "", "theme to use (located in /themes/THEMENAME/)")
111 assertNoErr(t, rootCmd.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"}))
112
113
114 rootCmd.Flags().StringP("two", "t", "", "this is two word flags")
115 rootCmd.Flags().BoolP("two-w-default", "T", false, "this is not two word flags")
116
117 echoCmd := &Command{
118 Use: "echo [string to echo]",
119 Aliases: []string{"say"},
120 Short: "Echo anything to the screen",
121 Long: "an utterly useless command for testing.",
122 Example: "Just run cobra-test echo",
123 Run: emptyRun,
124 }
125
126 echoCmd.Flags().String("filename", "", "Enter a filename")
127 assertNoErr(t, echoCmd.MarkFlagFilename("filename", "json", "yaml", "yml"))
128 echoCmd.Flags().String("config", "", "config to use (located in /config/PROFILE/)")
129 assertNoErr(t, echoCmd.Flags().SetAnnotation("config", BashCompSubdirsInDir, []string{"config"}))
130
131 printCmd := &Command{
132 Use: "print [string to print]",
133 Args: MinimumNArgs(1),
134 Short: "Print anything to the screen",
135 Long: "an absolutely utterly useless command for testing.",
136 Run: emptyRun,
137 }
138
139 deprecatedCmd := &Command{
140 Use: "deprecated [can't do anything here]",
141 Args: NoArgs,
142 Short: "A command which is deprecated",
143 Long: "an absolutely utterly useless command for testing deprecation!.",
144 Deprecated: "Please use echo instead",
145 Run: emptyRun,
146 }
147
148 colonCmd := &Command{
149 Use: "cmd:colon",
150 Run: emptyRun,
151 }
152
153 timesCmd := &Command{
154 Use: "times [# times] [string to echo]",
155 SuggestFor: []string{"counts"},
156 Args: OnlyValidArgs,
157 ValidArgs: []string{"one", "two", "three", "four"},
158 Short: "Echo anything to the screen more times",
159 Long: "a slightly useless command for testing.",
160 Run: emptyRun,
161 }
162
163 echoCmd.AddCommand(timesCmd)
164 rootCmd.AddCommand(echoCmd, printCmd, deprecatedCmd, colonCmd)
165
166 buf := new(bytes.Buffer)
167 assertNoErr(t, rootCmd.GenBashCompletion(buf))
168 output := buf.String()
169
170 check(t, output, "_root")
171 check(t, output, "_root_echo")
172 check(t, output, "_root_echo_times")
173 check(t, output, "_root_print")
174 check(t, output, "_root_cmd__colon")
175
176
177 check(t, output, `must_have_one_flag+=("--introot=")`)
178 check(t, output, `must_have_one_flag+=("--persistent-filename=")`)
179
180 checkNumOccurrences(t, output, `__custom_func`, 2)
181 checkNumOccurrences(t, output, `__root_custom_func`, 3)
182
183 check(t, output, `COMPREPLY=( "hello" )`)
184
185 check(t, output, `must_have_one_noun+=("pod")`)
186
187 check(t, output, `noun_aliases+=("pods")`)
188 check(t, output, `noun_aliases+=("rc")`)
189 checkOmit(t, output, `must_have_one_noun+=("pods")`)
190
191 check(t, output, `flags_completion+=("_filedir")`)
192
193 check(t, output, `must_have_one_noun+=("three")`)
194
195 check(t, output, fmt.Sprintf(`flags_completion+=("__%s_handle_filename_extension_flag json|yaml|yml")`, rootCmd.Name()))
196
197 checkRegex(t, output, fmt.Sprintf(`_root_echo\(\)\n{[^}]*flags_completion\+=\("__%s_handle_filename_extension_flag json\|yaml\|yml"\)`, rootCmd.Name()))
198
199 check(t, output, `flags_completion+=("__complete_custom")`)
200
201 check(t, output, fmt.Sprintf(`flags_completion+=("__%s_handle_subdirs_in_dir_flag themes")`, rootCmd.Name()))
202
203 checkRegex(t, output, fmt.Sprintf(`_root_echo\(\)\n{[^}]*flags_completion\+=\("__%s_handle_subdirs_in_dir_flag config"\)`, rootCmd.Name()))
204
205
206 check(t, output, `two_word_flags+=("--two")`)
207 check(t, output, `two_word_flags+=("-t")`)
208 checkOmit(t, output, `two_word_flags+=("--two-w-default")`)
209 checkOmit(t, output, `two_word_flags+=("-T")`)
210
211
212 check(t, output, `local_nonpersistent_flags+=("--two")`)
213 check(t, output, `local_nonpersistent_flags+=("--two=")`)
214 check(t, output, `local_nonpersistent_flags+=("-t")`)
215 check(t, output, `local_nonpersistent_flags+=("--two-w-default")`)
216 check(t, output, `local_nonpersistent_flags+=("-T")`)
217
218 checkOmit(t, output, deprecatedCmd.Name())
219
220
221 if err := exec.Command("which", "shellcheck").Run(); err != nil {
222 return
223 }
224 if err := runShellCheck(output); err != nil {
225 t.Fatalf("shellcheck failed: %v", err)
226 }
227 }
228
229 func TestBashCompletionHiddenFlag(t *testing.T) {
230 c := &Command{Use: "c", Run: emptyRun}
231
232 const flagName = "hiddenFlag"
233 c.Flags().Bool(flagName, false, "")
234 assertNoErr(t, c.Flags().MarkHidden(flagName))
235
236 buf := new(bytes.Buffer)
237 assertNoErr(t, c.GenBashCompletion(buf))
238 output := buf.String()
239
240 if strings.Contains(output, flagName) {
241 t.Errorf("Expected completion to not include %q flag: Got %v", flagName, output)
242 }
243 }
244
245 func TestBashCompletionDeprecatedFlag(t *testing.T) {
246 c := &Command{Use: "c", Run: emptyRun}
247
248 const flagName = "deprecated-flag"
249 c.Flags().Bool(flagName, false, "")
250 assertNoErr(t, c.Flags().MarkDeprecated(flagName, "use --not-deprecated instead"))
251
252 buf := new(bytes.Buffer)
253 assertNoErr(t, c.GenBashCompletion(buf))
254 output := buf.String()
255
256 if strings.Contains(output, flagName) {
257 t.Errorf("expected completion to not include %q flag: Got %v", flagName, output)
258 }
259 }
260
261 func TestBashCompletionTraverseChildren(t *testing.T) {
262 c := &Command{Use: "c", Run: emptyRun, TraverseChildren: true}
263
264 c.Flags().StringP("string-flag", "s", "", "string flag")
265 c.Flags().BoolP("bool-flag", "b", false, "bool flag")
266
267 buf := new(bytes.Buffer)
268 assertNoErr(t, c.GenBashCompletion(buf))
269 output := buf.String()
270
271
272 checkOmit(t, output, `local_nonpersistent_flags+=("--string-flag")`)
273 checkOmit(t, output, `local_nonpersistent_flags+=("--string-flag=")`)
274 checkOmit(t, output, `local_nonpersistent_flags+=("-s")`)
275 checkOmit(t, output, `local_nonpersistent_flags+=("--bool-flag")`)
276 checkOmit(t, output, `local_nonpersistent_flags+=("-b")`)
277 }
278
279 func TestBashCompletionNoActiveHelp(t *testing.T) {
280 c := &Command{Use: "c", Run: emptyRun}
281
282 buf := new(bytes.Buffer)
283 assertNoErr(t, c.GenBashCompletion(buf))
284 output := buf.String()
285
286
287 activeHelpVar := activeHelpEnvVar(c.Name())
288 check(t, output, fmt.Sprintf("%s=0", activeHelpVar))
289 }
290
View as plain text