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