github.com/Racer159/helm-experiment@v0.0.0-20230822001441-1eb31183f614/src/plugin_test.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package cmd
    17  
    18  import (
    19  	"bytes"
    20  	"os"
    21  	"runtime"
    22  	"sort"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/spf13/cobra"
    27  	"github.com/spf13/pflag"
    28  
    29  	"helm.sh/helm/v3/pkg/release"
    30  )
    31  
    32  func TestManuallyProcessArgs(t *testing.T) {
    33  	input := []string{
    34  		"--debug",
    35  		"--foo", "bar",
    36  		"--kubeconfig=/home/foo",
    37  		"--kubeconfig", "/home/foo",
    38  		"--kube-context=test1",
    39  		"--kube-context", "test1",
    40  		"--kube-as-user", "pikachu",
    41  		"--kube-as-group", "teatime",
    42  		"--kube-as-group", "admins",
    43  		"-n=test2",
    44  		"-n", "test2",
    45  		"--namespace=test2",
    46  		"--namespace", "test2",
    47  		"--home=/tmp",
    48  		"command",
    49  	}
    50  
    51  	expectKnown := []string{
    52  		"--debug",
    53  		"--kubeconfig=/home/foo",
    54  		"--kubeconfig", "/home/foo",
    55  		"--kube-context=test1",
    56  		"--kube-context", "test1",
    57  		"--kube-as-user", "pikachu",
    58  		"--kube-as-group", "teatime",
    59  		"--kube-as-group", "admins",
    60  		"-n=test2",
    61  		"-n", "test2",
    62  		"--namespace=test2",
    63  		"--namespace", "test2",
    64  	}
    65  
    66  	expectUnknown := []string{
    67  		"--foo", "bar", "--home=/tmp", "command",
    68  	}
    69  
    70  	known, unknown := manuallyProcessArgs(input)
    71  
    72  	for i, k := range known {
    73  		if k != expectKnown[i] {
    74  			t.Errorf("expected known flag %d to be %q, got %q", i, expectKnown[i], k)
    75  		}
    76  	}
    77  	for i, k := range unknown {
    78  		if k != expectUnknown[i] {
    79  			t.Errorf("expected unknown flag %d to be %q, got %q", i, expectUnknown[i], k)
    80  		}
    81  	}
    82  
    83  }
    84  
    85  func TestLoadPlugins(t *testing.T) {
    86  	settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
    87  	settings.RepositoryConfig = "testdata/helmhome/helm/repositories.yaml"
    88  	settings.RepositoryCache = "testdata/helmhome/helm/repository"
    89  
    90  	var (
    91  		out bytes.Buffer
    92  		cmd cobra.Command
    93  	)
    94  	loadPlugins(&cmd, &out)
    95  
    96  	envs := strings.Join([]string{
    97  		"fullenv",
    98  		"testdata/helmhome/helm/plugins/fullenv",
    99  		"testdata/helmhome/helm/plugins",
   100  		"testdata/helmhome/helm/repositories.yaml",
   101  		"testdata/helmhome/helm/repository",
   102  		os.Args[0],
   103  	}, "\n")
   104  
   105  	// Test that the YAML file was correctly converted to a command.
   106  	tests := []struct {
   107  		use    string
   108  		short  string
   109  		long   string
   110  		expect string
   111  		args   []string
   112  		code   int
   113  	}{
   114  		{"args", "echo args", "This echos args", "-a -b -c\n", []string{"-a", "-b", "-c"}, 0},
   115  		{"echo", "echo stuff", "This echos stuff", "hello\n", []string{}, 0},
   116  		{"env", "env stuff", "show the env", "env\n", []string{}, 0},
   117  		{"exitwith", "exitwith code", "This exits with the specified exit code", "", []string{"2"}, 2},
   118  		{"fullenv", "show env vars", "show all env vars", envs + "\n", []string{}, 0},
   119  	}
   120  
   121  	plugins := cmd.Commands()
   122  
   123  	if len(plugins) != len(tests) {
   124  		t.Fatalf("Expected %d plugins, got %d", len(tests), len(plugins))
   125  	}
   126  
   127  	for i := 0; i < len(plugins); i++ {
   128  		out.Reset()
   129  		tt := tests[i]
   130  		pp := plugins[i]
   131  		if pp.Use != tt.use {
   132  			t.Errorf("%d: Expected Use=%q, got %q", i, tt.use, pp.Use)
   133  		}
   134  		if pp.Short != tt.short {
   135  			t.Errorf("%d: Expected Use=%q, got %q", i, tt.short, pp.Short)
   136  		}
   137  		if pp.Long != tt.long {
   138  			t.Errorf("%d: Expected Use=%q, got %q", i, tt.long, pp.Long)
   139  		}
   140  
   141  		// Currently, plugins assume a Linux subsystem. Skip the execution
   142  		// tests until this is fixed
   143  		if runtime.GOOS != "windows" {
   144  			if err := pp.RunE(pp, tt.args); err != nil {
   145  				if tt.code > 0 {
   146  					perr, ok := err.(pluginError)
   147  					if !ok {
   148  						t.Errorf("Expected %s to return pluginError: got %v(%T)", tt.use, err, err)
   149  					}
   150  					if perr.code != tt.code {
   151  						t.Errorf("Expected %s to return %d: got %d", tt.use, tt.code, perr.code)
   152  					}
   153  				} else {
   154  					t.Errorf("Error running %s: %+v", tt.use, err)
   155  				}
   156  			}
   157  			if out.String() != tt.expect {
   158  				t.Errorf("Expected %s to output:\n%s\ngot\n%s", tt.use, tt.expect, out.String())
   159  			}
   160  		}
   161  	}
   162  }
   163  
   164  type staticCompletionDetails struct {
   165  	use       string
   166  	validArgs []string
   167  	flags     []string
   168  	next      []staticCompletionDetails
   169  }
   170  
   171  func TestLoadPluginsForCompletion(t *testing.T) {
   172  	settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
   173  
   174  	var out bytes.Buffer
   175  
   176  	cmd := &cobra.Command{
   177  		Use: "completion",
   178  	}
   179  
   180  	loadPlugins(cmd, &out)
   181  
   182  	tests := []staticCompletionDetails{
   183  		{"args", []string{}, []string{}, []staticCompletionDetails{}},
   184  		{"echo", []string{}, []string{}, []staticCompletionDetails{}},
   185  		{"env", []string{}, []string{"global"}, []staticCompletionDetails{
   186  			{"list", []string{}, []string{"a", "all", "log"}, []staticCompletionDetails{}},
   187  			{"remove", []string{"all", "one"}, []string{}, []staticCompletionDetails{}},
   188  		}},
   189  		{"exitwith", []string{}, []string{}, []staticCompletionDetails{
   190  			{"code", []string{}, []string{"a", "b"}, []staticCompletionDetails{}},
   191  		}},
   192  		{"fullenv", []string{}, []string{"q", "z"}, []staticCompletionDetails{
   193  			{"empty", []string{}, []string{}, []staticCompletionDetails{}},
   194  			{"full", []string{}, []string{}, []staticCompletionDetails{
   195  				{"less", []string{}, []string{"a", "all"}, []staticCompletionDetails{}},
   196  				{"more", []string{"one", "two"}, []string{"b", "ball"}, []staticCompletionDetails{}},
   197  			}},
   198  		}},
   199  	}
   200  	checkCommand(t, cmd.Commands(), tests)
   201  }
   202  
   203  func checkCommand(t *testing.T, plugins []*cobra.Command, tests []staticCompletionDetails) {
   204  	if len(plugins) != len(tests) {
   205  		t.Fatalf("Expected commands %v, got %v", tests, plugins)
   206  	}
   207  
   208  	for i := 0; i < len(plugins); i++ {
   209  		pp := plugins[i]
   210  		tt := tests[i]
   211  		if pp.Use != tt.use {
   212  			t.Errorf("%s: Expected Use=%q, got %q", pp.Name(), tt.use, pp.Use)
   213  		}
   214  
   215  		targs := tt.validArgs
   216  		pargs := pp.ValidArgs
   217  		if len(targs) != len(pargs) {
   218  			t.Fatalf("%s: expected args %v, got %v", pp.Name(), targs, pargs)
   219  		}
   220  
   221  		sort.Strings(targs)
   222  		sort.Strings(pargs)
   223  		for j := range targs {
   224  			if targs[j] != pargs[j] {
   225  				t.Errorf("%s: expected validArg=%q, got %q", pp.Name(), targs[j], pargs[j])
   226  			}
   227  		}
   228  
   229  		tflags := tt.flags
   230  		var pflags []string
   231  		pp.LocalFlags().VisitAll(func(flag *pflag.Flag) {
   232  			pflags = append(pflags, flag.Name)
   233  			if len(flag.Shorthand) > 0 && flag.Shorthand != flag.Name {
   234  				pflags = append(pflags, flag.Shorthand)
   235  			}
   236  		})
   237  		if len(tflags) != len(pflags) {
   238  			t.Fatalf("%s: expected flags %v, got %v", pp.Name(), tflags, pflags)
   239  		}
   240  
   241  		sort.Strings(tflags)
   242  		sort.Strings(pflags)
   243  		for j := range tflags {
   244  			if tflags[j] != pflags[j] {
   245  				t.Errorf("%s: expected flag=%q, got %q", pp.Name(), tflags[j], pflags[j])
   246  			}
   247  		}
   248  		// Check the next level
   249  		checkCommand(t, pp.Commands(), tt.next)
   250  	}
   251  }
   252  
   253  func TestPluginDynamicCompletion(t *testing.T) {
   254  
   255  	tests := []cmdTestCase{{
   256  		name:   "completion for plugin",
   257  		cmd:    "__complete args ''",
   258  		golden: "output/plugin_args_comp.txt",
   259  		rels:   []*release.Release{},
   260  	}, {
   261  		name:   "completion for plugin with flag",
   262  		cmd:    "__complete args --myflag ''",
   263  		golden: "output/plugin_args_flag_comp.txt",
   264  		rels:   []*release.Release{},
   265  	}, {
   266  		name:   "completion for plugin with global flag",
   267  		cmd:    "__complete args --namespace mynamespace ''",
   268  		golden: "output/plugin_args_ns_comp.txt",
   269  		rels:   []*release.Release{},
   270  	}, {
   271  		name:   "completion for plugin with multiple args",
   272  		cmd:    "__complete args --myflag --namespace mynamespace start",
   273  		golden: "output/plugin_args_many_args_comp.txt",
   274  		rels:   []*release.Release{},
   275  	}, {
   276  		name:   "completion for plugin no directive",
   277  		cmd:    "__complete echo -n mynamespace ''",
   278  		golden: "output/plugin_echo_no_directive.txt",
   279  		rels:   []*release.Release{},
   280  	}}
   281  	for _, test := range tests {
   282  		settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
   283  		runTestCmd(t, []cmdTestCase{test})
   284  	}
   285  }
   286  
   287  func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
   288  	settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
   289  	settings.RepositoryConfig = "testdata/helmhome/helm/repository"
   290  
   291  	os.Setenv("HELM_NO_PLUGINS", "1")
   292  
   293  	out := bytes.NewBuffer(nil)
   294  	cmd := &cobra.Command{}
   295  	loadPlugins(cmd, out)
   296  	plugins := cmd.Commands()
   297  
   298  	if len(plugins) != 0 {
   299  		t.Fatalf("Expected 0 plugins, got %d", len(plugins))
   300  	}
   301  }
   302  
   303  func TestPluginCmdsCompletion(t *testing.T) {
   304  
   305  	tests := []cmdTestCase{{
   306  		name:   "completion for plugin update",
   307  		cmd:    "__complete plugin update ''",
   308  		golden: "output/plugin_list_comp.txt",
   309  		rels:   []*release.Release{},
   310  	}, {
   311  		name:   "completion for plugin update, no filter",
   312  		cmd:    "__complete plugin update full",
   313  		golden: "output/plugin_list_comp.txt",
   314  		rels:   []*release.Release{},
   315  	}, {
   316  		name:   "completion for plugin update repetition",
   317  		cmd:    "__complete plugin update args ''",
   318  		golden: "output/plugin_repeat_comp.txt",
   319  		rels:   []*release.Release{},
   320  	}, {
   321  		name:   "completion for plugin uninstall",
   322  		cmd:    "__complete plugin uninstall ''",
   323  		golden: "output/plugin_list_comp.txt",
   324  		rels:   []*release.Release{},
   325  	}, {
   326  		name:   "completion for plugin uninstall, no filter",
   327  		cmd:    "__complete plugin uninstall full",
   328  		golden: "output/plugin_list_comp.txt",
   329  		rels:   []*release.Release{},
   330  	}, {
   331  		name:   "completion for plugin uninstall repetition",
   332  		cmd:    "__complete plugin uninstall args ''",
   333  		golden: "output/plugin_repeat_comp.txt",
   334  		rels:   []*release.Release{},
   335  	}, {
   336  		name:   "completion for plugin list",
   337  		cmd:    "__complete plugin list ''",
   338  		golden: "output/empty_nofile_comp.txt",
   339  		rels:   []*release.Release{},
   340  	}, {
   341  		name:   "completion for plugin install no args",
   342  		cmd:    "__complete plugin install ''",
   343  		golden: "output/empty_default_comp.txt",
   344  		rels:   []*release.Release{},
   345  	}, {
   346  		name:   "completion for plugin install one arg",
   347  		cmd:    "__complete plugin list /tmp ''",
   348  		golden: "output/empty_nofile_comp.txt",
   349  		rels:   []*release.Release{},
   350  	}, {}}
   351  	for _, test := range tests {
   352  		settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
   353  		runTestCmd(t, []cmdTestCase{test})
   354  	}
   355  }
   356  
   357  func TestPluginFileCompletion(t *testing.T) {
   358  	checkFileCompletion(t, "plugin", false)
   359  }
   360  
   361  func TestPluginInstallFileCompletion(t *testing.T) {
   362  	checkFileCompletion(t, "plugin install", true)
   363  	checkFileCompletion(t, "plugin install mypath", false)
   364  }
   365  
   366  func TestPluginListFileCompletion(t *testing.T) {
   367  	checkFileCompletion(t, "plugin list", false)
   368  }
   369  
   370  func TestPluginUninstallFileCompletion(t *testing.T) {
   371  	checkFileCompletion(t, "plugin uninstall", false)
   372  	checkFileCompletion(t, "plugin uninstall myplugin", false)
   373  }
   374  
   375  func TestPluginUpdateFileCompletion(t *testing.T) {
   376  	checkFileCompletion(t, "plugin update", false)
   377  	checkFileCompletion(t, "plugin update myplugin", false)
   378  }