gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

suite.go (5783B)


      1 package suite
      2 
      3 import (
      4 	"flag"
      5 	"fmt"
      6 	"os"
      7 	"reflect"
      8 	"regexp"
      9 	"runtime/debug"
     10 	"sync"
     11 	"testing"
     12 	"time"
     13 
     14 	"github.com/stretchr/testify/assert"
     15 	"github.com/stretchr/testify/require"
     16 )
     17 
     18 var allTestsFilter = func(_, _ string) (bool, error) { return true, nil }
     19 var matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run")
     20 
     21 // Suite is a basic testing suite with methods for storing and
     22 // retrieving the current *testing.T context.
     23 type Suite struct {
     24 	*assert.Assertions
     25 
     26 	mu      sync.RWMutex
     27 	require *require.Assertions
     28 	t       *testing.T
     29 
     30 	// Parent suite to have access to the implemented methods of parent struct
     31 	s TestingSuite
     32 }
     33 
     34 // T retrieves the current *testing.T context.
     35 func (suite *Suite) T() *testing.T {
     36 	suite.mu.RLock()
     37 	defer suite.mu.RUnlock()
     38 	return suite.t
     39 }
     40 
     41 // SetT sets the current *testing.T context.
     42 func (suite *Suite) SetT(t *testing.T) {
     43 	suite.mu.Lock()
     44 	defer suite.mu.Unlock()
     45 	suite.t = t
     46 	suite.Assertions = assert.New(t)
     47 	suite.require = require.New(t)
     48 }
     49 
     50 // SetS needs to set the current test suite as parent
     51 // to get access to the parent methods
     52 func (suite *Suite) SetS(s TestingSuite) {
     53 	suite.s = s
     54 }
     55 
     56 // Require returns a require context for suite.
     57 func (suite *Suite) Require() *require.Assertions {
     58 	suite.mu.Lock()
     59 	defer suite.mu.Unlock()
     60 	if suite.require == nil {
     61 		suite.require = require.New(suite.T())
     62 	}
     63 	return suite.require
     64 }
     65 
     66 // Assert returns an assert context for suite.  Normally, you can call
     67 // `suite.NoError(expected, actual)`, but for situations where the embedded
     68 // methods are overridden (for example, you might want to override
     69 // assert.Assertions with require.Assertions), this method is provided so you
     70 // can call `suite.Assert().NoError()`.
     71 func (suite *Suite) Assert() *assert.Assertions {
     72 	suite.mu.Lock()
     73 	defer suite.mu.Unlock()
     74 	if suite.Assertions == nil {
     75 		suite.Assertions = assert.New(suite.T())
     76 	}
     77 	return suite.Assertions
     78 }
     79 
     80 func recoverAndFailOnPanic(t *testing.T) {
     81 	r := recover()
     82 	failOnPanic(t, r)
     83 }
     84 
     85 func failOnPanic(t *testing.T, r interface{}) {
     86 	if r != nil {
     87 		t.Errorf("test panicked: %v\n%s", r, debug.Stack())
     88 		t.FailNow()
     89 	}
     90 }
     91 
     92 // Run provides suite functionality around golang subtests.  It should be
     93 // called in place of t.Run(name, func(t *testing.T)) in test suite code.
     94 // The passed-in func will be executed as a subtest with a fresh instance of t.
     95 // Provides compatibility with go test pkg -run TestSuite/TestName/SubTestName.
     96 func (suite *Suite) Run(name string, subtest func()) bool {
     97 	oldT := suite.T()
     98 
     99 	if setupSubTest, ok := suite.s.(SetupSubTest); ok {
    100 		setupSubTest.SetupSubTest()
    101 	}
    102 
    103 	defer func() {
    104 		suite.SetT(oldT)
    105 		if tearDownSubTest, ok := suite.s.(TearDownSubTest); ok {
    106 			tearDownSubTest.TearDownSubTest()
    107 		}
    108 	}()
    109 
    110 	return oldT.Run(name, func(t *testing.T) {
    111 		suite.SetT(t)
    112 		subtest()
    113 	})
    114 }
    115 
    116 // Run takes a testing suite and runs all of the tests attached
    117 // to it.
    118 func Run(t *testing.T, suite TestingSuite) {
    119 	defer recoverAndFailOnPanic(t)
    120 
    121 	suite.SetT(t)
    122 	suite.SetS(suite)
    123 
    124 	var suiteSetupDone bool
    125 
    126 	var stats *SuiteInformation
    127 	if _, ok := suite.(WithStats); ok {
    128 		stats = newSuiteInformation()
    129 	}
    130 
    131 	tests := []testing.InternalTest{}
    132 	methodFinder := reflect.TypeOf(suite)
    133 	suiteName := methodFinder.Elem().Name()
    134 
    135 	for i := 0; i < methodFinder.NumMethod(); i++ {
    136 		method := methodFinder.Method(i)
    137 
    138 		ok, err := methodFilter(method.Name)
    139 		if err != nil {
    140 			fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
    141 			os.Exit(1)
    142 		}
    143 
    144 		if !ok {
    145 			continue
    146 		}
    147 
    148 		if !suiteSetupDone {
    149 			if stats != nil {
    150 				stats.Start = time.Now()
    151 			}
    152 
    153 			if setupAllSuite, ok := suite.(SetupAllSuite); ok {
    154 				setupAllSuite.SetupSuite()
    155 			}
    156 
    157 			suiteSetupDone = true
    158 		}
    159 
    160 		test := testing.InternalTest{
    161 			Name: method.Name,
    162 			F: func(t *testing.T) {
    163 				parentT := suite.T()
    164 				suite.SetT(t)
    165 				defer recoverAndFailOnPanic(t)
    166 				defer func() {
    167 					r := recover()
    168 
    169 					if stats != nil {
    170 						passed := !t.Failed() && r == nil
    171 						stats.end(method.Name, passed)
    172 					}
    173 
    174 					if afterTestSuite, ok := suite.(AfterTest); ok {
    175 						afterTestSuite.AfterTest(suiteName, method.Name)
    176 					}
    177 
    178 					if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
    179 						tearDownTestSuite.TearDownTest()
    180 					}
    181 
    182 					suite.SetT(parentT)
    183 					failOnPanic(t, r)
    184 				}()
    185 
    186 				if setupTestSuite, ok := suite.(SetupTestSuite); ok {
    187 					setupTestSuite.SetupTest()
    188 				}
    189 				if beforeTestSuite, ok := suite.(BeforeTest); ok {
    190 					beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
    191 				}
    192 
    193 				if stats != nil {
    194 					stats.start(method.Name)
    195 				}
    196 
    197 				method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
    198 			},
    199 		}
    200 		tests = append(tests, test)
    201 	}
    202 	if suiteSetupDone {
    203 		defer func() {
    204 			if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
    205 				tearDownAllSuite.TearDownSuite()
    206 			}
    207 
    208 			if suiteWithStats, measureStats := suite.(WithStats); measureStats {
    209 				stats.End = time.Now()
    210 				suiteWithStats.HandleStats(suiteName, stats)
    211 			}
    212 		}()
    213 	}
    214 
    215 	runTests(t, tests)
    216 }
    217 
    218 // Filtering method according to set regular expression
    219 // specified command-line argument -m
    220 func methodFilter(name string) (bool, error) {
    221 	if ok, _ := regexp.MatchString("^Test", name); !ok {
    222 		return false, nil
    223 	}
    224 	return regexp.MatchString(*matchMethod, name)
    225 }
    226 
    227 func runTests(t testing.TB, tests []testing.InternalTest) {
    228 	if len(tests) == 0 {
    229 		t.Log("warning: no tests to run")
    230 		return
    231 	}
    232 
    233 	r, ok := t.(runner)
    234 	if !ok { // backwards compatibility with Go 1.6 and below
    235 		if !testing.RunTests(allTestsFilter, tests) {
    236 			t.Fail()
    237 		}
    238 		return
    239 	}
    240 
    241 	for _, test := range tests {
    242 		r.Run(test.Name, test.F)
    243 	}
    244 }
    245 
    246 type runner interface {
    247 	Run(name string, f func(t *testing.T)) bool
    248 }