...

Source file src/github.com/redhat-developer/odo/tests/helper/helper_generic.go

Documentation: github.com/redhat-developer/odo/tests/helper

     1  package helper
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"runtime"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/onsi/gomega/types"
    18  
    19  	"github.com/tidwall/gjson"
    20  
    21  	"github.com/redhat-developer/odo/pkg/config"
    22  	envcontext "github.com/redhat-developer/odo/pkg/config/context"
    23  	"github.com/redhat-developer/odo/pkg/preference"
    24  	"github.com/redhat-developer/odo/pkg/segment"
    25  	"github.com/redhat-developer/odo/tests/helper/registry_server"
    26  
    27  	dfutil "github.com/devfile/library/v2/pkg/util"
    28  
    29  	. "github.com/onsi/ginkgo/v2"
    30  	. "github.com/onsi/gomega"
    31  	"github.com/onsi/gomega/gexec"
    32  )
    33  
    34  // RandString returns a random string of given length
    35  func RandString(n int) string {
    36  	return dfutil.GenerateRandomString(n)
    37  }
    38  
    39  // WaitForCmdOut runs a command until it gets
    40  // the expected output.
    41  // It accepts 5 arguments, program (program to be run)
    42  // args (arguments to the program)
    43  // timeoutInMinutes (the time to wait for the output)
    44  // errOnFail (flag to set if test should fail if command fails)
    45  // check (function with output check logic)
    46  // It times out if the command doesn't fetch the
    47  // expected output  within the timeout period.
    48  func WaitForCmdOut(program string, args []string, timeoutInMinutes int, errOnFail bool, check func(output string) bool, includeStdErr ...bool) bool {
    49  	pingTimeout := time.After(time.Duration(timeoutInMinutes) * time.Minute)
    50  	// this is a test package so time.Tick() is acceptable
    51  	// nolint
    52  	tick := time.Tick(time.Second)
    53  	for {
    54  		select {
    55  		case <-pingTimeout:
    56  			Fail(fmt.Sprintf("Timeout after %v minutes", timeoutInMinutes))
    57  
    58  		case <-tick:
    59  			session := CmdRunner(program, args...)
    60  			if errOnFail {
    61  				Eventually(session).Should(gexec.Exit(0), runningCmd(session.Command))
    62  			} else {
    63  				Eventually(session).Should(gexec.Exit(), runningCmd(session.Command))
    64  			}
    65  			session.Wait()
    66  			output := string(session.Out.Contents())
    67  
    68  			if len(includeStdErr) > 0 && includeStdErr[0] {
    69  				output += "\n"
    70  				output += string(session.Err.Contents())
    71  			}
    72  			if check(strings.TrimSpace(output)) {
    73  				return true
    74  			}
    75  		}
    76  	}
    77  }
    78  
    79  // MatchAllInOutput ensures all strings are in output
    80  func MatchAllInOutput(output string, tomatch []string) {
    81  	for _, i := range tomatch {
    82  		Expect(output).To(ContainSubstring(i))
    83  	}
    84  }
    85  
    86  // DontMatchAllInOutput ensures all strings are not in output
    87  func DontMatchAllInOutput(output string, tonotmatch []string) {
    88  	for _, i := range tonotmatch {
    89  		Expect(output).ToNot(ContainSubstring(i))
    90  	}
    91  }
    92  
    93  // Unindented returns the unindented version of the jsonStr passed to it
    94  func Unindented(jsonStr string) (string, error) {
    95  	var tmpMap map[string]interface{}
    96  	err := json.Unmarshal([]byte(jsonStr), &tmpMap)
    97  	if err != nil {
    98  		return "", err
    99  	}
   100  
   101  	obj, err := json.Marshal(tmpMap)
   102  	if err != nil {
   103  		return "", err
   104  	}
   105  	return string(obj), err
   106  }
   107  
   108  // ExtractLines returns all lines of the given `output` string
   109  func ExtractLines(output string) ([]string, error) {
   110  	scanner := bufio.NewScanner(strings.NewReader(output))
   111  	lines := make([]string, 0)
   112  	for scanner.Scan() {
   113  		lines = append(lines, scanner.Text())
   114  	}
   115  	return lines, scanner.Err()
   116  }
   117  
   118  // FindFirstElementIndexByPredicate returns the index of the first element in `slice` that satisfies the given `predicate`.
   119  func FindFirstElementIndexByPredicate(slice []string, predicate func(string) bool) (int, bool) {
   120  	for i, s := range slice {
   121  		if predicate(s) {
   122  			return i, true
   123  		}
   124  	}
   125  	return 0, false
   126  }
   127  
   128  // FindFirstElementIndexMatchingRegExp returns the index of the first element in `slice` that contains any match of
   129  // the given regular expression `regularExpression`.
   130  func FindFirstElementIndexMatchingRegExp(slice []string, regularExpression string) (int, bool) {
   131  	return FindFirstElementIndexByPredicate(slice, func(s string) bool {
   132  		matched, err := regexp.MatchString(regularExpression, s)
   133  		Expect(err).To(BeNil(), func() string {
   134  			return fmt.Sprintf("regular expression error: %v", err)
   135  		})
   136  		return matched
   137  	})
   138  }
   139  
   140  // GetUserHomeDir gets the user home directory
   141  func GetUserHomeDir() string {
   142  	homeDir, err := os.UserHomeDir()
   143  	Expect(err).NotTo(HaveOccurred())
   144  	return homeDir
   145  }
   146  
   147  // LocalKubeconfigSet sets the KUBECONFIG to the temporary config file
   148  func LocalKubeconfigSet(context string) {
   149  	originalKubeCfg := os.Getenv("KUBECONFIG")
   150  	if originalKubeCfg == "" {
   151  		homeDir := GetUserHomeDir()
   152  		originalKubeCfg = filepath.Join(homeDir, ".kube", "config")
   153  	}
   154  	copyKubeConfigFile(originalKubeCfg, filepath.Join(context, "config"))
   155  }
   156  
   157  // GetCliRunner gets the running cli against Kubernetes or OpenShift
   158  func GetCliRunner() CliRunner {
   159  	if IsKubernetesCluster() {
   160  		return NewKubectlRunner("kubectl")
   161  	}
   162  	return NewOcRunner("oc")
   163  }
   164  
   165  // IsJSON returns true if a string is in json format
   166  func IsJSON(s string) bool {
   167  	var js interface{}
   168  	return json.Unmarshal([]byte(s), &js) == nil
   169  }
   170  
   171  type CommonVar struct {
   172  	// Project is new clean project/namespace for each test
   173  	Project string
   174  	// Context is a new temporary directory
   175  	Context string
   176  	// ConfigDir is a new temporary directory
   177  	ConfigDir string
   178  	// CliRunner is program command (oc or kubectl runner) according to cluster
   179  	CliRunner CliRunner
   180  	// original values to get restored after the test is done
   181  	OriginalWorkingDirectory string
   182  	OriginalKubeconfig       string
   183  	registryServer           RegistryServer
   184  	registryUrl              string
   185  	// Ginkgo test realted
   186  	testFileName string
   187  	testCase     string
   188  	testFailed   bool
   189  	testDuration float64
   190  }
   191  
   192  // CommonBeforeEach is common function runs before every test Spec (It)
   193  // returns CommonVar values that are used within the test script
   194  func CommonBeforeEach() CommonVar {
   195  	SetDefaultEventuallyTimeout(10 * time.Minute)
   196  	SetDefaultConsistentlyDuration(30 * time.Second)
   197  
   198  	commonVar := CommonVar{}
   199  	commonVar.Context = CreateNewContext()
   200  	commonVar.ConfigDir = CreateNewContext()
   201  	commonVar.CliRunner = GetCliRunner()
   202  	commonVar.OriginalKubeconfig = os.Getenv("KUBECONFIG")
   203  	specLabels := CurrentSpecReport().Labels()
   204  	if NeedsCluster(specLabels) {
   205  		LocalKubeconfigSet(commonVar.ConfigDir)
   206  		if IsAuth(specLabels) {
   207  			commonVar.Project = commonVar.CliRunner.CreateAndSetRandNamespaceProject()
   208  		} else {
   209  			commonVar.CliRunner.AssertNonAuthenticated()
   210  		}
   211  	} else {
   212  		// Disable the use of in-cluster configuration (seen in IBM Cloud pipeline)
   213  		os.Unsetenv("KUBERNETES_SERVICE_HOST")
   214  		// Create an empty kubeconfig file in the config dir and point KUBECONFIG to this file
   215  		kubeconfig, err := os.CreateTemp(commonVar.ConfigDir, "kubeconfig")
   216  		Expect(err).To(BeNil())
   217  		err = kubeconfig.Close()
   218  		Expect(err).To(BeNil())
   219  		os.Setenv("KUBECONFIG", kubeconfig.Name())
   220  
   221  		if NeedsPodman(specLabels) {
   222  			originalPodmanCmdInitTimeout, present := os.LookupEnv("PODMAN_CMD_INIT_TIMEOUT")
   223  			DeferCleanup(func() {
   224  				var resetErr error
   225  				if present {
   226  					resetErr = os.Setenv("PODMAN_CMD_INIT_TIMEOUT", originalPodmanCmdInitTimeout)
   227  				} else {
   228  					resetErr = os.Unsetenv("PODMAN_CMD_INIT_TIMEOUT")
   229  				}
   230  				Expect(resetErr).ShouldNot(HaveOccurred())
   231  			})
   232  			Expect(os.Setenv("PODMAN_CMD_INIT_TIMEOUT", "30s")).ShouldNot(HaveOccurred())
   233  
   234  			// Generate a dedicated containers.conf with a specific namespace
   235  			GenerateAndSetContainersConf(commonVar.ConfigDir)
   236  		}
   237  	}
   238  	commonVar.OriginalWorkingDirectory = Getwd()
   239  	Chdir(commonVar.Context)
   240  
   241  	configPath := filepath.Join(commonVar.ConfigDir, "preference.yaml")
   242  	os.Setenv("GLOBALODOCONFIG", configPath)
   243  
   244  	// Create context with env var configuration
   245  	ctx := context.Background()
   246  	envConfig, err := config.GetConfiguration()
   247  	Expect(err).To(BeNil())
   248  	ctx = envcontext.WithEnvConfig(ctx, *envConfig)
   249  
   250  	// Set ConsentTelemetry to false so that it does not prompt to set a preference value
   251  	cfg, _ := preference.NewClient(ctx)
   252  	err = cfg.SetConfiguration(preference.ConsentTelemetrySetting, "false")
   253  	Expect(err).To(BeNil())
   254  
   255  	// Set UpdateNotification to false so that it does not display notifications related to updates
   256  	err = cfg.SetConfiguration(preference.UpdateNotificationSetting, "false")
   257  	Expect(err).To(BeNil())
   258  
   259  	// Use ephemeral volumes (emptyDir) in tests to make test faster
   260  	err = cfg.SetConfiguration(preference.EphemeralSetting, "true")
   261  	Expect(err).To(BeNil())
   262  	SetDefaultDevfileRegistry(&commonVar)
   263  	return commonVar
   264  }
   265  
   266  // CommonAfterEach is common function that cleans up after every test Spec (It)
   267  func CommonAfterEach(commonVar CommonVar) {
   268  	// 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
   269  	// Ginkgo test related variables
   270  	commonVar.testFileName = CurrentSpecReport().ContainerHierarchyLocations[0].FileName
   271  	commonVar.testCase = CurrentSpecReport().FullText()
   272  	commonVar.testFailed = CurrentSpecReport().Failed()
   273  	commonVar.testDuration = CurrentSpecReport().RunTime.Seconds()
   274  
   275  	var prNum string
   276  	var resultsRow string
   277  	prNum = os.Getenv("GIT_PR_NUMBER")
   278  	passedOrFailed := "PASSED"
   279  	if commonVar.testFailed {
   280  		passedOrFailed = "FAILED"
   281  	}
   282  	clusterType := "OCP"
   283  	if IsKubernetesCluster() {
   284  		clusterType = "KUBERNETES"
   285  	}
   286  	testDate := strings.Split(time.Now().Format(time.RFC3339), "T")[0]
   287  	resultsRow = prNum + "," + testDate + "," + clusterType + "," + commonVar.testFileName + "," + commonVar.testCase + "," + passedOrFailed + "," + strconv.FormatFloat(commonVar.testDuration, 'E', -1, 64) + "\n"
   288  	testResultsFile := filepath.Join("/", "tmp", "testResults.txt")
   289  	if runtime.GOOS == "windows" {
   290  		testResultsFile = filepath.Join(os.Getenv("TEMP"), "testResults.txt")
   291  	}
   292  	f, err := os.OpenFile(testResultsFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
   293  	if err != nil {
   294  		fmt.Println("Error when opening file: ", err)
   295  	} else {
   296  		_, err = f.WriteString(resultsRow)
   297  		if err != nil {
   298  			fmt.Println("Error when writing to file: ", err)
   299  		}
   300  		if err = f.Close(); err != nil {
   301  			fmt.Println("Error when closing file: ", err)
   302  		}
   303  	}
   304  
   305  	if commonVar.registryServer != nil {
   306  		err = commonVar.registryServer.Stop()
   307  		if err != nil {
   308  			fmt.Fprintf(GinkgoWriter, "[warn] failed to stop mock registry server at %q: %v\n", commonVar.registryServer.GetUrl(), err)
   309  		}
   310  		commonVar.registryServer = nil
   311  		commonVar.registryUrl = ""
   312  	}
   313  
   314  	if commonVar.Project != "" && commonVar.CliRunner.HasNamespaceProject(commonVar.Project) {
   315  		// delete the random project/namespace created in CommonBeforeEach
   316  		commonVar.CliRunner.DeleteNamespaceProject(commonVar.Project, false)
   317  	}
   318  	// restores the original kubeconfig and working directory
   319  	Chdir(commonVar.OriginalWorkingDirectory)
   320  	err = os.Setenv("KUBECONFIG", commonVar.OriginalKubeconfig)
   321  	Expect(err).NotTo(HaveOccurred())
   322  
   323  	// delete the temporary context directory
   324  	DeleteDir(commonVar.Context)
   325  	DeleteDir(commonVar.ConfigDir)
   326  
   327  	os.Unsetenv("GLOBALODOCONFIG")
   328  }
   329  
   330  // JsonPathContentIs expects that the content of the path to equal value
   331  func JsonPathContentIs(json string, path string, value string) {
   332  	result := gjson.Get(json, path)
   333  	Expect(result.String()).To(Equal(value), fmt.Sprintf("content of path %q should be %q but is %q", path, value, result.String()))
   334  }
   335  
   336  // JsonPathContentContain expects that the content of the path to contain value
   337  func JsonPathContentContain(json string, path string, value string) {
   338  	result := gjson.Get(json, path)
   339  	Expect(result.String()).To(ContainSubstring(value), fmt.Sprintf("content of path %q should contain %q but is %q", path, value, result.String()))
   340  }
   341  
   342  // JsonPathSatisfiesAll expects content of the path to satisfy all the matchers passed to it
   343  func JsonPathSatisfiesAll(json string, path string, matchers ...types.GomegaMatcher) {
   344  	result := gjson.Get(json, path)
   345  	Expect(result.String()).Should(SatisfyAll(matchers...))
   346  }
   347  
   348  // JsonPathDoesNotExist expects that the content of the path does not exist in the JSON string
   349  func JsonPathDoesNotExist(json string, path string) {
   350  	result := gjson.Get(json, path)
   351  	Expect(result.Exists()).To(BeFalse(),
   352  		fmt.Sprintf("content should not contain %q but is %q", path, result.String()))
   353  }
   354  
   355  // JsonPathExist expects that the content of the path exists in the JSON string
   356  func JsonPathExist(json string, path string) {
   357  	result := gjson.Get(json, path)
   358  	Expect(result.Exists()).To(BeTrue(),
   359  		fmt.Sprintf("content should contain %q", path))
   360  }
   361  func JsonPathContentIsValidUserPort(json string, path string) {
   362  	result := gjson.Get(json, path)
   363  	intVal, err := strconv.Atoi(result.String())
   364  	Expect(err).ToNot(HaveOccurred())
   365  	Expect(intVal).To(SatisfyAll(
   366  		BeNumerically(">=", 1024),
   367  		BeNumerically("<=", 65535),
   368  	))
   369  }
   370  
   371  func JsonPathContentHasLen(json string, path string, len int) {
   372  	result := gjson.Get(json, path+".#")
   373  	intVal, err := strconv.Atoi(result.String())
   374  	Expect(err).ToNot(HaveOccurred())
   375  	Expect(intVal).To(Equal(len), fmt.Sprintf("%q should contain exactly %d elements", path, len))
   376  }
   377  
   378  // GenerateProjectName generates a new projectName 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
   379  func GenerateProjectName() string {
   380  	//Get current test filename and remove file path, file extension and replace undescores with hyphens
   381  	currGinkgoTestFileName := strings.Replace(strings.Split(strings.Split(CurrentSpecReport().
   382  		ContainerHierarchyLocations[0].FileName, "/")[len(strings.Split(CurrentSpecReport().ContainerHierarchyLocations[0].FileName, "/"))-1], ".")[0], "_", "-", -1)
   383  	currGinkgoTestLineNum := fmt.Sprint(CurrentSpecReport().LineNumber())
   384  	projectName := currGinkgoTestFileName + currGinkgoTestLineNum + RandString(3)
   385  	return projectName
   386  }
   387  
   388  // RunTestSpecs defines a common way how test specs in test suite are executed
   389  func RunTestSpecs(t *testing.T, description string) {
   390  	os.Setenv(segment.TrackingConsentEnv, "no")
   391  	RegisterFailHandler(Fail)
   392  	RunSpecs(t, description)
   393  }
   394  
   395  func IsKubernetesCluster() bool {
   396  	k8s, err := strconv.ParseBool(os.Getenv("KUBERNETES"))
   397  	if err != nil {
   398  		return false
   399  	}
   400  	return k8s
   401  }
   402  
   403  type ResourceInfo struct {
   404  	ResourceType string
   405  	ResourceName string
   406  	Namespace    string
   407  }
   408  
   409  func SetDefaultDevfileRegistry(commonVar *CommonVar) {
   410  	commonVar.registryUrl = os.Getenv("DEVFILE_REGISTRY")
   411  	if commonVar.registryUrl == "" {
   412  		commonVar.registryServer = registry_server.NewMockRegistryServer()
   413  		var err error
   414  		commonVar.registryUrl, err = commonVar.registryServer.Start()
   415  		Expect(err).ShouldNot(HaveOccurred())
   416  	}
   417  	fmt.Printf("Using Devfile Registry URL at: %q\n", commonVar.registryUrl)
   418  
   419  	const registryName string = "DefaultDevfileRegistry"
   420  	Cmd("odo", "preference", "remove", "registry", registryName, "-f").ShouldPass()
   421  	Cmd("odo", "preference", "add", "registry", registryName, commonVar.registryUrl).ShouldPass()
   422  }
   423  
   424  func (c CommonVar) GetDevfileRegistryURL() string {
   425  	return c.registryUrl
   426  }
   427  
   428  func GetOdoVersion() (version string, gitCommit string) {
   429  	odoVersion := Cmd("odo", "version", "--client", "-o", "json").ShouldPass().Out()
   430  	return gjson.Get(odoVersion, "version").String(), gjson.Get(odoVersion, "gitCommit").String()
   431  }
   432  

View as plain text