github.com/loafoe/helm@v1.0.1/cmd/helm/helm_test.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"bytes"
    21  	"io/ioutil"
    22  	"os"
    23  	"os/exec"
    24  	"runtime"
    25  	"strings"
    26  	"testing"
    27  
    28  	shellwords "github.com/mattn/go-shellwords"
    29  	"github.com/spf13/cobra"
    30  
    31  	"helm.sh/helm/v3/internal/test"
    32  	"helm.sh/helm/v3/pkg/action"
    33  	"helm.sh/helm/v3/pkg/chartutil"
    34  	"helm.sh/helm/v3/pkg/cli"
    35  	kubefake "helm.sh/helm/v3/pkg/kube/fake"
    36  	"helm.sh/helm/v3/pkg/release"
    37  	"helm.sh/helm/v3/pkg/storage"
    38  	"helm.sh/helm/v3/pkg/storage/driver"
    39  	"helm.sh/helm/v3/pkg/time"
    40  )
    41  
    42  func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() }
    43  
    44  func init() {
    45  	action.Timestamper = testTimestamper
    46  }
    47  
    48  func runTestCmd(t *testing.T, tests []cmdTestCase) {
    49  	t.Helper()
    50  	for _, tt := range tests {
    51  		for i := 0; i <= tt.repeat; i++ {
    52  			t.Run(tt.name, func(t *testing.T) {
    53  				defer resetEnv()()
    54  
    55  				storage := storageFixture()
    56  				for _, rel := range tt.rels {
    57  					if err := storage.Create(rel); err != nil {
    58  						t.Fatal(err)
    59  					}
    60  				}
    61  				t.Logf("running cmd (attempt %d): %s", i+1, tt.cmd)
    62  				_, out, err := executeActionCommandC(storage, tt.cmd)
    63  				if tt.wantError && err == nil {
    64  					t.Errorf("expected error, got success with the following output:\n%s", out)
    65  				}
    66  				if !tt.wantError && err != nil {
    67  					t.Errorf("expected no error, got: '%v'", err)
    68  				}
    69  				if tt.golden != "" {
    70  					test.AssertGoldenString(t, out, tt.golden)
    71  				}
    72  			})
    73  		}
    74  	}
    75  }
    76  
    77  func storageFixture() *storage.Storage {
    78  	return storage.Init(driver.NewMemory())
    79  }
    80  
    81  func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, string, error) {
    82  	return executeActionCommandStdinC(store, nil, cmd)
    83  }
    84  
    85  func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string) (*cobra.Command, string, error) {
    86  	args, err := shellwords.Parse(cmd)
    87  	if err != nil {
    88  		return nil, "", err
    89  	}
    90  
    91  	buf := new(bytes.Buffer)
    92  
    93  	actionConfig := &action.Configuration{
    94  		Releases:     store,
    95  		KubeClient:   &kubefake.PrintingKubeClient{Out: ioutil.Discard},
    96  		Capabilities: chartutil.DefaultCapabilities,
    97  		Log:          func(format string, v ...interface{}) {},
    98  	}
    99  
   100  	root, err := newRootCmd(actionConfig, buf, args)
   101  	if err != nil {
   102  		return nil, "", err
   103  	}
   104  
   105  	root.SetOut(buf)
   106  	root.SetErr(buf)
   107  	root.SetArgs(args)
   108  
   109  	oldStdin := os.Stdin
   110  	if in != nil {
   111  		root.SetIn(in)
   112  		os.Stdin = in
   113  	}
   114  
   115  	if mem, ok := store.Driver.(*driver.Memory); ok {
   116  		mem.SetNamespace(settings.Namespace())
   117  	}
   118  	c, err := root.ExecuteC()
   119  
   120  	result := buf.String()
   121  
   122  	os.Stdin = oldStdin
   123  
   124  	return c, result, err
   125  }
   126  
   127  // cmdTestCase describes a test case that works with releases.
   128  type cmdTestCase struct {
   129  	name      string
   130  	cmd       string
   131  	golden    string
   132  	wantError bool
   133  	// Rels are the available releases at the start of the test.
   134  	rels []*release.Release
   135  	// Number of repeats (in case a feature was previously flaky and the test checks
   136  	// it's now stably producing identical results). 0 means test is run exactly once.
   137  	repeat int
   138  }
   139  
   140  func executeActionCommand(cmd string) (*cobra.Command, string, error) {
   141  	return executeActionCommandC(storageFixture(), cmd)
   142  }
   143  
   144  func resetEnv() func() {
   145  	origEnv := os.Environ()
   146  	return func() {
   147  		os.Clearenv()
   148  		for _, pair := range origEnv {
   149  			kv := strings.SplitN(pair, "=", 2)
   150  			os.Setenv(kv[0], kv[1])
   151  		}
   152  		settings = cli.New()
   153  	}
   154  }
   155  
   156  func testChdir(t *testing.T, dir string) func() {
   157  	t.Helper()
   158  	old, err := os.Getwd()
   159  	if err != nil {
   160  		t.Fatal(err)
   161  	}
   162  	if err := os.Chdir(dir); err != nil {
   163  		t.Fatal(err)
   164  	}
   165  	return func() { os.Chdir(old) }
   166  }
   167  
   168  func TestPluginExitCode(t *testing.T) {
   169  	if os.Getenv("RUN_MAIN_FOR_TESTING") == "1" {
   170  		os.Args = []string{"helm", "exitwith", "2"}
   171  
   172  		// We DO call helm's main() here. So this looks like a normal `helm` process.
   173  		main()
   174  
   175  		// As main calls os.Exit, we never reach this line.
   176  		// But the test called this block of code catches and verifies the exit code.
   177  		return
   178  	}
   179  
   180  	// Currently, plugins assume a Linux subsystem. Skip the execution
   181  	// tests until this is fixed
   182  	if runtime.GOOS != "windows" {
   183  		// Do a second run of this specific test(TestPluginExitCode) with RUN_MAIN_FOR_TESTING=1 set,
   184  		// So that the second run is able to run main() and this first run can verify the exit status returned by that.
   185  		//
   186  		// This technique originates from https://talks.golang.org/2014/testing.slide#23.
   187  		cmd := exec.Command(os.Args[0], "-test.run=TestPluginExitCode")
   188  		cmd.Env = append(
   189  			os.Environ(),
   190  			"RUN_MAIN_FOR_TESTING=1",
   191  			// See pkg/cli/environment.go for which envvars can be used for configuring these passes
   192  			// and also see plugin_test.go for how a plugin env can be set up.
   193  			// We just does the same setup as plugin_test.go via envvars
   194  			"HELM_PLUGINS=testdata/helmhome/helm/plugins",
   195  			"HELM_REPOSITORY_CONFIG=testdata/helmhome/helm/repositories.yaml",
   196  			"HELM_REPOSITORY_CACHE=testdata/helmhome/helm/repository",
   197  		)
   198  		stdout := &bytes.Buffer{}
   199  		stderr := &bytes.Buffer{}
   200  		cmd.Stdout = stdout
   201  		cmd.Stderr = stderr
   202  		err := cmd.Run()
   203  		exiterr, ok := err.(*exec.ExitError)
   204  
   205  		if !ok {
   206  			t.Fatalf("Unexpected error returned by os.Exit: %T", err)
   207  		}
   208  
   209  		if stdout.String() != "" {
   210  			t.Errorf("Expected no write to stdout: Got %q", stdout.String())
   211  		}
   212  
   213  		expectedStderr := "Error: plugin \"exitwith\" exited with error\n"
   214  		if stderr.String() != expectedStderr {
   215  			t.Errorf("Expected %q written to stderr: Got %q", expectedStderr, stderr.String())
   216  		}
   217  
   218  		if exiterr.ExitCode() != 2 {
   219  			t.Errorf("Expected exit code 2: Got %d", exiterr.ExitCode())
   220  		}
   221  	}
   222  }