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