github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/pkg/test/framework/ginkgo_wrapper.go (about)

     1  // Copyright (c) 2021, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package framework
     5  
     6  import (
     7  	"fmt"
     8  	"github.com/onsi/gomega"
     9  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test"
    10  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework/metrics"
    11  	"go.uber.org/zap"
    12  	"os"
    13  	"reflect"
    14  
    15  	"github.com/onsi/ginkgo/v2"
    16  	"github.com/verrazzano/verrazzano/tests/e2e/pkg"
    17  )
    18  
    19  type TestFramework struct {
    20  	Pkg     string
    21  	Metrics *zap.SugaredLogger
    22  	Logs    *zap.SugaredLogger
    23  }
    24  
    25  func NewTestFramework(pkg string) *TestFramework {
    26  	t := new(TestFramework)
    27  
    28  	t.Pkg = pkg
    29  
    30  	logLevel, isSet := os.LookupEnv("FRAMEWORK_LOG_LEVEL")
    31  	if !isSet {
    32  		logLevel = "info"
    33  	}
    34  	t.Metrics, _ = metrics.NewLogger(pkg, metrics.MetricsIndex, logLevel)
    35  	t.Logs, _ = metrics.NewLogger(pkg, metrics.TestLogIndex, logLevel, "stdout")
    36  
    37  	t.initDumpDirectoryIfNecessary()
    38  	return t
    39  }
    40  
    41  func (t *TestFramework) RegisterFailHandler() {
    42  	gomega.RegisterFailHandler(t.Fail)
    43  }
    44  
    45  // initDumpDirectoryIfNecessary - sets the DUMP_DIRECTORY env variable to a default if not set externally
    46  func (t *TestFramework) initDumpDirectoryIfNecessary() {
    47  	if _, dumpDirIsSet := os.LookupEnv(test.DumpDirectoryEnvVarName); !dumpDirIsSet {
    48  		dumpDirectory := t.Pkg
    49  		dumpRoot, exists := os.LookupEnv(test.DumpRootDirectoryEnvVarName)
    50  		if exists {
    51  			dumpDirectory = fmt.Sprintf("%s/%s", dumpRoot, t.Pkg)
    52  		}
    53  		t.Logs.Infof("Defaulting %s to %s", test.DumpDirectoryEnvVarName, dumpDirectory)
    54  		os.Setenv(test.DumpDirectoryEnvVarName, dumpDirectory)
    55  	}
    56  }
    57  
    58  // AfterEach wraps Ginkgo AfterEach to emit a metric
    59  func (t *TestFramework) AfterEach(args ...interface{}) bool {
    60  	body := getFunctionBody(args...)
    61  	f := func() {
    62  		metrics.Emit(t.Metrics.With(metrics.Duration, metrics.DurationMillis()))
    63  		reflect.ValueOf(body).Call([]reflect.Value{})
    64  	}
    65  	args[0] = f
    66  
    67  	return ginkgo.AfterEach(args...)
    68  }
    69  
    70  // BeforeEach wraps Ginkgo BeforeEach to emit a metric
    71  func (t *TestFramework) BeforeEach(args ...interface{}) bool {
    72  	body := getFunctionBody(args...)
    73  	f := func() {
    74  		reflect.ValueOf(body).Call([]reflect.Value{})
    75  	}
    76  	args[0] = f
    77  	return ginkgo.BeforeEach(args...)
    78  }
    79  
    80  // It wraps Ginkgo It to emit a metric
    81  func (t *TestFramework) It(text string, args ...interface{}) bool {
    82  	return ginkgo.It(text, t.MakeGinkgoArgs(args...)...)
    83  }
    84  
    85  func (t *TestFramework) WhenMeetsConditionFunc(condition string, conditionFunc func() (bool, error)) func(string, ...interface{}) bool {
    86  	return func(text string, args ...interface{}) bool {
    87  		met, err := conditionFunc()
    88  		if err != nil {
    89  			t.Logs.Errorf("Error checking condition %s: %v", condition, err)
    90  			return false
    91  		}
    92  		if !met {
    93  			t.Logs.Infof("Skipping test because condition is not met: %s", condition)
    94  			return true
    95  		}
    96  		return ginkgo.It(text, t.MakeGinkgoArgs(args...)...)
    97  	}
    98  }
    99  
   100  func (t *TestFramework) ItMinimumVersion(text string, version string, kubeconfigPath string, args ...interface{}) bool {
   101  	supported, err := pkg.IsVerrazzanoMinVersion(version, kubeconfigPath)
   102  	if err != nil {
   103  		t.Logs.Errorf("Error getting Verrazzano version: %v", err)
   104  		return false
   105  	}
   106  	if !supported {
   107  		t.Logs.Infof("Skipping test because Verrazzano version is less than %s", version)
   108  		return true
   109  	}
   110  	return ginkgo.It(text, t.MakeGinkgoArgs(args...)...)
   111  }
   112  
   113  func (t *TestFramework) MakeGinkgoArgs(args ...interface{}) []interface{} {
   114  	body := getFunctionBodyPos(len(args)-1, args...)
   115  	f := func() {
   116  		metrics.Emit(t.Metrics) // Starting point metric
   117  		reflect.ValueOf(body).Call([]reflect.Value{})
   118  	}
   119  
   120  	args[len(args)-1] = ginkgo.Offset(1)
   121  	args = append(args, f)
   122  	return args
   123  }
   124  
   125  // Describe wraps Ginkgo Describe to emit a metric
   126  func (t *TestFramework) Describe(text string, args ...interface{}) bool {
   127  	body := getFunctionBodyPos(len(args)-1, args...)
   128  	f := func() {
   129  		metrics.Emit(t.Metrics)
   130  		reflect.ValueOf(body).Call([]reflect.Value{})
   131  		metrics.Emit(t.Metrics.With(metrics.Duration, metrics.DurationMillis()))
   132  	}
   133  	args[len(args)-1] = ginkgo.Offset(1)
   134  	args = append(args, f)
   135  	return ginkgo.Describe(text, args...)
   136  }
   137  
   138  // DescribeTable - wrapper function for Ginkgo DescribeTable
   139  func (t *TestFramework) DescribeTable(text string, args ...interface{}) bool {
   140  	body := getFunctionBody(args...)
   141  	funcType := reflect.TypeOf(body)
   142  	f := reflect.MakeFunc(funcType, func(args []reflect.Value) (results []reflect.Value) {
   143  		metrics.Emit(t.Metrics)
   144  		rv := reflect.ValueOf(body).Call(args)
   145  		metrics.Emit(t.Metrics.With(metrics.Duration, metrics.DurationMillis()))
   146  		return rv
   147  	})
   148  	args[0] = f.Interface()
   149  	return ginkgo.DescribeTable(text, args...)
   150  }
   151  
   152  // BeforeSuiteFunc wrap a function to be called with ginkgo.BeforeSuiteFunc. ginkgo.BeforeSuiteFunc
   153  // // hard codes the call stack location, which requires calling it from the package level.
   154  func (t *TestFramework) BeforeSuiteFunc(body func()) func() {
   155  	t.failIfNilBody(body)
   156  	f := func() {
   157  		metrics.Emit(t.Metrics)
   158  		reflect.ValueOf(body).Call([]reflect.Value{})
   159  	}
   160  	return f
   161  }
   162  
   163  // AfterSuiteFunc wrap a function to be called with ginkgo.AfterSuiteFunc. ginkgo.AfterSuiteFunc
   164  // hard codes the call stack location, which requires calling it from the package level.
   165  func (t *TestFramework) AfterSuiteFunc(body func()) func() {
   166  	t.failIfNilBody(body)
   167  	f := func() {
   168  		metrics.Emit(t.Metrics.With(metrics.Duration, metrics.DurationMillis()))
   169  		reflect.ValueOf(body).Call([]reflect.Value{})
   170  	}
   171  	return f
   172  }
   173  
   174  // Entry - wrapper function for Ginkgo Entry
   175  func (t *TestFramework) Entry(description interface{}, args ...interface{}) ginkgo.TableEntry {
   176  	// insert an Offset into the args, but not as the last item, so that the right code location is reported
   177  	f := args[len(args)-1]
   178  	args[len(args)-1] = ginkgo.Offset(6) // need to go 6 up the stack to get the caller
   179  	args = append(args, f)
   180  	return ginkgo.Entry(description, args...)
   181  }
   182  
   183  // Fail - wrapper function for Ginkgo Fail
   184  func (t *TestFramework) Fail(message string, callerSkip ...int) {
   185  	// Recover only to emit a fail, then re-panic
   186  	defer func() {
   187  		if p := recover(); p != nil {
   188  			metrics.EmitFail(t.Metrics)
   189  			panic(p)
   190  		}
   191  	}()
   192  	ginkgo.Fail(message, callerSkip...)
   193  }
   194  
   195  // Context - wrapper function for Ginkgo Context
   196  func (t *TestFramework) Context(text string, args ...interface{}) bool {
   197  	return t.Describe(text, args...)
   198  }
   199  
   200  // When - wrapper function for Ginkgo When
   201  func (t *TestFramework) When(text string, args ...interface{}) bool {
   202  	return ginkgo.When(text, args...)
   203  }
   204  
   205  // SynchronizedBeforeSuite - wrapper function for Ginkgo SynchronizedBeforeSuite
   206  func (t *TestFramework) SynchronizedBeforeSuite(process1Body func() []byte, allProcessBody func([]byte)) bool {
   207  	return ginkgo.SynchronizedBeforeSuite(process1Body, allProcessBody)
   208  }
   209  
   210  // SynchronizedAfterSuite - wrapper function for Ginkgo SynchronizedAfterSuite
   211  func (t *TestFramework) SynchronizedAfterSuite(allProcessBody func(), process1Body func()) bool {
   212  	return ginkgo.SynchronizedAfterSuite(allProcessBody, process1Body)
   213  }
   214  
   215  // JustBeforeEach - wrapper function for Ginkgo JustBeforeEach
   216  func (t *TestFramework) JustBeforeEach(args ...interface{}) bool {
   217  	return ginkgo.JustBeforeEach(args...)
   218  }
   219  
   220  // JustAfterEach - wrapper function for Ginkgo JustAfterEach
   221  func (t *TestFramework) JustAfterEach(args ...interface{}) bool {
   222  	return ginkgo.JustAfterEach(args...)
   223  }
   224  
   225  // BeforeAll - wrapper function for Ginkgo BeforeAll
   226  func (t *TestFramework) BeforeAll(args ...interface{}) bool {
   227  	return ginkgo.BeforeAll(args...)
   228  }
   229  
   230  // AfterAll - wrapper function for Ginkgo AfterAll
   231  func (t *TestFramework) AfterAll(args ...interface{}) bool {
   232  	return ginkgo.AfterAll(args...)
   233  }
   234  
   235  // VzCurrentGinkgoTestDescription - wrapper function for ginkgo CurrentGinkgoTestDescription
   236  func VzCurrentGinkgoTestDescription() ginkgo.SpecReport {
   237  	pkg.Log(pkg.Debug, "VzCurrentGinkgoTestDescription wrapper")
   238  	return ginkgo.CurrentSpecReport()
   239  }
   240  
   241  func (t *TestFramework) failIfNilBody(body func()) {
   242  	if body == nil {
   243  		ginkgo.Fail("Unsupported body type - expected non-nil")
   244  	}
   245  }
   246  
   247  func failIfNotFunction(body interface{}) {
   248  	if !isBodyFunc(body) {
   249  		ginkgo.Fail("Unsupported body type - expected function")
   250  	}
   251  }
   252  
   253  func getFunctionBodyPos(pos int, args ...interface{}) interface{} {
   254  	if args == nil {
   255  		ginkgo.Fail("Unsupported args type - expected non-nil")
   256  	}
   257  	body := args[pos]
   258  	failIfNotFunction(body)
   259  	return body
   260  }
   261  
   262  func getFunctionBody(args ...interface{}) interface{} {
   263  	return getFunctionBodyPos(0, args...)
   264  }