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