// Copyright 2013-2022 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" "strings" "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) } } 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") // 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", "--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", "--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", "--") 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 expected = strings.Join([]string{ "--array", "--bslice", "--first", "--help", "--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 expected = strings.Join([]string{ "--array", "-a", "--bslice", "-b", "--first", "-f", "--help", "-h", "--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 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:golint,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 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 TestCommandAliasesCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", Run: emptyRun, } subCmd := &Command{ Use: "sandstone", Aliases: []string{"slate", "pumice", "pegmatite"}, Run: emptyRun, } rootCmd.AddCommand(subCmd) testcases := []struct { desc string toComplete string expectedCompletion string }{ { desc: "command name", toComplete: "sand", expectedCompletion: "sandstone", }, { desc: "command name if an alias also matches", toComplete: "s", expectedCompletion: "sandstone", }, { desc: "alias if command name does not match", toComplete: "sla", expectedCompletion: "slate", }, { desc: "only one alias if multiple match", toComplete: "p", expectedCompletion: "pumice", }, } for _, tc := range testcases { t.Run(tc.desc, func(t *testing.T) { args := append([]string{ShellCompRequestCmd}, tc.toComplete) output, err := executeCommand(rootCmd, args...) expectedCompletion := strings.Join([]string{ tc.expectedCompletion, ":4", "Completion ended with directive: ShellCompDirectiveNoFileComp", "", }, "\n") switch { case err == nil && output != expectedCompletion: t.Errorf("expected: %q, got: %q", expectedCompletion, output) case err != nil: t.Errorf("Unexpected error %q", err) } }) } } 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", // " 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 no completion command is created if there are not other sub-commands 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 } } 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} 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", "--help", "-h", "--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 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 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 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 }, } rootCmd.AddCommand(childCmd, childCmd2, childCmd3) _ = 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") 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"), }, } 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) } }) } }