Repository navigation
Expand file tree
/
Copy pathcompletion_shell_test.go
More file actions
154 lines (150 loc) · 6.77 KB
/
Copy pathcompletion_shell_test.go
File metadata and controls
154 lines (150 loc) · 6.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package cli
import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
"github.com/echocat/cli4g/codec"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNativeShellCompletion(t *testing.T) {
if testing.Short() {
t.Skip("builds the completion fixture and runs available native shells")
}
directory := t.TempDir()
name := "cli4g-completion-test"
binary := filepath.Join(directory, name)
if runtime.GOOS == "windows" {
binary += ".exe"
}
build := func(variant, publicCommand string) {
flags := "-X main.variant=" + variant + " -X main.completionCommand=" + publicCommand + " -X main.completionRequestCommand=__suggest"
command := exec.Command("go", "build", "-ldflags", flags, "-o", binary, "./internal/testdata/completion")
output, err := command.CombinedOutput()
require.NoError(t, err, "%s", output)
}
build("initial", "shell-setup")
require.NoError(t, os.WriteFile(filepath.Join(directory, "config.yaml"), nil, 0600))
require.NoError(t, os.WriteFile(filepath.Join(directory, "space file.yaml"), nil, 0600))
if runtime.GOOS != "windows" {
require.NoError(t, os.WriteFile(filepath.Join(directory, "colon:file.yaml"), nil, 0600))
}
app := NewSimpleApplication(name).WithCompletion(true).
WithCompletionCommand(NewCompletionCommand().WithName("shell-setup")).WithCompletionRequestCommandName("__suggest").MustCreate()
defer func() { assert.NoError(t, app.Close()) }()
paths := make(map[CompletionShell]string)
for _, shell := range []CompletionShell{CompletionBash, CompletionNushell} {
path, err := app.InstallCompletion(nil, shell, directory)
require.NoError(t, err)
paths[shell] = path
}
bashDriver := filepath.Join(directory, "complete.bash")
require.NoError(t, os.WriteFile(bashDriver, []byte(`source "$1"
compopt() { :; }
COMP_LINE="$2"
COMP_POINT="$5"
[[ $(complete -p cli4g-completion-test) =~ -F\ ([^\ ]+) ]] || exit 1
"${BASH_REMATCH[1]}" "$3" "$4" ""
if ((${#COMPREPLY[@]})); then printf '%s\0' "${COMPREPLY[@]}"; fi
`), 0600))
assertBash := func(t *testing.T, line, current string, want []string) {
t.Helper()
bash := bashForCompletionTest(t)
command := exec.Command(bash, "--noprofile", "--norc", bashDriver, paths[CompletionBash], line, binary, current, strconv.Itoa(len(line)))
command.Dir = directory
// Exercise %q's C-locale escaping regardless of the host locale. The
// mandatory terminal suite separately covers the UTF-8 locale.
command.Env = append(os.Environ(), "LC_ALL=C")
output, err := command.CombinedOutput()
require.NoError(t, err, "%s", output)
values := []string{}
if len(output) > 0 {
quoted := strings.Split(strings.TrimSuffix(string(output), "\x00"), "\x00")
// These controlled fixture suggestions are shell syntax. Execute them
// through the echo fixture to compare native arguments, not %q's
// locale-dependent spelling (for example über vs $'\303\274ber').
arguments := filepath.Join(directory, "completion-arguments.bash")
require.NoError(t, os.WriteFile(arguments, []byte("exec \"$1\" __echo-args "+strings.Join(quoted, " ")+"\n"), 0600))
command = exec.Command(bash, "--noprofile", "--norc", arguments, binary)
command.Dir = directory
command.Env = append(os.Environ(), "LC_ALL=C")
output, err = command.CombinedOutput()
require.NoError(t, err, "%s", output)
require.NoError(t, json.Unmarshal(output, &values), "%s", output)
}
assert.Equal(t, want, values)
}
assertNu := func(t *testing.T, line string, want []string) {
t.Helper()
if _, err := exec.LookPath("nu"); err != nil {
t.Skip("Nushell is not installed")
}
command := exec.Command("nu", "-n", "-c", "source "+strconv.Quote(paths[CompletionNushell])+"; "+strconv.Quote(line)+" | commandline complete --detailed | to json --raw")
command.Dir = directory
command.Env = append(os.Environ(), "PATH="+directory+string(os.PathListSeparator)+os.Getenv("PATH"))
output, err := command.CombinedOutput()
require.NoError(t, err, "%s", output)
var candidates []codec.CompletionCandidate
require.NoError(t, json.Unmarshal(output, &candidates), "%s", output)
assert.Equal(t, want, completionTexts(codec.CompletionResult{Candidates: candidates}))
}
t.Run("bash", func(t *testing.T) {
assertBash(t, name+" --format=ya", "ya", []string{"yaml"})
assertBash(t, name+" --format ü", "ü", []string{"über"})
assertBash(t, name+" --format 'two wo", "two wo", []string{"two words"})
assertBash(t, name+" --context cluster-b --namespace cl", "cl", []string{"cluster-b-namespace"})
assertBash(t, name+" get po", "po", []string{"pods"})
assertBash(t, name+" --initial", "--initial", []string{"--initial"})
assertBash(t, name+" --format nope", "nope", []string{})
assertBash(t, name+" --input=config", "config", []string{"config.yaml"})
assertBash(t, name+` --input="space fi`, "space fi", []string{"space file.yaml"})
if runtime.GOOS != "windows" {
assertBash(t, name+" --input=colon:fi", "fi", []string{"file.yaml"})
}
})
t.Run("nushell", func(t *testing.T) {
assertNu(t, name+" --format=ya", []string{"--format=yaml"})
assertNu(t, name+" --format ü", []string{"über"})
assertNu(t, name+" --format 'two wo", []string{`"two words"`})
// The inserted representation must still be one literal native argument.
command := exec.Command("nu", "-n", "-c", "^"+strconv.Quote(binary)+` __echo-args "two words"`)
output, err := command.CombinedOutput()
require.NoError(t, err, "%s", output)
var arguments []string
require.NoError(t, json.Unmarshal(output, &arguments))
assert.Equal(t, []string{"two words"}, arguments)
assertNu(t, name+" --context cluster-b --namespace cl", []string{"cluster-b-namespace"})
assertNu(t, name+" get po", []string{"pods"})
assertNu(t, name+" --initial", []string{"--initial"})
assertNu(t, name+" --input=config", []string{"--input=config.yaml"})
assertNu(t, name+` --input="space fi`, []string{"--input=`space file.yaml`"})
})
// Reuse exactly the installed adapters against a replaced executable.
build("updated", "shell-admin")
t.Run("bash_after_binary_update", func(t *testing.T) {
assertBash(t, name+" --up", "--up", []string{"--updated"})
})
t.Run("nushell_after_binary_update", func(t *testing.T) {
assertNu(t, name+" --up", []string{"--updated"})
})
}
func BenchmarkCompletionProcess(b *testing.B) {
path := filepath.Join(b.TempDir(), "completion-test")
if runtime.GOOS == "windows" {
path += ".exe"
}
output, err := exec.Command("go", "build", "-o", path, "./internal/testdata/completion").CombinedOutput()
require.NoError(b, err, "%s", output)
for b.Loop() {
command := exec.Command(path, DefaultCompletionRequestCommandName, "1", "nu")
command.Stdin = strings.NewReader(`{"words":["--format","j"],"word":1,"offset":1}`)
if output, err := command.CombinedOutput(); err != nil {
b.Fatalf("%v: %s", err, output)
}
}
}