mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
336 lines
11 KiB
Go
336 lines
11 KiB
Go
package helper
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/tidwall/gjson"
|
|
|
|
"github.com/redhat-developer/odo/pkg/preference"
|
|
"github.com/redhat-developer/odo/pkg/segment"
|
|
|
|
dfutil "github.com/devfile/library/pkg/util"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/onsi/gomega/gexec"
|
|
)
|
|
|
|
// RandString returns a random string of given length
|
|
func RandString(n int) string {
|
|
return dfutil.GenerateRandomString(n)
|
|
}
|
|
|
|
// WaitForCmdOut runs a command until it gets
|
|
// the expected output.
|
|
// It accepts 5 arguments, program (program to be run)
|
|
// args (arguments to the program)
|
|
// timeoutInMinutes (the time to wait for the output)
|
|
// errOnFail (flag to set if test should fail if command fails)
|
|
// check (function with output check logic)
|
|
// It times out if the command doesn't fetch the
|
|
// expected output within the timeout period.
|
|
func WaitForCmdOut(program string, args []string, timeoutInMinutes int, errOnFail bool, check func(output string) bool, includeStdErr ...bool) bool {
|
|
pingTimeout := time.After(time.Duration(timeoutInMinutes) * time.Minute)
|
|
// this is a test package so time.Tick() is acceptable
|
|
// nolint
|
|
tick := time.Tick(time.Second)
|
|
for {
|
|
select {
|
|
case <-pingTimeout:
|
|
Fail(fmt.Sprintf("Timeout after %v minutes", timeoutInMinutes))
|
|
|
|
case <-tick:
|
|
session := CmdRunner(program, args...)
|
|
if errOnFail {
|
|
Eventually(session).Should(gexec.Exit(0), runningCmd(session.Command))
|
|
} else {
|
|
Eventually(session).Should(gexec.Exit(), runningCmd(session.Command))
|
|
}
|
|
session.Wait()
|
|
output := string(session.Out.Contents())
|
|
|
|
if len(includeStdErr) > 0 && includeStdErr[0] {
|
|
output += "\n"
|
|
output += string(session.Err.Contents())
|
|
}
|
|
if check(strings.TrimSpace(output)) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MatchAllInOutput ensures all strings are in output
|
|
func MatchAllInOutput(output string, tomatch []string) {
|
|
for _, i := range tomatch {
|
|
Expect(output).To(ContainSubstring(i))
|
|
}
|
|
}
|
|
|
|
// DontMatchAllInOutput ensures all strings are not in output
|
|
func DontMatchAllInOutput(output string, tonotmatch []string) {
|
|
for _, i := range tonotmatch {
|
|
Expect(output).ToNot(ContainSubstring(i))
|
|
}
|
|
}
|
|
|
|
// Unindented returns the unindented version of the jsonStr passed to it
|
|
func Unindented(jsonStr string) (string, error) {
|
|
var tmpMap map[string]interface{}
|
|
err := json.Unmarshal([]byte(jsonStr), &tmpMap)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
obj, err := json.Marshal(tmpMap)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(obj), err
|
|
}
|
|
|
|
// ExtractLines returns all lines of the given `output` string
|
|
func ExtractLines(output string) ([]string, error) {
|
|
scanner := bufio.NewScanner(strings.NewReader(output))
|
|
lines := make([]string, 0)
|
|
for scanner.Scan() {
|
|
lines = append(lines, scanner.Text())
|
|
}
|
|
return lines, scanner.Err()
|
|
}
|
|
|
|
// FindFirstElementIndexByPredicate returns the index of the first element in `slice` that satisfies the given `predicate`.
|
|
func FindFirstElementIndexByPredicate(slice []string, predicate func(string) bool) (int, bool) {
|
|
for i, s := range slice {
|
|
if predicate(s) {
|
|
return i, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
// FindFirstElementIndexMatchingRegExp returns the index of the first element in `slice` that contains any match of
|
|
// the given regular expression `regularExpression`.
|
|
func FindFirstElementIndexMatchingRegExp(slice []string, regularExpression string) (int, bool) {
|
|
return FindFirstElementIndexByPredicate(slice, func(s string) bool {
|
|
matched, err := regexp.MatchString(regularExpression, s)
|
|
Expect(err).To(BeNil(), func() string {
|
|
return fmt.Sprintf("regular expression error: %v", err)
|
|
})
|
|
return matched
|
|
})
|
|
}
|
|
|
|
// GetUserHomeDir gets the user home directory
|
|
func GetUserHomeDir() string {
|
|
homeDir, err := os.UserHomeDir()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return homeDir
|
|
}
|
|
|
|
// LocalKubeconfigSet sets the KUBECONFIG to the temporary config file
|
|
func LocalKubeconfigSet(context string) {
|
|
originalKubeCfg := os.Getenv("KUBECONFIG")
|
|
if originalKubeCfg == "" {
|
|
homeDir := GetUserHomeDir()
|
|
originalKubeCfg = filepath.Join(homeDir, ".kube", "config")
|
|
}
|
|
copyKubeConfigFile(originalKubeCfg, filepath.Join(context, "config"))
|
|
}
|
|
|
|
// GetCliRunner gets the running cli against Kubernetes or OpenShift
|
|
func GetCliRunner() CliRunner {
|
|
if IsKubernetesCluster() {
|
|
return NewKubectlRunner("kubectl")
|
|
}
|
|
return NewOcRunner("oc")
|
|
}
|
|
|
|
// IsJSON returns true if a string is in json format
|
|
func IsJSON(s string) bool {
|
|
var js interface{}
|
|
return json.Unmarshal([]byte(s), &js) == nil
|
|
}
|
|
|
|
type CommonVar struct {
|
|
// Project is new clean project/namespace for each test
|
|
Project string
|
|
// Context is a new temporary directory
|
|
Context string
|
|
// ConfigDir is a new temporary directory
|
|
ConfigDir string
|
|
// CliRunner is program command (oc or kubectl runner) according to cluster
|
|
CliRunner CliRunner
|
|
// original values to get restored after the test is done
|
|
OriginalWorkingDirectory string
|
|
OriginalKubeconfig string
|
|
// Ginkgo test realted
|
|
testFileName string
|
|
testCase string
|
|
testFailed bool
|
|
testDuration float64
|
|
}
|
|
|
|
const SetupClusterTrue = true
|
|
const SetupClusterFalse = false
|
|
|
|
// CommonBeforeEach is common function runs before every test Spec (It)
|
|
// returns CommonVar values that are used within the test script
|
|
func CommonBeforeEach(setupCluster bool) CommonVar {
|
|
SetDefaultEventuallyTimeout(10 * time.Minute)
|
|
SetDefaultConsistentlyDuration(30 * time.Second)
|
|
|
|
commonVar := CommonVar{}
|
|
commonVar.Context = CreateNewContext()
|
|
commonVar.ConfigDir = CreateNewContext()
|
|
commonVar.CliRunner = GetCliRunner()
|
|
commonVar.OriginalKubeconfig = os.Getenv("KUBECONFIG")
|
|
if setupCluster {
|
|
LocalKubeconfigSet(commonVar.ConfigDir)
|
|
commonVar.Project = commonVar.CliRunner.CreateAndSetRandNamespaceProject()
|
|
}
|
|
commonVar.OriginalWorkingDirectory = Getwd()
|
|
os.Setenv("GLOBALODOCONFIG", filepath.Join(commonVar.ConfigDir, "preference.yaml"))
|
|
// Set ConsentTelemetry to false so that it does not prompt to set a preference value
|
|
cfg, _ := preference.NewClient()
|
|
err := cfg.SetConfiguration(preference.ConsentTelemetrySetting, "false")
|
|
Expect(err).To(BeNil())
|
|
// Use ephemeral volumes (emptyDir) in tests to make test faster
|
|
err = cfg.SetConfiguration(preference.EphemeralSetting, "true")
|
|
Expect(err).To(BeNil())
|
|
SetDefaultDevfileRegistryAsStaging()
|
|
return commonVar
|
|
}
|
|
|
|
// CommonAfterEach is common function that cleans up after every test Spec (It)
|
|
func CommonAfterEach(commonVar CommonVar) {
|
|
// Get details, including test filename, test case name, test result, and test duration for each test spec and adds it to local testResults.txt file
|
|
// Ginkgo test related variables
|
|
commonVar.testFileName = CurrentSpecReport().ContainerHierarchyLocations[0].FileName
|
|
commonVar.testCase = CurrentSpecReport().FullText()
|
|
commonVar.testFailed = CurrentSpecReport().Failed()
|
|
commonVar.testDuration = CurrentSpecReport().RunTime.Seconds()
|
|
|
|
var prNum string
|
|
var resultsRow string
|
|
prNum = os.Getenv("GIT_PR_NUMBER")
|
|
passedOrFailed := "PASSED"
|
|
if commonVar.testFailed {
|
|
passedOrFailed = "FAILED"
|
|
}
|
|
clusterType := "OCP"
|
|
if IsKubernetesCluster() {
|
|
clusterType = "KUBERNETES"
|
|
}
|
|
testDate := strings.Split(time.Now().Format(time.RFC3339), "T")[0]
|
|
resultsRow = prNum + "," + testDate + "," + clusterType + "," + commonVar.testFileName + "," + commonVar.testCase + "," + passedOrFailed + "," + strconv.FormatFloat(commonVar.testDuration, 'E', -1, 64) + "\n"
|
|
testResultsFile := filepath.Join("/", "tmp", "testResults.txt")
|
|
if runtime.GOOS == "windows" {
|
|
testResultsFile = filepath.Join(os.Getenv("TEMP"), "testResults.txt")
|
|
}
|
|
f, err := os.OpenFile(testResultsFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
|
if err != nil {
|
|
fmt.Println("Error when opening file: ", err)
|
|
} else {
|
|
_, err = f.WriteString(resultsRow)
|
|
if err != nil {
|
|
fmt.Println("Error when writing to file: ", err)
|
|
}
|
|
if err = f.Close(); err != nil {
|
|
fmt.Println("Error when closing file: ", err)
|
|
}
|
|
}
|
|
|
|
if commonVar.Project != "" {
|
|
// delete the random project/namespace created in CommonBeforeEach
|
|
commonVar.CliRunner.DeleteNamespaceProject(commonVar.Project, false)
|
|
}
|
|
// restores the original kubeconfig and working directory
|
|
Chdir(commonVar.OriginalWorkingDirectory)
|
|
err = os.Setenv("KUBECONFIG", commonVar.OriginalKubeconfig)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// delete the temporary context directory
|
|
DeleteDir(commonVar.Context)
|
|
DeleteDir(commonVar.ConfigDir)
|
|
|
|
os.Unsetenv("GLOBALODOCONFIG")
|
|
}
|
|
|
|
// JsonPathContentIs expects that the content of the path to equal value
|
|
func JsonPathContentIs(json string, path string, value string) {
|
|
result := gjson.Get(json, path)
|
|
Expect(result.String()).To(Equal(value), fmt.Sprintf("content of path %q should be %q but is %q", path, value, result.String()))
|
|
}
|
|
|
|
// JsonPathContentContain expects that the content of the path to contain value
|
|
func JsonPathContentContain(json string, path string, value string) {
|
|
result := gjson.Get(json, path)
|
|
Expect(result.String()).To(ContainSubstring(value), fmt.Sprintf("content of path %q should contain %q but is %q", path, value, result.String()))
|
|
}
|
|
|
|
// JsonPathDoesNotExist expects that the content of the path does not exist in the JSON string
|
|
func JsonPathDoesNotExist(json string, path string) {
|
|
result := gjson.Get(json, path)
|
|
Expect(result.Exists()).To(BeFalse(),
|
|
fmt.Sprintf("content should not contain %q but is %q", path, result.String()))
|
|
}
|
|
|
|
func JsonPathContentIsValidUserPort(json string, path string) {
|
|
result := gjson.Get(json, path)
|
|
intVal, err := strconv.Atoi(result.String())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(intVal).To(SatisfyAll(
|
|
BeNumerically(">=", 1024),
|
|
BeNumerically("<=", 65535),
|
|
))
|
|
}
|
|
|
|
// SetProjectName sets projectNames based on the name of the test file name (without path and replacing _ with -), line number of current ginkgo execution, and a random string of 3 letters
|
|
func SetProjectName() string {
|
|
//Get current test filename and remove file path, file extension and replace undescores with hyphens
|
|
currGinkgoTestFileName := strings.Replace(strings.Split(strings.Split(CurrentSpecReport().
|
|
ContainerHierarchyLocations[0].FileName, "/")[len(strings.Split(CurrentSpecReport().ContainerHierarchyLocations[0].FileName, "/"))-1], ".")[0], "_", "-", -1)
|
|
currGinkgoTestLineNum := fmt.Sprint(CurrentSpecReport().LineNumber())
|
|
projectName := currGinkgoTestFileName + currGinkgoTestLineNum + RandString(3)
|
|
return projectName
|
|
}
|
|
|
|
// RunTestSpecs defines a common way how test specs in test suite are executed
|
|
func RunTestSpecs(t *testing.T, description string) {
|
|
os.Setenv(segment.TrackingConsentEnv, "no")
|
|
RegisterFailHandler(Fail)
|
|
RunSpecs(t, description)
|
|
}
|
|
|
|
func IsKubernetesCluster() bool {
|
|
return os.Getenv("KUBERNETES") == "true"
|
|
}
|
|
|
|
type ResourceInfo struct {
|
|
ResourceType string
|
|
ResourceName string
|
|
Namespace string
|
|
}
|
|
|
|
func SetDefaultDevfileRegistryAsStaging() {
|
|
const registryName string = "DefaultDevfileRegistry"
|
|
addRegistryURL := "https://registry.stage.devfile.io"
|
|
proxy := os.Getenv("DEVFILE_PROXY")
|
|
if proxy != "" {
|
|
addRegistryURL = "http://" + proxy
|
|
}
|
|
Cmd("odo", "preference", "remove", "registry", registryName, "-f").ShouldPass()
|
|
Cmd("odo", "preference", "add", "registry", registryName, addRegistryURL).ShouldPass()
|
|
}
|