Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
- "1.21"
- "1.22"
- "1.23"
- "1.25"
steps:
- uses: actions/checkout@v5
- name: Setup Go
Expand Down
55 changes: 55 additions & 0 deletions suite/method.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package suite

import (
"flag"
"fmt"
"os"
"reflect"
"regexp"
"strings"
"testing"
)

var (
matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run")
matchMethodRE *regexp.Regexp
)

func isTestMethod(method reflect.Method) bool {
if !strings.HasPrefix(method.Name, "Test") {
return false
}

// compile once if needed
if *matchMethod != "" && matchMethodRE == nil {
var err error
matchMethodRE, err = regexp.Compile(*matchMethod)
if err != nil {
fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
os.Exit(1)
}
}

// Apply -testify.m filter
if matchMethodRE != nil && !matchMethodRE.MatchString(method.Name) {
return false
}

return true
}

func checkMethodSignature(method reflect.Method) (test, bool) {
if method.Type.NumIn() > 1 || method.Type.NumOut() > 0 {
return test{
name: method.Name,
run: func(t *testing.T) {
t.Errorf(
"testify: suite method %q has invalid signature: expected no input or output parameters, method has %d input parameters and %d output parameters",
method.Name, method.Type.NumIn()-1, method.Type.NumOut(),
)
},
}, false
}

return test{}, true
}
20 changes: 20 additions & 0 deletions suite/recovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package suite

import (
"runtime/debug"
"testing"
)

func recoverAndFailOnPanic(t *testing.T) {
t.Helper()
r := recover()
failOnPanic(t, r)
}

func failOnPanic(t *testing.T, r interface{}) {
t.Helper()
if r != nil {
t.Errorf("test panicked: %v\n%s", r, debug.Stack())
t.FailNow()
}
}
70 changes: 70 additions & 0 deletions suite/sharedsuite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package suite

import (
"sync"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type sharedSuite struct {
*assert.Assertions

mu sync.RWMutex
require *require.Assertions
t *testing.T

// Parent suite to have access to the implemented methods of parent struct
s TestingSuite
}

// T retrieves the current *testing.T context.
func (suite *sharedSuite) T() *testing.T {
suite.mu.RLock()
defer suite.mu.RUnlock()
return suite.t
}

// SetT sets the current *testing.T context.
func (suite *sharedSuite) SetT(t *testing.T) {
suite.mu.Lock()
defer suite.mu.Unlock()
suite.t = t
suite.Assertions = assert.New(t)
suite.require = require.New(t)
}

// SetS needs to set the current test suite as parent
// to get access to the parent methods
func (suite *sharedSuite) SetS(s TestingSuite) {
suite.s = s
}

// Require returns a require context for suite.
func (suite *sharedSuite) Require() *require.Assertions {
suite.mu.Lock()
defer suite.mu.Unlock()
if suite.require == nil {
panic("'Require' must not be called before 'Run' or 'SetT'")
}
return suite.require
}

// Assert returns an assert context for suite. Normally, you can call:
//
// suite.NoError(err)
//
// But for situations where the embedded methods are overridden (for example,
// you might want to override assert.Assertions with require.Assertions), this
// method is provided so you can call:
//
// suite.Assert().NoError(err)
func (suite *sharedSuite) Assert() *assert.Assertions {
suite.mu.Lock()
defer suite.mu.Unlock()
if suite.Assertions == nil {
panic("'Assert' must not be called before 'Run' or 'SetT'")
}
return suite.Assertions
}
136 changes: 8 additions & 128 deletions suite/suite.go
Original file line number Diff line number Diff line change
@@ -1,98 +1,15 @@
package suite

import (
"flag"
"fmt"
"os"
"reflect"
"regexp"
"runtime/debug"
"strings"
"sync"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run")

// Suite is a basic testing suite with methods for storing and
// retrieving the current *testing.T context.
type Suite struct {
*assert.Assertions

mu sync.RWMutex
require *require.Assertions
t *testing.T

// Parent suite to have access to the implemented methods of parent struct
s TestingSuite
}

// T retrieves the current *testing.T context.
func (suite *Suite) T() *testing.T {
suite.mu.RLock()
defer suite.mu.RUnlock()
return suite.t
}

// SetT sets the current *testing.T context.
func (suite *Suite) SetT(t *testing.T) {
suite.mu.Lock()
defer suite.mu.Unlock()
suite.t = t
suite.Assertions = assert.New(t)
suite.require = require.New(t)
}

// SetS needs to set the current test suite as parent
// to get access to the parent methods
func (suite *Suite) SetS(s TestingSuite) {
suite.s = s
}

// Require returns a require context for suite.
func (suite *Suite) Require() *require.Assertions {
suite.mu.Lock()
defer suite.mu.Unlock()
if suite.require == nil {
panic("'Require' must not be called before 'Run' or 'SetT'")
}
return suite.require
}

// Assert returns an assert context for suite. Normally, you can call:
//
// suite.NoError(err)
//
// But for situations where the embedded methods are overridden (for example,
// you might want to override assert.Assertions with require.Assertions), this
// method is provided so you can call:
//
// suite.Assert().NoError(err)
func (suite *Suite) Assert() *assert.Assertions {
suite.mu.Lock()
defer suite.mu.Unlock()
if suite.Assertions == nil {
panic("'Assert' must not be called before 'Run' or 'SetT'")
}
return suite.Assertions
}

func recoverAndFailOnPanic(t *testing.T) {
t.Helper()
r := recover()
failOnPanic(t, r)
}

func failOnPanic(t *testing.T, r interface{}) {
t.Helper()
if r != nil {
t.Errorf("test panicked: %v\n%s", r, debug.Stack())
t.FailNow()
}
sharedSuite
}

// Run provides suite functionality around golang subtests. It should be
Expand Down Expand Up @@ -120,11 +37,6 @@ func (suite *Suite) Run(name string, subtest func()) bool {
})
}

