// 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) } } 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 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", // " 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", "--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 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() } }) } }