spf13--cobra/completions_test.go

4125 lines
122 KiB
Go
Raw Normal View History

2023-03-06 02:28:31 +00:00
// Copyright 2013-2023 The Cobra Authors
2022-09-16 13:55:56 +02:00
//
// 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"
2021-05-03 18:33:57 +02:00
"context"
"fmt"
"os"
"strings"
"sync"
"testing"
)
2025-04-26 20:37:36 +00:00
// validArgsFunc checks if the number of arguments is valid for a command.
// It returns an empty slice and ShellCompDirectiveNoFileComp if there are any arguments,
// otherwise, it returns a list of completion suggestions and ShellCompDirectiveDefault.
// AddCompletions appends completion suggestions to the provided completions slice.
// It filters suggestions based on the prefix provided in toComplete.
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
}
2025-04-26 20:37:36 +00:00
// validArgsFunc2 checks if the number of arguments is zero and returns possible completions for a given prefix.
//
// Args:
// cmd: The command being executed.
// args: The current arguments provided to the command.
// toComplete: The prefix that needs completion.
//
// Returns:
// A slice of strings representing possible completions and a ShellCompDirective indicating the directive for shell completion.
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
}
2025-04-26 20:37:36 +00:00
// TestCmdNameCompletionInGo tests the command name completion functionality in Go.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestNoCmdNameCompletionInGo tests the behavior of command name completion in Go when different flags and arguments are present.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestValidArgsCompletionInGo tests the completion of valid arguments in a Go command.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestValidArgsAndCmdCompletionInGo tests that sub-commands and valid arguments are correctly completed.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestValidArgsFuncAndCmdCompletionInGo tests the completion functionality of a root command and its child command.
// It verifies that both sub-commands and valid arguments are correctly completed without and with prefixes,
// as well as with descriptions. The function uses a test environment to execute commands and compare the output against expected results.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestFlagNameCompletionInGo tests the completion of flag names in a Go command.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestFlagNameCompletionInGoWithDesc tests the completion of flag names in a Go command.
// It verifies that flags are correctly completed when the user provides a prefix and without any prefix.
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)
}
}
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
// customMultiString is a custom Value type that accepts multiple values,
// but does not include "Slice" or "Array" in its "Type" string.
type customMultiString []string
var _ SliceValue = (*customMultiString)(nil)
2025-04-26 20:37:36 +00:00
// String returns a string representation of the customMultiString.
// It uses fmt.Sprintf to convert the underlying value to a string.
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
func (s *customMultiString) String() string {
return fmt.Sprintf("%v", *s)
}
2025-04-26 20:37:36 +00:00
// Set appends a value to the customMultiString and returns an error if any.
// Parameters:
// - v: The string value to be appended.
// Returns:
// - error: An error if appending fails; otherwise, nil.
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
func (s *customMultiString) Set(v string) error {
*s = append(*s, v)
return nil
}
2025-04-26 20:37:36 +00:00
// Type returns the type of customMultiString, which is "multi string".
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
func (s *customMultiString) Type() string {
return "multi string"
}
2025-04-26 20:37:36 +00:00
// GetSlice returns a copy of the slice stored in the customMultiString instance.
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
func (s *customMultiString) GetSlice() []string {
return *s
}
2025-04-26 20:37:36 +00:00
// TestFlagNameCompletionRepeat tests the completion of flag names without repetition.
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")
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
rootCmd.Flags().VarP(&customMultiString{}, "multi", "m", "multi string flag")
multiFlag := rootCmd.Flags().Lookup("multi")
// Test that flag names are not repeated unless they are an array or slice
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
firstFlag.Changed = false
expected := strings.Join([]string{
"--array",
"--bslice",
"--help",
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
"--multi",
"--second",
"--slice",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are not repeated unless they are an array or slice
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--second=false", "--")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
firstFlag.Changed = false
secondFlag.Changed = false
expected = strings.Join([]string{
"--array",
"--bslice",
"--help",
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
"--multi",
"--slice",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are not repeated unless they are an array or slice
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--slice", "1", "--slice=2", "--array", "val", "--bslice", "true", "--multi", "val", "--")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
sliceFlag.Changed = false
arrayFlag.Changed = false
bsliceFlag.Changed = false
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
multiFlag.Changed = false
expected = strings.Join([]string{
"--array",
"--bslice",
"--first",
"--help",
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
"--multi",
"--second",
"--slice",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are not repeated unless they are an array or slice, using shortname
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
sliceFlag.Changed = false
arrayFlag.Changed = false
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
multiFlag.Changed = false
expected = strings.Join([]string{
"--array",
"-a",
"--bslice",
"-b",
"--first",
"-f",
"--help",
"-h",
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
"--multi",
"-m",
"--second",
"-s",
"--slice",
"-l",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are not repeated unless they are an array or slice, using shortname with prefix
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-a")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
sliceFlag.Changed = false
arrayFlag.Changed = false
completion: improve detection for flags that accept multiple values (#2210) The completion code attempts to detect whether a flag can be specified more than once, and therefore should provide completion even if already set. Currently, this code depends on conventions used in the pflag package, which uses an "Array" or "Slice" suffix or for some types a "stringTo" prefix. Cobra allows custom value types to be used, which may not use the same convention for naming, and therefore currently aren't detected to allow multiple values. The pflag module defines a [SliceValue] interface, which is implemented by the Slice and Array value types it provides (unfortunately, it's not currently implemented by the "stringTo" values). This patch adds a reduced interface based on the [SliceValue] interface mentioned above to allow detecting Value-types that accept multiple values. Custom types can implement this interface to make completion work for those values. I deliberately used a reduced interface to keep the requirements for this detection as low as possible, without enforcing the other methods defined in the interface (Append, Replace) which may not apply to all custom types. Future improvements can likely still be made, considering either implementing the SliceValue interface for the "stringTo" values or defining a separate "MapValue" interface for those types. Possibly providing the reduced interface as part of the pflag module and to export it. [SliceValue]: https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/flag.go#L193-L203 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com> Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
2024-12-28 23:41:43 +01:00
multiFlag.Changed = false
expected = strings.Join([]string{
"-a",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
2025-04-26 20:37:36 +00:00
// TestRequiredFlagNameCompletionInGo tests the completion of required flags in a command-line interface.
// It ensures that required flags are suggested even without the - prefix and that they are not suggested once present.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestFlagFileExtFilterCompletionInGo tests the completion logic for flags with file extensions.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestFlagDirFilterCompletionInGo tests the completion logic for directory filtering flags in a Go command.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestValidArgsFuncCmdContext tests the ValidArgsFunction of a child command using a context with specific values.
2021-05-03 18:33:57 +02:00
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)
}
}
2025-04-26 20:37:36 +00:00
// TestValidArgsFuncSingleCmd tests the valid arguments function for a single command.
//
// It creates a root command with a valid arguments function and an empty run function. Then it tests
// completing an empty string and a string with a prefix, checking if the output matches the expected results.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestValidArgsFuncSingleCmdInvalidArg tests the behavior of a root command without subcommands when completing with an invalid number of arguments. The test ensures that the correct error message and completion directive are returned.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestValidArgsFuncChildCmds tests the completion functionality for child commands with valid argument functions.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestValidArgsFuncAliases tests the functionality of valid arguments function aliases in a command.
// It verifies that the completion works correctly with different sub-commands and prefixes.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestValidArgsFuncInBashScript tests that the bash completion function is generated correctly when a valid args function is set for a command.
// It creates a root command with a child command and validates that the bash completion output includes the valid args function.
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")
}
2025-04-26 20:37:36 +00:00
// TestNoValidArgsFuncInBashScript tests the generation of Bash completion script when no valid arguments function is provided.
// It asserts that the generated script does not include a completion function.
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")
}
2025-04-26 20:37:36 +00:00
// TestCompleteCmdInBashScript tests the generation of bash completion for a command.
// It sets up a root command and adds a child command to it. Then, it generates bash completion
// and checks if the output contains the expected shell completion flag without description requests.
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)
}
2025-04-26 20:37:36 +00:00
// TestCompleteNoDesCmdInZshScript tests that a zsh completion script is generated without descriptions for commands.
// It verifies the expected command name and description absence in the generated script.
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)
}
2025-04-26 20:37:36 +00:00
// TestCompleteCmdInZshScript tests the generation of Zsh completion for a command.
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)
}
2025-04-26 20:37:36 +00:00
// TestFlagCompletionInGo tests flag completion functionality in a Go command.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestValidArgsFuncChildCmdsWithDesc tests the completion of sub-commands using valid args functions.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestFlagCompletionWorksRootCommandAddedAfterFlags tests that flag completion works for a subcommand after it is added to the root command.
// It specifically checks if the completion function is registered correctly and if the flag completion provides the expected suggestions.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestFlagCompletionForPersistentFlagsCalledFromSubCmd tests that persistent flag completion works for a command called from a subcommand.
// It sets up a root command with a persistent string flag and a child command with its own valid arguments function and a boolean flag.
// The test executes the command to trigger the flag completion and checks if the output matches the expected result.
// Args:
// - t: A testing.T instance for assertions and logging.
// Returns:
// - None
// Raises:
// - An error if an unexpected error occurs during the execution of the command.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestFlagCompletionConcurrentRegistration tests the registration of flag completion concurrently to ensure proper handling of concurrency.
//
2025-04-26 20:37:36 +00:00
// It registers flags on a root command and a child command and then attempts to register completion functions for these flags concurrently in different goroutines. The test asserts that flag completion works correctly for each flag, regardless of whether it belongs to the root or child command.
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)
}
}
}
2025-04-26 20:37:36 +00:00
// TestFlagCompletionInGoWithDesc tests flag completion functionality in a Go application with descriptions.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestValidArgsNotValidArgsFunc tests the behavior when both ValidArgs and ValidArgsFunction are present.
// It ensures that only ValidArgs is considered during command completion.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestArgAliasesCompletionInGo tests the completion behavior of argument aliases in a Go command.
//
// It creates a root command with specific valid arguments and their aliases. The test cases check
// the completion behavior when there are matching valid arguments and when no matches exist, ensuring that
// only the aliased arguments are returned for incomplete input prefixes.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestCompleteHelp tests the completion of the help command in a root command with sub-commands.
func TestCompleteHelp(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
child1Cmd := &Command{
Use: "child1",
Run: emptyRun,
}
child2Cmd := &Command{
Use: "child2",
Run: emptyRun,
}
rootCmd.AddCommand(child1Cmd, child2Cmd)
child3Cmd := &Command{
Use: "child3",
Run: emptyRun,
}
child1Cmd.AddCommand(child3Cmd)
// Test that completion includes the help command
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"child1",
"child2",
"completion",
"help",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test sub-commands are completed on first level of help command
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "help", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"child1",
"child2",
"completion",
"help", // "<program> help help" is a valid command, so should be completed
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test sub-commands are completed on first level of help command
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "help", "child1", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"child3",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
2025-04-26 20:37:36 +00:00
// removeCompCmd removes the completion command from the given root command.
// It searches through the root command's subcommands and removes the one with the name `compCmdName`.
func removeCompCmd(rootCmd *Command) {
// Remove completion command for the next test
for _, cmd := range rootCmd.commands {
if cmd.Name() == compCmdName {
rootCmd.RemoveCommand(cmd)
return
}
}
}
2025-04-26 20:37:36 +00:00
// TestDefaultCompletionCmd tests the default completion command behavior in different scenarios.
func TestDefaultCompletionCmd(t *testing.T) {
rootCmd := &Command{
Use: "root",
Args: NoArgs,
Run: emptyRun,
}
// Test that when there are no sub-commands, the completion command is not created if it is not called directly.
assertNoErr(t, rootCmd.Execute())
for _, cmd := range rootCmd.commands {
if cmd.Name() == compCmdName {
t.Errorf("Should not have a 'completion' command when there are no other sub-commands of root")
break
}
}
// Test that when there are no sub-commands, the completion command is created when it is called directly.
_, err := executeCommand(rootCmd, compCmdName)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the arguments
rootCmd.args = nil
// Remove completion command for the next test
removeCompCmd(rootCmd)
// Add a sub-command
subCmd := &Command{
Use: "sub",
Run: emptyRun,
}
rootCmd.AddCommand(subCmd)
// Test that a completion command is created if there are other sub-commands
found := false
assertNoErr(t, rootCmd.Execute())
for _, cmd := range rootCmd.commands {
if cmd.Name() == compCmdName {
found = true
break
}
}
if !found {
t.Errorf("Should have a 'completion' command when there are other sub-commands of root")
}
// Remove completion command for the next test
removeCompCmd(rootCmd)
// Test that the default completion command can be disabled
rootCmd.CompletionOptions.DisableDefaultCmd = true
assertNoErr(t, rootCmd.Execute())
for _, cmd := range rootCmd.commands {
if cmd.Name() == compCmdName {
t.Errorf("Should not have a 'completion' command when the feature is disabled")
break
}
}
// Re-enable for next test
rootCmd.CompletionOptions.DisableDefaultCmd = false
// Test that completion descriptions are enabled by default
output, err := executeCommand(rootCmd, compCmdName, "zsh")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
check(t, output, ShellCompRequestCmd)
checkOmit(t, output, ShellCompNoDescRequestCmd)
// Remove completion command for the next test
removeCompCmd(rootCmd)
// Test that completion descriptions can be disabled completely
rootCmd.CompletionOptions.DisableDescriptions = true
output, err = executeCommand(rootCmd, compCmdName, "zsh")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
check(t, output, ShellCompNoDescRequestCmd)
// Re-enable for next test
rootCmd.CompletionOptions.DisableDescriptions = false
// Remove completion command for the next test
removeCompCmd(rootCmd)
var compCmd *Command
// Test that the --no-descriptions flag is present on all shells
assertNoErr(t, rootCmd.Execute())
for _, shell := range []string{"bash", "fish", "powershell", "zsh"} {
if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag == nil {
t.Errorf("Missing --%s flag for %s shell", compCmdNoDescFlagName, shell)
}
}
// Remove completion command for the next test
removeCompCmd(rootCmd)
// Test that the '--no-descriptions' flag can be disabled
rootCmd.CompletionOptions.DisableNoDescFlag = true
assertNoErr(t, rootCmd.Execute())
for _, shell := range []string{"fish", "zsh", "bash", "powershell"} {
if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag != nil {
t.Errorf("Unexpected --%s flag for %s shell", compCmdNoDescFlagName, shell)
}
}
// Re-enable for next test
rootCmd.CompletionOptions.DisableNoDescFlag = false
// Remove completion command for the next test
removeCompCmd(rootCmd)
// Test that the '--no-descriptions' flag is disabled when descriptions are disabled
rootCmd.CompletionOptions.DisableDescriptions = true
assertNoErr(t, rootCmd.Execute())
for _, shell := range []string{"fish", "zsh", "bash", "powershell"} {
if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag != nil {
t.Errorf("Unexpected --%s flag for %s shell", compCmdNoDescFlagName, shell)
}
}
// Re-enable for next test
rootCmd.CompletionOptions.DisableDescriptions = false
// Remove completion command for the next test
removeCompCmd(rootCmd)
// Test that the 'completion' command can be hidden
rootCmd.CompletionOptions.HiddenDefaultCmd = true
assertNoErr(t, rootCmd.Execute())
compCmd, _, err = rootCmd.Find([]string{compCmdName})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if compCmd.Hidden == false {
t.Error("Default 'completion' command should be hidden but it is not")
}
// Re-enable for next test
rootCmd.CompletionOptions.HiddenDefaultCmd = false
// Remove completion command for the next test
removeCompCmd(rootCmd)
}
2025-04-26 20:37:36 +00:00
// TestCompleteCompletion tests the completion functionality of a root command without any sub-commands and with sub-commands.
func TestCompleteCompletion(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
// Test that when there are no sub-commands, the 'completion' command is not completed
// (because it is not created).
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that when there are no sub-commands, completion can be triggered for the default
// 'completion' command
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"bash",
"fish",
"powershell",
"zsh",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Add a sub-command
subCmd := &Command{
Use: "sub",
Run: emptyRun,
}
rootCmd.AddCommand(subCmd)
// Test sub-commands of the completion command
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"bash",
"fish",
"powershell",
"zsh",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test there are no completions for the sub-commands of the completion command
var compCmd *Command
for _, cmd := range rootCmd.Commands() {
if cmd.Name() == compCmdName {
compCmd = cmd
break
}
}
for _, shell := range compCmd.Commands() {
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, compCmdName, shell.Name(), "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
}
2025-04-26 20:37:36 +00:00
// TestMultipleShorthandFlagCompletion tests the completion functionality of multiple shorthand flags in a command.
//
// It sets up a root command with various flags and their shorthand versions. The test then executes the command with different combinations of shorthand flags to verify that the correct completions are returned.
//
// Parameters:
// - t: A pointer to a testing.T instance used for assertions and error reporting.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestCompleteWithDisableFlagParsing tests the behavior of Cobra's flag completion when DisableFlagParsing is set on a command.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestCompleteWithRootAndLegacyArgs tests a lonely root command that uses legacyArgs(). The root command should accept any number of arguments and completion should behave accordingly.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestCompletionFuncCompatibility tests the compatibility of completion functions with different formats and types.
func TestCompletionFuncCompatibility(t *testing.T) {
t.Run("validate signature", func(t *testing.T) {
t.Run("format with []string", func(t *testing.T) {
var userComp func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
// check against new signature
var _ CompletionFunc = userComp
// check Command accepts
cmd := Command{
ValidArgsFunction: userComp,
}
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
})
t.Run("format with []Completion", func(t *testing.T) {
var userComp func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)
// check against new signature
var _ CompletionFunc = userComp
// check Command accepts
cmd := Command{
ValidArgsFunction: userComp,
}
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
})
t.Run("format with CompletionFunc", func(t *testing.T) {
var userComp CompletionFunc
// check helper against old signature
var _ func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) = userComp
var _ func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) = userComp
// check Command accepts
cmd := Command{
ValidArgsFunction: userComp,
}
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
})
})
t.Run("user defined completion helper", func(t *testing.T) {
t.Run("type helper", func(t *testing.T) {
// This is a type that may have been defined by the user of the library
// This replicates the issue https://github.com/docker/cli/issues/5827
// https://github.com/docker/cli/blob/b6e7eba4470ecdca460e4b63270fba8179674ad6/cli/command/completion/functions.go#L18
type UserCompletionTypeHelper func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
var userComp UserCompletionTypeHelper
// Here we are validating the existing type validates the CompletionFunc type
var _ CompletionFunc = userComp
cmd := Command{
ValidArgsFunction: userComp,
}
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
})
t.Run("type alias helper", func(t *testing.T) {
// This is a type that may have been defined by the user of the library
// This replicates the possible fix that was tried here https://github.com/docker/cli/pull/5828
// https://github.com/docker/cli/blob/ae3d4db9f658259dace9dee515718be7c1b1f517/cli/command/completion/functions.go#L18
type UserCompletionTypeAliasHelper = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
var userComp UserCompletionTypeAliasHelper
// Here we are validating the existing type validates the CompletionFunc type
var _ CompletionFunc = userComp
cmd := Command{
ValidArgsFunction: userComp,
}
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
})
})
}
2025-04-26 20:37:36 +00:00
// TestFixedCompletions tests the FixedCompletions completion function.
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)
}
}
2025-04-26 20:37:36 +00:00
// TestFixedCompletionsWithCompletionHelpers tests the FixedCompletions function with various completion helpers to ensure correct behavior.
func TestFixedCompletionsWithCompletionHelpers(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
// here we are mixing string, [Completion] and [CompletionWithDesc]
choices := []string{"apple", Completion("banana"), CompletionWithDesc("orange", "orange are orange")}
childCmd := &Command{
Use: "child",
ValidArgsFunction: FixedCompletions(choices, ShellCompDirectiveNoFileComp),
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
t.Run("completion with description", func(t *testing.T) {
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "a")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"apple",
"banana",
"orange\torange are orange", // this one has the description as expected with [ShellCompRequestCmd] flag
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", "",
}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
})
t.Run("completion with no description", func(t *testing.T) {
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child", "a")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"apple",
"banana",
"orange", // the description is absent as expected with [ShellCompNoDescRequestCmd] flag
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", "",
}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
})
}
2025-04-26 20:37:36 +00:00
// TestCompletionForGroupedFlags tests the completion functionality for grouped flags in a command.
// It verifies that flags within a group are suggested together and only when required, and that
// flags outside of a group are not suggested with a leading '-' prefix unless they are part of a required group.
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{
2023-07-16 18:38:22 +02:00
"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)
}
})
}
}
2025-04-26 20:37:36 +00:00
// TestCompletionForOneRequiredGroupFlags tests the completion logic when one group of flags is required.
//
// It sets up a command with multiple subcommands and flags, marking some flags as part of a required group. The test cases
// verify that only the required flags are suggested for completion when no flag from the group is provided. It also checks
// scenarios where flags within the group are already present or other conditions affect the suggestion.
2023-07-16 18:38:22 +02:00
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)
}
})
}
}
2025-04-26 20:37:36 +00:00
// TestCompletionForMutuallyExclusiveFlags tests completion behavior for commands with mutually exclusive flags.
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)
}
})
}
}
2025-04-26 20:37:36 +00:00
// TestArgsNotDetectedAsFlagsCompletionInGo is a regression test ensuring that the bug described in https://github.com/spf13/cobra/issues/1816 does not occur anymore.
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)
}
})
}
}
2025-04-26 20:37:36 +00:00
// TestGetFlagCompletion tests the GetFlagCompletionFunc method of the Command type.
// It verifies that the function correctly retrieves and returns the completion function for a given flag,
// including both local and persistent flags, as well as child commands. The test cases cover various scenarios
// to ensure the functionality works as expected.
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)
}
}
})
}
}
2025-04-26 20:37:36 +00:00
// TestGetEnvConfig tests the getEnvConfig function with various test cases to ensure it correctly handles environment variable overrides and fallbacks.
// Each test case checks different scenarios for command and global environment variables, as well as their expected outcomes.
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)
}
})
}
}
2025-04-26 20:37:36 +00:00
// TestDisableDescriptions tests the functionality of disabling descriptions for command completion.
func TestDisableDescriptions(t *testing.T) {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
childCmd := &Command{
Use: "thechild",
Short: "The child command",
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
specificDescriptionsEnvVar := configEnvVar(rootCmd.Name(), configEnvVarSuffixDescriptions)
globalDescriptionsEnvVar := configEnvVar(configEnvVarGlobalPrefix, configEnvVarSuffixDescriptions)
const (
descLineWithDescription = "first\tdescription"
descLineWithoutDescription = "first"
)
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
comps := []string{descLineWithDescription}
return comps, ShellCompDirectiveDefault
}
testCases := []struct {
desc string
globalEnvValue string
specificEnvValue string
expectedLine string
}{
{
"No env variables set",
"",
"",
descLineWithDescription,
},
{
"Global value false",
"false",
"",
descLineWithoutDescription,
},
{
"Specific value false",
"",
"false",
descLineWithoutDescription,
},
{
"Both values false",
"false",
"false",
descLineWithoutDescription,
},
{
"Both values true",
"true",
"true",
descLineWithDescription,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
if err := os.Setenv(specificDescriptionsEnvVar, tc.specificEnvValue); err != nil {
t.Errorf("Unexpected error setting %s: %v", specificDescriptionsEnvVar, err)
}
if err := os.Setenv(globalDescriptionsEnvVar, tc.globalEnvValue); err != nil {
t.Errorf("Unexpected error setting %s: %v", globalDescriptionsEnvVar, err)
}
var run = func() {
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "thechild", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
tc.expectedLine,
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
run()
// For empty cases, test also unset state
if tc.specificEnvValue == "" {
if err := os.Unsetenv(specificDescriptionsEnvVar); err != nil {
t.Errorf("Unexpected error unsetting %s: %v", specificDescriptionsEnvVar, err)
}
run()
}
if tc.globalEnvValue == "" {
if err := os.Unsetenv(globalDescriptionsEnvVar); err != nil {
t.Errorf("Unexpected error unsetting %s: %v", globalDescriptionsEnvVar, err)
}
run()
}
})
}
}
// A test to make sure the InitDefaultCompletionCmd function works as expected
// in case a project calls it directly.
func TestInitDefaultCompletionCmd(t *testing.T) {
testCases := []struct {
desc string
hasChildCmd bool
args []string
expectCompCmd bool
}{
{
desc: "no child command and not calling the completion command",
hasChildCmd: false,
args: []string{"somearg"},
expectCompCmd: false,
},
{
desc: "no child command but calling the completion command",
hasChildCmd: false,
args: []string{"completion"},
expectCompCmd: true,
},
{
desc: "no child command but calling __complete on the root command",
hasChildCmd: false,
args: []string{"__complete", ""},
expectCompCmd: false,
},
{
desc: "no child command but calling __complete on the completion command",
hasChildCmd: false,
args: []string{"__complete", "completion", ""},
expectCompCmd: true,
},
{
desc: "with child command",
hasChildCmd: true,
args: []string{"child"},
expectCompCmd: true,
},
{
desc: "no child command not passing args",
hasChildCmd: false,
args: nil,
expectCompCmd: false,
},
{
desc: "with child command not passing args",
hasChildCmd: true,
args: nil,
expectCompCmd: true,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
childCmd := &Command{Use: "child", Run: emptyRun}
expectedNumSubCommands := 0
if tc.hasChildCmd {
rootCmd.AddCommand(childCmd)
expectedNumSubCommands++
}
if tc.expectCompCmd {
expectedNumSubCommands++
}
if len(tc.args) > 0 && tc.args[0] == "__complete" {
expectedNumSubCommands++
}
// Setup the __complete command to mimic real world scenarios
rootCmd.initCompleteCmd(tc.args)
// Call the InitDefaultCompletionCmd function directly
if tc.args == nil {
rootCmd.InitDefaultCompletionCmd()
} else {
rootCmd.InitDefaultCompletionCmd(tc.args...)
}
// Check if the completion command was added
if len(rootCmd.Commands()) != expectedNumSubCommands {
t.Errorf("Expected %d subcommands, got %d", expectedNumSubCommands, len(rootCmd.Commands()))
}
})
}
}