From fd0ed80ff2f15370bf0606632e274d946394bb54 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Thu, 8 Aug 2019 23:59:56 +0000 Subject: [PATCH] internal/test/suite Signed-off-by: Tibor Vass --- internal/test/suite/interfaces.go | 33 ++++++++++ internal/test/suite/suite.go | 96 +++++++++++++++++++++++++++++ internal/test/suite/testify.LICENSE | 21 +++++++ 3 files changed, 150 insertions(+) create mode 100644 internal/test/suite/interfaces.go create mode 100644 internal/test/suite/suite.go create mode 100644 internal/test/suite/testify.LICENSE diff --git a/internal/test/suite/interfaces.go b/internal/test/suite/interfaces.go new file mode 100644 index 0000000000..263de86ab8 --- /dev/null +++ b/internal/test/suite/interfaces.go @@ -0,0 +1,33 @@ +package suite + +import "testing" + +// SetupAllSuite has a SetupSuite method, which will run before the +// tests in the suite are run. +type SetupAllSuite interface { + SetUpSuite(t *testing.T) +} + +// SetupTestSuite has a SetupTest method, which will run before each +// test in the suite. +type SetupTestSuite interface { + SetUpTest(t *testing.T) +} + +// TearDownAllSuite has a TearDownSuite method, which will run after +// all the tests in the suite have been run. +type TearDownAllSuite interface { + TearDownSuite(t *testing.T) +} + +// TearDownTestSuite has a TearDownTest method, which will run after +// each test in the suite. +type TearDownTestSuite interface { + TearDownTest(t *testing.T) +} + +// TimeoutTestSuite has a OnTimeout method, which will run after +// a single test times out after a period specified by -timeout flag. +type TimeoutTestSuite interface { + OnTimeout() +} diff --git a/internal/test/suite/suite.go b/internal/test/suite/suite.go new file mode 100644 index 0000000000..7bfebcb59b --- /dev/null +++ b/internal/test/suite/suite.go @@ -0,0 +1,96 @@ +// Package suite is a simplified version of testify's suite package which has unnecessary dependencies. +// Please remove this package whenever possible. +package suite + +import ( + "flag" + "fmt" + "reflect" + "runtime/debug" + "strings" + "testing" + "time" +) + +// TimeoutFlag is the flag to set a per-test timeout when running tests. Defaults to `-timeout`. +var TimeoutFlag = flag.Duration("timeout", 0, "per-test panic after duration `d` (default 0, timeout disabled)") + +var typTestingT = reflect.TypeOf(new(testing.T)) + +// Run takes a testing suite and runs all of the tests attached to it. +func Run(t *testing.T, suite interface{}) { + defer failOnPanic(t) + + suiteSetupDone := false + + methodFinder := reflect.TypeOf(suite) + suiteName := methodFinder.Elem().Name() + for index := 0; index < methodFinder.NumMethod(); index++ { + method := methodFinder.Method(index) + if !methodFilter(method.Name, method.Type) { + continue + } + if !suiteSetupDone { + if setupAllSuite, ok := suite.(SetupAllSuite); ok { + setupAllSuite.SetUpSuite(t) + } + defer func() { + if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok { + tearDownAllSuite.TearDownSuite(t) + } + }() + suiteSetupDone = true + } + t.Run(suiteName+"/"+method.Name, func(t *testing.T) { + defer failOnPanic(t) + + if setupTestSuite, ok := suite.(SetupTestSuite); ok { + setupTestSuite.SetUpTest(t) + } + defer func() { + if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok { + tearDownTestSuite.TearDownTest(t) + } + }() + + var timeout <-chan time.Time + if *TimeoutFlag > 0 { + timeout = time.After(*TimeoutFlag) + } + panicCh := make(chan error) + go func() { + defer func() { + if r := recover(); r != nil { + panicCh <- fmt.Errorf("test panicked: %v\n%s", r, debug.Stack()) + } else { + close(panicCh) + } + }() + method.Func.Call([]reflect.Value{reflect.ValueOf(suite), reflect.ValueOf(t)}) + }() + select { + case err := <-panicCh: + if err != nil { + t.Fatal(err.Error()) + } + case <-timeout: + if timeoutSuite, ok := suite.(TimeoutTestSuite); ok { + timeoutSuite.OnTimeout() + } + t.Fatalf("timeout: test timed out after %s since start of test", *TimeoutFlag) + } + }) + } +} + +func failOnPanic(t *testing.T) { + r := recover() + if r != nil { + t.Errorf("test suite panicked: %v\n%s", r, debug.Stack()) + t.FailNow() + } +} + +func methodFilter(name string, typ reflect.Type) bool { + return strings.HasPrefix(name, "Test") && typ.NumIn() == 2 && typ.In(1) == typTestingT // 2 params: method receiver and *testing.T +} diff --git a/internal/test/suite/testify.LICENSE b/internal/test/suite/testify.LICENSE new file mode 100644 index 0000000000..f38ec5956b --- /dev/null +++ b/internal/test/suite/testify.LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.