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  }