github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/e2e/porch_test.go (about) 1 // Copyright 2022 The kpt Authors 2 // 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 //go:build porch 16 17 package e2e 18 19 import ( 20 "bufio" 21 "bytes" 22 "errors" 23 "io/fs" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "strings" 28 "testing" 29 "time" 30 31 "github.com/GoogleContainerTools/kpt/pkg/test/porch" 32 "github.com/google/go-cmp/cmp" 33 "sigs.k8s.io/kustomize/kyaml/yaml" 34 ) 35 36 const ( 37 updateGoldenFiles = "UPDATE_GOLDEN_FILES" 38 testGitNamespace = "test-git-namespace" 39 ) 40 41 func TestPorch(t *testing.T) { 42 abs, err := filepath.Abs(filepath.Join(".", "testdata", "porch")) 43 if err != nil { 44 t.Fatalf("Failed to get absolute path to testdata directory: %v", err) 45 } 46 runTests(t, abs) 47 } 48 49 func runUtilityCommand(t *testing.T, command string, args ...string) error { 50 cmd := exec.Command(command, args...) 51 t.Logf("running utility command %s %s", command, strings.Join(args, " ")) 52 return cmd.Run() 53 } 54 55 func runTests(t *testing.T, path string) { 56 gitServerURL := startGitServer(t, path) 57 testCases := scanTestCases(t, path) 58 59 // remove any tmp files from previous test runs 60 err := runUtilityCommand(t, "rm", "-rf", "/tmp/porch-e2e") 61 if err != nil { 62 t.Fatalf("Failed to clean up older run: %v", err) 63 } 64 err = runUtilityCommand(t, "mkdir", "/tmp/porch-e2e") 65 if err != nil { 66 t.Fatalf("Failed to create temp directory: %v", err) 67 } 68 69 for _, tc := range testCases { 70 t.Run(tc.TestCase, func(t *testing.T) { 71 if tc.Skip != "" { 72 t.Skipf("Skipping test: %s", tc.Skip) 73 } 74 repoURL := gitServerURL + "/" + strings.ReplaceAll(tc.TestCase, "/", "-") 75 runTestCase(t, repoURL, tc) 76 }) 77 } 78 } 79 80 func runTestCase(t *testing.T, repoURL string, tc porch.TestCaseConfig) { 81 porch.KubectlCreateNamespace(t, tc.TestCase) 82 t.Cleanup(func() { 83 porch.KubectlDeleteNamespace(t, tc.TestCase) 84 }) 85 86 if tc.Repository != "" { 87 porch.RegisterRepository(t, repoURL, tc.TestCase, tc.Repository) 88 } 89 90 for i := range tc.Commands { 91 time.Sleep(1 * time.Second) 92 command := &tc.Commands[i] 93 cmd := exec.Command("kpt", command.Args...) 94 95 var stdout, stderr bytes.Buffer 96 if command.Stdin != "" { 97 cmd.Stdin = strings.NewReader(command.Stdin) 98 } 99 cmd.Stdout = &stdout 100 cmd.Stderr = &stderr 101 102 t.Logf("running command %v", strings.Join(cmd.Args, " ")) 103 err := cmd.Run() 104 105 if command.Yaml { 106 reorderYamlStdout(t, &stdout) 107 } 108 109 cleanupStderr(t, &stderr) 110 111 if os.Getenv(updateGoldenFiles) != "" { 112 updateCommand(command, err, stdout.String(), stderr.String()) 113 } 114 115 if got, want := exitCode(err), command.ExitCode; got != want { 116 t.Errorf("unexpected exit code from 'kpt %s'; got %d, want %d", strings.Join(command.Args, " "), got, want) 117 } 118 if got, want := stdout.String(), command.Stdout; got != want { 119 t.Errorf("unexpected stdout content from 'kpt %s'; (-want, +got) %s", strings.Join(command.Args, " "), cmp.Diff(want, got)) 120 } 121 if got, want := stderr.String(), command.Stderr; got != want { 122 t.Errorf("unexpected stderr content from 'kpt %s'; (-want, +got) %s", strings.Join(command.Args, " "), cmp.Diff(want, got)) 123 } 124 125 // hack here; but if the command registered a repo, give a few extra seconds for the repo to reach readiness 126 for _, arg := range command.Args { 127 if arg == "register" { 128 time.Sleep(5 * time.Second) 129 } 130 } 131 } 132 133 if os.Getenv(updateGoldenFiles) != "" { 134 porch.WriteTestCaseConfig(t, &tc) 135 } 136 } 137 138 // remove PASS lines from kpt fn eval, which includes a duration and will vary 139 func cleanupStderr(t *testing.T, buf *bytes.Buffer) { 140 scanner := bufio.NewScanner(buf) 141 var newBuf bytes.Buffer 142 for scanner.Scan() { 143 line := scanner.Text() 144 if !strings.Contains(line, "[PASS]") { 145 newBuf.Write([]byte(line)) 146 newBuf.Write([]byte("\n")) 147 } 148 } 149 150 buf.Reset() 151 if _, err := buf.Write(newBuf.Bytes()); err != nil { 152 t.Fatalf("Failed to update cleaned up stderr: %v", err) 153 } 154 } 155 156 func reorderYamlStdout(t *testing.T, buf *bytes.Buffer) { 157 if buf.Len() == 0 { 158 return 159 } 160 161 // strip out the resourceVersion:, creationTimestamp: 162 // because that will change with every run 163 scanner := bufio.NewScanner(buf) 164 var newBuf bytes.Buffer 165 for scanner.Scan() { 166 line := scanner.Text() 167 if !strings.Contains(line, "resourceVersion:") && 168 !strings.Contains(line, "creationTimestamp:") { 169 newBuf.Write([]byte(line)) 170 newBuf.Write([]byte("\n")) 171 } 172 } 173 174 var data interface{} 175 if err := yaml.Unmarshal(newBuf.Bytes(), &data); err != nil { 176 // not yaml. 177 return 178 } 179 180 var stable bytes.Buffer 181 encoder := yaml.NewEncoder(&stable) 182 encoder.SetIndent(2) 183 if err := encoder.Encode(data); err != nil { 184 t.Fatalf("Failed to re-encode yaml output: %v", err) 185 } 186 buf.Reset() 187 if _, err := buf.Write(stable.Bytes()); err != nil { 188 t.Fatalf("Failed to update reordered yaml output: %v", err) 189 } 190 } 191 192 func startGitServer(t *testing.T, path string) string { 193 gitServerURL := "http://git-server." + testGitNamespace + ".svc.cluster.local:8080" 194 195 gitServerImage := porch.GetGitServerImageName(t) 196 t.Logf("Git Image: %s", gitServerImage) 197 198 configFile := filepath.Join(path, "git-server.yaml") 199 configBytes, err := os.ReadFile(configFile) 200 if err != nil { 201 t.Fatalf("Failed to read git server config file %q: %v", configFile, err) 202 } 203 config := string(configBytes) 204 config = strings.ReplaceAll(config, "GIT_SERVER_IMAGE", gitServerImage) 205 206 t.Cleanup(func() { 207 porch.KubectlDeleteNamespace(t, testGitNamespace) 208 }) 209 210 porch.KubectlApply(t, config) 211 porch.KubectlWaitForDeployment(t, testGitNamespace, "git-server") 212 porch.KubectlWaitForService(t, testGitNamespace, "git-server") 213 porch.KubectlWaitForGitDNS(t, gitServerURL) 214 215 return gitServerURL 216 } 217 218 func scanTestCases(t *testing.T, root string) []porch.TestCaseConfig { 219 testCases := []porch.TestCaseConfig{} 220 221 if err := filepath.Walk(root, func(path string, info fs.FileInfo, err error) error { 222 if err != nil { 223 return err 224 } 225 if !info.IsDir() { 226 return nil 227 } 228 if path == root { 229 return nil 230 } 231 232 tc := porch.ReadTestCaseConfig(t, info.Name(), path) 233 testCases = append(testCases, tc) 234 235 return nil 236 }); err != nil { 237 t.Fatalf("Failed to scan test cases: %v", err) 238 } 239 240 return testCases 241 } 242 243 func updateCommand(command *porch.Command, exit error, stdout, stderr string) { 244 command.ExitCode = exitCode(exit) 245 command.Stdout = stdout 246 command.Stderr = stderr 247 } 248 249 func exitCode(exit error) int { 250 var ee *exec.ExitError 251 if errors.As(exit, &ee) { 252 return ee.ExitCode() 253 } 254 return 0 255 }