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  }