type test = struct {
name string
run func(t *testing.T)
}

// Run takes a testing suite and runs all of the tests attached
// to it.
func Run(t *testing.T, suite TestingSuite) {
Expand All @@ -138,43 +50,22 @@ func Run(t *testing.T, suite TestingSuite) {
stats = newSuiteInformation()
}

var tests []test
var tests tests
methodFinder := reflect.TypeOf(suite)
suiteName := methodFinder.Elem().Name()

var matchMethodRE *regexp.Regexp
if *matchMethod != "" {
var err error
matchMethodRE, err = regexp.Compile(*matchMethod)
if err != nil {
fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
os.Exit(1)
}
}

for i := 0; i < methodFinder.NumMethod(); i++ {
method := methodFinder.Method(i)

if !strings.HasPrefix(method.Name, "Test") {
continue
}
// Apply -testify.m filter
if matchMethodRE != nil && !matchMethodRE.MatchString(method.Name) {
if !isTestMethod(method) {
continue
}
// Check method signature
if method.Type.NumIn() > 1 || method.Type.NumOut() > 0 {
tests = append(tests, test{
name: method.Name,
run: func(t *testing.T) {
t.Errorf(
"testify: suite method %q has invalid signature: expected no input or output parameters, method has %d input parameters and %d output parameters",
method.Name, method.Type.NumIn()-1, method.Type.NumOut(),
)
},
})

if test, ok := checkMethodSignature(method); !ok {
tests = append(tests, test)
continue
}

test := test{
name: method.Name,
run: func(t *testing.T) {
Expand Down Expand Up @@ -238,16 +129,5 @@ func Run(t *testing.T, suite TestingSuite) {
}
}()

runTests(t, tests)
}

func runTests(t *testing.T, tests []test) {
if len(tests) == 0 {
t.Log("warning: no tests to run")
return
}

for _, test := range tests {
t.Run(test.name, test.run)
}
tests.run(t)
}
Loading