Write an openapi spec for the worker API and use `deepmap/oapi-codegen`
to generate scaffolding for the server-side using the `labstack/echo`
server.
Incidentally, echo by default returns the errors in the same format that
worker API always has:
{ "message": "..." }
The API itself is unchanged to make this change easier to understand. It
will be changed to better suit our needs in future commits.
171 lines
4.4 KiB
Go
171 lines
4.4 KiB
Go
package suite
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"regexp"
|
|
"runtime/debug"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var allTestsFilter = func(_, _ string) (bool, error) { return true, nil }
|
|
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
|
|
require *require.Assertions
|
|
t *testing.T
|
|
}
|
|
|
|
// T retrieves the current *testing.T context.
|
|
func (suite *Suite) T() *testing.T {
|
|
return suite.t
|
|
}
|
|
|
|
// SetT sets the current *testing.T context.
|
|
func (suite *Suite) SetT(t *testing.T) {
|
|
suite.t = t
|
|
suite.Assertions = assert.New(t)
|
|
suite.require = require.New(t)
|
|
}
|
|
|
|
// Require returns a require context for suite.
|
|
func (suite *Suite) Require() *require.Assertions {
|
|
if suite.require == nil {
|
|
suite.require = require.New(suite.T())
|
|
}
|
|
return suite.require
|
|
}
|
|
|
|
// Assert returns an assert context for suite. Normally, you can call
|
|
// `suite.NoError(expected, actual)`, 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()`.
|
|
func (suite *Suite) Assert() *assert.Assertions {
|
|
if suite.Assertions == nil {
|
|
suite.Assertions = assert.New(suite.T())
|
|
}
|
|
return suite.Assertions
|
|
}
|
|
|
|
func failOnPanic(t *testing.T) {
|
|
r := recover()
|
|
if r != nil {
|
|
t.Errorf("test panicked: %v\n%s", r, debug.Stack())
|
|
t.FailNow()
|
|
}
|
|
}
|
|
|
|
// Run provides suite functionality around golang subtests. It should be
|
|
// called in place of t.Run(name, func(t *testing.T)) in test suite code.
|
|
// The passed-in func will be executed as a subtest with a fresh instance of t.
|
|
// Provides compatibility with go test pkg -run TestSuite/TestName/SubTestName.
|
|
func (suite *Suite) Run(name string, subtest func()) bool {
|
|
oldT := suite.T()
|
|
defer suite.SetT(oldT)
|
|
return oldT.Run(name, func(t *testing.T) {
|
|
suite.SetT(t)
|
|
subtest()
|
|
})
|
|
}
|
|
|
|
// Run takes a testing suite and runs all of the tests attached
|
|
// to it.
|
|
func Run(t *testing.T, suite TestingSuite) {
|
|
testsSync := &sync.WaitGroup{}
|
|
suite.SetT(t)
|
|
defer failOnPanic(t)
|
|
|
|
suiteSetupDone := false
|
|
|
|
methodFinder := reflect.TypeOf(suite)
|
|
tests := []testing.InternalTest{}
|
|
for index := 0; index < methodFinder.NumMethod(); index++ {
|
|
method := methodFinder.Method(index)
|
|
ok, err := methodFilter(method.Name)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
if !ok {
|
|
continue
|
|
}
|
|
if !suiteSetupDone {
|
|
if setupAllSuite, ok := suite.(SetupAllSuite); ok {
|
|
setupAllSuite.SetupSuite()
|
|
}
|
|
defer func() {
|
|
if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
|
|
testsSync.Wait()
|
|
tearDownAllSuite.TearDownSuite()
|
|
}
|
|
}()
|
|
suiteSetupDone = true
|
|
}
|
|
test := testing.InternalTest{
|
|
Name: method.Name,
|
|
F: func(t *testing.T) {
|
|
defer testsSync.Done()
|
|
parentT := suite.T()
|
|
suite.SetT(t)
|
|
defer failOnPanic(t)
|
|
|
|
if setupTestSuite, ok := suite.(SetupTestSuite); ok {
|
|
setupTestSuite.SetupTest()
|
|
}
|
|
if beforeTestSuite, ok := suite.(BeforeTest); ok {
|
|
beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
|
|
}
|
|
defer func() {
|
|
if afterTestSuite, ok := suite.(AfterTest); ok {
|
|
afterTestSuite.AfterTest(methodFinder.Elem().Name(), method.Name)
|
|
}
|
|
if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
|
|
tearDownTestSuite.TearDownTest()
|
|
}
|
|
suite.SetT(parentT)
|
|
}()
|
|
method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
|
|
},
|
|
}
|
|
tests = append(tests, test)
|
|
testsSync.Add(1)
|
|
}
|
|
runTests(t, tests)
|
|
}
|
|
|
|
func runTests(t testing.TB, tests []testing.InternalTest) {
|
|
r, ok := t.(runner)
|
|
if !ok { // backwards compatibility with Go 1.6 and below
|
|
if !testing.RunTests(allTestsFilter, tests) {
|
|
t.Fail()
|
|
}
|
|
return
|
|
}
|
|
|
|
for _, test := range tests {
|
|
r.Run(test.Name, test.F)
|
|
}
|
|
}
|
|
|
|
// Filtering method according to set regular expression
|
|
// specified command-line argument -m
|
|
func methodFilter(name string) (bool, error) {
|
|
if ok, _ := regexp.MatchString("^Test", name); !ok {
|
|
return false, nil
|
|
}
|
|
return regexp.MatchString(*matchMethod, name)
|
|
}
|
|
|
|
type runner interface {
|
|
Run(name string, f func(t *testing.T)) bool
|
|
}
|