github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/test/common.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // SPDX-FileCopyrightText: 2021-Present The Jackal Authors
     3  
     4  // Package test provides e2e tests for Jackal.
     5  package test
     6  
     7  import (
     8  	"bufio"
     9  	"context"
    10  	"fmt"
    11  	"os"
    12  	"regexp"
    13  	"runtime"
    14  	"strings"
    15  	"testing"
    16  
    17  	"slices"
    18  
    19  	"github.com/Racer159/jackal/src/pkg/utils/exec"
    20  	"github.com/defenseunicorns/pkg/helpers"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  // JackalE2ETest Struct holding common fields most of the tests will utilize.
    25  type JackalE2ETest struct {
    26  	JackalBinPath     string
    27  	Arch              string
    28  	ApplianceMode     bool
    29  	ApplianceModeKeep bool
    30  	RunClusterTests   bool
    31  }
    32  
    33  var logRegex = regexp.MustCompile(`Saving log file to (?P<logFile>.*?\.log)`)
    34  
    35  // GetCLIName looks at the OS and CPU architecture to determine which Jackal binary needs to be run.
    36  func GetCLIName() string {
    37  	var binaryName string
    38  	switch runtime.GOOS {
    39  	case "linux":
    40  		binaryName = "jackal"
    41  	case "darwin":
    42  		switch runtime.GOARCH {
    43  		case "arm64":
    44  			binaryName = "jackal-mac-apple"
    45  		default:
    46  			binaryName = "jackal-mac-intel"
    47  		}
    48  	case "windows":
    49  		if runtime.GOARCH == "amd64" {
    50  			binaryName = "jackal.exe"
    51  		}
    52  	}
    53  	return binaryName
    54  }
    55  
    56  // SetupWithCluster performs actions for each test that requires a K8s cluster.
    57  func (e2e *JackalE2ETest) SetupWithCluster(t *testing.T) {
    58  	if !e2e.RunClusterTests {
    59  		t.Skip("")
    60  	}
    61  	_ = exec.CmdWithPrint("sh", "-c", fmt.Sprintf("%s tools kubectl describe nodes | grep -A 99 Non-terminated", e2e.JackalBinPath))
    62  }
    63  
    64  // Jackal executes a Jackal command.
    65  func (e2e *JackalE2ETest) Jackal(args ...string) (string, string, error) {
    66  	if !slices.Contains(args, "--tmpdir") && !slices.Contains(args, "tools") {
    67  		tmpdir, err := os.MkdirTemp("", "jackal-")
    68  		if err != nil {
    69  			return "", "", err
    70  		}
    71  		defer os.RemoveAll(tmpdir)
    72  		args = append(args, "--tmpdir", tmpdir)
    73  	}
    74  	if !slices.Contains(args, "--jackal-cache") && !slices.Contains(args, "tools") && os.Getenv("CI") == "true" {
    75  		// We make the cache dir relative to the working directory to make it work on the Windows Runners
    76  		// - they use two drives which filepath.Rel cannot cope with.
    77  		cwd, err := os.Getwd()
    78  		if err != nil {
    79  			return "", "", err
    80  		}
    81  		cacheDir, err := os.MkdirTemp(cwd, "jackal-")
    82  		if err != nil {
    83  			return "", "", err
    84  		}
    85  		args = append(args, "--jackal-cache", cacheDir)
    86  		defer os.RemoveAll(cacheDir)
    87  	}
    88  	return exec.CmdWithContext(context.TODO(), exec.PrintCfg(), e2e.JackalBinPath, args...)
    89  }
    90  
    91  // Kubectl executes `jackal tools kubectl ...`
    92  func (e2e *JackalE2ETest) Kubectl(args ...string) (string, string, error) {
    93  	tk := []string{"tools", "kubectl"}
    94  	args = append(tk, args...)
    95  	return e2e.Jackal(args...)
    96  }
    97  
    98  // CleanFiles removes files and directories that have been created during the test.
    99  func (e2e *JackalE2ETest) CleanFiles(files ...string) {
   100  	for _, file := range files {
   101  		_ = os.RemoveAll(file)
   102  	}
   103  }
   104  
   105  // GetMismatchedArch determines what architecture our tests are running on,
   106  // and returns the opposite architecture.
   107  func (e2e *JackalE2ETest) GetMismatchedArch() string {
   108  	switch e2e.Arch {
   109  	case "arm64":
   110  		return "amd64"
   111  	default:
   112  		return "arm64"
   113  	}
   114  }
   115  
   116  // GetLogFileContents gets the log file contents from a given run's std error.
   117  func (e2e *JackalE2ETest) GetLogFileContents(t *testing.T, stdErr string) string {
   118  	get, err := helpers.MatchRegex(logRegex, stdErr)
   119  	require.NoError(t, err)
   120  	logFile := get("logFile")
   121  	logContents, err := os.ReadFile(logFile)
   122  	require.NoError(t, err)
   123  	return string(logContents)
   124  }
   125  
   126  // SetupDockerRegistry uses the host machine's docker daemon to spin up a local registry for testing purposes.
   127  func (e2e *JackalE2ETest) SetupDockerRegistry(t *testing.T, port int) {
   128  	// spin up a local registry
   129  	registryImage := "registry:2.8.3"
   130  	err := exec.CmdWithPrint("docker", "run", "-d", "--restart=always", "-p", fmt.Sprintf("%d:5000", port), "--name", fmt.Sprintf("registry-%d", port), registryImage)
   131  	require.NoError(t, err)
   132  }
   133  
   134  // TeardownRegistry removes the local registry.
   135  func (e2e *JackalE2ETest) TeardownRegistry(t *testing.T, port int) {
   136  	// remove the local registry
   137  	err := exec.CmdWithPrint("docker", "rm", "-f", fmt.Sprintf("registry-%d", port))
   138  	require.NoError(t, err)
   139  }
   140  
   141  // GetJackalVersion returns the current build/jackal version
   142  func (e2e *JackalE2ETest) GetJackalVersion(t *testing.T) string {
   143  	// Get the version of the CLI
   144  	stdOut, stdErr, err := e2e.Jackal("version")
   145  	require.NoError(t, err, stdOut, stdErr)
   146  	return strings.Trim(stdOut, "\n")
   147  }
   148  
   149  // StripMessageFormatting strips any ANSI color codes and extra spaces from a given string
   150  func (e2e *JackalE2ETest) StripMessageFormatting(input string) string {
   151  	// Regex to strip any color codes from the output - https://regex101.com/r/YFyIwC/2
   152  	ansiRegex := regexp.MustCompile(`\x1b\[(.*?)m`)
   153  	unAnsiInput := ansiRegex.ReplaceAllString(input, "")
   154  	// Regex to strip any more than two spaces or newline - https://regex101.com/r/wqQmys/1
   155  	multiSpaceRegex := regexp.MustCompile(`\s{2,}|\n`)
   156  	return multiSpaceRegex.ReplaceAllString(unAnsiInput, " ")
   157  }
   158  
   159  // NormalizeYAMLFilenames normalizes YAML filenames / paths across Operating Systems (i.e Windows vs Linux)
   160  func (e2e *JackalE2ETest) NormalizeYAMLFilenames(input string) string {
   161  	if runtime.GOOS != "windows" {
   162  		return input
   163  	}
   164  
   165  	// Match YAML lines that have files in them https://regex101.com/r/C78kRD/1
   166  	fileMatcher := regexp.MustCompile(`^(?P<start>.* )(?P<file>[^:\n]+\/.*)$`)
   167  	scanner := bufio.NewScanner(strings.NewReader(input))
   168  
   169  	output := ""
   170  	for scanner.Scan() {
   171  		line := scanner.Text()
   172  		get, err := helpers.MatchRegex(fileMatcher, line)
   173  		if err != nil {
   174  			output += line + "\n"
   175  			continue
   176  		}
   177  		output += fmt.Sprintf("%s\"%s\"\n", get("start"), strings.ReplaceAll(get("file"), "/", "\\\\"))
   178  	}
   179  
   180  	return output
   181  }