spf13--cobra/completions_test.go
Marc Khouzam 5a793296fb Redirect bash completion v1 to v2 when possible
We are no longer actively maintaining bash completion v1 in favor of its
more rich v2 version.  Previously, using bash completion v2 required
projects to be aware of its existence and to explicitly call
GenBashCompletionV2().

With this commit, any projects calling GenBashCompletion() will
automatically be redirected to using the v2 version.

One exception is if the project uses the legacy custom completion logic
which is not supported in v2.  We can detect that by looking for the
use of the field `BashCompletionFunction` on the root command.

Note that descriptions are kept off when calling GenBashCompletion().
This means that to enable completion descriptions for bash, a project
must still explicitly call GenBashCompletionV2().

Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com>
2022-11-25 15:27:50 -05:00

3132 lines
87 KiB
Go

// Copyright 2013-2022 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cobra
import (
"bytes"
"context"
"strings"
"testing"
)
func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
if len(args) != 0 {
return nil, ShellCompDirectiveNoFileComp
}
var completions []string
for _, comp := range []string{"one\tThe first", "two\tThe second"} {
if strings.HasPrefix(comp, toComplete) {
completions = append(completions, comp)
}
}
return completions, ShellCompDirectiveDefault
}
func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
if len(args) != 0 {
return nil, ShellCompDirectiveNoFileComp
}
var completions []string
for _, comp := range []string{"three\tThe third", "four\tThe fourth"} {
if strings.HasPrefix(comp, toComplete) {
completions = append(completions, comp)
}
}
return completions, ShellCompDirectiveDefault
}
func TestCmdNameCompletionInGo(t *testing.T) {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
childCmd1 := &Command{
Use: "firstChild",
Short: "First command",
Run: emptyRun,
}
childCmd2 := &Command{
Use: "secondChild",
Run: emptyRun,
}
hiddenCmd := &Command{
Use: "testHidden",
Hidden: true, // Not completed
Run: emptyRun,
}
deprecatedCmd := &Command{
Use: "testDeprecated",
Deprecated: "deprecated", // Not completed
Run: emptyRun,
}
aliasedCmd := &Command{
Use: "aliased",
Short: "A command with aliases",
Aliases: []string{"testAlias", "testSynonym"}, // Not completed
Run: emptyRun,
}
rootCmd.AddCommand(childCmd1, childCmd2, hiddenCmd, deprecatedCmd, aliasedCmd)
// Test that sub-command names are completed
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"aliased",
"completion",
"firstChild",
"help",
"secondChild",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that sub-command names are completed with prefix
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "s")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"secondChild",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that even with no valid sub-command matches, hidden, deprecated and
// aliases are not completed
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "test")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that sub-command names are completed with description
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"aliased\tA command with aliases",
"completion\tGenerate the autocompletion script for the specified shell",
"firstChild\tFirst command",
"help\tHelp about any command",
"secondChild",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestNoCmdNameCompletionInGo(t *testing.T) {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
rootCmd.Flags().String("localroot", "", "local root flag")
childCmd1 := &Command{
Use: "childCmd1",
Short: "First command",
Args: MinimumNArgs(0),
Run: emptyRun,
}
rootCmd.AddCommand(childCmd1)
childCmd1.PersistentFlags().StringP("persistent", "p", "", "persistent flag")
persistentFlag := childCmd1.PersistentFlags().Lookup("persistent")
childCmd1.Flags().StringP("nonPersistent", "n", "", "non-persistent flag")
nonPersistentFlag := childCmd1.Flags().Lookup("nonPersistent")
childCmd2 := &Command{
Use: "childCmd2",
Run: emptyRun,
}
childCmd1.AddCommand(childCmd2)
// Test that sub-command names are not completed if there is an argument already
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "arg1", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that sub-command names are not completed if a local non-persistent flag is present
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "--nonPersistent", "value", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
nonPersistentFlag.Changed = false
expected = strings.Join([]string{
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that sub-command names are completed if a local non-persistent flag is present and TraverseChildren is set to true
// set TraverseChildren to true on the root cmd
rootCmd.TraverseChildren = true
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset TraverseChildren for next command
rootCmd.TraverseChildren = false
expected = strings.Join([]string{
"childCmd1",
"completion",
"help",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that sub-command names from a child cmd are completed if a local non-persistent flag is present
// and TraverseChildren is set to true on the root cmd
rootCmd.TraverseChildren = true
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "childCmd1", "--nonPersistent", "value", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset TraverseChildren for next command
rootCmd.TraverseChildren = false
// Reset the flag for the next command
nonPersistentFlag.Changed = false
expected = strings.Join([]string{
"childCmd2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that we don't use Traverse when we shouldn't.
// This command should not return a completion since the command line is invalid without TraverseChildren.
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "childCmd1", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that sub-command names are not completed if a local non-persistent short flag is present
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-n", "value", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
nonPersistentFlag.Changed = false
expected = strings.Join([]string{
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that sub-command names are completed with a persistent flag
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "--persistent", "value", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
persistentFlag.Changed = false
expected = strings.Join([]string{
"childCmd2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that sub-command names are completed with a persistent short flag
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-p", "value", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
persistentFlag.Changed = false
expected = strings.Join([]string{
"childCmd2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestValidArgsCompletionInGo(t *testing.T) {
rootCmd := &Command{
Use: "root",
ValidArgs: []string{"one", "two", "three"},
Args: MinimumNArgs(1),
}
// Test that validArgs are completed
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"one",
"two",
"three",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that validArgs are completed with prefix
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "o")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"one",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that validArgs don't repeat
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "one", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestValidArgsAndCmdCompletionInGo(t *testing.T) {
rootCmd := &Command{
Use: "root",
ValidArgs: []string{"one", "two"},
Run: emptyRun,
}
childCmd := &Command{
Use: "thechild",
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
// Test that both sub-commands and validArgs are completed
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"completion",
"help",
"thechild",
"one",
"two",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that both sub-commands and validArgs are completed with prefix
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"thechild",
"two",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestValidArgsFuncAndCmdCompletionInGo(t *testing.T) {
rootCmd := &Command{
Use: "root",
ValidArgsFunction: validArgsFunc,
Run: emptyRun,
}
childCmd := &Command{
Use: "thechild",
Short: "The child command",
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
// Test that both sub-commands and validArgsFunction are completed
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"completion",
"help",
"thechild",
"one",
"two",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that both sub-commands and validArgs are completed with prefix
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"thechild",
"two",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that both sub-commands and validArgs are completed with description
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"thechild\tThe child command",
"two\tThe second",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestFlagNameCompletionInGo(t *testing.T) {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
childCmd := &Command{
Use: "childCmd",
Version: "1.2.3",
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
rootCmd.Flags().IntP("first", "f", -1, "first flag")
rootCmd.PersistentFlags().BoolP("second", "s", false, "second flag")
childCmd.Flags().String("subFlag", "", "sub flag")
// Test that flag names are not shown if the user has not given the '-' prefix
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"childCmd",
"completion",
"help",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are completed
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--first",
"-f",
"--help",
"-h",
"--second",
"-s",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are completed when a prefix is given
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--f")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--first",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are completed in a sub-cmd
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "-")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--second",
"-s",
"--help",
"-h",
"--subFlag",
"--version",
"-v",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestFlagNameCompletionInGoWithDesc(t *testing.T) {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
childCmd := &Command{
Use: "childCmd",
Short: "first command",
Version: "1.2.3",
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
rootCmd.Flags().IntP("first", "f", -1, "first flag\nlonger description for flag")
rootCmd.PersistentFlags().BoolP("second", "s", false, "second flag")
childCmd.Flags().String("subFlag", "", "sub flag")
// Test that flag names are not shown if the user has not given the '-' prefix
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"childCmd\tfirst command",
"completion\tGenerate the autocompletion script for the specified shell",
"help\tHelp about any command",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are completed
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "-")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--first\tfirst flag",
"-f\tfirst flag",
"--help\thelp for root",
"-h\thelp for root",
"--second\tsecond flag",
"-s\tsecond flag",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are completed when a prefix is given
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--f")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--first\tfirst flag",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are completed in a sub-cmd
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "childCmd", "-")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--second\tsecond flag",
"-s\tsecond flag",
"--help\thelp for childCmd",
"-h\thelp for childCmd",
"--subFlag\tsub flag",
"--version\tversion for childCmd",
"-v\tversion for childCmd",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestFlagNameCompletionRepeat(t *testing.T) {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
childCmd := &Command{
Use: "childCmd",
Short: "first command",
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
rootCmd.Flags().IntP("first", "f", -1, "first flag")
firstFlag := rootCmd.Flags().Lookup("first")
rootCmd.Flags().BoolP("second", "s", false, "second flag")
secondFlag := rootCmd.Flags().Lookup("second")
rootCmd.Flags().StringArrayP("array", "a", nil, "array flag")
arrayFlag := rootCmd.Flags().Lookup("array")
rootCmd.Flags().IntSliceP("slice", "l", nil, "slice flag")
sliceFlag := rootCmd.Flags().Lookup("slice")
rootCmd.Flags().BoolSliceP("bslice", "b", nil, "bool slice flag")
bsliceFlag := rootCmd.Flags().Lookup("bslice")
// Test that flag names are not repeated unless they are an array or slice
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
firstFlag.Changed = false
expected := strings.Join([]string{
"--array",
"--bslice",
"--help",
"--second",
"--slice",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are not repeated unless they are an array or slice
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--second=false", "--")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
firstFlag.Changed = false
secondFlag.Changed = false
expected = strings.Join([]string{
"--array",
"--bslice",
"--help",
"--slice",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are not repeated unless they are an array or slice
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--slice", "1", "--slice=2", "--array", "val", "--bslice", "true", "--")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
sliceFlag.Changed = false
arrayFlag.Changed = false
bsliceFlag.Changed = false
expected = strings.Join([]string{
"--array",
"--bslice",
"--first",
"--help",
"--second",
"--slice",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are not repeated unless they are an array or slice, using shortname
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
sliceFlag.Changed = false
arrayFlag.Changed = false
expected = strings.Join([]string{
"--array",
"-a",
"--bslice",
"-b",
"--first",
"-f",
"--help",
"-h",
"--second",
"-s",
"--slice",
"-l",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that flag names are not repeated unless they are an array or slice, using shortname with prefix
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-a")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
sliceFlag.Changed = false
arrayFlag.Changed = false
expected = strings.Join([]string{
"-a",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestRequiredFlagNameCompletionInGo(t *testing.T) {
rootCmd := &Command{
Use: "root",
ValidArgs: []string{"realArg"},
Run: emptyRun,
}
childCmd := &Command{
Use: "childCmd",
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"subArg"}, ShellCompDirectiveNoFileComp
},
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
rootCmd.Flags().IntP("requiredFlag", "r", -1, "required flag")
assertNoErr(t, rootCmd.MarkFlagRequired("requiredFlag"))
requiredFlag := rootCmd.Flags().Lookup("requiredFlag")
rootCmd.PersistentFlags().IntP("requiredPersistent", "p", -1, "required persistent")
assertNoErr(t, rootCmd.MarkPersistentFlagRequired("requiredPersistent"))
requiredPersistent := rootCmd.PersistentFlags().Lookup("requiredPersistent")
rootCmd.Flags().StringP("release", "R", "", "Release name")
childCmd.Flags().BoolP("subRequired", "s", false, "sub required flag")
assertNoErr(t, childCmd.MarkFlagRequired("subRequired"))
childCmd.Flags().BoolP("subNotRequired", "n", false, "sub not required flag")
// Test that a required flag is suggested even without the - prefix
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"childCmd",
"completion",
"help",
"--requiredFlag",
"-r",
"--requiredPersistent",
"-p",
"realArg",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that a required flag is suggested without other flags when using the '-' prefix
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--requiredFlag",
"-r",
"--requiredPersistent",
"-p",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that if no required flag matches, the normal flags are suggested
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--relea")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--release",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test required flags for sub-commands
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--requiredPersistent",
"-p",
"--subRequired",
"-s",
"subArg",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "-")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--requiredPersistent",
"-p",
"--subRequired",
"-s",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "--subNot")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--subNotRequired",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that when a required flag is present, it is not suggested anymore
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredFlag", "1", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
requiredFlag.Changed = false
expected = strings.Join([]string{
"--requiredPersistent",
"-p",
"realArg",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that when a persistent required flag is present, it is not suggested anymore
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredPersistent", "1", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
requiredPersistent.Changed = false
expected = strings.Join([]string{
"childCmd",
"completion",
"help",
"--requiredFlag",
"-r",
"realArg",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that when all required flags are present, normal completion is done
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredFlag", "1", "--requiredPersistent", "1", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flags for the next command
requiredFlag.Changed = false
requiredPersistent.Changed = false
expected = strings.Join([]string{
"realArg",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestFlagFileExtFilterCompletionInGo(t *testing.T) {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
// No extensions. Should be ignored.
rootCmd.Flags().StringP("file", "f", "", "file flag")
assertNoErr(t, rootCmd.MarkFlagFilename("file"))
// Single extension
rootCmd.Flags().StringP("log", "l", "", "log flag")
assertNoErr(t, rootCmd.MarkFlagFilename("log", "log"))
// Multiple extensions
rootCmd.Flags().StringP("yaml", "y", "", "yaml flag")
assertNoErr(t, rootCmd.MarkFlagFilename("yaml", "yaml", "yml"))
// Directly using annotation
rootCmd.Flags().StringP("text", "t", "", "text flag")
assertNoErr(t, rootCmd.Flags().SetAnnotation("text", BashCompFilenameExt, []string{"txt"}))
// Test that the completion logic returns the proper info for the completion
// script to handle the file filtering
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--file", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--log", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"log",
":8",
"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--yaml", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"yaml", "yml",
":8",
"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--yaml=")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"yaml", "yml",
":8",
"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-y", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"yaml", "yml",
":8",
"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-y=")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"yaml", "yml",
":8",
"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--text", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"txt",
":8",
"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestFlagDirFilterCompletionInGo(t *testing.T) {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
// Filter directories
rootCmd.Flags().StringP("dir", "d", "", "dir flag")
assertNoErr(t, rootCmd.MarkFlagDirname("dir"))
// Filter directories within a directory
rootCmd.Flags().StringP("subdir", "s", "", "subdir")
assertNoErr(t, rootCmd.Flags().SetAnnotation("subdir", BashCompSubdirsInDir, []string{"themes"}))
// Multiple directory specification get ignored
rootCmd.Flags().StringP("manydir", "m", "", "manydir")
assertNoErr(t, rootCmd.Flags().SetAnnotation("manydir", BashCompSubdirsInDir, []string{"themes", "colors"}))
// Test that the completion logic returns the proper info for the completion
// script to handle the directory filtering
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--dir", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
":16",
"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-d", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
":16",
"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--subdir", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"themes",
":16",
"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--subdir=")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"themes",
":16",
"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"themes",
":16",
"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s=")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"themes",
":16",
"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--manydir", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
":16",
"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestValidArgsFuncCmdContext(t *testing.T) {
validArgsFunc := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
ctx := cmd.Context()
if ctx == nil {
t.Error("Received nil context in completion func")
} else if ctx.Value("testKey") != "123" {
t.Error("Received invalid context")
}
return nil, ShellCompDirectiveDefault
}
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
childCmd := &Command{
Use: "childCmd",
ValidArgsFunction: validArgsFunc,
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
//nolint:golint,staticcheck // We can safely use a basic type as key in tests.
ctx := context.WithValue(context.Background(), "testKey", "123")
// Test completing an empty string on the childCmd
_, output, err := executeCommandWithContextC(ctx, rootCmd, ShellCompNoDescRequestCmd, "childCmd", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestValidArgsFuncSingleCmd(t *testing.T) {
rootCmd := &Command{
Use: "root",
ValidArgsFunction: validArgsFunc,
Run: emptyRun,
}
// Test completing an empty string
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"one",
"two",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check completing with a prefix
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"two",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestValidArgsFuncSingleCmdInvalidArg(t *testing.T) {
rootCmd := &Command{
Use: "root",
// If we don't specify a value for Args, this test fails.
// This is only true for a root command without any subcommands, and is caused
// by the fact that the __complete command becomes a subcommand when there should not be one.
// The problem is in the implementation of legacyArgs().
Args: MinimumNArgs(1),
ValidArgsFunction: validArgsFunc,
Run: emptyRun,
}
// Check completing with wrong number of args
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "unexpectedArg", "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestValidArgsFuncChildCmds(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
child1Cmd := &Command{
Use: "child1",
ValidArgsFunction: validArgsFunc,
Run: emptyRun,
}
child2Cmd := &Command{
Use: "child2",
ValidArgsFunction: validArgsFunc2,
Run: emptyRun,
}
rootCmd.AddCommand(child1Cmd, child2Cmd)
// Test completion of first sub-command with empty argument
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"one",
"two",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test completion of first sub-command with a prefix to complete
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"two",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check completing with wrong number of args
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "unexpectedArg", "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test completion of second sub-command with empty argument
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"three",
"four",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"three",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check completing with wrong number of args
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "unexpectedArg", "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestValidArgsFuncAliases(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
child := &Command{
Use: "child",
Aliases: []string{"son", "daughter"},
ValidArgsFunction: validArgsFunc,
Run: emptyRun,
}
rootCmd.AddCommand(child)
// Test completion of first sub-command with empty argument
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "son", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"one",
"two",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test completion of first sub-command with a prefix to complete
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "daughter", "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"two",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check completing with wrong number of args
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "son", "unexpectedArg", "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestValidArgsFuncInBashScript(t *testing.T) {
rootCmd := &Command{
Use: "root",
Args: NoArgs,
// Set the legacy BashCompletionFunction to force the
// use of bash completion V1
BashCompletionFunction: bashCompletionFunc,
Run: emptyRun,
}
child := &Command{
Use: "child",
ValidArgsFunction: validArgsFunc,
BashCompletionFunction: bashCompletionFunc,
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,
// Set the legacy BashCompletionFunction to force the
// use of bash completion V1
BashCompletionFunction: bashCompletionFunc,
Run: emptyRun,
}
child := &Command{
Use: "child",
Run: emptyRun,
}
rootCmd.AddCommand(child)
buf := new(bytes.Buffer)
assertNoErr(t, rootCmd.GenBashCompletion(buf))
output := buf.String()
checkOmit(t, output, "has_completion_function=1")
}
func TestCompleteCmdInBashScript(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
child := &Command{
Use: "child",
ValidArgsFunction: validArgsFunc,
Run: emptyRun,
}
rootCmd.AddCommand(child)
buf := new(bytes.Buffer)
assertNoErr(t, rootCmd.GenBashCompletion(buf))
output := buf.String()
check(t, output, ShellCompNoDescRequestCmd)
}
func TestCompleteNoDesCmdInZshScript(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
child := &Command{
Use: "child",
ValidArgsFunction: validArgsFunc,
Run: emptyRun,
}
rootCmd.AddCommand(child)
buf := new(bytes.Buffer)
assertNoErr(t, rootCmd.GenZshCompletionNoDesc(buf))
output := buf.String()
check(t, output, ShellCompNoDescRequestCmd)
}
func TestCompleteCmdInZshScript(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
child := &Command{
Use: "child",
ValidArgsFunction: validArgsFunc,
Run: emptyRun,
}
rootCmd.AddCommand(child)
buf := new(bytes.Buffer)
assertNoErr(t, rootCmd.GenZshCompletion(buf))
output := buf.String()
check(t, output, ShellCompRequestCmd)
checkOmit(t, output, ShellCompNoDescRequestCmd)
}
func TestFlagCompletionInGo(t *testing.T) {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
assertNoErr(t, rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
completions := []string{}
for _, comp := range []string{"1\tThe first", "2\tThe second", "10\tThe tenth"} {
if strings.HasPrefix(comp, toComplete) {
completions = append(completions, comp)
}
}
return completions, ShellCompDirectiveDefault
}))
rootCmd.Flags().String("filename", "", "Enter a filename")
assertNoErr(t, rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
completions := []string{}
for _, comp := range []string{"file.yaml\tYAML format", "myfile.json\tJSON format", "file.xml\tXML format"} {
if strings.HasPrefix(comp, toComplete) {
completions = append(completions, comp)
}
}
return completions, ShellCompDirectiveNoSpace | ShellCompDirectiveNoFileComp
}))
// Test completing an empty string
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"1",
"2",
"10",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check completing with a prefix
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "1")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"1",
"10",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test completing an empty string
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--filename", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"file.yaml",
"myfile.json",
"file.xml",
":6",
"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check completing with a prefix
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--filename", "f")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"file.yaml",
"file.xml",
":6",
"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestValidArgsFuncChildCmdsWithDesc(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
child1Cmd := &Command{
Use: "child1",
ValidArgsFunction: validArgsFunc,
Run: emptyRun,
}
child2Cmd := &Command{
Use: "child2",
ValidArgsFunction: validArgsFunc2,
Run: emptyRun,
}
rootCmd.AddCommand(child1Cmd, child2Cmd)
// Test completion of first sub-command with empty argument
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child1", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"one\tThe first",
"two\tThe second",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test completion of first sub-command with a prefix to complete
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"two\tThe second",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check completing with wrong number of args
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "unexpectedArg", "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test completion of second sub-command with empty argument
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"three\tThe third",
"four\tThe fourth",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"three\tThe third",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check completing with wrong number of args
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "unexpectedArg", "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestFlagCompletionWithNotInterspersedArgs(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
childCmd := &Command{
Use: "child",
Run: emptyRun,
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"--validarg", "test"}, ShellCompDirectiveDefault
},
}
childCmd2 := &Command{
Use: "child2",
Run: emptyRun,
ValidArgs: []string{"arg1", "arg2"},
}
rootCmd.AddCommand(childCmd, childCmd2)
childCmd.Flags().Bool("bool", false, "test bool flag")
childCmd.Flags().String("string", "", "test string flag")
_ = childCmd.RegisterFlagCompletionFunc("string", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"myval"}, ShellCompDirectiveDefault
})
// Test flag completion with no argument
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "--")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"--bool\ttest bool flag",
"--help\thelp for child",
"--string\ttest string flag",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that no flags are completed after the -- arg
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "-")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--validarg",
"test",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that no flags are completed after the -- arg with a flag set
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--bool", "--", "-")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--validarg",
"test",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// set Interspersed to false which means that no flags should be completed after the first arg
childCmd.Flags().SetInterspersed(false)
// Test that no flags are completed after the first arg
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "arg", "--")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--validarg",
"test",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that no flags are completed after the fist arg with a flag set
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--string", "t", "arg", "--")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--validarg",
"test",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check that args are still completed after --
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--validarg",
"test",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check that args are still completed even if flagname with ValidArgsFunction exists
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--string", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--validarg",
"test",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check that args are still completed even if flagname with ValidArgsFunction exists
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "--", "a")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"arg1",
"arg2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check that --validarg is not parsed as flag after --
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--validarg", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--validarg",
"test",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check that --validarg is not parsed as flag after an arg
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "arg", "--validarg", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--validarg",
"test",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check that --validarg is added to args for the ValidArgsFunction
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return args, ShellCompDirectiveDefault
}
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--validarg", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--validarg",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check that --validarg is added to args for the ValidArgsFunction and toComplete is also set correctly
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return append(args, toComplete), ShellCompDirectiveDefault
}
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--validarg", "--toComp=ab")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"--validarg",
"--toComp=ab",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestFlagCompletionWorksRootCommandAddedAfterFlags(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
childCmd := &Command{
Use: "child",
Run: emptyRun,
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"--validarg", "test"}, ShellCompDirectiveDefault
},
}
childCmd.Flags().Bool("bool", false, "test bool flag")
childCmd.Flags().String("string", "", "test string flag")
_ = childCmd.RegisterFlagCompletionFunc("string", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"myval"}, ShellCompDirectiveDefault
})
// Important: This is a test for https://github.com/spf13/cobra/issues/1437
// Only add the subcommand after RegisterFlagCompletionFunc was called, do not change this order!
rootCmd.AddCommand(childCmd)
// Test that flag completion works for the subcmd
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "--string", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"myval",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestFlagCompletionInGoWithDesc(t *testing.T) {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
assertNoErr(t, rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
completions := []string{}
for _, comp := range []string{"1\tThe first", "2\tThe second", "10\tThe tenth"} {
if strings.HasPrefix(comp, toComplete) {
completions = append(completions, comp)
}
}
return completions, ShellCompDirectiveDefault
}))
rootCmd.Flags().String("filename", "", "Enter a filename")
assertNoErr(t, rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
completions := []string{}
for _, comp := range []string{"file.yaml\tYAML format", "myfile.json\tJSON format", "file.xml\tXML format"} {
if strings.HasPrefix(comp, toComplete) {
completions = append(completions, comp)
}
}
return completions, ShellCompDirectiveNoSpace | ShellCompDirectiveNoFileComp
}))
// Test completing an empty string
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"1\tThe first",
"2\tThe second",
"10\tThe tenth",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check completing with a prefix
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "1")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"1\tThe first",
"10\tThe tenth",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test completing an empty string
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--filename", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"file.yaml\tYAML format",
"myfile.json\tJSON format",
"file.xml\tXML format",
":6",
"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check completing with a prefix
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--filename", "f")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"file.yaml\tYAML format",
"file.xml\tXML format",
":6",
"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestValidArgsNotValidArgsFunc(t *testing.T) {
rootCmd := &Command{
Use: "root",
ValidArgs: []string{"one", "two"},
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"three", "four"}, ShellCompDirectiveNoFileComp
},
Run: emptyRun,
}
// Test that if both ValidArgs and ValidArgsFunction are present
// only ValidArgs is considered
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"one",
"two",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Check completing with a prefix
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"two",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func 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 no completion command is created if there are not other sub-commands
assertNoErr(t, rootCmd.Execute())
for _, cmd := range rootCmd.commands {
if cmd.Name() == compCmdName {
t.Errorf("Should not have a 'completion' command when there are no other sub-commands of root")
break
}
}
subCmd := &Command{
Use: "sub",
Run: emptyRun,
}
rootCmd.AddCommand(subCmd)
// Test that a completion command is created if there are other sub-commands
found := false
assertNoErr(t, rootCmd.Execute())
for _, cmd := range rootCmd.commands {
if cmd.Name() == compCmdName {
found = true
break
}
}
if !found {
t.Errorf("Should have a 'completion' command when there are other sub-commands of root")
}
// Remove completion command for the next test
removeCompCmd(rootCmd)
// Test that the default completion command can be disabled
rootCmd.CompletionOptions.DisableDefaultCmd = true
assertNoErr(t, rootCmd.Execute())
for _, cmd := range rootCmd.commands {
if cmd.Name() == compCmdName {
t.Errorf("Should not have a 'completion' command when the feature is disabled")
break
}
}
// Re-enable for next test
rootCmd.CompletionOptions.DisableDefaultCmd = false
// Test that completion descriptions are enabled by default
output, err := executeCommand(rootCmd, compCmdName, "zsh")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
check(t, output, ShellCompRequestCmd)
checkOmit(t, output, ShellCompNoDescRequestCmd)
// Remove completion command for the next test
removeCompCmd(rootCmd)
// Test that completion descriptions can be disabled completely
rootCmd.CompletionOptions.DisableDescriptions = true
output, err = executeCommand(rootCmd, compCmdName, "zsh")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
check(t, output, ShellCompNoDescRequestCmd)
// Re-enable for next test
rootCmd.CompletionOptions.DisableDescriptions = false
// Remove completion command for the next test
removeCompCmd(rootCmd)
var compCmd *Command
// Test that the --no-descriptions flag is present on all shells
assertNoErr(t, rootCmd.Execute())
for _, shell := range []string{"bash", "fish", "powershell", "zsh"} {
if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag == nil {
t.Errorf("Missing --%s flag for %s shell", compCmdNoDescFlagName, shell)
}
}
// Remove completion command for the next test
removeCompCmd(rootCmd)
// Test that the '--no-descriptions' flag can be disabled
rootCmd.CompletionOptions.DisableNoDescFlag = true
assertNoErr(t, rootCmd.Execute())
for _, shell := range []string{"fish", "zsh", "bash", "powershell"} {
if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag != nil {
t.Errorf("Unexpected --%s flag for %s shell", compCmdNoDescFlagName, shell)
}
}
// Re-enable for next test
rootCmd.CompletionOptions.DisableNoDescFlag = false
// Remove completion command for the next test
removeCompCmd(rootCmd)
// Test that the '--no-descriptions' flag is disabled when descriptions are disabled
rootCmd.CompletionOptions.DisableDescriptions = true
assertNoErr(t, rootCmd.Execute())
for _, shell := range []string{"fish", "zsh", "bash", "powershell"} {
if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag != nil {
t.Errorf("Unexpected --%s flag for %s shell", compCmdNoDescFlagName, shell)
}
}
// Re-enable for next test
rootCmd.CompletionOptions.DisableDescriptions = false
// Remove completion command for the next test
removeCompCmd(rootCmd)
// Test that the 'completion' command can be hidden
rootCmd.CompletionOptions.HiddenDefaultCmd = true
assertNoErr(t, rootCmd.Execute())
compCmd, _, err = rootCmd.Find([]string{compCmdName})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if compCmd.Hidden == false {
t.Error("Default 'completion' command should be hidden but it is not")
}
// Re-enable for next test
rootCmd.CompletionOptions.HiddenDefaultCmd = false
// Remove completion command for the next test
removeCompCmd(rootCmd)
}
func TestCompleteCompletion(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
subCmd := &Command{
Use: "sub",
Run: emptyRun,
}
rootCmd.AddCommand(subCmd)
// Test sub-commands of the completion command
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"bash",
"fish",
"powershell",
"zsh",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test there are no completions for the sub-commands of the completion command
var compCmd *Command
for _, cmd := range rootCmd.Commands() {
if cmd.Name() == compCmdName {
compCmd = cmd
break
}
}
for _, shell := range compCmd.Commands() {
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, compCmdName, shell.Name(), "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
}
func TestMultipleShorthandFlagCompletion(t *testing.T) {
rootCmd := &Command{
Use: "root",
ValidArgs: []string{"foo", "bar"},
Run: emptyRun,
}
f := rootCmd.Flags()
f.BoolP("short", "s", false, "short flag 1")
f.BoolP("short2", "d", false, "short flag 2")
f.StringP("short3", "f", "", "short flag 3")
_ = rootCmd.RegisterFlagCompletionFunc("short3", func(*Command, []string, string) ([]string, ShellCompDirective) {
return []string{"works"}, ShellCompDirectiveNoFileComp
})
// Test that a single shorthand flag works
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"foo",
"bar",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that multiple boolean shorthand flags work
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sd", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"foo",
"bar",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that multiple boolean + string shorthand flags work
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"works",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that multiple boolean + string with equal sign shorthand flags work
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf=")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"works",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that multiple boolean + string with equal sign with value shorthand flags work
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf=abc", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"foo",
"bar",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestCompleteWithDisableFlagParsing(t *testing.T) {
flagValidArgs := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"--flag", "-f"}, ShellCompDirectiveNoFileComp
}
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
childCmd := &Command{
Use: "child",
Run: emptyRun,
DisableFlagParsing: true,
ValidArgsFunction: flagValidArgs,
}
rootCmd.AddCommand(childCmd)
rootCmd.PersistentFlags().StringP("persistent", "p", "", "persistent flag")
childCmd.Flags().StringP("nonPersistent", "n", "", "non-persistent flag")
// Test that when DisableFlagParsing==true, ValidArgsFunction is called to complete flag names,
// after Cobra tried to complete the flags it knows about.
childCmd.DisableFlagParsing = true
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child", "-")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"--persistent",
"-p",
"--help",
"-h",
"--nonPersistent",
"-n",
"--flag",
"-f",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that when DisableFlagParsing==false, Cobra completes the flags itself and ValidArgsFunction is not called
childCmd.DisableFlagParsing = false
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child", "-")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Cobra was not told of any flags, so it returns nothing
expected = strings.Join([]string{
"--persistent",
"-p",
"--help",
"-h",
"--nonPersistent",
"-n",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestCompleteWithRootAndLegacyArgs(t *testing.T) {
// Test a lonely root command which uses legacyArgs(). In such a case, the root
// command should accept any number of arguments and completion should behave accordingly.
rootCmd := &Command{
Use: "root",
Args: nil, // Args must be nil to trigger the legacyArgs() function
Run: emptyRun,
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"arg1", "arg2"}, ShellCompDirectiveNoFileComp
},
}
// Make sure the first arg is completed
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"arg1",
"arg2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Make sure the completion of arguments continues
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "arg1", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"arg1",
"arg2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestFixedCompletions(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
choices := []string{"apple", "banana", "orange"}
childCmd := &Command{
Use: "child",
ValidArgsFunction: FixedCompletions(choices, ShellCompDirectiveNoFileComp),
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child", "a")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"apple",
"banana",
"orange",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
func TestCompletionForGroupedFlags(t *testing.T) {
getCmd := func() *Command {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
childCmd := &Command{
Use: "child",
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"subArg"}, ShellCompDirectiveNoFileComp
},
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
rootCmd.PersistentFlags().Int("ingroup1", -1, "ingroup1")
rootCmd.PersistentFlags().String("ingroup2", "", "ingroup2")
childCmd.Flags().Bool("ingroup3", false, "ingroup3")
childCmd.Flags().Bool("nogroup", false, "nogroup")
// Add flags to a group
childCmd.MarkFlagsRequiredTogether("ingroup1", "ingroup2", "ingroup3")
return rootCmd
}
// Each test case uses a unique command from the function above.
testcases := []struct {
desc string
args []string
expectedOutput string
}{
{
desc: "flags in group not suggested without - prefix",
args: []string{"child", ""},
expectedOutput: strings.Join([]string{
"subArg",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "flags in group suggested with - prefix",
args: []string{"child", "-"},
expectedOutput: strings.Join([]string{
"--ingroup1",
"--ingroup2",
"--help",
"-h",
"--ingroup3",
"--nogroup",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "when flag in group present, other flags in group suggested even without - prefix",
args: []string{"child", "--ingroup2", "value", ""},
expectedOutput: strings.Join([]string{
"--ingroup1",
"--ingroup3",
"subArg",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "when all flags in group present, flags not suggested without - prefix",
args: []string{"child", "--ingroup1", "8", "--ingroup2", "value2", "--ingroup3", ""},
expectedOutput: strings.Join([]string{
"subArg",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "group ignored if some flags not applicable",
args: []string{"--ingroup2", "value", ""},
expectedOutput: strings.Join([]string{
"child",
"completion",
"help",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
}
for _, tc := range testcases {
t.Run(tc.desc, func(t *testing.T) {
c := getCmd()
args := []string{ShellCompNoDescRequestCmd}
args = append(args, tc.args...)
output, err := executeCommand(c, args...)
switch {
case err == nil && output != tc.expectedOutput:
t.Errorf("expected: %q, got: %q", tc.expectedOutput, output)
case err != nil:
t.Errorf("Unexpected error %q", err)
}
})
}
}
func TestCompletionForMutuallyExclusiveFlags(t *testing.T) {
getCmd := func() *Command {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
childCmd := &Command{
Use: "child",
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"subArg"}, ShellCompDirectiveNoFileComp
},
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
rootCmd.PersistentFlags().IntSlice("ingroup1", []int{1}, "ingroup1")
rootCmd.PersistentFlags().String("ingroup2", "", "ingroup2")
childCmd.Flags().Bool("ingroup3", false, "ingroup3")
childCmd.Flags().Bool("nogroup", false, "nogroup")
// Add flags to a group
childCmd.MarkFlagsMutuallyExclusive("ingroup1", "ingroup2", "ingroup3")
return rootCmd
}
// Each test case uses a unique command from the function above.
testcases := []struct {
desc string
args []string
expectedOutput string
}{
{
desc: "flags in mutually exclusive group not suggested without the - prefix",
args: []string{"child", ""},
expectedOutput: strings.Join([]string{
"subArg",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "flags in mutually exclusive group suggested with the - prefix",
args: []string{"child", "-"},
expectedOutput: strings.Join([]string{
"--ingroup1",
"--ingroup2",
"--help",
"-h",
"--ingroup3",
"--nogroup",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "when flag in mutually exclusive group present, other flags in group not suggested even with the - prefix",
args: []string{"child", "--ingroup1", "8", "-"},
expectedOutput: strings.Join([]string{
"--ingroup1", // Should be suggested again since it is a slice
"--help",
"-h",
"--nogroup",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "group ignored if some flags not applicable",
args: []string{"--ingroup1", "8", "-"},
expectedOutput: strings.Join([]string{
"--help",
"-h",
"--ingroup1",
"--ingroup2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
}
for _, tc := range testcases {
t.Run(tc.desc, func(t *testing.T) {
c := getCmd()
args := []string{ShellCompNoDescRequestCmd}
args = append(args, tc.args...)
output, err := executeCommand(c, args...)
switch {
case err == nil && output != tc.expectedOutput:
t.Errorf("expected: %q, got: %q", tc.expectedOutput, output)
case err != nil:
t.Errorf("Unexpected error %q", err)
}
})
}
}
func TestCompletionCobraFlags(t *testing.T) {
getCmd := func() *Command {
rootCmd := &Command{
Use: "root",
Version: "1.1.1",
Run: emptyRun,
}
childCmd := &Command{
Use: "child",
Version: "1.1.1",
Run: emptyRun,
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"extra"}, ShellCompDirectiveNoFileComp
},
}
childCmd2 := &Command{
Use: "child2",
Version: "1.1.1",
Run: emptyRun,
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"extra2"}, ShellCompDirectiveNoFileComp
},
}
childCmd3 := &Command{
Use: "child3",
Version: "1.1.1",
Run: emptyRun,
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"extra3"}, ShellCompDirectiveNoFileComp
},
}
rootCmd.AddCommand(childCmd, childCmd2, childCmd3)
_ = childCmd.Flags().Bool("bool", false, "A bool flag")
_ = childCmd.MarkFlagRequired("bool")
// Have a command that adds its own help and version flag
_ = childCmd2.Flags().BoolP("help", "h", false, "My own help")
_ = childCmd2.Flags().BoolP("version", "v", false, "My own version")
// Have a command that only adds its own -v flag
_ = childCmd3.Flags().BoolP("verbose", "v", false, "Not a version flag")
return rootCmd
}
// Each test case uses a unique command from the function above.
testcases := []struct {
desc string
args []string
expectedOutput string
}{
{
desc: "completion of help and version flags",
args: []string{"-"},
expectedOutput: strings.Join([]string{
"--help",
"-h",
"--version",
"-v",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "no completion after --help flag",
args: []string{"--help", ""},
expectedOutput: strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "no completion after -h flag",
args: []string{"-h", ""},
expectedOutput: strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "no completion after --version flag",
args: []string{"--version", ""},
expectedOutput: strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "no completion after -v flag",
args: []string{"-v", ""},
expectedOutput: strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "no completion after --help flag even with other completions",
args: []string{"child", "--help", ""},
expectedOutput: strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "no completion after -h flag even with other completions",
args: []string{"child", "-h", ""},
expectedOutput: strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "no completion after --version flag even with other completions",
args: []string{"child", "--version", ""},
expectedOutput: strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "no completion after -v flag even with other completions",
args: []string{"child", "-v", ""},
expectedOutput: strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "no completion after -v flag even with other flag completions",
args: []string{"child", "-v", "-"},
expectedOutput: strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "completion after --help flag when created by program",
args: []string{"child2", "--help", ""},
expectedOutput: strings.Join([]string{
"extra2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "completion after -h flag when created by program",
args: []string{"child2", "-h", ""},
expectedOutput: strings.Join([]string{
"extra2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "completion after --version flag when created by program",
args: []string{"child2", "--version", ""},
expectedOutput: strings.Join([]string{
"extra2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "completion after -v flag when created by program",
args: []string{"child2", "-v", ""},
expectedOutput: strings.Join([]string{
"extra2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "completion after --version when only -v flag was created by program",
args: []string{"child3", "--version", ""},
expectedOutput: strings.Join([]string{
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "completion after -v flag when only -v flag was created by program",
args: []string{"child3", "-v", ""},
expectedOutput: strings.Join([]string{
"extra3",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
}
for _, tc := range testcases {
t.Run(tc.desc, func(t *testing.T) {
c := getCmd()
args := []string{ShellCompNoDescRequestCmd}
args = append(args, tc.args...)
output, err := executeCommand(c, args...)
switch {
case err == nil && output != tc.expectedOutput:
t.Errorf("expected: %q, got: %q", tc.expectedOutput, output)
case err != nil:
t.Errorf("Unexpected error %q", err)
}
})
}
}