Validate shell completion across supported shells

This commit is contained in:
2026-11-03 04:16:39 +00:00
parent 96250d1887
commit 0450ecfc16
6 changed files with 207 additions and 29 deletions

93
completion_shell_test.go Normal file
View File

@@ -0,0 +1,93 @@
package commandkit
import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
"testing"
)
func TestCompletionConsumerInShells(t *testing.T) {
if os.Getenv("COMMANDKIT_COMPLETION_SHELLS") != "1" {
t.Skip("set COMMANDKIT_COMPLETION_SHELLS=1 to run shell completion checks")
}
dir := t.TempDir()
app := filepath.Join(dir, "probe")
build := exec.Command("go", "build", "-o", app, "./testdata/completion")
if output, err := build.CombinedOutput(); err != nil {
t.Fatalf("build completion fixture: %v\n%s", err, output)
}
if err := os.Mkdir(filepath.Join(dir, "partial dir"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "partial file"), nil, 0o644); err != nil {
t.Fatal(err)
}
tests := []struct {
name, line string
want []string
directory bool
}{
{"canonical command", "probe ru", []string{"run"}, false},
{"entered alias", "probe shortcut --format=ya", []string{"shortcut", "--format=yaml"}, false},
{"choice", "probe run --format=te", []string{"run", "--format=text"}, false},
{"no file fallback", `probe run --format=./partial\ f`, []string{"run", "--format=./partial f"}, false},
{"separated file", `probe run --config ./partial\ f`, []string{"run", "--config", "./partial file"}, false},
{"attached file", `probe run --config=./partial\ f`, []string{"run", "--config=./partial file"}, false},
{"short attached file", `probe run -c./partial\ f`, []string{"run", "-c./partial file"}, false},
{"short cluster equals file", `probe run -sc=./partial\ f`, []string{"run", "-sc=./partial file"}, false},
{"separated directory", "probe run --directory ./partial", []string{"run", "--directory", "./partial dir"}, true},
{"attached directory", "probe run --directory=./partial", []string{"run", "--directory=./partial dir"}, true},
{"empty operand", "probe run ", []string{"run", "alpha"}, false},
{"leading dash", "probe run --for", []string{"run", "--format"}, false},
}
for _, shell := range []string{"bash", "zsh", "fish"} {
generator := exec.Command(app, "completion", shell)
script, err := generator.CombinedOutput()
if err != nil {
t.Fatalf("generate %s script: %v\n%s", shell, err, script)
}
scriptPath := filepath.Join(dir, shell+".completion")
if err := os.WriteFile(scriptPath, script, 0o600); err != nil {
t.Fatal(err)
}
for _, tc := range tests {
t.Run(shell+"/"+tc.name, func(t *testing.T) {
log := filepath.Join(dir, "log")
if err := os.WriteFile(log, nil, 0o600); err != nil {
t.Fatal(err)
}
terminal := exec.Command("python3", "internal/cobra/testdata/completion_terminal.py", shell, scriptPath, tc.line)
terminal.Env = append(os.Environ(), "PATH="+dir+string(os.PathListSeparator)+os.Getenv("PATH"), "HOME="+dir, "TERM=xterm", "PROBE_LOG="+log, "PROBE_DIR="+dir)
if output, err := terminal.CombinedOutput(); err != nil {
t.Fatalf("shell completion: %v\n%s", err, output)
}
content, err := os.ReadFile(log)
if err != nil {
t.Fatal(err)
}
var got []string
if err := json.Unmarshal(content, &got); err != nil {
t.Fatalf("fixture output %q: %v", content, err)
}
if tc.directory && len(got) > 0 {
got[len(got)-1] = strings.TrimSuffix(got[len(got)-1], "/")
}
if !reflect.DeepEqual(got, tc.want) {
t.Fatalf("edited arguments=%q, want %q", got, tc.want)
}
})
}
t.Run(shell+"/descriptions", func(t *testing.T) {
terminal := exec.Command("python3", "internal/cobra/testdata/completion_terminal.py", shell, scriptPath, "probe run --format=")
terminal.Env = append(os.Environ(), "PATH="+dir+string(os.PathListSeparator)+os.Getenv("PATH"), "HOME="+dir, "TERM=xterm", "PROBE_LOG="+filepath.Join(dir, "log"), "PROBE_DIR="+dir, "PROBE_LIST=1")
output, err := terminal.CombinedOutput()
if err != nil || !strings.Contains(string(output), "Readable text") || !strings.Contains(string(output), "YAML output") {
t.Fatalf("completion descriptions: %v\n%s", err, output)
}
})
}
}