| // Copyright 2013-2023 The Cobra Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package cobra | |
| import ( | |
| "bytes" | |
| "context" | |
| "fmt" | |
| "os" | |
| "strings" | |
| "sync" | |
| "testing" | |
| ) | |
| func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| if len(args) != 0 { | |
| return nil, ShellCompDirectiveNoFileComp | |
| } | |
| var completions []string | |
| for _, comp := range []string{"one\tThe first", "two\tThe second"} { | |
| if strings.HasPrefix(comp, toComplete) { | |
| completions = append(completions, comp) | |
| } | |
| } | |
| return completions, ShellCompDirectiveDefault | |
| } | |
| func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| if len(args) != 0 { | |
| return nil, ShellCompDirectiveNoFileComp | |
| } | |
| var completions []string | |
| for _, comp := range []string{"three\tThe third", "four\tThe fourth"} { | |
| if strings.HasPrefix(comp, toComplete) { | |
| completions = append(completions, comp) | |
| } | |
| } | |
| return completions, ShellCompDirectiveDefault | |
| } | |
| func TestCmdNameCompletionInGo(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| childCmd1 := &Command{ | |
| Use: "firstChild", | |
| Short: "First command", | |
| Run: emptyRun, | |
| } | |
| childCmd2 := &Command{ | |
| Use: "secondChild", | |
| Run: emptyRun, | |
| } | |
| hiddenCmd := &Command{ | |
| Use: "testHidden", | |
| Hidden: true, // Not completed | |
| Run: emptyRun, | |
| } | |
| deprecatedCmd := &Command{ | |
| Use: "testDeprecated", | |
| Deprecated: "deprecated", // Not completed | |
| Run: emptyRun, | |
| } | |
| aliasedCmd := &Command{ | |
| Use: "aliased", | |
| Short: "A command with aliases", | |
| Aliases: []string{"testAlias", "testSynonym"}, // Not completed | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd1, childCmd2, hiddenCmd, deprecatedCmd, aliasedCmd) | |
| // Test that sub-command names are completed | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "aliased", | |
| "completion", | |
| "firstChild", | |
| "help", | |
| "secondChild", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that sub-command names are completed with prefix | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "s") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "secondChild", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that even with no valid sub-command matches, hidden, deprecated and | |
| // aliases are not completed | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "test") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that sub-command names are completed with description | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "aliased\tA command with aliases", | |
| "completion\tGenerate the autocompletion script for the specified shell", | |
| "firstChild\tFirst command", | |
| "help\tHelp about any command", | |
| "secondChild", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestNoCmdNameCompletionInGo(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| rootCmd.Flags().String("localroot", "", "local root flag") | |
| childCmd1 := &Command{ | |
| Use: "childCmd1", | |
| Short: "First command", | |
| Args: MinimumNArgs(0), | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd1) | |
| childCmd1.PersistentFlags().StringP("persistent", "p", "", "persistent flag") | |
| persistentFlag := childCmd1.PersistentFlags().Lookup("persistent") | |
| childCmd1.Flags().StringP("nonPersistent", "n", "", "non-persistent flag") | |
| nonPersistentFlag := childCmd1.Flags().Lookup("nonPersistent") | |
| childCmd2 := &Command{ | |
| Use: "childCmd2", | |
| Run: emptyRun, | |
| } | |
| childCmd1.AddCommand(childCmd2) | |
| // Test that sub-command names are not completed if there is an argument already | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "arg1", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that sub-command names are not completed if a local non-persistent flag is present | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "--nonPersistent", "value", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the flag for the next command | |
| nonPersistentFlag.Changed = false | |
| expected = strings.Join([]string{ | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that sub-command names are completed if a local non-persistent flag is present and TraverseChildren is set to true | |
| // set TraverseChildren to true on the root cmd | |
| rootCmd.TraverseChildren = true | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset TraverseChildren for next command | |
| rootCmd.TraverseChildren = false | |
| expected = strings.Join([]string{ | |
| "childCmd1", | |
| "completion", | |
| "help", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that sub-command names from a child cmd are completed if a local non-persistent flag is present | |
| // and TraverseChildren is set to true on the root cmd | |
| rootCmd.TraverseChildren = true | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "childCmd1", "--nonPersistent", "value", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset TraverseChildren for next command | |
| rootCmd.TraverseChildren = false | |
| // Reset the flag for the next command | |
| nonPersistentFlag.Changed = false | |
| expected = strings.Join([]string{ | |
| "childCmd2", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that we don't use Traverse when we shouldn't. | |
| // This command should not return a completion since the command line is invalid without TraverseChildren. | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "childCmd1", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that sub-command names are not completed if a local non-persistent short flag is present | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-n", "value", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the flag for the next command | |
| nonPersistentFlag.Changed = false | |
| expected = strings.Join([]string{ | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that sub-command names are completed with a persistent flag | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "--persistent", "value", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the flag for the next command | |
| persistentFlag.Changed = false | |
| expected = strings.Join([]string{ | |
| "childCmd2", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that sub-command names are completed with a persistent short flag | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-p", "value", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the flag for the next command | |
| persistentFlag.Changed = false | |
| expected = strings.Join([]string{ | |
| "childCmd2", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestValidArgsCompletionInGo(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| ValidArgs: []string{"one", "two", "three"}, | |
| Args: MinimumNArgs(1), | |
| } | |
| // Test that validArgs are completed | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "one", | |
| "two", | |
| "three", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that validArgs are completed with prefix | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "o") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "one", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that validArgs don't repeat | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "one", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestValidArgsAndCmdCompletionInGo(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| ValidArgs: []string{"one", "two"}, | |
| Run: emptyRun, | |
| } | |
| childCmd := &Command{ | |
| Use: "thechild", | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| // Test that both sub-commands and validArgs are completed | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "completion", | |
| "help", | |
| "thechild", | |
| "one", | |
| "two", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that both sub-commands and validArgs are completed with prefix | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "thechild", | |
| "two", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestValidArgsFuncAndCmdCompletionInGo(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| ValidArgsFunction: validArgsFunc, | |
| Run: emptyRun, | |
| } | |
| childCmd := &Command{ | |
| Use: "thechild", | |
| Short: "The child command", | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| // Test that both sub-commands and validArgsFunction are completed | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "completion", | |
| "help", | |
| "thechild", | |
| "one", | |
| "two", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that both sub-commands and validArgs are completed with prefix | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "thechild", | |
| "two", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that both sub-commands and validArgs are completed with description | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "thechild\tThe child command", | |
| "two\tThe second", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestFlagNameCompletionInGo(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| childCmd := &Command{ | |
| Use: "childCmd", | |
| Version: "1.2.3", | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| rootCmd.Flags().IntP("first", "f", -1, "first flag") | |
| rootCmd.PersistentFlags().BoolP("second", "s", false, "second flag") | |
| childCmd.Flags().String("subFlag", "", "sub flag") | |
| // Test that flag names are not shown if the user has not given the '-' prefix | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "childCmd", | |
| "completion", | |
| "help", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that flag names are completed | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--first", | |
| "-f", | |
| "--help", | |
| "-h", | |
| "--second", | |
| "-s", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that flag names are completed when a prefix is given | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--f") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--first", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that flag names are completed in a sub-cmd | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "-") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--second", | |
| "-s", | |
| "--help", | |
| "-h", | |
| "--subFlag", | |
| "--version", | |
| "-v", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestFlagNameCompletionInGoWithDesc(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| childCmd := &Command{ | |
| Use: "childCmd", | |
| Short: "first command", | |
| Version: "1.2.3", | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| rootCmd.Flags().IntP("first", "f", -1, "first flag\nlonger description for flag") | |
| rootCmd.PersistentFlags().BoolP("second", "s", false, "second flag") | |
| childCmd.Flags().String("subFlag", "", "sub flag") | |
| // Test that flag names are not shown if the user has not given the '-' prefix | |
| output, err := executeCommand(rootCmd, ShellCompRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "childCmd\tfirst command", | |
| "completion\tGenerate the autocompletion script for the specified shell", | |
| "help\tHelp about any command", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that flag names are completed | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "-") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--first\tfirst flag", | |
| "-f\tfirst flag", | |
| "--help\thelp for root", | |
| "-h\thelp for root", | |
| "--second\tsecond flag", | |
| "-s\tsecond flag", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that flag names are completed when a prefix is given | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--f") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--first\tfirst flag", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that flag names are completed in a sub-cmd | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "childCmd", "-") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--second\tsecond flag", | |
| "-s\tsecond flag", | |
| "--help\thelp for childCmd", | |
| "-h\thelp for childCmd", | |
| "--subFlag\tsub flag", | |
| "--version\tversion for childCmd", | |
| "-v\tversion for childCmd", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| // customMultiString is a custom Value type that accepts multiple values, | |
| // but does not include "Slice" or "Array" in its "Type" string. | |
| type customMultiString []string | |
| var _ SliceValue = (*customMultiString)(nil) | |
| func (s *customMultiString) String() string { | |
| return fmt.Sprintf("%v", *s) | |
| } | |
| func (s *customMultiString) Set(v string) error { | |
| *s = append(*s, v) | |
| return nil | |
| } | |
| func (s *customMultiString) Type() string { | |
| return "multi string" | |
| } | |
| func (s *customMultiString) GetSlice() []string { | |
| return *s | |
| } | |
| func TestFlagNameCompletionRepeat(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| childCmd := &Command{ | |
| Use: "childCmd", | |
| Short: "first command", | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| rootCmd.Flags().IntP("first", "f", -1, "first flag") | |
| firstFlag := rootCmd.Flags().Lookup("first") | |
| rootCmd.Flags().BoolP("second", "s", false, "second flag") | |
| secondFlag := rootCmd.Flags().Lookup("second") | |
| rootCmd.Flags().StringArrayP("array", "a", nil, "array flag") | |
| arrayFlag := rootCmd.Flags().Lookup("array") | |
| rootCmd.Flags().IntSliceP("slice", "l", nil, "slice flag") | |
| sliceFlag := rootCmd.Flags().Lookup("slice") | |
| rootCmd.Flags().BoolSliceP("bslice", "b", nil, "bool slice flag") | |
| bsliceFlag := rootCmd.Flags().Lookup("bslice") | |
| rootCmd.Flags().VarP(&customMultiString{}, "multi", "m", "multi string flag") | |
| multiFlag := rootCmd.Flags().Lookup("multi") | |
| // Test that flag names are not repeated unless they are an array or slice | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the flag for the next command | |
| firstFlag.Changed = false | |
| expected := strings.Join([]string{ | |
| "--array", | |
| "--bslice", | |
| "--help", | |
| "--multi", | |
| "--second", | |
| "--slice", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that flag names are not repeated unless they are an array or slice | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--second=false", "--") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the flag for the next command | |
| firstFlag.Changed = false | |
| secondFlag.Changed = false | |
| expected = strings.Join([]string{ | |
| "--array", | |
| "--bslice", | |
| "--help", | |
| "--multi", | |
| "--slice", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that flag names are not repeated unless they are an array or slice | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--slice", "1", "--slice=2", "--array", "val", "--bslice", "true", "--multi", "val", "--") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the flag for the next command | |
| sliceFlag.Changed = false | |
| arrayFlag.Changed = false | |
| bsliceFlag.Changed = false | |
| multiFlag.Changed = false | |
| expected = strings.Join([]string{ | |
| "--array", | |
| "--bslice", | |
| "--first", | |
| "--help", | |
| "--multi", | |
| "--second", | |
| "--slice", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that flag names are not repeated unless they are an array or slice, using shortname | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the flag for the next command | |
| sliceFlag.Changed = false | |
| arrayFlag.Changed = false | |
| multiFlag.Changed = false | |
| expected = strings.Join([]string{ | |
| "--array", | |
| "-a", | |
| "--bslice", | |
| "-b", | |
| "--first", | |
| "-f", | |
| "--help", | |
| "-h", | |
| "--multi", | |
| "-m", | |
| "--second", | |
| "-s", | |
| "--slice", | |
| "-l", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that flag names are not repeated unless they are an array or slice, using shortname with prefix | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-a") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the flag for the next command | |
| sliceFlag.Changed = false | |
| arrayFlag.Changed = false | |
| multiFlag.Changed = false | |
| expected = strings.Join([]string{ | |
| "-a", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestRequiredFlagNameCompletionInGo(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| ValidArgs: []string{"realArg"}, | |
| Run: emptyRun, | |
| } | |
| childCmd := &Command{ | |
| Use: "childCmd", | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"subArg"}, ShellCompDirectiveNoFileComp | |
| }, | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| rootCmd.Flags().IntP("requiredFlag", "r", -1, "required flag") | |
| assertNoErr(t, rootCmd.MarkFlagRequired("requiredFlag")) | |
| requiredFlag := rootCmd.Flags().Lookup("requiredFlag") | |
| rootCmd.PersistentFlags().IntP("requiredPersistent", "p", -1, "required persistent") | |
| assertNoErr(t, rootCmd.MarkPersistentFlagRequired("requiredPersistent")) | |
| requiredPersistent := rootCmd.PersistentFlags().Lookup("requiredPersistent") | |
| rootCmd.Flags().StringP("release", "R", "", "Release name") | |
| childCmd.Flags().BoolP("subRequired", "s", false, "sub required flag") | |
| assertNoErr(t, childCmd.MarkFlagRequired("subRequired")) | |
| childCmd.Flags().BoolP("subNotRequired", "n", false, "sub not required flag") | |
| // Test that a required flag is suggested even without the - prefix | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "childCmd", | |
| "completion", | |
| "help", | |
| "--requiredFlag", | |
| "-r", | |
| "--requiredPersistent", | |
| "-p", | |
| "realArg", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that a required flag is suggested without other flags when using the '-' prefix | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--requiredFlag", | |
| "-r", | |
| "--requiredPersistent", | |
| "-p", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that if no required flag matches, the normal flags are suggested | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--relea") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--release", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test required flags for sub-commands | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--requiredPersistent", | |
| "-p", | |
| "--subRequired", | |
| "-s", | |
| "subArg", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "-") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--requiredPersistent", | |
| "-p", | |
| "--subRequired", | |
| "-s", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "--subNot") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--subNotRequired", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that when a required flag is present, it is not suggested anymore | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredFlag", "1", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the flag for the next command | |
| requiredFlag.Changed = false | |
| expected = strings.Join([]string{ | |
| "--requiredPersistent", | |
| "-p", | |
| "realArg", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that when a persistent required flag is present, it is not suggested anymore | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredPersistent", "1", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the flag for the next command | |
| requiredPersistent.Changed = false | |
| expected = strings.Join([]string{ | |
| "childCmd", | |
| "completion", | |
| "help", | |
| "--requiredFlag", | |
| "-r", | |
| "realArg", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that when all required flags are present, normal completion is done | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredFlag", "1", "--requiredPersistent", "1", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the flags for the next command | |
| requiredFlag.Changed = false | |
| requiredPersistent.Changed = false | |
| expected = strings.Join([]string{ | |
| "realArg", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestFlagFileExtFilterCompletionInGo(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| // No extensions. Should be ignored. | |
| rootCmd.Flags().StringP("file", "f", "", "file flag") | |
| assertNoErr(t, rootCmd.MarkFlagFilename("file")) | |
| // Single extension | |
| rootCmd.Flags().StringP("log", "l", "", "log flag") | |
| assertNoErr(t, rootCmd.MarkFlagFilename("log", "log")) | |
| // Multiple extensions | |
| rootCmd.Flags().StringP("yaml", "y", "", "yaml flag") | |
| assertNoErr(t, rootCmd.MarkFlagFilename("yaml", "yaml", "yml")) | |
| // Directly using annotation | |
| rootCmd.Flags().StringP("text", "t", "", "text flag") | |
| assertNoErr(t, rootCmd.Flags().SetAnnotation("text", BashCompFilenameExt, []string{"txt"})) | |
| // Test that the completion logic returns the proper info for the completion | |
| // script to handle the file filtering | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--file", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--log", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "log", | |
| ":8", | |
| "Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--yaml", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "yaml", "yml", | |
| ":8", | |
| "Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--yaml=") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "yaml", "yml", | |
| ":8", | |
| "Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-y", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "yaml", "yml", | |
| ":8", | |
| "Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-y=") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "yaml", "yml", | |
| ":8", | |
| "Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--text", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "txt", | |
| ":8", | |
| "Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestFlagDirFilterCompletionInGo(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| // Filter directories | |
| rootCmd.Flags().StringP("dir", "d", "", "dir flag") | |
| assertNoErr(t, rootCmd.MarkFlagDirname("dir")) | |
| // Filter directories within a directory | |
| rootCmd.Flags().StringP("subdir", "s", "", "subdir") | |
| assertNoErr(t, rootCmd.Flags().SetAnnotation("subdir", BashCompSubdirsInDir, []string{"themes"})) | |
| // Multiple directory specification get ignored | |
| rootCmd.Flags().StringP("manydir", "m", "", "manydir") | |
| assertNoErr(t, rootCmd.Flags().SetAnnotation("manydir", BashCompSubdirsInDir, []string{"themes", "colors"})) | |
| // Test that the completion logic returns the proper info for the completion | |
| // script to handle the directory filtering | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--dir", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| ":16", | |
| "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-d", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| ":16", | |
| "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--subdir", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "themes", | |
| ":16", | |
| "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--subdir=") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "themes", | |
| ":16", | |
| "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "themes", | |
| ":16", | |
| "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s=") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "themes", | |
| ":16", | |
| "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--manydir", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| ":16", | |
| "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestValidArgsFuncCmdContext(t *testing.T) { | |
| validArgsFunc := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| ctx := cmd.Context() | |
| if ctx == nil { | |
| t.Error("Received nil context in completion func") | |
| } else if ctx.Value("testKey") != "123" { | |
| t.Error("Received invalid context") | |
| } | |
| return nil, ShellCompDirectiveDefault | |
| } | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| childCmd := &Command{ | |
| Use: "childCmd", | |
| ValidArgsFunction: validArgsFunc, | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| //nolint:staticcheck // We can safely use a basic type as key in tests. | |
| ctx := context.WithValue(context.Background(), "testKey", "123") | |
| // Test completing an empty string on the childCmd | |
| _, output, err := executeCommandWithContextC(ctx, rootCmd, ShellCompNoDescRequestCmd, "childCmd", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestValidArgsFuncSingleCmd(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| ValidArgsFunction: validArgsFunc, | |
| Run: emptyRun, | |
| } | |
| // Test completing an empty string | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "one", | |
| "two", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check completing with a prefix | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "two", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestValidArgsFuncSingleCmdInvalidArg(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| // If we don't specify a value for Args, this test fails. | |
| // This is only true for a root command without any subcommands, and is caused | |
| // by the fact that the __complete command becomes a subcommand when there should not be one. | |
| // The problem is in the implementation of legacyArgs(). | |
| Args: MinimumNArgs(1), | |
| ValidArgsFunction: validArgsFunc, | |
| Run: emptyRun, | |
| } | |
| // Check completing with wrong number of args | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "unexpectedArg", "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestValidArgsFuncChildCmds(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| child1Cmd := &Command{ | |
| Use: "child1", | |
| ValidArgsFunction: validArgsFunc, | |
| Run: emptyRun, | |
| } | |
| child2Cmd := &Command{ | |
| Use: "child2", | |
| ValidArgsFunction: validArgsFunc2, | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(child1Cmd, child2Cmd) | |
| // Test completion of first sub-command with empty argument | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "one", | |
| "two", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test completion of first sub-command with a prefix to complete | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "two", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check completing with wrong number of args | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "unexpectedArg", "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test completion of second sub-command with empty argument | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "three", | |
| "four", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "three", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check completing with wrong number of args | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "unexpectedArg", "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestValidArgsFuncAliases(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| child := &Command{ | |
| Use: "child", | |
| Aliases: []string{"son", "daughter"}, | |
| ValidArgsFunction: validArgsFunc, | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(child) | |
| // Test completion of first sub-command with empty argument | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "son", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "one", | |
| "two", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test completion of first sub-command with a prefix to complete | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "daughter", "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "two", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check completing with wrong number of args | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "son", "unexpectedArg", "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestValidArgsFuncInBashScript(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| child := &Command{ | |
| Use: "child", | |
| ValidArgsFunction: validArgsFunc, | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(child) | |
| buf := new(bytes.Buffer) | |
| assertNoErr(t, rootCmd.GenBashCompletion(buf)) | |
| output := buf.String() | |
| check(t, output, "has_completion_function=1") | |
| } | |
| func TestNoValidArgsFuncInBashScript(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| child := &Command{ | |
| Use: "child", | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(child) | |
| buf := new(bytes.Buffer) | |
| assertNoErr(t, rootCmd.GenBashCompletion(buf)) | |
| output := buf.String() | |
| checkOmit(t, output, "has_completion_function=1") | |
| } | |
| func TestCompleteCmdInBashScript(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| child := &Command{ | |
| Use: "child", | |
| ValidArgsFunction: validArgsFunc, | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(child) | |
| buf := new(bytes.Buffer) | |
| assertNoErr(t, rootCmd.GenBashCompletion(buf)) | |
| output := buf.String() | |
| check(t, output, ShellCompNoDescRequestCmd) | |
| } | |
| func TestCompleteNoDesCmdInZshScript(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| child := &Command{ | |
| Use: "child", | |
| ValidArgsFunction: validArgsFunc, | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(child) | |
| buf := new(bytes.Buffer) | |
| assertNoErr(t, rootCmd.GenZshCompletionNoDesc(buf)) | |
| output := buf.String() | |
| check(t, output, ShellCompNoDescRequestCmd) | |
| } | |
| func TestCompleteCmdInZshScript(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| child := &Command{ | |
| Use: "child", | |
| ValidArgsFunction: validArgsFunc, | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(child) | |
| buf := new(bytes.Buffer) | |
| assertNoErr(t, rootCmd.GenZshCompletion(buf)) | |
| output := buf.String() | |
| check(t, output, ShellCompRequestCmd) | |
| checkOmit(t, output, ShellCompNoDescRequestCmd) | |
| } | |
| func TestFlagCompletionInGo(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot") | |
| assertNoErr(t, rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| completions := []string{} | |
| for _, comp := range []string{"1\tThe first", "2\tThe second", "10\tThe tenth"} { | |
| if strings.HasPrefix(comp, toComplete) { | |
| completions = append(completions, comp) | |
| } | |
| } | |
| return completions, ShellCompDirectiveDefault | |
| })) | |
| rootCmd.Flags().String("filename", "", "Enter a filename") | |
| assertNoErr(t, rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| completions := []string{} | |
| for _, comp := range []string{"file.yaml\tYAML format", "myfile.json\tJSON format", "file.xml\tXML format"} { | |
| if strings.HasPrefix(comp, toComplete) { | |
| completions = append(completions, comp) | |
| } | |
| } | |
| return completions, ShellCompDirectiveNoSpace | ShellCompDirectiveNoFileComp | |
| })) | |
| // Test completing an empty string | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "1", | |
| "2", | |
| "10", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check completing with a prefix | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "1") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "1", | |
| "10", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test completing an empty string | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--filename", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "file.yaml", | |
| "myfile.json", | |
| "file.xml", | |
| ":6", | |
| "Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check completing with a prefix | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--filename", "f") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "file.yaml", | |
| "file.xml", | |
| ":6", | |
| "Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestValidArgsFuncChildCmdsWithDesc(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| child1Cmd := &Command{ | |
| Use: "child1", | |
| ValidArgsFunction: validArgsFunc, | |
| Run: emptyRun, | |
| } | |
| child2Cmd := &Command{ | |
| Use: "child2", | |
| ValidArgsFunction: validArgsFunc2, | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(child1Cmd, child2Cmd) | |
| // Test completion of first sub-command with empty argument | |
| output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child1", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "one\tThe first", | |
| "two\tThe second", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test completion of first sub-command with a prefix to complete | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "two\tThe second", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check completing with wrong number of args | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "unexpectedArg", "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test completion of second sub-command with empty argument | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "three\tThe third", | |
| "four\tThe fourth", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "three\tThe third", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check completing with wrong number of args | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "unexpectedArg", "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestFlagCompletionWithNotInterspersedArgs(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Run: emptyRun} | |
| childCmd := &Command{ | |
| Use: "child", | |
| Run: emptyRun, | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"--validarg", "test"}, ShellCompDirectiveDefault | |
| }, | |
| } | |
| childCmd2 := &Command{ | |
| Use: "child2", | |
| Run: emptyRun, | |
| ValidArgs: []string{"arg1", "arg2"}, | |
| } | |
| rootCmd.AddCommand(childCmd, childCmd2) | |
| childCmd.Flags().Bool("bool", false, "test bool flag") | |
| childCmd.Flags().String("string", "", "test string flag") | |
| _ = childCmd.RegisterFlagCompletionFunc("string", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"myval"}, ShellCompDirectiveDefault | |
| }) | |
| // Test flag completion with no argument | |
| output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "--") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "--bool\ttest bool flag", | |
| "--help\thelp for child", | |
| "--string\ttest string flag", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that no flags are completed after the -- arg | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "-") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--validarg", | |
| "test", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that no flags are completed after the -- arg with a flag set | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--bool", "--", "-") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--validarg", | |
| "test", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // set Interspersed to false which means that no flags should be completed after the first arg | |
| childCmd.Flags().SetInterspersed(false) | |
| // Test that no flags are completed after the first arg | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "arg", "--") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--validarg", | |
| "test", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that no flags are completed after the fist arg with a flag set | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--string", "t", "arg", "--") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--validarg", | |
| "test", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check that args are still completed after -- | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--validarg", | |
| "test", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check that args are still completed even if flagname with ValidArgsFunction exists | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--string", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--validarg", | |
| "test", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check that args are still completed even if flagname with ValidArgsFunction exists | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "--", "a") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "arg1", | |
| "arg2", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check that --validarg is not parsed as flag after -- | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--validarg", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--validarg", | |
| "test", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check that --validarg is not parsed as flag after an arg | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "arg", "--validarg", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--validarg", | |
| "test", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check that --validarg is added to args for the ValidArgsFunction | |
| childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return args, ShellCompDirectiveDefault | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--validarg", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--validarg", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check that --validarg is added to args for the ValidArgsFunction and toComplete is also set correctly | |
| childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return append(args, toComplete), ShellCompDirectiveDefault | |
| } | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--validarg", "--toComp=ab") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "--validarg", | |
| "--toComp=ab", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestFlagCompletionWorksRootCommandAddedAfterFlags(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Run: emptyRun} | |
| childCmd := &Command{ | |
| Use: "child", | |
| Run: emptyRun, | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"--validarg", "test"}, ShellCompDirectiveDefault | |
| }, | |
| } | |
| childCmd.Flags().Bool("bool", false, "test bool flag") | |
| childCmd.Flags().String("string", "", "test string flag") | |
| _ = childCmd.RegisterFlagCompletionFunc("string", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"myval"}, ShellCompDirectiveDefault | |
| }) | |
| // Important: This is a test for https://github.com/spf13/cobra/issues/1437 | |
| // Only add the subcommand after RegisterFlagCompletionFunc was called, do not change this order! | |
| rootCmd.AddCommand(childCmd) | |
| // Test that flag completion works for the subcmd | |
| output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "--string", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "myval", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestFlagCompletionForPersistentFlagsCalledFromSubCmd(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Run: emptyRun} | |
| rootCmd.PersistentFlags().String("string", "", "test string flag") | |
| _ = rootCmd.RegisterFlagCompletionFunc("string", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"myval"}, ShellCompDirectiveDefault | |
| }) | |
| childCmd := &Command{ | |
| Use: "child", | |
| Run: emptyRun, | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"--validarg", "test"}, ShellCompDirectiveDefault | |
| }, | |
| } | |
| childCmd.Flags().Bool("bool", false, "test bool flag") | |
| rootCmd.AddCommand(childCmd) | |
| // Test that persistent flag completion works for the subcmd | |
| output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "--string", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "myval", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| // This test tries to register flag completion concurrently to make sure the | |
| // code handles concurrency properly. | |
| // This was reported as a problem when tests are run concurrently: | |
| // https://github.com/spf13/cobra/issues/1320 | |
| // | |
| // NOTE: this test can sometimes pass even if the code were to not handle | |
| // concurrency properly. This is not great but the important part is that | |
| // it should never fail. Therefore, if the tests fails sometimes, we will | |
| // still be able to know there is a problem. | |
| func TestFlagCompletionConcurrentRegistration(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Run: emptyRun} | |
| const maxFlags = 50 | |
| for i := 1; i < maxFlags; i += 2 { | |
| flagName := fmt.Sprintf("flag%d", i) | |
| rootCmd.Flags().String(flagName, "", fmt.Sprintf("test %s flag on root", flagName)) | |
| } | |
| childCmd := &Command{ | |
| Use: "child", | |
| Run: emptyRun, | |
| } | |
| for i := 2; i <= maxFlags; i += 2 { | |
| flagName := fmt.Sprintf("flag%d", i) | |
| childCmd.Flags().String(flagName, "", fmt.Sprintf("test %s flag on child", flagName)) | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| // Register completion in different threads to test concurrency. | |
| var wg sync.WaitGroup | |
| for i := 1; i <= maxFlags; i++ { | |
| index := i | |
| flagName := fmt.Sprintf("flag%d", i) | |
| wg.Add(1) | |
| go func() { | |
| defer wg.Done() | |
| cmd := rootCmd | |
| if index%2 == 0 { | |
| cmd = childCmd | |
| } | |
| _ = cmd.RegisterFlagCompletionFunc(flagName, func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{fmt.Sprintf("flag%d", index)}, ShellCompDirectiveDefault | |
| }) | |
| }() | |
| } | |
| wg.Wait() | |
| // Test that flag completion works for each flag | |
| for i := 1; i <= 6; i++ { | |
| var output string | |
| var err error | |
| flagName := fmt.Sprintf("flag%d", i) | |
| if i%2 == 1 { | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--"+flagName, "") | |
| } else { | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--"+flagName, "") | |
| } | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| flagName, | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| } | |
| func TestFlagCompletionInGoWithDesc(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot") | |
| assertNoErr(t, rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| completions := []string{} | |
| for _, comp := range []string{"1\tThe first", "2\tThe second", "10\tThe tenth"} { | |
| if strings.HasPrefix(comp, toComplete) { | |
| completions = append(completions, comp) | |
| } | |
| } | |
| return completions, ShellCompDirectiveDefault | |
| })) | |
| rootCmd.Flags().String("filename", "", "Enter a filename") | |
| assertNoErr(t, rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| completions := []string{} | |
| for _, comp := range []string{"file.yaml\tYAML format", "myfile.json\tJSON format", "file.xml\tXML format"} { | |
| if strings.HasPrefix(comp, toComplete) { | |
| completions = append(completions, comp) | |
| } | |
| } | |
| return completions, ShellCompDirectiveNoSpace | ShellCompDirectiveNoFileComp | |
| })) | |
| // Test completing an empty string | |
| output, err := executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "1\tThe first", | |
| "2\tThe second", | |
| "10\tThe tenth", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check completing with a prefix | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "1") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "1\tThe first", | |
| "10\tThe tenth", | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test completing an empty string | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--filename", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "file.yaml\tYAML format", | |
| "myfile.json\tJSON format", | |
| "file.xml\tXML format", | |
| ":6", | |
| "Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check completing with a prefix | |
| output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--filename", "f") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "file.yaml\tYAML format", | |
| "file.xml\tXML format", | |
| ":6", | |
| "Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestValidArgsNotValidArgsFunc(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| ValidArgs: []string{"one", "two"}, | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"three", "four"}, ShellCompDirectiveNoFileComp | |
| }, | |
| Run: emptyRun, | |
| } | |
| // Test that if both ValidArgs and ValidArgsFunction are present | |
| // only ValidArgs is considered | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "one", | |
| "two", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Check completing with a prefix | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "two", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestArgAliasesCompletionInGo(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Args: OnlyValidArgs, | |
| ValidArgs: []string{"one", "two", "three"}, | |
| ArgAliases: []string{"un", "deux", "trois"}, | |
| Run: emptyRun, | |
| } | |
| // Test that argaliases are not completed when there are validargs that match | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "one", | |
| "two", | |
| "three", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that argaliases are not completed when there are validargs that match using a prefix | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "two", | |
| "three", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that argaliases are completed when there are no validargs that match | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "tr") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "trois", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestCompleteHelp(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| child1Cmd := &Command{ | |
| Use: "child1", | |
| Run: emptyRun, | |
| } | |
| child2Cmd := &Command{ | |
| Use: "child2", | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(child1Cmd, child2Cmd) | |
| child3Cmd := &Command{ | |
| Use: "child3", | |
| Run: emptyRun, | |
| } | |
| child1Cmd.AddCommand(child3Cmd) | |
| // Test that completion includes the help command | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "child1", | |
| "child2", | |
| "completion", | |
| "help", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test sub-commands are completed on first level of help command | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "help", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "child1", | |
| "child2", | |
| "completion", | |
| "help", // "<program> help help" is a valid command, so should be completed | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test sub-commands are completed on first level of help command | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "help", "child1", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "child3", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func removeCompCmd(rootCmd *Command) { | |
| // Remove completion command for the next test | |
| for _, cmd := range rootCmd.commands { | |
| if cmd.Name() == compCmdName { | |
| rootCmd.RemoveCommand(cmd) | |
| return | |
| } | |
| } | |
| } | |
| func TestDefaultCompletionCmd(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Args: NoArgs, | |
| Run: emptyRun, | |
| } | |
| // Test that when there are no sub-commands, the completion command is not created if it is not called directly. | |
| assertNoErr(t, rootCmd.Execute()) | |
| for _, cmd := range rootCmd.commands { | |
| if cmd.Name() == compCmdName { | |
| t.Errorf("Should not have a 'completion' command when there are no other sub-commands of root") | |
| break | |
| } | |
| } | |
| // Test that when there are no sub-commands, the completion command is created when it is called directly. | |
| _, err := executeCommand(rootCmd, compCmdName) | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Reset the arguments | |
| rootCmd.args = nil | |
| // Remove completion command for the next test | |
| removeCompCmd(rootCmd) | |
| // Add a sub-command | |
| subCmd := &Command{ | |
| Use: "sub", | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(subCmd) | |
| // Test that a completion command is created if there are other sub-commands | |
| found := false | |
| assertNoErr(t, rootCmd.Execute()) | |
| for _, cmd := range rootCmd.commands { | |
| if cmd.Name() == compCmdName { | |
| found = true | |
| break | |
| } | |
| } | |
| if !found { | |
| t.Errorf("Should have a 'completion' command when there are other sub-commands of root") | |
| } | |
| // Remove completion command for the next test | |
| removeCompCmd(rootCmd) | |
| // Test that the default completion command can be disabled | |
| rootCmd.CompletionOptions.DisableDefaultCmd = true | |
| assertNoErr(t, rootCmd.Execute()) | |
| for _, cmd := range rootCmd.commands { | |
| if cmd.Name() == compCmdName { | |
| t.Errorf("Should not have a 'completion' command when the feature is disabled") | |
| break | |
| } | |
| } | |
| // Re-enable for next test | |
| rootCmd.CompletionOptions.DisableDefaultCmd = false | |
| // Test that completion descriptions are enabled by default | |
| output, err := executeCommand(rootCmd, compCmdName, "zsh") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| check(t, output, ShellCompRequestCmd) | |
| checkOmit(t, output, ShellCompNoDescRequestCmd) | |
| // Remove completion command for the next test | |
| removeCompCmd(rootCmd) | |
| // Test that completion descriptions can be disabled completely | |
| rootCmd.CompletionOptions.DisableDescriptions = true | |
| output, err = executeCommand(rootCmd, compCmdName, "zsh") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| check(t, output, ShellCompNoDescRequestCmd) | |
| // Re-enable for next test | |
| rootCmd.CompletionOptions.DisableDescriptions = false | |
| // Remove completion command for the next test | |
| removeCompCmd(rootCmd) | |
| var compCmd *Command | |
| // Test that the --no-descriptions flag is present on all shells | |
| assertNoErr(t, rootCmd.Execute()) | |
| for _, shell := range []string{"bash", "fish", "powershell", "zsh"} { | |
| if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag == nil { | |
| t.Errorf("Missing --%s flag for %s shell", compCmdNoDescFlagName, shell) | |
| } | |
| } | |
| // Remove completion command for the next test | |
| removeCompCmd(rootCmd) | |
| // Test that the '--no-descriptions' flag can be disabled | |
| rootCmd.CompletionOptions.DisableNoDescFlag = true | |
| assertNoErr(t, rootCmd.Execute()) | |
| for _, shell := range []string{"fish", "zsh", "bash", "powershell"} { | |
| if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag != nil { | |
| t.Errorf("Unexpected --%s flag for %s shell", compCmdNoDescFlagName, shell) | |
| } | |
| } | |
| // Re-enable for next test | |
| rootCmd.CompletionOptions.DisableNoDescFlag = false | |
| // Remove completion command for the next test | |
| removeCompCmd(rootCmd) | |
| // Test that the '--no-descriptions' flag is disabled when descriptions are disabled | |
| rootCmd.CompletionOptions.DisableDescriptions = true | |
| assertNoErr(t, rootCmd.Execute()) | |
| for _, shell := range []string{"fish", "zsh", "bash", "powershell"} { | |
| if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag != nil { | |
| t.Errorf("Unexpected --%s flag for %s shell", compCmdNoDescFlagName, shell) | |
| } | |
| } | |
| // Re-enable for next test | |
| rootCmd.CompletionOptions.DisableDescriptions = false | |
| // Remove completion command for the next test | |
| removeCompCmd(rootCmd) | |
| // Test that the 'completion' command can be hidden | |
| rootCmd.CompletionOptions.HiddenDefaultCmd = true | |
| assertNoErr(t, rootCmd.Execute()) | |
| compCmd, _, err = rootCmd.Find([]string{compCmdName}) | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| if compCmd.Hidden == false { | |
| t.Error("Default 'completion' command should be hidden but it is not") | |
| } | |
| // Re-enable for next test | |
| rootCmd.CompletionOptions.HiddenDefaultCmd = false | |
| // Remove completion command for the next test | |
| removeCompCmd(rootCmd) | |
| } | |
| func TestCompleteCompletion(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| // Test that when there are no sub-commands, the 'completion' command is not completed | |
| // (because it is not created). | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that when there are no sub-commands, completion can be triggered for the default | |
| // 'completion' command | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "bash", | |
| "fish", | |
| "powershell", | |
| "zsh", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Add a sub-command | |
| subCmd := &Command{ | |
| Use: "sub", | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(subCmd) | |
| // Test sub-commands of the completion command | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "bash", | |
| "fish", | |
| "powershell", | |
| "zsh", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test there are no completions for the sub-commands of the completion command | |
| var compCmd *Command | |
| for _, cmd := range rootCmd.Commands() { | |
| if cmd.Name() == compCmdName { | |
| compCmd = cmd | |
| break | |
| } | |
| } | |
| for _, shell := range compCmd.Commands() { | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, compCmdName, shell.Name(), "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| } | |
| func TestMultipleShorthandFlagCompletion(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| ValidArgs: []string{"foo", "bar"}, | |
| Run: emptyRun, | |
| } | |
| f := rootCmd.Flags() | |
| f.BoolP("short", "s", false, "short flag 1") | |
| f.BoolP("short2", "d", false, "short flag 2") | |
| f.StringP("short3", "f", "", "short flag 3") | |
| _ = rootCmd.RegisterFlagCompletionFunc("short3", func(*Command, []string, string) ([]string, ShellCompDirective) { | |
| return []string{"works"}, ShellCompDirectiveNoFileComp | |
| }) | |
| // Test that a single shorthand flag works | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "foo", | |
| "bar", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that multiple boolean shorthand flags work | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sd", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "foo", | |
| "bar", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that multiple boolean + string shorthand flags work | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "works", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that multiple boolean + string with equal sign shorthand flags work | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf=") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "works", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that multiple boolean + string with equal sign with value shorthand flags work | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf=abc", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "foo", | |
| "bar", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestCompleteWithDisableFlagParsing(t *testing.T) { | |
| flagValidArgs := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"--flag", "-f"}, ShellCompDirectiveNoFileComp | |
| } | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| childCmd := &Command{ | |
| Use: "child", | |
| Run: emptyRun, | |
| DisableFlagParsing: true, | |
| ValidArgsFunction: flagValidArgs, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| rootCmd.PersistentFlags().StringP("persistent", "p", "", "persistent flag") | |
| childCmd.Flags().StringP("nonPersistent", "n", "", "non-persistent flag") | |
| // Test that when DisableFlagParsing==true, ValidArgsFunction is called to complete flag names, | |
| // after Cobra tried to complete the flags it knows about. | |
| childCmd.DisableFlagParsing = true | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child", "-") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "--persistent", | |
| "-p", | |
| "--nonPersistent", | |
| "-n", | |
| "--flag", | |
| "-f", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Test that when DisableFlagParsing==false, Cobra completes the flags itself and ValidArgsFunction is not called | |
| childCmd.DisableFlagParsing = false | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child", "-") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| // Cobra was not told of any flags, so it returns nothing | |
| expected = strings.Join([]string{ | |
| "--persistent", | |
| "-p", | |
| "--help", | |
| "-h", | |
| "--nonPersistent", | |
| "-n", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestCompleteWithRootAndLegacyArgs(t *testing.T) { | |
| // Test a lonely root command which uses legacyArgs(). In such a case, the root | |
| // command should accept any number of arguments and completion should behave accordingly. | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Args: nil, // Args must be nil to trigger the legacyArgs() function | |
| Run: emptyRun, | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"arg1", "arg2"}, ShellCompDirectiveNoFileComp | |
| }, | |
| } | |
| // Make sure the first arg is completed | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "arg1", | |
| "arg2", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| // Make sure the completion of arguments continues | |
| output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "arg1", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected = strings.Join([]string{ | |
| "arg1", | |
| "arg2", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", "", | |
| }, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestCompletionFuncCompatibility(t *testing.T) { | |
| t.Run("validate signature", func(t *testing.T) { | |
| t.Run("format with []string", func(t *testing.T) { | |
| var userComp func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) | |
| // check against new signature | |
| var _ CompletionFunc = userComp //nolint:staticcheck // LHS type is needed for this use case | |
| // check Command accepts | |
| cmd := Command{ | |
| ValidArgsFunction: userComp, | |
| } | |
| _ = cmd.RegisterFlagCompletionFunc("foo", userComp) | |
| }) | |
| t.Run("format with []Completion", func(t *testing.T) { | |
| var userComp func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) | |
| // check against new signature | |
| var _ CompletionFunc = userComp //nolint:staticcheck // LHS type is needed for this use case | |
| // check Command accepts | |
| cmd := Command{ | |
| ValidArgsFunction: userComp, | |
| } | |
| _ = cmd.RegisterFlagCompletionFunc("foo", userComp) | |
| }) | |
| t.Run("format with CompletionFunc", func(t *testing.T) { | |
| var userComp CompletionFunc | |
| // check helper against old signature | |
| var _ func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) = userComp //nolint:staticcheck // LHS type is needed for this use case | |
| var _ func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) = userComp //nolint:staticcheck // LHS type is needed for this use case | |
| // check Command accepts | |
| cmd := Command{ | |
| ValidArgsFunction: userComp, | |
| } | |
| _ = cmd.RegisterFlagCompletionFunc("foo", userComp) | |
| }) | |
| }) | |
| t.Run("user defined completion helper", func(t *testing.T) { | |
| t.Run("type helper", func(t *testing.T) { | |
| // This is a type that may have been defined by the user of the library | |
| // This replicates the issue https://github.com/docker/cli/issues/5827 | |
| // https://github.com/docker/cli/blob/b6e7eba4470ecdca460e4b63270fba8179674ad6/cli/command/completion/functions.go#L18 | |
| type UserCompletionTypeHelper func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) | |
| var userComp UserCompletionTypeHelper | |
| // Here we are validating the existing type validates the CompletionFunc type | |
| var _ CompletionFunc = userComp | |
| cmd := Command{ | |
| ValidArgsFunction: userComp, | |
| } | |
| _ = cmd.RegisterFlagCompletionFunc("foo", userComp) | |
| }) | |
| t.Run("type alias helper", func(t *testing.T) { | |
| // This is a type that may have been defined by the user of the library | |
| // This replicates the possible fix that was tried here https://github.com/docker/cli/pull/5828 | |
| // https://github.com/docker/cli/blob/ae3d4db9f658259dace9dee515718be7c1b1f517/cli/command/completion/functions.go#L18 | |
| type UserCompletionTypeAliasHelper = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) | |
| var userComp UserCompletionTypeAliasHelper | |
| // Here we are validating the existing type validates the CompletionFunc type | |
| var _ CompletionFunc = userComp //nolint:staticcheck // LHS type is needed for this use case | |
| cmd := Command{ | |
| ValidArgsFunction: userComp, | |
| } | |
| _ = cmd.RegisterFlagCompletionFunc("foo", userComp) | |
| }) | |
| }) | |
| } | |
| func TestFixedCompletions(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| choices := []string{"apple", "banana", "orange"} | |
| childCmd := &Command{ | |
| Use: "child", | |
| ValidArgsFunction: FixedCompletions(choices, ShellCompDirectiveNoFileComp), | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child", "a") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "apple", | |
| "banana", | |
| "orange", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| func TestFixedCompletionsWithCompletionHelpers(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} | |
| // here we are mixing string, [Completion] and [CompletionWithDesc] | |
| choices := []string{"apple", Completion("banana"), CompletionWithDesc("orange", "orange are orange")} | |
| childCmd := &Command{ | |
| Use: "child", | |
| ValidArgsFunction: FixedCompletions(choices, ShellCompDirectiveNoFileComp), | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| t.Run("completion with description", func(t *testing.T) { | |
| output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "a") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "apple", | |
| "banana", | |
| "orange\torange are orange", // this one has the description as expected with [ShellCompRequestCmd] flag | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", "", | |
| }, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| }) | |
| t.Run("completion with no description", func(t *testing.T) { | |
| output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child", "a") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| "apple", | |
| "banana", | |
| "orange", // the description is absent as expected with [ShellCompNoDescRequestCmd] flag | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", "", | |
| }, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| }) | |
| } | |
| func TestCompletionForGroupedFlags(t *testing.T) { | |
| getCmd := func() *Command { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| childCmd := &Command{ | |
| Use: "child", | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"subArg"}, ShellCompDirectiveNoFileComp | |
| }, | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| rootCmd.PersistentFlags().Int("ingroup1", -1, "ingroup1") | |
| rootCmd.PersistentFlags().String("ingroup2", "", "ingroup2") | |
| childCmd.Flags().Bool("ingroup3", false, "ingroup3") | |
| childCmd.Flags().Bool("nogroup", false, "nogroup") | |
| // Add flags to a group | |
| childCmd.MarkFlagsRequiredTogether("ingroup1", "ingroup2", "ingroup3") | |
| return rootCmd | |
| } | |
| // Each test case uses a unique command from the function above. | |
| testcases := []struct { | |
| desc string | |
| args []string | |
| expectedOutput string | |
| }{ | |
| { | |
| desc: "flags in group not suggested without - prefix", | |
| args: []string{"child", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "subArg", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "flags in group suggested with - prefix", | |
| args: []string{"child", "-"}, | |
| expectedOutput: strings.Join([]string{ | |
| "--ingroup1", | |
| "--ingroup2", | |
| "--help", | |
| "-h", | |
| "--ingroup3", | |
| "--nogroup", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "when flag in group present, other flags in group suggested even without - prefix", | |
| args: []string{"child", "--ingroup2", "value", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "--ingroup1", | |
| "--ingroup3", | |
| "subArg", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "when all flags in group present, flags not suggested without - prefix", | |
| args: []string{"child", "--ingroup1", "8", "--ingroup2", "value2", "--ingroup3", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "subArg", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "group ignored if some flags not applicable", | |
| args: []string{"--ingroup2", "value", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "child", | |
| "completion", | |
| "help", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| } | |
| for _, tc := range testcases { | |
| t.Run(tc.desc, func(t *testing.T) { | |
| c := getCmd() | |
| args := []string{ShellCompNoDescRequestCmd} | |
| args = append(args, tc.args...) | |
| output, err := executeCommand(c, args...) | |
| switch { | |
| case err == nil && output != tc.expectedOutput: | |
| t.Errorf("expected: %q, got: %q", tc.expectedOutput, output) | |
| case err != nil: | |
| t.Errorf("Unexpected error %q", err) | |
| } | |
| }) | |
| } | |
| } | |
| func TestCompletionForOneRequiredGroupFlags(t *testing.T) { | |
| getCmd := func() *Command { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| childCmd := &Command{ | |
| Use: "child", | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"subArg"}, ShellCompDirectiveNoFileComp | |
| }, | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| rootCmd.PersistentFlags().Int("ingroup1", -1, "ingroup1") | |
| rootCmd.PersistentFlags().String("ingroup2", "", "ingroup2") | |
| childCmd.Flags().Bool("ingroup3", false, "ingroup3") | |
| childCmd.Flags().Bool("nogroup", false, "nogroup") | |
| // Add flags to a group | |
| childCmd.MarkFlagsOneRequired("ingroup1", "ingroup2", "ingroup3") | |
| return rootCmd | |
| } | |
| // Each test case uses a unique command from the function above. | |
| testcases := []struct { | |
| desc string | |
| args []string | |
| expectedOutput string | |
| }{ | |
| { | |
| desc: "flags in group suggested without - prefix", | |
| args: []string{"child", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "--ingroup1", | |
| "--ingroup2", | |
| "--ingroup3", | |
| "subArg", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "flags in group suggested with - prefix", | |
| args: []string{"child", "-"}, | |
| expectedOutput: strings.Join([]string{ | |
| "--ingroup1", | |
| "--ingroup2", | |
| "--ingroup3", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "when any flag in group present, other flags in group not suggested without - prefix", | |
| args: []string{"child", "--ingroup2", "value", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "subArg", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "when all flags in group present, flags not suggested without - prefix", | |
| args: []string{"child", "--ingroup1", "8", "--ingroup2", "value2", "--ingroup3", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "subArg", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "group ignored if some flags not applicable", | |
| args: []string{"--ingroup2", "value", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "child", | |
| "completion", | |
| "help", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| } | |
| for _, tc := range testcases { | |
| t.Run(tc.desc, func(t *testing.T) { | |
| c := getCmd() | |
| args := []string{ShellCompNoDescRequestCmd} | |
| args = append(args, tc.args...) | |
| output, err := executeCommand(c, args...) | |
| switch { | |
| case err == nil && output != tc.expectedOutput: | |
| t.Errorf("expected: %q, got: %q", tc.expectedOutput, output) | |
| case err != nil: | |
| t.Errorf("Unexpected error %q", err) | |
| } | |
| }) | |
| } | |
| } | |
| func TestCompletionForMutuallyExclusiveFlags(t *testing.T) { | |
| getCmd := func() *Command { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| childCmd := &Command{ | |
| Use: "child", | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"subArg"}, ShellCompDirectiveNoFileComp | |
| }, | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| rootCmd.PersistentFlags().IntSlice("ingroup1", []int{1}, "ingroup1") | |
| rootCmd.PersistentFlags().String("ingroup2", "", "ingroup2") | |
| childCmd.Flags().Bool("ingroup3", false, "ingroup3") | |
| childCmd.Flags().Bool("nogroup", false, "nogroup") | |
| // Add flags to a group | |
| childCmd.MarkFlagsMutuallyExclusive("ingroup1", "ingroup2", "ingroup3") | |
| return rootCmd | |
| } | |
| // Each test case uses a unique command from the function above. | |
| testcases := []struct { | |
| desc string | |
| args []string | |
| expectedOutput string | |
| }{ | |
| { | |
| desc: "flags in mutually exclusive group not suggested without the - prefix", | |
| args: []string{"child", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "subArg", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "flags in mutually exclusive group suggested with the - prefix", | |
| args: []string{"child", "-"}, | |
| expectedOutput: strings.Join([]string{ | |
| "--ingroup1", | |
| "--ingroup2", | |
| "--help", | |
| "-h", | |
| "--ingroup3", | |
| "--nogroup", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "when flag in mutually exclusive group present, other flags in group not suggested even with the - prefix", | |
| args: []string{"child", "--ingroup1", "8", "-"}, | |
| expectedOutput: strings.Join([]string{ | |
| "--ingroup1", // Should be suggested again since it is a slice | |
| "--help", | |
| "-h", | |
| "--nogroup", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "group ignored if some flags not applicable", | |
| args: []string{"--ingroup1", "8", "-"}, | |
| expectedOutput: strings.Join([]string{ | |
| "--help", | |
| "-h", | |
| "--ingroup1", | |
| "--ingroup2", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| } | |
| for _, tc := range testcases { | |
| t.Run(tc.desc, func(t *testing.T) { | |
| c := getCmd() | |
| args := []string{ShellCompNoDescRequestCmd} | |
| args = append(args, tc.args...) | |
| output, err := executeCommand(c, args...) | |
| switch { | |
| case err == nil && output != tc.expectedOutput: | |
| t.Errorf("expected: %q, got: %q", tc.expectedOutput, output) | |
| case err != nil: | |
| t.Errorf("Unexpected error %q", err) | |
| } | |
| }) | |
| } | |
| } | |
| func TestCompletionCobraFlags(t *testing.T) { | |
| getCmd := func() *Command { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Version: "1.1.1", | |
| Run: emptyRun, | |
| } | |
| childCmd := &Command{ | |
| Use: "child", | |
| Version: "1.1.1", | |
| Run: emptyRun, | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"extra"}, ShellCompDirectiveNoFileComp | |
| }, | |
| } | |
| childCmd2 := &Command{ | |
| Use: "child2", | |
| Version: "1.1.1", | |
| Run: emptyRun, | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"extra2"}, ShellCompDirectiveNoFileComp | |
| }, | |
| } | |
| childCmd3 := &Command{ | |
| Use: "child3", | |
| Version: "1.1.1", | |
| Run: emptyRun, | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"extra3"}, ShellCompDirectiveNoFileComp | |
| }, | |
| } | |
| childCmd4 := &Command{ | |
| Use: "child4", | |
| Version: "1.1.1", | |
| Run: emptyRun, | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"extra4"}, ShellCompDirectiveNoFileComp | |
| }, | |
| DisableFlagParsing: true, | |
| } | |
| childCmd5 := &Command{ | |
| Use: "child5", | |
| Version: "1.1.1", | |
| Run: emptyRun, | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"extra5"}, ShellCompDirectiveNoFileComp | |
| }, | |
| DisableFlagParsing: true, | |
| } | |
| rootCmd.AddCommand(childCmd, childCmd2, childCmd3, childCmd4, childCmd5) | |
| _ = childCmd.Flags().Bool("bool", false, "A bool flag") | |
| _ = childCmd.MarkFlagRequired("bool") | |
| // Have a command that adds its own help and version flag | |
| _ = childCmd2.Flags().BoolP("help", "h", false, "My own help") | |
| _ = childCmd2.Flags().BoolP("version", "v", false, "My own version") | |
| // Have a command that only adds its own -v flag | |
| _ = childCmd3.Flags().BoolP("verbose", "v", false, "Not a version flag") | |
| // Have a command that DisablesFlagParsing but that also adds its own help and version flags | |
| _ = childCmd5.Flags().BoolP("help", "h", false, "My own help") | |
| _ = childCmd5.Flags().BoolP("version", "v", false, "My own version") | |
| return rootCmd | |
| } | |
| // Each test case uses a unique command from the function above. | |
| testcases := []struct { | |
| desc string | |
| args []string | |
| expectedOutput string | |
| }{ | |
| { | |
| desc: "completion of help and version flags", | |
| args: []string{"-"}, | |
| expectedOutput: strings.Join([]string{ | |
| "--help", | |
| "-h", | |
| "--version", | |
| "-v", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "no completion after --help flag", | |
| args: []string{"--help", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "no completion after -h flag", | |
| args: []string{"-h", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "no completion after --version flag", | |
| args: []string{"--version", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "no completion after -v flag", | |
| args: []string{"-v", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "no completion after --help flag even with other completions", | |
| args: []string{"child", "--help", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "no completion after -h flag even with other completions", | |
| args: []string{"child", "-h", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "no completion after --version flag even with other completions", | |
| args: []string{"child", "--version", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "no completion after -v flag even with other completions", | |
| args: []string{"child", "-v", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "no completion after -v flag even with other flag completions", | |
| args: []string{"child", "-v", "-"}, | |
| expectedOutput: strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "completion after --help flag when created by program", | |
| args: []string{"child2", "--help", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "extra2", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "completion after -h flag when created by program", | |
| args: []string{"child2", "-h", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "extra2", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "completion after --version flag when created by program", | |
| args: []string{"child2", "--version", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "extra2", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "completion after -v flag when created by program", | |
| args: []string{"child2", "-v", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "extra2", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "completion after --version when only -v flag was created by program", | |
| args: []string{"child3", "--version", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "completion after -v flag when only -v flag was created by program", | |
| args: []string{"child3", "-v", ""}, | |
| expectedOutput: strings.Join([]string{ | |
| "extra3", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "no completion for --help/-h and --version/-v flags when DisableFlagParsing=true", | |
| args: []string{"child4", "-"}, | |
| expectedOutput: strings.Join([]string{ | |
| "extra4", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| { | |
| desc: "completions for program-defined --help/-h and --version/-v flags even when DisableFlagParsing=true", | |
| args: []string{"child5", "-"}, | |
| expectedOutput: strings.Join([]string{ | |
| "--help", | |
| "-h", | |
| "--version", | |
| "-v", | |
| "extra5", | |
| ":4", | |
| "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), | |
| }, | |
| } | |
| for _, tc := range testcases { | |
| t.Run(tc.desc, func(t *testing.T) { | |
| c := getCmd() | |
| args := []string{ShellCompNoDescRequestCmd} | |
| args = append(args, tc.args...) | |
| output, err := executeCommand(c, args...) | |
| switch { | |
| case err == nil && output != tc.expectedOutput: | |
| t.Errorf("expected: %q, got: %q", tc.expectedOutput, output) | |
| case err != nil: | |
| t.Errorf("Unexpected error %q", err) | |
| } | |
| }) | |
| } | |
| } | |
| func TestArgsNotDetectedAsFlagsCompletionInGo(t *testing.T) { | |
| // Regression test that ensures the bug described in | |
| // https://github.com/spf13/cobra/issues/1816 does not occur anymore. | |
| root := Command{ | |
| Use: "root", | |
| ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"service", "1-123", "11-123"}, ShellCompDirectiveNoFileComp | |
| }, | |
| } | |
| completion := `service | |
| 1-123 | |
| 11-123 | |
| :4 | |
| Completion ended with directive: ShellCompDirectiveNoFileComp | |
| ` | |
| testcases := []struct { | |
| desc string | |
| args []string | |
| expectedOutput string | |
| }{ | |
| { | |
| desc: "empty", | |
| args: []string{""}, | |
| expectedOutput: completion, | |
| }, | |
| { | |
| desc: "service only", | |
| args: []string{"service", ""}, | |
| expectedOutput: completion, | |
| }, | |
| { | |
| desc: "service last", | |
| args: []string{"1-123", "service", ""}, | |
| expectedOutput: completion, | |
| }, | |
| { | |
| desc: "two digit prefixed dash last", | |
| args: []string{"service", "11-123", ""}, | |
| expectedOutput: completion, | |
| }, | |
| { | |
| desc: "one digit prefixed dash last", | |
| args: []string{"service", "1-123", ""}, | |
| expectedOutput: completion, | |
| }, | |
| } | |
| for _, tc := range testcases { | |
| t.Run(tc.desc, func(t *testing.T) { | |
| args := []string{ShellCompNoDescRequestCmd} | |
| args = append(args, tc.args...) | |
| output, err := executeCommand(&root, args...) | |
| switch { | |
| case err == nil && output != tc.expectedOutput: | |
| t.Errorf("expected: %q, got: %q", tc.expectedOutput, output) | |
| case err != nil: | |
| t.Errorf("Unexpected error %q", err) | |
| } | |
| }) | |
| } | |
| } | |
| func TestGetFlagCompletion(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Run: emptyRun} | |
| rootCmd.Flags().String("rootflag", "", "root flag") | |
| _ = rootCmd.RegisterFlagCompletionFunc("rootflag", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"rootvalue"}, ShellCompDirectiveKeepOrder | |
| }) | |
| rootCmd.PersistentFlags().String("persistentflag", "", "persistent flag") | |
| _ = rootCmd.RegisterFlagCompletionFunc("persistentflag", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"persistentvalue"}, ShellCompDirectiveDefault | |
| }) | |
| childCmd := &Command{Use: "child", Run: emptyRun} | |
| childCmd.Flags().String("childflag", "", "child flag") | |
| _ = childCmd.RegisterFlagCompletionFunc("childflag", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| return []string{"childvalue"}, ShellCompDirectiveNoFileComp | ShellCompDirectiveNoSpace | |
| }) | |
| rootCmd.AddCommand(childCmd) | |
| testcases := []struct { | |
| desc string | |
| cmd *Command | |
| flagName string | |
| exists bool | |
| comps []string | |
| directive ShellCompDirective | |
| }{ | |
| { | |
| desc: "get flag completion function for command", | |
| cmd: rootCmd, | |
| flagName: "rootflag", | |
| exists: true, | |
| comps: []string{"rootvalue"}, | |
| directive: ShellCompDirectiveKeepOrder, | |
| }, | |
| { | |
| desc: "get persistent flag completion function for command", | |
| cmd: rootCmd, | |
| flagName: "persistentflag", | |
| exists: true, | |
| comps: []string{"persistentvalue"}, | |
| directive: ShellCompDirectiveDefault, | |
| }, | |
| { | |
| desc: "get flag completion function for child command", | |
| cmd: childCmd, | |
| flagName: "childflag", | |
| exists: true, | |
| comps: []string{"childvalue"}, | |
| directive: ShellCompDirectiveNoFileComp | ShellCompDirectiveNoSpace, | |
| }, | |
| { | |
| desc: "get persistent flag completion function for child command", | |
| cmd: childCmd, | |
| flagName: "persistentflag", | |
| exists: true, | |
| comps: []string{"persistentvalue"}, | |
| directive: ShellCompDirectiveDefault, | |
| }, | |
| { | |
| desc: "cannot get flag completion function for local parent flag", | |
| cmd: childCmd, | |
| flagName: "rootflag", | |
| exists: false, | |
| }, | |
| } | |
| for _, tc := range testcases { | |
| t.Run(tc.desc, func(t *testing.T) { | |
| compFunc, exists := tc.cmd.GetFlagCompletionFunc(tc.flagName) | |
| if tc.exists != exists { | |
| t.Errorf("Unexpected result looking for flag completion function") | |
| } | |
| if exists { | |
| comps, directive := compFunc(tc.cmd, []string{}, "") | |
| if strings.Join(tc.comps, " ") != strings.Join(comps, " ") { | |
| t.Errorf("Unexpected completions %q", comps) | |
| } | |
| if tc.directive != directive { | |
| t.Errorf("Unexpected directive %q", directive) | |
| } | |
| } | |
| }) | |
| } | |
| } | |
| func TestGetEnvConfig(t *testing.T) { | |
| testCases := []struct { | |
| desc string | |
| use string | |
| suffix string | |
| cmdVar string | |
| globalVar string | |
| cmdVal string | |
| globalVal string | |
| expected string | |
| }{ | |
| { | |
| desc: "Command envvar overrides global", | |
| use: "root", | |
| suffix: "test", | |
| cmdVar: "ROOT_TEST", | |
| globalVar: "COBRA_TEST", | |
| cmdVal: "cmd", | |
| globalVal: "global", | |
| expected: "cmd", | |
| }, | |
| { | |
| desc: "Missing/empty command envvar falls back to global", | |
| use: "root", | |
| suffix: "test", | |
| cmdVar: "ROOT_TEST", | |
| globalVar: "COBRA_TEST", | |
| cmdVal: "", | |
| globalVal: "global", | |
| expected: "global", | |
| }, | |
| { | |
| desc: "Missing/empty command and global envvars fall back to empty", | |
| use: "root", | |
| suffix: "test", | |
| cmdVar: "ROOT_TEST", | |
| globalVar: "COBRA_TEST", | |
| cmdVal: "", | |
| globalVal: "", | |
| expected: "", | |
| }, | |
| { | |
| desc: "Periods in command use transform to underscores in env var name", | |
| use: "foo.bar", | |
| suffix: "test", | |
| cmdVar: "FOO_BAR_TEST", | |
| globalVar: "COBRA_TEST", | |
| cmdVal: "cmd", | |
| globalVal: "global", | |
| expected: "cmd", | |
| }, | |
| { | |
| desc: "Dashes in command use transform to underscores in env var name", | |
| use: "quux-BAZ", | |
| suffix: "test", | |
| cmdVar: "QUUX_BAZ_TEST", | |
| globalVar: "COBRA_TEST", | |
| cmdVal: "cmd", | |
| globalVal: "global", | |
| expected: "cmd", | |
| }, | |
| } | |
| for _, tc := range testCases { | |
| t.Run(tc.desc, func(t *testing.T) { | |
| // Could make env handling cleaner with t.Setenv with Go >= 1.17 | |
| err := os.Setenv(tc.cmdVar, tc.cmdVal) | |
| defer func() { | |
| assertNoErr(t, os.Unsetenv(tc.cmdVar)) | |
| }() | |
| assertNoErr(t, err) | |
| err = os.Setenv(tc.globalVar, tc.globalVal) | |
| defer func() { | |
| assertNoErr(t, os.Unsetenv(tc.globalVar)) | |
| }() | |
| assertNoErr(t, err) | |
| cmd := &Command{Use: tc.use} | |
| got := getEnvConfig(cmd, tc.suffix) | |
| if got != tc.expected { | |
| t.Errorf("expected: %q, got: %q", tc.expected, got) | |
| } | |
| }) | |
| } | |
| } | |
| func TestDisableDescriptions(t *testing.T) { | |
| rootCmd := &Command{ | |
| Use: "root", | |
| Run: emptyRun, | |
| } | |
| childCmd := &Command{ | |
| Use: "thechild", | |
| Short: "The child command", | |
| Run: emptyRun, | |
| } | |
| rootCmd.AddCommand(childCmd) | |
| specificDescriptionsEnvVar := configEnvVar(rootCmd.Name(), configEnvVarSuffixDescriptions) | |
| globalDescriptionsEnvVar := configEnvVar(configEnvVarGlobalPrefix, configEnvVarSuffixDescriptions) | |
| const ( | |
| descLineWithDescription = "first\tdescription" | |
| descLineWithoutDescription = "first" | |
| ) | |
| childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { | |
| comps := []string{descLineWithDescription} | |
| return comps, ShellCompDirectiveDefault | |
| } | |
| testCases := []struct { | |
| desc string | |
| globalEnvValue string | |
| specificEnvValue string | |
| expectedLine string | |
| }{ | |
| { | |
| "No env variables set", | |
| "", | |
| "", | |
| descLineWithDescription, | |
| }, | |
| { | |
| "Global value false", | |
| "false", | |
| "", | |
| descLineWithoutDescription, | |
| }, | |
| { | |
| "Specific value false", | |
| "", | |
| "false", | |
| descLineWithoutDescription, | |
| }, | |
| { | |
| "Both values false", | |
| "false", | |
| "false", | |
| descLineWithoutDescription, | |
| }, | |
| { | |
| "Both values true", | |
| "true", | |
| "true", | |
| descLineWithDescription, | |
| }, | |
| } | |
| for _, tc := range testCases { | |
| t.Run(tc.desc, func(t *testing.T) { | |
| if err := os.Setenv(specificDescriptionsEnvVar, tc.specificEnvValue); err != nil { | |
| t.Errorf("Unexpected error setting %s: %v", specificDescriptionsEnvVar, err) | |
| } | |
| if err := os.Setenv(globalDescriptionsEnvVar, tc.globalEnvValue); err != nil { | |
| t.Errorf("Unexpected error setting %s: %v", globalDescriptionsEnvVar, err) | |
| } | |
| var run = func() { | |
| output, err := executeCommand(rootCmd, ShellCompRequestCmd, "thechild", "") | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| expected := strings.Join([]string{ | |
| tc.expectedLine, | |
| ":0", | |
| "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") | |
| if output != expected { | |
| t.Errorf("expected: %q, got: %q", expected, output) | |
| } | |
| } | |
| run() | |
| // For empty cases, test also unset state | |
| if tc.specificEnvValue == "" { | |
| if err := os.Unsetenv(specificDescriptionsEnvVar); err != nil { | |
| t.Errorf("Unexpected error unsetting %s: %v", specificDescriptionsEnvVar, err) | |
| } | |
| run() | |
| } | |
| if tc.globalEnvValue == "" { | |
| if err := os.Unsetenv(globalDescriptionsEnvVar); err != nil { | |
| t.Errorf("Unexpected error unsetting %s: %v", globalDescriptionsEnvVar, err) | |
| } | |
| run() | |
| } | |
| }) | |
| } | |
| } | |
| // A test to make sure the InitDefaultCompletionCmd function works as expected | |
| // in case a project calls it directly. | |
| func TestInitDefaultCompletionCmd(t *testing.T) { | |
| testCases := []struct { | |
| desc string | |
| hasChildCmd bool | |
| args []string | |
| expectCompCmd bool | |
| }{ | |
| { | |
| desc: "no child command and not calling the completion command", | |
| hasChildCmd: false, | |
| args: []string{"somearg"}, | |
| expectCompCmd: false, | |
| }, | |
| { | |
| desc: "no child command but calling the completion command", | |
| hasChildCmd: false, | |
| args: []string{"completion"}, | |
| expectCompCmd: true, | |
| }, | |
| { | |
| desc: "no child command but calling __complete on the root command", | |
| hasChildCmd: false, | |
| args: []string{"__complete", ""}, | |
| expectCompCmd: false, | |
| }, | |
| { | |
| desc: "no child command but calling __complete on the completion command", | |
| hasChildCmd: false, | |
| args: []string{"__complete", "completion", ""}, | |
| expectCompCmd: true, | |
| }, | |
| { | |
| desc: "with child command", | |
| hasChildCmd: true, | |
| args: []string{"child"}, | |
| expectCompCmd: true, | |
| }, | |
| { | |
| desc: "no child command not passing args", | |
| hasChildCmd: false, | |
| args: nil, | |
| expectCompCmd: false, | |
| }, | |
| { | |
| desc: "with child command not passing args", | |
| hasChildCmd: true, | |
| args: nil, | |
| expectCompCmd: true, | |
| }, | |
| } | |
| for _, tc := range testCases { | |
| t.Run(tc.desc, func(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Run: emptyRun} | |
| childCmd := &Command{Use: "child", Run: emptyRun} | |
| expectedNumSubCommands := 0 | |
| if tc.hasChildCmd { | |
| rootCmd.AddCommand(childCmd) | |
| expectedNumSubCommands++ | |
| } | |
| if tc.expectCompCmd { | |
| expectedNumSubCommands++ | |
| } | |
| if len(tc.args) > 0 && tc.args[0] == "__complete" { | |
| expectedNumSubCommands++ | |
| } | |
| // Setup the __complete command to mimic real world scenarios | |
| rootCmd.initCompleteCmd(tc.args) | |
| // Call the InitDefaultCompletionCmd function directly | |
| if tc.args == nil { | |
| rootCmd.InitDefaultCompletionCmd() | |
| } else { | |
| rootCmd.InitDefaultCompletionCmd(tc.args...) | |
| } | |
| // Check if the completion command was added | |
| if len(rootCmd.Commands()) != expectedNumSubCommands { | |
| t.Errorf("Expected %d subcommands, got %d", expectedNumSubCommands, len(rootCmd.Commands())) | |
| } | |
| }) | |
| } | |
| } | |
| func TestCustomDefaultShellCompDirective(t *testing.T) { | |
| rootCmd := &Command{Use: "root", Run: emptyRun} | |
| rootCmd.PersistentFlags().String("string", "", "test string flag") | |
| // use ShellCompDirectiveNoFileComp instead of the default, which is ShellCompDirectiveDefault. | |
| rootCmd.CompletionOptions.SetDefaultShellCompDirective(ShellCompDirectiveNoFileComp) | |
| // child1 inherits the custom DefaultShellCompDirective. | |
| childCmd1 := &Command{Use: "child1", Run: emptyRun} | |
| // child2 resets the custom DefaultShellCompDirective to the default value. | |
| childCmd2 := &Command{Use: "child2", Run: emptyRun} | |
| childCmd2.CompletionOptions.SetDefaultShellCompDirective(ShellCompDirectiveDefault) | |
| rootCmd.AddCommand(childCmd1, childCmd2) | |
| testCases := []struct { | |
| desc string | |
| args []string | |
| expectedDirective string | |
| }{ | |
| { | |
| "flag completion on root command with custom DefaultShellCompDirective", | |
| []string{"--string", ""}, | |
| "ShellCompDirectiveNoFileComp", | |
| }, | |
| { | |
| "flag completion on subcommand with inherited custom DefaultShellCompDirective", | |
| []string{"child1", "--string", ""}, | |
| "ShellCompDirectiveNoFileComp", | |
| }, | |
| { | |
| "flag completion on subcommand with reset DefaultShellCompDirective", | |
| []string{"child2", "--string", ""}, | |
| "ShellCompDirectiveDefault", | |
| }, | |
| } | |
| for _, tc := range testCases { | |
| t.Run(tc.desc, func(t *testing.T) { | |
| args := []string{ShellCompNoDescRequestCmd} | |
| args = append(args, tc.args...) | |
| output, err := executeCommand(rootCmd, args...) | |
| if err != nil { | |
| t.Errorf("Unexpected error: %v", err) | |
| } | |
| outputWords := strings.Split(strings.TrimSpace(output), " ") | |
| directive := outputWords[len(outputWords)-1] | |
| if directive != tc.expectedDirective { | |
| t.Errorf("expected: %q, got: %q", tc.expectedDirective, directive) | |
| } | |
| }) | |
| } | |
| } |