...

Source file src/github.com/stretchr/testify/suite/suite.go

Documentation: github.com/stretchr/testify/suite

     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  		panic("'Require' must not be called before 'Run' or 'SetT'")
    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  		panic("'Assert' must not be called before 'Run' or 'SetT'")
    76  	}
    77  	return suite.Assertions
    78  }
    79  
    80  func recoverAndFailOnPanic(t *testing.T) {
    81  	t.Helper()
    82  	r := recover()
    83  	failOnPanic(t, r)
    84  }
    85  
    86  func failOnPanic(t *testing.T, r interface{}) {
    87  	t.Helper()
    88  	if r != nil {
    89  		t.Errorf("test panicked: %v\n%s", r, debug.Stack())
    90  		t.FailNow()
    91  	}
    92  }
    93  
    94  // Run provides suite functionality around golang subtests.  It should be
    95  // called in place of t.Run(name, func(t *testing.T)) in test suite code.
    96  // The passed-in func will be executed as a subtest with a fresh instance of t.
    97  // Provides compatibility with go test pkg -run TestSuite/TestName/SubTestName.
    98  func (suite *Suite) Run(name string, subtest func()) bool {
    99  	oldT := suite.T()
   100  
   101  	return oldT.Run(name, func(t *testing.T) {
   102  		suite.SetT(t)
   103  		defer suite.SetT(oldT)
   104  
   105  		defer recoverAndFailOnPanic(t)
   106  
   107  		if setupSubTest, ok := suite.s.(SetupSubTest); ok {
   108  			setupSubTest.SetupSubTest()
   109  		}
   110  
   111  		if tearDownSubTest, ok := suite.s.(TearDownSubTest); ok {
   112  			defer tearDownSubTest.TearDownSubTest()
   113  		}
   114  
   115  		subtest()
   116  	})
   117  }
   118  
   119  // Run takes a testing suite and runs all of the tests attached
   120  // to it.
   121  func Run(t *testing.T, suite TestingSuite) {
   122  	defer recoverAndFailOnPanic(t)
   123  
   124  	suite.SetT(t)
   125  	suite.SetS(suite)
   126  
   127  	var suiteSetupDone bool
   128  
   129  	var stats *SuiteInformation
   130  	if _, ok := suite.(WithStats); ok {
   131  		stats = newSuiteInformation()
   132  	}
   133  
   134  	tests := []testing.InternalTest{}
   135  	methodFinder := reflect.TypeOf(suite)
   136  	suiteName := methodFinder.Elem().Name()
   137  
   138  	for i := 0; i < methodFinder.NumMethod(); i++ {
   139  		method := methodFinder.Method(i)
   140  
   141  		ok, err := methodFilter(method.Name)
   142  		if err != nil {
   143  			fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
   144  			os.Exit(1)
   145  		}
   146  
   147  		if !ok {
   148  			continue
   149  		}
   150  
   151  		if !suiteSetupDone {
   152  			if stats != nil {
   153  				stats.Start = time.Now()
   154  			}
   155  
   156  			if setupAllSuite, ok := suite.(SetupAllSuite); ok {
   157  				setupAllSuite.SetupSuite()
   158  			}
   159  
   160  			suiteSetupDone = true
   161  		}
   162  
   163  		test := testing.InternalTest{
   164  			Name: method.Name,
   165  			F: func(t *testing.T) {
   166  				parentT := suite.T()
   167  				suite.SetT(t)
   168  				defer recoverAndFailOnPanic(t)
   169  				defer func() {
   170  					t.Helper()
   171  
   172  					r := recover()
   173  
   174  					if stats != nil {
   175  						passed := !t.Failed() && r == nil
   176  						stats.end(method.Name, passed)
   177  					}
   178  
   179  					if afterTestSuite, ok := suite.(AfterTest); ok {
   180  						afterTestSuite.AfterTest(suiteName, method.Name)
   181  					}
   182  
   183  					if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
   184  						tearDownTestSuite.TearDownTest()
   185  					}
   186  
   187  					suite.SetT(parentT)
   188  					failOnPanic(t, r)
   189  				}()
   190  
   191  				if setupTestSuite, ok := suite.(SetupTestSuite); ok {
   192  					setupTestSuite.SetupTest()
   193  				}
   194  				if beforeTestSuite, ok := suite.(BeforeTest); ok {
   195  					beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
   196  				}
   197  
   198  				if stats != nil {
   199  					stats.start(method.Name)
   200  				}
   201  
   202  				method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
   203  			},
   204  		}
   205  		tests = append(tests, test)
   206  	}
   207  	if suiteSetupDone {
   208  		defer func() {
   209  			if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
   210  				tearDownAllSuite.TearDownSuite()
   211  			}
   212  
   213  			if suiteWithStats, measureStats := suite.(WithStats); measureStats {
   214  				stats.End = time.Now()
   215  				suiteWithStats.HandleStats(suiteName, stats)
   216  			}
   217  		}()
   218  	}
   219  
   220  	runTests(t, tests)
   221  }
   222  
   223  // Filtering method according to set regular expression
   224  // specified command-line argument -m
   225  func methodFilter(name string) (bool, error) {
   226  	if ok, _ := regexp.MatchString("^Test", name); !ok {
   227  		return false, nil
   228  	}
   229  	return regexp.MatchString(*matchMethod, name)
   230  }
   231  
   232  func runTests(t testing.TB, tests []testing.InternalTest) {
   233  	if len(tests) == 0 {
   234  		t.Log("warning: no tests to run")
   235  		return
   236  	}
   237  
   238  	r, ok := t.(runner)
   239  	if !ok { // backwards compatibility with Go 1.6 and below
   240  		if !testing.RunTests(allTestsFilter, tests) {
   241  			t.Fail()
   242  		}
   243  		return
   244  	}
   245  
   246  	for _, test := range tests {
   247  		r.Run(test.Name, test.F)
   248  	}
   249  }
   250  
   251  type runner interface {
   252  	Run(name string, f func(t *testing.T)) bool
   253  }
   254  

View as plain text