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 }