1
16
17 package plugin
18
19 import (
20 "fmt"
21 "os"
22 "path/filepath"
23 "reflect"
24 "strings"
25 "testing"
26
27 "k8s.io/cli-runtime/pkg/genericiooptions"
28 )
29
30 func TestPluginPathsAreUnaltered(t *testing.T) {
31 tempDir, err := os.MkdirTemp(os.TempDir(), "test-cmd-plugins")
32 if err != nil {
33 t.Fatalf("unexpected error: %v", err)
34 }
35 tempDir2, err := os.MkdirTemp(os.TempDir(), "test-cmd-plugins2")
36 if err != nil {
37 t.Fatalf("unexpected error: %v", err)
38 }
39
40 defer func() {
41 if err := os.RemoveAll(tempDir); err != nil {
42 panic(fmt.Errorf("unexpected cleanup error: %v", err))
43 }
44 if err := os.RemoveAll(tempDir2); err != nil {
45 panic(fmt.Errorf("unexpected cleanup error: %v", err))
46 }
47 }()
48
49 ioStreams, _, _, errOut := genericiooptions.NewTestIOStreams()
50 verifier := newFakePluginPathVerifier()
51 pluginPaths := []string{tempDir, tempDir2}
52 o := &PluginListOptions{
53 Verifier: verifier,
54 IOStreams: ioStreams,
55
56 PluginPaths: pluginPaths,
57 }
58
59
60 if _, err := os.CreateTemp(tempDir, "kubectl-"); err != nil {
61 t.Fatalf("unexpected error %v", err)
62 }
63 if _, err := os.CreateTemp(tempDir2, "kubectl-"); err != nil {
64 t.Fatalf("unexpected error %v", err)
65 }
66
67 if err := o.Run(); err != nil {
68 t.Fatalf("unexpected error %v - %v", err, errOut.String())
69 }
70
71
72 if len(verifier.seenUnsorted) != len(pluginPaths) {
73 t.Fatalf("saw unexpected plugin paths. Expecting %v, got %v", pluginPaths, verifier.seenUnsorted)
74 }
75 for actual := range verifier.seenUnsorted {
76 if !strings.HasPrefix(verifier.seenUnsorted[actual], pluginPaths[actual]) {
77 t.Fatalf("expected PATH slice to be unaltered. Expecting %v, but got %v", pluginPaths[actual], verifier.seenUnsorted[actual])
78 }
79 }
80 }
81
82 func TestPluginPathsAreValid(t *testing.T) {
83 tempDir, err := os.MkdirTemp(os.TempDir(), "test-cmd-plugins")
84 if err != nil {
85 t.Fatalf("unexpected error: %v", err)
86 }
87
88 defer func() {
89 if err := os.RemoveAll(tempDir); err != nil {
90 panic(fmt.Errorf("unexpected cleanup error: %v", err))
91 }
92 }()
93
94 tc := []struct {
95 name string
96 pluginPaths []string
97 pluginFile func() (*os.File, error)
98 verifier *fakePluginPathVerifier
99 expectVerifyErrors []error
100 expectErr string
101 expectErrOut string
102 expectOut string
103 }{
104 {
105 name: "ensure no plugins found if no files begin with kubectl- prefix",
106 pluginPaths: []string{tempDir},
107 verifier: newFakePluginPathVerifier(),
108 pluginFile: func() (*os.File, error) {
109 return os.CreateTemp(tempDir, "notkubectl-")
110 },
111 expectErr: "error: unable to find any kubectl plugins in your PATH\n",
112 },
113 {
114 name: "ensure de-duplicated plugin-paths slice",
115 pluginPaths: []string{tempDir, tempDir},
116 verifier: newFakePluginPathVerifier(),
117 pluginFile: func() (*os.File, error) {
118 return os.CreateTemp(tempDir, "kubectl-")
119 },
120 expectOut: "The following compatible plugins are available:",
121 },
122 {
123 name: "ensure no errors when empty string or blank path are specified",
124 pluginPaths: []string{tempDir, "", " "},
125 verifier: newFakePluginPathVerifier(),
126 pluginFile: func() (*os.File, error) {
127 return os.CreateTemp(tempDir, "kubectl-")
128 },
129 expectOut: "The following compatible plugins are available:",
130 },
131 }
132
133 for _, test := range tc {
134 t.Run(test.name, func(t *testing.T) {
135 ioStreams, _, out, errOut := genericiooptions.NewTestIOStreams()
136 o := &PluginListOptions{
137 Verifier: test.verifier,
138 IOStreams: ioStreams,
139
140 PluginPaths: test.pluginPaths,
141 }
142
143
144 if test.pluginFile != nil {
145 if _, err := test.pluginFile(); err != nil {
146 t.Fatalf("unexpected error creating plugin file: %v", err)
147 }
148 }
149
150 for _, expected := range test.expectVerifyErrors {
151 for _, actual := range test.verifier.errors {
152 if expected != actual {
153 t.Fatalf("unexpected error: expected %v, but got %v", expected, actual)
154 }
155 }
156 }
157
158 err := o.Run()
159 if err == nil && len(test.expectErr) > 0 {
160 t.Fatalf("unexpected non-error: expected %v, but got nothing", test.expectErr)
161 } else if err != nil && len(test.expectErr) == 0 {
162 t.Fatalf("unexpected error: expected nothing, but got %v", err.Error())
163 } else if err != nil && err.Error() != test.expectErr {
164 t.Fatalf("unexpected error: expected %v, but got %v", test.expectErr, err.Error())
165 }
166
167 if len(test.expectErrOut) == 0 && errOut.Len() > 0 {
168 t.Fatalf("unexpected error output: expected nothing, but got %v", errOut.String())
169 } else if len(test.expectErrOut) > 0 && !strings.Contains(errOut.String(), test.expectErrOut) {
170 t.Fatalf("unexpected error output: expected to contain %v, but got %v", test.expectErrOut, errOut.String())
171 }
172
173 if len(test.expectOut) == 0 && out.Len() > 0 {
174 t.Fatalf("unexpected output: expected nothing, but got %v", out.String())
175 } else if len(test.expectOut) > 0 && !strings.Contains(out.String(), test.expectOut) {
176 t.Fatalf("unexpected output: expected to contain %v, but got %v", test.expectOut, out.String())
177 }
178 })
179 }
180 }
181
182 func TestListPlugins(t *testing.T) {
183 pluginPath, _ := filepath.Abs("./testdata")
184 expectPlugins := []string{
185 filepath.Join(pluginPath, "kubectl-create-foo"),
186 filepath.Join(pluginPath, "kubectl-foo"),
187 filepath.Join(pluginPath, "kubectl-version"),
188 }
189
190 verifier := newFakePluginPathVerifier()
191 ioStreams, _, _, _ := genericiooptions.NewTestIOStreams()
192 pluginPaths := []string{pluginPath}
193
194 o := &PluginListOptions{
195 Verifier: verifier,
196 IOStreams: ioStreams,
197
198 PluginPaths: pluginPaths,
199 }
200
201 plugins, errs := o.ListPlugins()
202 if len(errs) > 0 {
203 t.Fatalf("unexpected errors: %v", errs)
204 }
205
206 if !reflect.DeepEqual(expectPlugins, plugins) {
207 t.Fatalf("saw unexpected plugins. Expecting %v, got %v", expectPlugins, plugins)
208 }
209 }
210
211 type duplicatePathError struct {
212 path string
213 }
214
215 func (d *duplicatePathError) Error() string {
216 return fmt.Sprintf("path %q already visited", d.path)
217 }
218
219 type fakePluginPathVerifier struct {
220 errors []error
221 seen map[string]bool
222 seenUnsorted []string
223 }
224
225 func (f *fakePluginPathVerifier) Verify(path string) []error {
226 if f.seen[path] {
227 err := &duplicatePathError{path}
228 f.errors = append(f.errors, err)
229 return []error{err}
230 }
231 f.seen[path] = true
232 f.seenUnsorted = append(f.seenUnsorted, path)
233 return nil
234 }
235
236 func newFakePluginPathVerifier() *fakePluginPathVerifier {
237 return &fakePluginPathVerifier{seen: make(map[string]bool)}
238 }
239
View as plain text