github.com/zhyoulun/cilium@v1.6.12/test/ginkgo-ext/scopes.go (about) 1 /* 2 * Copyright 2018-2019 Authors of Cilium 3 * Copyright 2017 Mirantis 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package ginkgoext 19 20 import ( 21 "bytes" 22 "flag" 23 "fmt" 24 "os" 25 "reflect" 26 "regexp" 27 "strings" 28 "sync/atomic" 29 "time" 30 31 "github.com/onsi/ginkgo" 32 "github.com/onsi/ginkgo/config" 33 ) 34 35 type scope struct { 36 parent *scope 37 children []*scope 38 counter int32 39 before []func() 40 after []func() 41 afterEach []func() 42 justAfterEach []func() 43 afterFail []func() 44 started int32 45 failed bool 46 normalTests int 47 focusedTests int 48 focused bool 49 } 50 51 var ( 52 currentScope = &scope{} 53 rootScope = currentScope 54 // countersInitialized protects repeat calls of calculate counters on 55 // rootScope. This relies on ginkgo being single-threaded to set the value 56 // safely. 57 countersInitialized bool 58 59 // failEnabled for tests that have failed on JustAfterEach function we need 60 // to handle differently, because `ginkgo.Fail` do a panic, and all the 61 // following functions will not be called. With the WrapFailfn if the fail 62 // is on any After function, will not panic, will mark the test as failed, 63 // and will trigger the Fail function at the end. 64 failEnabled = true 65 afterEachFailed = map[string]bool{} 66 afterEachCB = map[string]func(){} 67 68 // We wrap various ginkgo function here to track invocations and determine 69 // when to call AfterAll. When using a new ginkgo equivalent to It or 70 // Measure, it may need a matching wrapper similar to wrapItFunc. 71 72 Context = wrapContextFunc(ginkgo.Context, false) 73 FContext = wrapContextFunc(ginkgo.FContext, true) 74 PContext = wrapNilContextFunc(ginkgo.PContext) 75 XContext = wrapNilContextFunc(ginkgo.XContext) 76 Describe = wrapContextFunc(ginkgo.Describe, false) 77 FDescribe = wrapContextFunc(ginkgo.FDescribe, true) 78 PDescribe = wrapNilContextFunc(ginkgo.PDescribe) 79 XDescribe = wrapNilContextFunc(ginkgo.XDescribe) 80 It = wrapItFunc(ginkgo.It, false) 81 FIt = wrapItFunc(ginkgo.FIt, true) 82 PIt = ginkgo.PIt 83 XIt = ginkgo.XIt 84 Measure = wrapMeasureFunc(ginkgo.Measure, false) 85 JustBeforeEach = ginkgo.JustBeforeEach 86 BeforeSuite = ginkgo.BeforeSuite 87 AfterSuite = ginkgo.AfterSuite 88 Skip = ginkgo.Skip 89 Fail = FailWithToggle 90 CurrentGinkgoTestDescription = ginkgo.CurrentGinkgoTestDescription 91 GinkgoRecover = ginkgo.GinkgoRecover 92 GinkgoT = ginkgo.GinkgoT 93 RunSpecs = ginkgo.RunSpecs 94 RunSpecsWithCustomReporters = ginkgo.RunSpecsWithCustomReporters 95 RunSpecsWithDefaultAndCustomReporters = ginkgo.RunSpecsWithDefaultAndCustomReporters 96 GinkgoWriter = NewWriter(ginkgo.GinkgoWriter) 97 ) 98 99 type Done ginkgo.Done 100 101 func init() { 102 // Only use the Ginkgo options and discard all other options 103 args := []string{} 104 for _, arg := range os.Args[1:] { 105 if strings.Contains(arg, "--ginkgo") { 106 args = append(args, arg) 107 } 108 } 109 110 //Get GinkgoConfig flags 111 commandFlags := flag.NewFlagSet("ginkgo", flag.ContinueOnError) 112 commandFlags.SetOutput(new(bytes.Buffer)) 113 114 config.Flags(commandFlags, "ginkgo", true) 115 commandFlags.Parse(args) 116 } 117 118 // By allows you to better document large Its. 119 // 120 // Generally you should try to keep your Its short and to the point. This is 121 // not always possible, however, especially in the context of integration tests 122 // that capture a particular workflow. 123 // 124 // By allows you to document such flows. By must be called within a runnable 125 // node (It, BeforeEach, Measure, etc...) 126 // By will simply log the passed in text to the GinkgoWriter. 127 func By(message string, optionalValues ...interface{}) { 128 if len(optionalValues) > 0 { 129 message = fmt.Sprintf(message, optionalValues...) 130 } 131 fullmessage := fmt.Sprintf("STEP: %s", message) 132 GinkgoPrint(fullmessage) 133 } 134 135 // GinkgoPrint send the given message to the test writers to store it. 136 func GinkgoPrint(message string, optionalValues ...interface{}) { 137 if len(optionalValues) > 0 { 138 message = fmt.Sprintf(message, optionalValues...) 139 } 140 fmt.Fprintln(GinkgoWriter, message) 141 fmt.Fprintln(ginkgo.GinkgoWriter, message) 142 } 143 144 // GetTestName returns the test Name in a single string without spaces or / 145 func GetTestName() string { 146 testDesc := ginkgo.CurrentGinkgoTestDescription() 147 name := strings.Replace(testDesc.FullTestText, " ", "_", -1) 148 name = strings.Trim(name, "*") 149 return strings.Replace(name, "/", "-", -1) 150 } 151 152 // BeforeAll runs the function once before any test in context 153 func BeforeAll(body func()) bool { 154 if currentScope != nil { 155 if body == nil { 156 currentScope.before = nil 157 return true 158 } 159 currentScope.before = append(currentScope.before, body) 160 return BeforeEach(func() {}) 161 } 162 return true 163 } 164 165 // AfterAll runs the function once after any test in context 166 func AfterAll(body func()) bool { 167 if currentScope != nil { 168 if body == nil { 169 currentScope.before = nil 170 return true 171 } 172 currentScope.after = append(currentScope.after, body) 173 return AfterEach(func() {}) 174 } 175 return true 176 } 177 178 //JustAfterEach runs the function just after each test, before all AfterEeach, 179 //AfterFailed and AfterAll 180 func JustAfterEach(body func()) bool { 181 if currentScope != nil { 182 if body == nil { 183 currentScope.before = nil 184 return true 185 } 186 currentScope.justAfterEach = append(currentScope.justAfterEach, body) 187 return AfterEach(func() {}) 188 } 189 return true 190 } 191 192 // JustAfterFailed runs the function after test and JustAfterEach if the test 193 // has failed and before all AfterEach 194 func AfterFailed(body func()) bool { 195 if currentScope != nil { 196 if body == nil { 197 currentScope.before = nil 198 return true 199 } 200 currentScope.afterFail = append(currentScope.afterFail, body) 201 return AfterEach(func() {}) 202 } 203 return true 204 } 205 206 // justAfterEachStatus map to store what `justAfterEach` functions have been 207 // already executed for the given test 208 var justAfterEachStatus map[string]bool = map[string]bool{} 209 210 // runAllJustAfterEach runs all the `scope.justAfterEach` functions for the 211 // given scope and parent scopes. This function make sure that all the 212 // `JustAfterEach` functions are called before AfterEach functions. 213 func runAllJustAfterEach(cs *scope, testName string) { 214 if _, ok := justAfterEachStatus[testName]; ok { 215 // JustAfterEach calls are already executed in the children 216 return 217 } 218 219 for _, body := range cs.justAfterEach { 220 body() 221 } 222 223 if cs.parent != nil { 224 runAllJustAfterEach(cs.parent, testName) 225 } 226 } 227 228 // afterEachStatus map to store what `AfterEach` functions have been 229 // already executed for the given test 230 var afterEachStatus map[string]bool = map[string]bool{} 231 232 // runAllAfterEach runs all the `scope.AfterEach` functions for the 233 // given scope and parent scopes. This function make sure that all the 234 // `AfterEach` functions are called before AfterAll functions. 235 func runAllAfterEach(cs *scope, testName string) { 236 if _, ok := afterEachStatus[testName]; ok { 237 // AfterEach calls are already executed in the children 238 return 239 } 240 241 for _, body := range cs.afterEach { 242 body() 243 } 244 if cs.parent != nil { 245 runAllAfterEach(cs.parent, testName) 246 } 247 } 248 249 // afterFailedStatus map to store what `AfterFail` functions have been 250 // already executed for the given test. 251 var afterFailedStatus map[string]bool = map[string]bool{} 252 253 // runAllAfterFail runs all the afterFail functions for the given 254 // scope and parent scopes. This function make sure that all the `AfterFail` 255 // functions are called before AfterEach. 256 func runAllAfterFail(cs *scope, testName string) { 257 if _, ok := afterFailedStatus[testName]; ok { 258 // AfterFailcalls are already executed in the children 259 return 260 } 261 262 hasFailed, _ := afterEachFailed[testName] 263 if (ginkgo.CurrentGinkgoTestDescription().Failed || hasFailed) && len(cs.afterFail) > 0 { 264 GinkgoPrint("===================== TEST FAILED =====================") 265 for _, body := range cs.afterFail { 266 body() 267 } 268 GinkgoPrint("===================== Exiting AfterFailed =====================") 269 } 270 271 if cs.parent != nil { 272 runAllAfterFail(cs.parent, testName) 273 } 274 } 275 276 // RunAfterEach is a wrapper that executes all AfterEach functions that are 277 // stored in cs.afterEach array. 278 func RunAfterEach(cs *scope) { 279 if cs == nil { 280 return 281 } 282 283 // Decrement the test number due test or BeforeEach has been run. 284 atomic.AddInt32(&cs.counter, -1) 285 286 // Disabling the `ginkgo.Fail` function to avoid the panic and be able to 287 // gather all the logs. 288 failEnabled = false 289 defer func() { 290 failEnabled = true 291 }() 292 293 testName := ginkgo.CurrentGinkgoTestDescription().FullTestText 294 295 if _, ok := afterEachFailed[testName]; !ok { 296 afterEachFailed[testName] = false 297 } 298 299 runAllJustAfterEach(cs, testName) 300 justAfterEachStatus[testName] = true 301 302 runAllAfterFail(cs, testName) 303 afterFailedStatus[testName] = true 304 305 hasFailed := afterEachFailed[testName] || ginkgo.CurrentGinkgoTestDescription().Failed 306 307 runAllAfterEach(cs, testName) 308 afterEachStatus[testName] = true 309 310 // Run the afterFailed in case that something fails on afterEach 311 if hasFailed == false && afterEachFailed[testName] { 312 GinkgoPrint("Something has failed on AfterEach, running AfterFailed functions") 313 afterFailedStatus[testName] = false 314 runAllAfterFail(cs, testName) 315 } 316 317 // Only run afterAll when all the counters are 0 and all afterEach are executed 318 after := func() { 319 if cs.counter == 0 && cs.after != nil { 320 for _, after := range cs.after { 321 after() 322 } 323 } 324 } 325 after() 326 327 cb := afterEachCB[testName] 328 if cb != nil { 329 cb() 330 } 331 } 332 333 // AfterEach runs the function after each test in context 334 func AfterEach(body func(), timeout ...float64) bool { 335 if currentScope == nil { 336 return ginkgo.AfterEach(body, timeout...) 337 } 338 cs := currentScope 339 result := true 340 if cs.afterEach == nil { 341 // If no scope, register only one AfterEach in the scope, after that 342 // RunAfterEeach will run all afterEach functions registered in the 343 // scope. 344 fn := func() { 345 RunAfterEach(cs) 346 } 347 result = ginkgo.AfterEach(fn, timeout...) 348 } 349 cs.afterEach = append(cs.afterEach, body) 350 return result 351 } 352 353 // BeforeEach runs the function before each test in context 354 func BeforeEach(body interface{}, timeout ...float64) bool { 355 if currentScope == nil { 356 return ginkgo.BeforeEach(body, timeout...) 357 } 358 cs := currentScope 359 before := func() { 360 if atomic.CompareAndSwapInt32(&cs.started, 0, 1) && cs.before != nil { 361 defer func() { 362 if r := recover(); r != nil { 363 cs.failed = true 364 panic(r) 365 } 366 }() 367 for _, before := range cs.before { 368 before() 369 } 370 } else if cs.failed { 371 Fail("failed due to BeforeAll failure") 372 } 373 } 374 return ginkgo.BeforeEach(applyAdvice(body, before, nil), timeout...) 375 } 376 377 func wrapContextFunc(fn func(string, func()) bool, focused bool) func(string, func()) bool { 378 return func(text string, body func()) bool { 379 if currentScope == nil { 380 return fn(text, body) 381 } 382 newScope := &scope{parent: currentScope, focused: focused} 383 currentScope.children = append(currentScope.children, newScope) 384 currentScope = newScope 385 res := fn(text, body) 386 currentScope = currentScope.parent 387 return res 388 } 389 } 390 391 func wrapNilContextFunc(fn func(string, func()) bool) func(string, func()) bool { 392 return func(text string, body func()) bool { 393 oldScope := currentScope 394 currentScope = nil 395 res := fn(text, body) 396 currentScope = oldScope 397 return res 398 } 399 } 400 401 // wrapItFunc wraps gingko.Measure to track invocations and correctly 402 // execute AfterAll. This is tracked via scope.focusedTests and .normalTests. 403 // This function is similar to wrapMeasureFunc. 404 func wrapItFunc(fn func(string, interface{}, ...float64) bool, focused bool) func(string, interface{}, ...float64) bool { 405 if !countersInitialized { 406 countersInitialized = true 407 BeforeSuite(func() { 408 calculateCounters(rootScope, false) 409 }) 410 } 411 return func(text string, body interface{}, timeout ...float64) bool { 412 if currentScope == nil { 413 return fn(text, body, timeout...) 414 } 415 if focused || isTestFocussed(text) { 416 currentScope.focusedTests++ 417 } else { 418 currentScope.normalTests++ 419 } 420 return fn(text, wrapTest(body), timeout...) 421 } 422 } 423 424 // wrapMeasureFunc wraps gingko.Measure to track invocations and correctly 425 // execute AfterAll. This is tracked via scope.focusedTests and .normalTests. 426 // This function is similar to wrapItFunc. 427 func wrapMeasureFunc(fn func(text string, body interface{}, samples int) bool, focused bool) func(text string, body interface{}, samples int) bool { 428 if !countersInitialized { 429 countersInitialized = true 430 BeforeSuite(func() { 431 calculateCounters(rootScope, false) 432 }) 433 } 434 return func(text string, body interface{}, samples int) bool { 435 if currentScope == nil { 436 return fn(text, body, samples) 437 } 438 if focused || isTestFocussed(text) { 439 currentScope.focusedTests++ 440 } else { 441 currentScope.normalTests++ 442 } 443 return fn(text, wrapTest(body), samples) 444 } 445 } 446 447 // isTestFocussed checks the value of FocusString and return true if the given 448 // text name is focussed, returns false if the test is not focussed. 449 func isTestFocussed(text string) bool { 450 if config.GinkgoConfig.FocusString == "" { 451 return false 452 } 453 454 focusFilter := regexp.MustCompile(config.GinkgoConfig.FocusString) 455 return focusFilter.Match([]byte(text)) 456 } 457 458 func applyAdvice(f interface{}, before, after func()) interface{} { 459 fn := reflect.ValueOf(f) 460 template := func(in []reflect.Value) []reflect.Value { 461 if before != nil { 462 before() 463 } 464 if after != nil { 465 defer after() 466 } 467 return fn.Call(in) 468 } 469 v := reflect.MakeFunc(fn.Type(), template) 470 return v.Interface() 471 } 472 473 func wrapTest(f interface{}) interface{} { 474 cs := currentScope 475 after := func() { 476 for cs != nil { 477 cs = cs.parent 478 } 479 GinkgoPrint("=== Test Finished at %s====", time.Now().Format(time.RFC3339)) 480 } 481 return applyAdvice(f, nil, after) 482 } 483 484 // calculateCounters initialises the tracking counters that determine when 485 // AfterAll should be called. It is not idempotent and should be guarded 486 // against repeated initializations. 487 func calculateCounters(s *scope, focusedOnly bool) (int, bool) { 488 count := s.focusedTests 489 haveFocused := s.focusedTests > 0 490 var focusedChildren int 491 for _, child := range s.children { 492 if child.focused { 493 c, _ := calculateCounters(child, false) 494 focusedChildren += c 495 } 496 } 497 if focusedChildren > 0 { 498 haveFocused = true 499 count += focusedChildren 500 } 501 var normalChildren int 502 for _, child := range s.children { 503 if !child.focused { 504 c, f := calculateCounters(child, focusedOnly || haveFocused) 505 if f { 506 haveFocused = true 507 count += c 508 } else { 509 normalChildren += c 510 } 511 } 512 } 513 if !focusedOnly && !haveFocused { 514 count += s.normalTests + normalChildren 515 } 516 s.counter = int32(count) 517 return count, haveFocused 518 } 519 520 // FailWithToggle wraps `ginkgo.Fail` function to have a option to disable the 521 // panic when something fails when is running on AfterEach. 522 func FailWithToggle(message string, callerSkip ...int) { 523 524 if len(callerSkip) > 0 { 525 callerSkip[0] = callerSkip[0] + 1 526 } 527 528 if failEnabled { 529 ginkgo.Fail(message, callerSkip...) 530 } 531 532 testName := ginkgo.CurrentGinkgoTestDescription().FullTestText 533 afterEachFailed[testName] = true 534 535 afterEachCB[testName] = func() { 536 ginkgo.Fail(message, callerSkip...) 537 } 538 } 539 540 // SkipContextIf is a wrapper for the Context block which is being executed 541 // if the given condition is NOT met. 542 func SkipContextIf(condition func() bool, text string, body func()) bool { 543 if condition() { 544 return It(text, func() { 545 Skip("skipping due to unmet condition") 546 }) 547 } 548 549 return Context(text, body) 550 }