github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/tests/e2e/ginkgo-ext/scopes.go (about)

     1  /*
     2  Copyright 2017-2018 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Some changes are merged back from
    18  // https://github.com/cilium/cilium/blob/5b9699f71a930c1f99c17e80431071491dfaa732/test/ginkgo-ext/scopes.go
    19  
    20  package ginkgoext
    21  
    22  import (
    23  	"bytes"
    24  	"flag"
    25  	"os"
    26  	"reflect"
    27  	"regexp"
    28  	"strings"
    29  	"sync/atomic"
    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 bool
    55  
    56  	Context                               = wrapContextFunc(ginkgo.Context, false)
    57  	FContext                              = wrapContextFunc(ginkgo.FContext, true)
    58  	PContext                              = wrapNilContextFunc(ginkgo.PContext)
    59  	XContext                              = wrapNilContextFunc(ginkgo.XContext)
    60  	Describe                              = wrapContextFunc(ginkgo.Describe, false)
    61  	FDescribe                             = wrapContextFunc(ginkgo.FDescribe, true)
    62  	PDescribe                             = wrapNilContextFunc(ginkgo.PDescribe)
    63  	XDescribe                             = wrapNilContextFunc(ginkgo.XDescribe)
    64  	It                                    = wrapItFunc(ginkgo.It, false)
    65  	FIt                                   = wrapItFunc(ginkgo.FIt, true)
    66  	PIt                                   = ginkgo.PIt
    67  	XIt                                   = ginkgo.XIt
    68  	By                                    = ginkgo.By
    69  	JustBeforeEach                        = ginkgo.JustBeforeEach
    70  	BeforeSuite                           = ginkgo.BeforeSuite
    71  	AfterSuite                            = ginkgo.AfterSuite
    72  	Skip                                  = ginkgo.Skip
    73  	Fail                                  = ginkgo.Fail
    74  	CurrentGinkgoTestDescription          = ginkgo.CurrentGinkgoTestDescription
    75  	GinkgoRecover                         = ginkgo.GinkgoRecover
    76  	GinkgoT                               = ginkgo.GinkgoT
    77  	RunSpecs                              = ginkgo.RunSpecs
    78  	RunSpecsWithCustomReporters           = ginkgo.RunSpecsWithCustomReporters
    79  	RunSpecsWithDefaultAndCustomReporters = ginkgo.RunSpecsWithDefaultAndCustomReporters
    80  )
    81  
    82  type Done ginkgo.Done
    83  
    84  func init() {
    85  	// Only use the Ginkgo options and discard all other options
    86  	args := []string{}
    87  	for _, arg := range os.Args[1:] {
    88  		if strings.Contains(arg, "-ginkgo") {
    89  			args = append(args, arg)
    90  		}
    91  	}
    92  
    93  	//Get GinkgoConfig flags
    94  	commandFlags := flag.NewFlagSet("ginkgo", flag.ContinueOnError)
    95  	commandFlags.SetOutput(new(bytes.Buffer))
    96  
    97  	config.Flags(commandFlags, "ginkgo", true)
    98  	commandFlags.Parse(args)
    99  }
   100  
   101  // BeforeAll runs the function once before any test in context
   102  func BeforeAll(body func()) bool {
   103  	if currentScope != nil {
   104  		if body == nil {
   105  			currentScope.before = nil
   106  			return true
   107  		}
   108  		currentScope.before = append(currentScope.before, body)
   109  		return BeforeEach(func() {})
   110  	}
   111  	return true
   112  }
   113  
   114  // AfterAll runs the function once after any test in context
   115  func AfterAll(body func()) bool {
   116  	if currentScope != nil {
   117  		if body == nil {
   118  			currentScope.before = nil
   119  			return true
   120  		}
   121  		currentScope.after = append(currentScope.after, body)
   122  		return AfterEach(func() {})
   123  	}
   124  	return true
   125  }
   126  
   127  // JustAfterEach runs the function just after each test, before all AfterEach,
   128  // AfterFailed and AfterAll
   129  func JustAfterEach(body func()) bool {
   130  	if currentScope != nil {
   131  		if body == nil {
   132  			currentScope.before = nil
   133  			return true
   134  		}
   135  		currentScope.justAfterEach = append(currentScope.justAfterEach, body)
   136  		return AfterEach(func() {})
   137  	}
   138  	return true
   139  }
   140  
   141  // JustAfterFailed runs the function after test and JustAfterEach if the test
   142  // has failed and before all AfterEach
   143  func AfterFailed(body func()) bool {
   144  	if currentScope != nil {
   145  		if body == nil {
   146  			currentScope.before = nil
   147  			return true
   148  		}
   149  		currentScope.afterFail = append(currentScope.afterFail, body)
   150  		return AfterEach(func() {})
   151  	}
   152  	return true
   153  }
   154  
   155  // justAfterEachStatus map to store what `justAfterEach` functions have been
   156  // already executed for the given test
   157  var justAfterEachStatus map[string]bool = map[string]bool{}
   158  
   159  // runAllJustAfterEach runs all the `scope.justAfterEach` functions for the
   160  // given scope and parent scopes. This function make sure that all the
   161  // `JustAfterEach` functions are called before AfterEach functions.
   162  func runAllJustAfterEach(cs *scope, testName string) {
   163  	if _, ok := justAfterEachStatus[testName]; ok {
   164  		// JustAfterEach calls are already executed in the children
   165  		return
   166  	}
   167  
   168  	for _, body := range cs.justAfterEach {
   169  		body()
   170  	}
   171  
   172  	if cs.parent != nil {
   173  		runAllJustAfterEach(cs.parent, testName)
   174  	}
   175  }
   176  
   177  // afterFailedStatus map to store what `AfterFail` functions have been
   178  // already executed for the given test.
   179  var afterFailedStatus map[string]bool = map[string]bool{}
   180  
   181  // runAllAfterFail runs all the afterFail functions for the given
   182  // scope and parent scopes. This function make sure that all the `AfterFail`
   183  // functions are called before AfterEach.
   184  func runAllAfterFail(cs *scope, testName string) {
   185  	if _, ok := afterFailedStatus[testName]; ok {
   186  		// AfterFailcalls are already executed in the children
   187  		return
   188  	}
   189  
   190  	for _, body := range cs.afterFail {
   191  		if ginkgo.CurrentGinkgoTestDescription().Failed {
   192  			body()
   193  		}
   194  	}
   195  
   196  	if cs.parent != nil {
   197  		runAllAfterFail(cs.parent, testName)
   198  	}
   199  }
   200  
   201  // RunAfterEach is a wrapper that executes all AfterEach functions that are
   202  // stored in cs.afterEach array.
   203  func RunAfterEach(cs *scope) {
   204  	if cs == nil {
   205  		return
   206  	}
   207  	testName := ginkgo.CurrentGinkgoTestDescription().FullTestText
   208  	runAllJustAfterEach(cs, testName)
   209  	justAfterEachStatus[testName] = true
   210  
   211  	runAllAfterFail(cs, testName)
   212  	afterFailedStatus[testName] = true
   213  
   214  	for _, body := range cs.afterEach {
   215  		body()
   216  	}
   217  
   218  	// Only run afterAll when all the counters are 0 and all afterEach are executed
   219  	after := func() {
   220  		if cs.counter == 0 && cs.after != nil {
   221  			for _, after := range cs.after {
   222  				after()
   223  			}
   224  		}
   225  	}
   226  	after()
   227  }
   228  
   229  // AfterEach runs the function after each test in context
   230  func AfterEach(body func(), timeout ...float64) bool {
   231  	if currentScope == nil {
   232  		return ginkgo.AfterEach(body, timeout...)
   233  	}
   234  	cs := currentScope
   235  	result := true
   236  	if cs.afterEach == nil {
   237  		// If no scope, register only one AfterEach in the scope, after that
   238  		// RunAfterEeach will run all afterEach functions registered in the
   239  		// scope.
   240  		fn := func() {
   241  			RunAfterEach(cs)
   242  		}
   243  		result = ginkgo.AfterEach(fn, timeout...)
   244  	}
   245  	cs.afterEach = append(cs.afterEach, body)
   246  	return result
   247  }
   248  
   249  // BeforeEach runs the function before each test in context
   250  func BeforeEach(body interface{}, timeout ...float64) bool {
   251  	if currentScope == nil {
   252  		return ginkgo.BeforeEach(body, timeout...)
   253  	}
   254  	cs := currentScope
   255  	before := func() {
   256  		if atomic.CompareAndSwapInt32(&cs.started, 0, 1) && cs.before != nil {
   257  			defer func() {
   258  				if r := recover(); r != nil {
   259  					cs.failed = true
   260  					panic(r)
   261  				}
   262  			}()
   263  			for _, before := range cs.before {
   264  				before()
   265  			}
   266  		} else if cs.failed {
   267  			Fail("failed due to BeforeAll failure")
   268  		}
   269  	}
   270  	return ginkgo.BeforeEach(applyAdvice(body, before, nil), timeout...)
   271  }
   272  
   273  func wrapContextFunc(fn func(string, func()) bool, focused bool) func(string, func()) bool {
   274  	return func(text string, body func()) bool {
   275  		if currentScope == nil {
   276  			return fn(text, body)
   277  		}
   278  		newScope := &scope{parent: currentScope, focused: focused}
   279  		currentScope.children = append(currentScope.children, newScope)
   280  		currentScope = newScope
   281  		res := fn(text, body)
   282  		currentScope = currentScope.parent
   283  		return res
   284  	}
   285  }
   286  
   287  func wrapNilContextFunc(fn func(string, func()) bool) func(string, func()) bool {
   288  	return func(text string, body func()) bool {
   289  		oldScope := currentScope
   290  		currentScope = nil
   291  		res := fn(text, body)
   292  		currentScope = oldScope
   293  		return res
   294  	}
   295  }
   296  
   297  func wrapItFunc(fn func(string, interface{}, ...float64) bool, focused bool) func(string, interface{}, ...float64) bool {
   298  	if !countersInitialized {
   299  		countersInitialized = true
   300  		BeforeSuite(func() {
   301  			calculateCounters(rootScope, false)
   302  		})
   303  	}
   304  	return func(text string, body interface{}, timeout ...float64) bool {
   305  		if currentScope == nil {
   306  			return fn(text, body, timeout...)
   307  		}
   308  		if focused || isTestFocused(text) {
   309  			currentScope.focusedTests++
   310  		} else {
   311  			currentScope.normalTests++
   312  		}
   313  		return fn(text, wrapTest(body), timeout...)
   314  	}
   315  }
   316  
   317  // isTestFocused checks the value of FocusString and return true if the given
   318  // text name is focussed, returns false if the test is not focussed.
   319  func isTestFocused(text string) bool {
   320  	if config.GinkgoConfig.FocusString == "" && config.GinkgoConfig.SkipString == "" {
   321  		return false
   322  	}
   323  
   324  	skipFilter := regexp.MustCompile(config.GinkgoConfig.SkipString)
   325  	if skipFilter.Match([]byte(text)) {
   326  		return false
   327  	}
   328  
   329  	focusFilter := regexp.MustCompile(config.GinkgoConfig.FocusString)
   330  	return focusFilter.Match([]byte(text))
   331  }
   332  
   333  func applyAdvice(f interface{}, before, after func()) interface{} {
   334  	fn := reflect.ValueOf(f)
   335  	template := func(in []reflect.Value) []reflect.Value {
   336  		if before != nil {
   337  			before()
   338  		}
   339  		if after != nil {
   340  			defer after()
   341  		}
   342  		return fn.Call(in)
   343  	}
   344  	v := reflect.MakeFunc(fn.Type(), template)
   345  	return v.Interface()
   346  }
   347  
   348  func wrapTest(f interface{}) interface{} {
   349  	cs := currentScope
   350  	after := func() {
   351  		for cs != nil {
   352  			atomic.AddInt32(&cs.counter, -1)
   353  			cs = cs.parent
   354  		}
   355  	}
   356  	return applyAdvice(f, nil, after)
   357  }
   358  
   359  func calculateCounters(s *scope, focusedOnly bool) (int, bool) {
   360  	count := s.focusedTests
   361  	haveFocused := s.focusedTests > 0
   362  	var focusedChildren int
   363  	for _, child := range s.children {
   364  		if child.focused {
   365  			c, _ := calculateCounters(child, false)
   366  			focusedChildren += c
   367  		}
   368  	}
   369  	if focusedChildren > 0 {
   370  		haveFocused = true
   371  		count += focusedChildren
   372  	}
   373  	var normalChildren int
   374  	for _, child := range s.children {
   375  		if !child.focused {
   376  			c, f := calculateCounters(child, focusedOnly || haveFocused)
   377  			if f {
   378  				haveFocused = true
   379  				count += c
   380  			} else {
   381  				normalChildren += c
   382  			}
   383  		}
   384  	}
   385  	if !focusedOnly && !haveFocused {
   386  		count += s.normalTests + normalChildren
   387  	}
   388  	s.counter = int32(count)
   389  	return count, haveFocused
   390  }