github.com/opendevstack/tailor@v1.3.5-0.20220119161809-cab064e60a67/internal/test/e2e/e2e_test.go (about) 1 package e2e 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "strings" 11 "testing" 12 "text/template" 13 14 "github.com/google/go-cmp/cmp" 15 ) 16 17 type testCaseSteps []testCaseStep 18 19 type testCaseStep struct { 20 Before string `json:"before"` 21 Command string `json:"command"` 22 WantStdout bool `json:"wantStdout"` 23 WantStderr bool `json:"wantStderr"` 24 WantErr bool `json:"wantErr"` 25 WantResources map[string]bool `json:"wantResources"` 26 WantFields map[string]map[string]string `json:"wantFields"` 27 After string `json:"after"` 28 } 29 30 type outputData struct { 31 Project string 32 } 33 34 func TestE2E(t *testing.T) { 35 testProjectName := setup(t) 36 defer teardown(t, testProjectName) 37 38 err := os.Chdir("testdata") 39 if err != nil { 40 t.Fatalf("Fail to chdir to testdata: %s", err) 41 } 42 43 tailorBinary := getTailorBinary() 44 45 tempDir := exportInitialState(t, testProjectName, tailorBinary) 46 defer os.RemoveAll(tempDir) 47 48 walkSubdirs(t, ".", func(subdir string) { 49 ensureProjectIsInitialState(t, testProjectName, tailorBinary, tempDir) 50 runTestCase(t, testProjectName, tailorBinary, subdir) 51 }) 52 } 53 54 func ensureProjectIsInitialState(t *testing.T, testProjectName string, tailorBinary string, tempDir string) { 55 args := []string{ 56 "--non-interactive", 57 "-n", testProjectName, 58 "--template-dir", tempDir, 59 "apply", "--verify", 60 } 61 t.Logf("Apply and verify initial state: %s", strings.Join(args, " ")) 62 applyAndVerifyStdout, applyAndVerifyStderr, applyAndVerifyErr := runCmd(tailorBinary, args) 63 if applyAndVerifyErr != nil { 64 t.Fatalf( 65 "Could not apply and verify initial state:\nerr:\n%s\nstderr:\n%s\nstdout:\n%s", 66 applyAndVerifyErr, applyAndVerifyStderr, applyAndVerifyStdout, 67 ) 68 } 69 } 70 71 func exportInitialState(t *testing.T, testProjectName string, tailorBinary string) string { 72 args := []string{ 73 "--non-interactive", 74 "-n", testProjectName, 75 "export", 76 } 77 t.Logf("Running initial export: %s", strings.Join(args, " ")) 78 exportStdout, exportStderr, exportErr := runCmd(tailorBinary, args) 79 if exportErr != nil { 80 t.Fatalf("Could not export initial state: %s\n%s", exportErr, exportStderr) 81 } 82 tempDir, tempDirErr := ioutil.TempDir("..", "initial-export-") 83 if tempDirErr != nil { 84 t.Fatalf("Could not create temp dir: %s", tempDirErr) 85 } 86 writeErr := ioutil.WriteFile(tempDir+"/template.yml", exportStdout, 0644) 87 if writeErr != nil { 88 t.Logf("Failed to write file template.yml into %s", tempDir) 89 os.RemoveAll(tempDir) 90 t.Fatal(writeErr) 91 } 92 return tempDir 93 } 94 95 func walkSubdirs(t *testing.T, dir string, fun func(subdir string)) { 96 files, err := ioutil.ReadDir(dir) 97 if err != nil { 98 t.Fatal(err) 99 } 100 for _, f := range files { 101 if f.IsDir() { 102 fun(f.Name()) 103 } 104 } 105 } 106 107 func runTestCase(t *testing.T, testProjectName string, tailorBinary string, testCase string) { 108 t.Log("Running steps for test case:", testCase) 109 tcs, err := readTestCaseSteps(testCase) 110 if err != nil { 111 t.Fatal(err) 112 } 113 114 for i, tc := range tcs { 115 t.Run(fmt.Sprintf("%s step #%d", testCase, i), func(t *testing.T) { 116 stepDir := fmt.Sprintf("%s/%d", testCase, i) 117 templateData := outputData{ 118 Project: testProjectName, 119 } 120 runSurroundingCmd(t, "before", tc.Before, templateData) 121 args := []string{ 122 "--non-interactive", 123 "-n", testProjectName, 124 "--template-dir", stepDir, 125 } 126 args = append(args, strings.Split(tc.Command, " ")...) 127 t.Logf("Running tailor with: %s", strings.Join(args, " ")) 128 gotStdout, gotStderr, gotErr := runCmd(tailorBinary, args) 129 checkErr(t, tc.WantErr, gotErr, gotStderr) 130 checkStderr(t, tc.WantStderr, gotStderr, templateData, stepDir) 131 checkStdout(t, tc.WantStdout, gotStdout, templateData, stepDir) 132 checkResources(t, tc.WantResources, testProjectName) 133 checkFields(t, tc.WantFields, testProjectName, templateData) 134 runSurroundingCmd(t, "after", tc.After, templateData) 135 }) 136 } 137 138 } 139 140 func runSurroundingCmd(t *testing.T, kind string, command string, templateData outputData) { 141 if len(command) > 0 { 142 var cmdBuffer bytes.Buffer 143 tmpl, err := template.New(kind).Parse(command) 144 if err != nil { 145 t.Fatalf("Error parsing template: %s", err) 146 } 147 tmplErr := tmpl.Execute(&cmdBuffer, templateData) 148 if tmplErr != nil { 149 t.Fatalf("Error rendering template: %s", tmplErr) 150 } 151 commandParts := strings.Split(cmdBuffer.String(), " ") 152 commandCmd := commandParts[0] 153 commandArgs := commandParts[1:] 154 t.Logf("Running '%s' comamnd: %s %s", kind, commandCmd, strings.Join(commandArgs, " ")) 155 commandStdout, commandStderr, commandErr := runCmd(commandCmd, commandArgs) 156 if commandErr != nil { 157 t.Fatalf( 158 "Error running '%s' command:\nerr:\n%s\nstderr:\n%s\nstdout:\n%s", 159 kind, 160 commandErr, 161 commandStderr, 162 commandStdout, 163 ) 164 } 165 t.Logf("'%s' result: %s", kind, commandStdout) 166 } 167 } 168 169 func checkErr(t *testing.T, wantErr bool, gotErr error, gotStderr []byte) { 170 if wantErr { 171 if gotErr == nil { 172 t.Fatal("Want error, got none") 173 } 174 } else { 175 if gotErr != nil { 176 t.Fatalf("Got error: %s: %s", gotErr, gotStderr) 177 } 178 } 179 } 180 181 func checkStderr(t *testing.T, wantStderr bool, gotStderr []byte, templateData outputData, stepDir string) { 182 if wantStderr { 183 var wantStderr bytes.Buffer 184 tmpl, err := template.ParseFiles(fmt.Sprintf("%s/want.err", stepDir)) 185 if err != nil { 186 t.Fatal(err) 187 } 188 err = tmpl.Execute(&wantStderr, templateData) 189 if err != nil { 190 t.Fatal(err) 191 } 192 if diff := cmp.Diff(wantStderr.Bytes(), gotStderr); diff != "" { 193 t.Fatalf("Stderr mismatch (-want +got):\n%s", diff) 194 } 195 } 196 } 197 198 func checkStdout(t *testing.T, wantStdout bool, gotStdout []byte, templateData outputData, stepDir string) { 199 if wantStdout { 200 var wantStdout bytes.Buffer 201 tmpl, err := template.ParseFiles(fmt.Sprintf("%s/want.out", stepDir)) 202 if err != nil { 203 t.Fatal(err) 204 } 205 err = tmpl.Execute(&wantStdout, templateData) 206 if err != nil { 207 t.Fatal(err) 208 } 209 if diff := cmp.Diff(wantStdout.Bytes(), gotStdout); diff != "" { 210 t.Fatalf("Stdout mismatch (-want +got):\n%s", diff) 211 } 212 } 213 } 214 215 func checkResources(t *testing.T, wantResources map[string]bool, projectName string) { 216 for res, wantExists := range wantResources { 217 _, _, err := runCmd("oc", []string{"-n", projectName, "get", res}) 218 gotExists := err == nil 219 if gotExists != wantExists { 220 t.Fatalf("Resource %s: want exists=%t, got exists=%t\n", res, wantExists, gotExists) 221 } 222 } 223 } 224 225 func checkFields(t *testing.T, wantFields map[string]map[string]string, projectName string, templateData outputData) { 226 for res, jsonPaths := range wantFields { 227 for jsonPath, wantValTpl := range jsonPaths { 228 gotVal, _, err := runCmd("oc", []string{ 229 "-n", projectName, 230 "get", res, 231 "-o", fmt.Sprintf("jsonpath={%s}", jsonPath), 232 }) 233 if err != nil { 234 t.Fatalf("Could not get path %s of resource %s: %s", jsonPath, res, err) 235 } 236 tmpl, err := template.New("attachment").Parse(wantValTpl) 237 if err != nil { 238 t.Fatalf("Error parsing wanted value template: %s", err) 239 } 240 var wantValBuffer bytes.Buffer 241 err = tmpl.Execute(&wantValBuffer, templateData) 242 if err != nil { 243 t.Fatal(err) 244 } 245 wantVal := wantValBuffer.String() 246 if string(gotVal) != wantVal { 247 t.Fatalf("Field %s %s: want val=%s, got val=%s\n", res, jsonPath, wantVal, gotVal) 248 } 249 } 250 } 251 } 252 253 func runCmd(executable string, args []string) (outBytes, errBytes []byte, err error) { 254 cmd := exec.Command(executable, args...) 255 var stdout, stderr bytes.Buffer 256 cmd.Stdout = &stdout 257 cmd.Stderr = &stderr 258 err = cmd.Run() 259 outBytes = stdout.Bytes() 260 errBytes = stderr.Bytes() 261 return outBytes, errBytes, err 262 } 263 264 func readTestCaseSteps(folder string) (testCaseSteps, error) { 265 content, err := ioutil.ReadFile(folder + "/steps.json") 266 if err != nil { 267 return nil, fmt.Errorf("Cannot read file: %w", err) 268 } 269 270 var tc testCaseSteps 271 err = json.Unmarshal(content, &tc) 272 if err != nil { 273 return nil, fmt.Errorf("Cannot parse JSON: %w", err) 274 } 275 return tc, nil 276 }