spf13--cobra/completions_test.go
2024-04-08 11:14:43 +00:00

3758 lines
105 KiB
Go

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