Skip to content

Commit 00b68a1

Browse files
authored
Add tests for flag completion registration (#2053)
Different problems have been reported about flag completion registration. These two tests are the cases that were not being verified but had been mentioned as problematic. Ref: - #1320 - #1438 (comment) Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com>
1 parent b711e87 commit 00b68a1

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed

completions_test.go

+110
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ package cobra
1717
import (
1818
"bytes"
1919
"context"
20+
"fmt"
2021
"strings"
22+
"sync"
2123
"testing"
2224
)
2325

@@ -2040,6 +2042,114 @@ func TestFlagCompletionWorksRootCommandAddedAfterFlags(t *testing.T) {
20402042
}
20412043
}
20422044

2045+
func TestFlagCompletionForPersistentFlagsCalledFromSubCmd(t *testing.T) {
2046+
rootCmd := &Command{Use: "root", Run: emptyRun}
2047+
rootCmd.PersistentFlags().String("string", "", "test string flag")
2048+
_ = rootCmd.RegisterFlagCompletionFunc("string", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
2049+
return []string{"myval"}, ShellCompDirectiveDefault
2050+
})
2051+
2052+
childCmd := &Command{
2053+
Use: "child",
2054+
Run: emptyRun,
2055+
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
2056+
return []string{"--validarg", "test"}, ShellCompDirectiveDefault
2057+
},
2058+
}
2059+
childCmd.Flags().Bool("bool", false, "test bool flag")
2060+
rootCmd.AddCommand(childCmd)
2061+
2062+
// Test that persistent flag completion works for the subcmd
2063+
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "--string", "")
2064+
if err != nil {
2065+
t.Errorf("Unexpected error: %v", err)
2066+
}
2067+
2068+
expected := strings.Join([]string{
2069+
"myval",
2070+
":0",
2071+
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
2072+
2073+
if output != expected {
2074+
t.Errorf("expected: %q, got: %q", expected, output)
2075+
}
2076+
}
2077+
2078+
// This test tries to register flag completion concurrently to make sure the
2079+
// code handles concurrency properly.
2080+
// This was reported as a problem when tests are run concurrently:
2081+
// https://github.com/spf13/cobra/issues/1320
2082+
//
2083+
// NOTE: this test can sometimes pass even if the code were to not handle
2084+
// concurrency properly. This is not great but the important part is that
2085+
// it should never fail. Therefore, if the tests fails sometimes, we will
2086+
// still be able to know there is a problem.
2087+
func TestFlagCompletionConcurrentRegistration(t *testing.T) {
2088+
rootCmd := &Command{Use: "root", Run: emptyRun}
2089+
const maxFlags = 50
2090+
for i := 1; i < maxFlags; i += 2 {
2091+
flagName := fmt.Sprintf("flag%d", i)
2092+
rootCmd.Flags().String(flagName, "", fmt.Sprintf("test %s flag on root", flagName))
2093+
}
2094+
2095+
childCmd := &Command{
2096+
Use: "child",
2097+
Run: emptyRun,
2098+
}
2099+
for i := 2; i <= maxFlags; i += 2 {
2100+
flagName := fmt.Sprintf("flag%d", i)
2101+
childCmd.Flags().String(flagName, "", fmt.Sprintf("test %s flag on child", flagName))
2102+
}
2103+
2104+
rootCmd.AddCommand(childCmd)
2105+
2106+
// Register completion in different threads to test concurrency.
2107+
var wg sync.WaitGroup
2108+
for i := 1; i <= maxFlags; i++ {
2109+
index := i
2110+
flagName := fmt.Sprintf("flag%d", i)
2111+
wg.Add(1)
2112+
go func() {
2113+
defer wg.Done()
2114+
cmd := rootCmd
2115+
if index%2 == 0 {
2116+
cmd = childCmd
2117+
}
2118+
_ = cmd.RegisterFlagCompletionFunc(flagName, func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
2119+
return []string{fmt.Sprintf("flag%d", index)}, ShellCompDirectiveDefault
2120+
})
2121+
}()
2122+
}
2123+
2124+
wg.Wait()
2125+
2126+
// Test that flag completion works for each flag
2127+
for i := 1; i <= 6; i++ {
2128+
var output string
2129+
var err error
2130+
flagName := fmt.Sprintf("flag%d", i)
2131+
2132+
if i%2 == 1 {
2133+
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--"+flagName, "")
2134+
} else {
2135+
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--"+flagName, "")
2136+
}
2137+
2138+
if err != nil {
2139+
t.Errorf("Unexpected error: %v", err)
2140+
}
2141+
2142+
expected := strings.Join([]string{
2143+
flagName,
2144+
":0",
2145+
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
2146+
2147+
if output != expected {
2148+
t.Errorf("expected: %q, got: %q", expected, output)
2149+
}
2150+
}
2151+
}
2152+
20432153
func TestFlagCompletionInGoWithDesc(t *testing.T) {
20442154
rootCmd := &Command{
20452155
Use: "root",

0 commit comments

Comments
 (0)