package plugin import ( "bytes" "fmt" "io/ioutil" "os" "strings" "testing" "k8s.io/cli-runtime/pkg/genericclioptions" ) func TestPluginPathsAreUnaltered(t *testing.T) { tempDir, err := ioutil.TempDir(os.TempDir(), "test-cmd-plugins") if err != nil { t.Fatalf("unexpected error: %v", err) } tempDir2, err := ioutil.TempDir(os.TempDir(), "test-cmd-plugins2") if err != nil { t.Fatalf("unexpected error: %v", err) } // cleanup defer func() { if err := os.RemoveAll(tempDir); err != nil { panic(fmt.Errorf("unexpected cleanup error: %v", err)) } if err := os.RemoveAll(tempDir2); err != nil { panic(fmt.Errorf("unexpected cleanup error: %v", err)) } }() ioStreams, _, _, errOut := genericclioptions.NewTestIOStreams() verifier := newFakePluginPathVerifier() pluginPaths := []string{tempDir, tempDir2} o := &PluginListOptions{ Verifier: verifier, IOStreams: ioStreams, PluginPaths: pluginPaths, } // write at least one valid plugin file if _, err := ioutil.TempFile(tempDir, "kubectl-"); err != nil { t.Fatalf("unexpected error %v", err) } if _, err := ioutil.TempFile(tempDir2, "kubectl-"); err != nil { t.Fatalf("unexpected error %v", err) } if err := o.Run(); err != nil { t.Fatalf("unexpected error %v - %v", err, errOut.String()) } // ensure original paths remain unaltered if len(verifier.seenUnsorted) != len(pluginPaths) { t.Fatalf("saw unexpected plugin paths. Expecting %v, got %v", pluginPaths, verifier.seenUnsorted) } for actual := range verifier.seenUnsorted { if !strings.HasPrefix(verifier.seenUnsorted[actual], pluginPaths[actual]) { t.Fatalf("expected PATH slice to be unaltered. Expecting %v, but got %v", pluginPaths[actual], verifier.seenUnsorted[actual]) } } } func TestPluginPathsAreValid(t *testing.T) { tempDir, err := ioutil.TempDir(os.TempDir(), "test-cmd-plugins") if err != nil { t.Fatalf("unexpected error: %v", err) } // cleanup defer func() { if err := os.RemoveAll(tempDir); err != nil { panic(fmt.Errorf("unexpected cleanup error: %v", err)) } }() tc := []struct { name string pluginPaths []string pluginFile func() (*os.File, error) verifier *fakePluginPathVerifier expectVerifyErrors []error expectErr string }{ { name: "ensure no plugins found if no files begin with kubectl- prefix", pluginPaths: []string{tempDir}, verifier: newFakePluginPathVerifier(), pluginFile: func() (*os.File, error) { return ioutil.TempFile(tempDir, "notkubectl-") }, expectErr: "unable to find any kubectl plugins in your PATH", }, { name: "ensure de-duplicated plugin-paths slice", pluginPaths: []string{tempDir, tempDir}, verifier: newFakePluginPathVerifier(), pluginFile: func() (*os.File, error) { return ioutil.TempFile(tempDir, "kubectl-") }, }, } for _, test := range tc { t.Run(test.name, func(t *testing.T) { ioStreams, _, _, errOut := genericclioptions.NewTestIOStreams() o := &PluginListOptions{ Verifier: test.verifier, IOStreams: ioStreams, PluginPaths: test.pluginPaths, } // create files if test.pluginFile != nil { if _, err := test.pluginFile(); err != nil { t.Fatalf("unexpected error creating plugin file: %v", err) } } for _, expected := range test.expectVerifyErrors { for _, actual := range test.verifier.errors { if expected != actual { t.Fatalf("unexpected error: expected %v, but got %v", expected, actual) } } } err := o.Run() if err == nil && len(test.expectErr) > 0 { t.Fatalf("unexpected non-error: expecting %v", test.expectErr) } if err != nil && len(test.expectErr) == 0 { t.Fatalf("unexpected error: %v - %v", err, errOut.String()) } if err == nil { return } allErrs := bytes.NewBuffer(errOut.Bytes()) if _, err := allErrs.WriteString(err.Error()); err != nil { t.Fatalf("unexpected error: %v", err) } if len(test.expectErr) > 0 { if !strings.Contains(allErrs.String(), test.expectErr) { t.Fatalf("unexpected error: expected %v, but got %v", test.expectErr, allErrs.String()) } } }) } } type duplicatePathError struct { path string } func (d *duplicatePathError) Error() string { return fmt.Sprintf("path %q already visited", d.path) } type fakePluginPathVerifier struct { errors []error seen map[string]bool seenUnsorted []string } func (f *fakePluginPathVerifier) Verify(path string) []error { if f.seen[path] { err := &duplicatePathError{path} f.errors = append(f.errors, err) return []error{err} } f.seen[path] = true f.seenUnsorted = append(f.seenUnsorted, path) return nil } func newFakePluginPathVerifier() *fakePluginPathVerifier { return &fakePluginPathVerifier{seen: make(map[string]bool)} }