diff --git a/suite/LICENSE b/suite/LICENSE new file mode 100644 index 00000000..473b670a --- /dev/null +++ b/suite/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell + +Please consider promoting this project if you find it useful. + +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. diff --git a/suite/suite.go b/suite/suite.go new file mode 100644 index 00000000..344b3ebc --- /dev/null +++ b/suite/suite.go @@ -0,0 +1,205 @@ +/*Package suite provides compatibility with testify/suite. + +Suites can be used to group tests together, and to perform common setup and +teardown for each test in the suite. + +TODO: example + +Regular expression to select test suites specified command-line +argument "-run". Regular expression to select the methods +of test suites specified command-line argument "-m". +Suite object has assertion methods. +*/ +package suite + +import ( + "flag" + "reflect" + "regexp" + "strings" + "testing" + + "github.com/gotestyourself/gotestyourself/assert" +) + +var methodPattern = ®expValue{} + +func init() { + flag.Var(methodPattern, "test.m", "only run tests that match the regexp") +} + +// TestingSuite used is the interface for a test suite +type TestingSuite interface { + T() *testing.T + SetT(*testing.T) +} + +type setupSuite interface { + SetupSuite() +} + +type setupTest interface { + SetupTest() +} + +type teardownSuite interface { + TearDownSuite() +} + +type teardownTest interface { + TearDownTest() +} + +type beforeTest interface { + BeforeTest(suiteName, testName string) +} + +type afterTest interface { + AfterTest(suiteName, testName string) +} + +type helperT interface { + Helper() +} + +// Suite is an implementation of TestingSuite which can be embedded in a test +// suite. +type Suite struct { + 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 +} + +// Assert performs a comparison, marks the test as having failed if the comparison +// returns false, and stops execution immediately. +// +// This is equivalent to assert.Assert(t, comparison). +func (suite *Suite) Assert(comparison assert.BoolOrComparison, msgAndArgs ...interface{}) { + if ht, ok := testing.TB(suite.t).(helperT); ok { + ht.Helper() + } + // TODO: will print `comparison` instead of caller ast when used with bool + assert.Assert(suite.t, comparison, msgAndArgs...) +} + +// Check performs a comparison and marks the test as having failed if the comparison +// returns false. Returns the result of the comparison. +func (suite *Suite) Check(comparison assert.BoolOrComparison, msgAndArgs ...interface{}) bool { + if ht, ok := testing.TB(suite.t).(helperT); ok { + ht.Helper() + } + // TODO: will print `comparison` instead of caller ast when used with bool + return assert.Check(suite.t, comparison, msgAndArgs...) +} + +// Run all the tests in a testing suite +func Run(t *testing.T, suite TestingSuite) { + suite.SetT(t) + + if s, ok := suite.(setupSuite); ok { + s.SetupSuite() + } + defer func() { + if s, ok := suite.(teardownSuite); ok { + s.TearDownSuite() + } + }() + + suiteType := reflect.TypeOf(suite) + tests := []testing.InternalTest{} + for index := 0; index < suiteType.NumMethod(); index++ { + method := suiteType.Method(index) + if !isTestMethod(method.Name) { + continue + } + test := testing.InternalTest{ + Name: method.Name, + F: newTestFunc(suite, method), + } + tests = append(tests, test) + } + runTests(t, tests) +} + +func newTestFunc(suite TestingSuite, method reflect.Method) func(*testing.T) { + suiteType := reflect.TypeOf(suite) + return func(t *testing.T) { + parentT := suite.T() + suite.SetT(t) + if s, ok := suite.(setupTest); ok { + s.SetupTest() + } + if s, ok := suite.(beforeTest); ok { + s.BeforeTest(suiteType.Elem().Name(), method.Name) + } + defer func() { + if s, ok := suite.(afterTest); ok { + s.AfterTest(suiteType.Elem().Name(), method.Name) + } + if s, ok := suite.(teardownTest); ok { + s.TearDownTest() + } + suite.SetT(parentT) + }() + method.Func.Call([]reflect.Value{reflect.ValueOf(suite)}) + } +} + +type runner interface { + Run(name string, f func(t *testing.T)) bool +} + +func runTests(t testing.TB, tests []testing.InternalTest) { + r, ok := t.(runner) + if !ok { // backwards compatibility with Go 1.6 and below + allTestsFilter := func(_, _ string) (bool, error) { return true, nil } + if !testing.RunTests(allTestsFilter, tests) { + t.Fail() + } + return + } + + for _, test := range tests { + r.Run(test.Name, test.F) + } +} + +// TODO: should also check the next character after Test is uppercase +func isTestMethod(name string) bool { + if !strings.HasPrefix(name, "Test") { + return false + } + return methodPattern.Match(name) +} + +type regexpValue struct { + re *regexp.Regexp +} + +func (v *regexpValue) String() string { + if v.re == nil { + return "" + } + return v.re.String() +} + +func (v *regexpValue) Set(value string) error { + re, err := regexp.Compile(value) + v.re = re + return err +} + +func (v *regexpValue) Match(value string) bool { + if v.re == nil { + return true + } + return v.re.MatchString(value) +} diff --git a/suite/suite_test.go b/suite/suite_test.go new file mode 100644 index 00000000..a2480b58 --- /dev/null +++ b/suite/suite_test.go @@ -0,0 +1,96 @@ +package suite + +import ( + "testing" + + "github.com/gotestyourself/gotestyourself/assert" + is "github.com/gotestyourself/gotestyourself/assert/cmp" +) + +type fakeSuite struct { + Suite + + suiteT *testing.T + counter int + + beforeTestCalls []string + testCalls []string + afterTestCalls []string +} + +func (s *fakeSuite) assertAndIncrement(expected int) { + if ht, ok := testing.TB(s.t).(helperT); ok { + ht.Helper() + } + s.Assert(is.Equal(s.counter, expected)) + s.counter++ +} + +func (s *fakeSuite) baseCount() int { + return len(s.afterTestCalls) * 4 +} + +func (s *fakeSuite) SetupSuite() { + s.assertAndIncrement(0) + s.Assert(is.Equal(s.suiteT, s.T())) +} + +func (s *fakeSuite) SetupTest() { + s.assertAndIncrement(s.baseCount() + 1) + s.Assert(s.suiteT != s.T()) +} + +func (s *fakeSuite) BeforeTest(suiteName, testName string) { + s.assertAndIncrement(s.baseCount() + 2) + s.Assert(is.Equal(suiteName, "fakeSuite")) + s.beforeTestCalls = append(s.beforeTestCalls, testName) + s.Assert(s.suiteT != s.T()) +} + +func (s *fakeSuite) AfterTest(suiteName, testName string) { + s.assertAndIncrement(s.baseCount() + 3) + s.Assert(is.Equal(suiteName, "fakeSuite")) + s.afterTestCalls = append(s.afterTestCalls, testName) + s.Assert(s.suiteT != s.T()) +} + +func (s *fakeSuite) TearDownTest() { + s.assertAndIncrement(s.baseCount()) + s.Assert(s.suiteT != s.T()) +} + +func (s *fakeSuite) TearDownSuite() { + s.assertAndIncrement(s.baseCount() + 1) + s.Assert(is.Equal(s.suiteT, s.T())) +} + +func (s *fakeSuite) TestOne() { + s.testCalls = append(s.testCalls, "TestOne") + s.Assert(s.suiteT != s.T()) +} + +func (s *fakeSuite) TestTwo() { + s.testCalls = append(s.testCalls, "TestTwo") +} + +func (s *fakeSuite) TestSkip() { + s.testCalls = append(s.testCalls, "TestSkip") + s.T().Skip() +} + +func (s *fakeSuite) NonATestMethod() { +} + +func TestRunSuite(t *testing.T) { + fakeSuite := new(fakeSuite) + fakeSuite.suiteT = t + Run(t, fakeSuite) + + expectedCount := 14 // setupSuite=1 + teardownSuite=1 + (numTests=3 * numFixtures=4) + assert.Equal(t, fakeSuite.counter, expectedCount) + + expected := []string{"TestOne", "TestSkip", "TestTwo"} + assert.Assert(t, is.Compare(expected, fakeSuite.testCalls)) + assert.Assert(t, is.Compare(expected, fakeSuite.afterTestCalls)) + assert.Assert(t, is.Compare(expected, fakeSuite.beforeTestCalls)) +}