From 55fbc86ed679520b156a06793214d4bb591f6f30 Mon Sep 17 00:00:00 2001 From: renzo Date: Sun, 25 May 2025 10:14:53 +0200 Subject: [PATCH 1/3] suite: add support for user-defined test filtering --- suite/suite.go | 10 +++ suite/suite_test.go | 166 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) diff --git a/suite/suite.go b/suite/suite.go index a7ceba731..cd1336603 100644 --- a/suite/suite.go +++ b/suite/suite.go @@ -123,6 +123,12 @@ type test = struct { // Run takes a testing suite and runs all of the tests attached // to it. func Run(t *testing.T, suite TestingSuite) { + RunWithSkip(t, suite, func(_, _ string) bool { return false }) +} + +// RunWithSkip takes a testing suite and a skip function allowing +// for extra filtering on tests to be run +func RunWithSkip(t *testing.T, suite TestingSuite, skip func(string, string) bool) { defer recoverAndFailOnPanic(t) suite.SetT(t) @@ -150,6 +156,10 @@ func Run(t *testing.T, suite TestingSuite) { continue } + if skip(suiteName, method.Name) { + continue + } + test := test{ name: method.Name, run: func(t *testing.T) { diff --git a/suite/suite_test.go b/suite/suite_test.go index 6cf23459f..66a230db7 100644 --- a/suite/suite_test.go +++ b/suite/suite_test.go @@ -751,3 +751,169 @@ func TestUnInitializedSuites(t *testing.T) { }) }) } + +var register = map[string]bool{"SuiteTesterTestTwo": true} + +func skip(suiteName, testName string) bool { + return register[suiteName+testName] +} + +// TestRunSuiteWithSkip will be run by the 'go test' command, so within it, we +// can run our suite using the Run(*testing.T, TestingSuite) function. +func TestRunSuiteWithSkip(t *testing.T) { + suiteTester := new(SuiteTester) + RunWithSkip(t, suiteTester, skip) + + // Normally, the test would end here. The following are simply + // some assertions to ensure that the Run function is working as + // intended - they are not part of the example. + + // The suite was only run once, so the SetupSuite and TearDownSuite + // methods should have each been run only once. + assert.Equal(t, 1, suiteTester.SetupSuiteRunCount) + assert.Equal(t, 1, suiteTester.TearDownSuiteRunCount) + + assert.Len(t, suiteTester.SuiteNameAfter, 3) + assert.Len(t, suiteTester.SuiteNameBefore, 3) + assert.Len(t, suiteTester.TestNameAfter, 3) + assert.Len(t, suiteTester.TestNameBefore, 3) + + assert.Contains(t, suiteTester.TestNameAfter, "TestOne") + assert.NotContains(t, suiteTester.TestNameAfter, "TestTwo") + assert.Contains(t, suiteTester.TestNameAfter, "TestSkip") + assert.Contains(t, suiteTester.TestNameAfter, "TestSubtest") + + assert.Contains(t, suiteTester.TestNameBefore, "TestOne") + assert.NotContains(t, suiteTester.TestNameBefore, "TestTwo") + assert.Contains(t, suiteTester.TestNameBefore, "TestSkip") + assert.Contains(t, suiteTester.TestNameBefore, "TestSubtest") + + assert.Contains(t, suiteTester.SetupSubTestNames, "TestRunSuiteWithSkip/TestSubtest/first") + assert.Contains(t, suiteTester.SetupSubTestNames, "TestRunSuiteWithSkip/TestSubtest/second") + + assert.Contains(t, suiteTester.TearDownSubTestNames, "TestRunSuiteWithSkip/TestSubtest/first") + assert.Contains(t, suiteTester.TearDownSubTestNames, "TestRunSuiteWithSkip/TestSubtest/second") + + for _, suiteName := range suiteTester.SuiteNameAfter { + assert.Equal(t, "SuiteTester", suiteName) + } + + for _, suiteName := range suiteTester.SuiteNameBefore { + assert.Equal(t, "SuiteTester", suiteName) + } + + for _, when := range suiteTester.TimeAfter { + assert.False(t, when.IsZero()) + } + + for _, when := range suiteTester.TimeBefore { + assert.False(t, when.IsZero()) + } + + // There are four test methods (TestOne, TestTwo, TestSkip, and TestSubtest), so + // the SetupTest and TearDownTest methods (which should be run once for + // each test) should have been run four times. + assert.Equal(t, 3, suiteTester.SetupTestRunCount) + assert.Equal(t, 3, suiteTester.TearDownTestRunCount) + + // Each test should have been run once. + assert.Equal(t, 1, suiteTester.TestOneRunCount) + assert.Equal(t, 0, suiteTester.TestTwoRunCount) + assert.Equal(t, 1, suiteTester.TestSubtestRunCount) + + assert.Equal(t, 2, suiteTester.TearDownSubTestRunCount) + assert.Equal(t, 2, suiteTester.SetupSubTestRunCount) + + // Methods that don't match the test method identifier shouldn't + // have been run at all. + assert.Equal(t, 0, suiteTester.NonTestMethodRunCount) + + suiteSkipTester := new(SuiteSkipTester) + Run(t, suiteSkipTester) + + // The suite was only run once, so the SetupSuite and TearDownSuite + // methods should have each been run only once, even though SetupSuite + // called Skip() + assert.Equal(t, 1, suiteSkipTester.SetupSuiteRunCount) + assert.Equal(t, 1, suiteSkipTester.TearDownSuiteRunCount) + +} + +// TestRunSuiteWithSkip will be run by the 'go test' command, so within it, we +// can run our suite using the Run(*testing.T, TestingSuite) function. +func TestRunSuiteWithSkipAll(t *testing.T) { + suiteTester := new(SuiteTester) + RunWithSkip(t, suiteTester, func(_, _ string) bool { return true }) + + // Normally, the test would end here. The following are simply + // some assertions to ensure that the Run function is working as + // intended - they are not part of the example. + + // The suite was only run once, so the SetupSuite and TearDownSuite + // methods should have each been run only once. + assert.Equal(t, 0, suiteTester.SetupSuiteRunCount) + assert.Equal(t, 0, suiteTester.TearDownSuiteRunCount) + + assert.Len(t, suiteTester.SuiteNameAfter, 0) + assert.Len(t, suiteTester.SuiteNameBefore, 0) + assert.Len(t, suiteTester.TestNameAfter, 0) + assert.Len(t, suiteTester.TestNameBefore, 0) + + assert.NotContains(t, suiteTester.TestNameAfter, "TestOne") + assert.NotContains(t, suiteTester.TestNameAfter, "TestTwo") + assert.NotContains(t, suiteTester.TestNameAfter, "TestSkip") + assert.NotContains(t, suiteTester.TestNameAfter, "TestSubtest") + + assert.NotContains(t, suiteTester.TestNameBefore, "TestOne") + assert.NotContains(t, suiteTester.TestNameBefore, "TestTwo") + assert.NotContains(t, suiteTester.TestNameBefore, "TestSkip") + assert.NotContains(t, suiteTester.TestNameBefore, "TestSubtest") + + assert.NotContains(t, suiteTester.SetupSubTestNames, "TestRunSuiteWithSkipAll/TestSubtest/first") + assert.NotContains(t, suiteTester.SetupSubTestNames, "TestRunSuiteWithSkipAll/TestSubtest/second") + + assert.NotContains(t, suiteTester.TearDownSubTestNames, "TestRunSuiteWithSkipAll/TestSubtest/first") + assert.NotContains(t, suiteTester.TearDownSubTestNames, "TestRunSuiteWithSkipAll/TestSubtest/second") + + for _, suiteName := range suiteTester.SuiteNameAfter { + assert.Equal(t, "SuiteTester", suiteName) + } + + for _, suiteName := range suiteTester.SuiteNameBefore { + assert.Equal(t, "SuiteTester", suiteName) + } + + for _, when := range suiteTester.TimeAfter { + assert.False(t, when.IsZero()) + } + + for _, when := range suiteTester.TimeBefore { + assert.False(t, when.IsZero()) + } + + // There are four test methods (TestOne, TestTwo, TestSkip, and TestSubtest), so + // the SetupTest and TearDownTest methods (which should be run once for + // each test) should have been run four times. + assert.Equal(t, 0, suiteTester.SetupTestRunCount) + assert.Equal(t, 0, suiteTester.TearDownTestRunCount) + + // Each test should have been run once. + assert.Equal(t, 0, suiteTester.TestOneRunCount) + assert.Equal(t, 0, suiteTester.TestTwoRunCount) + assert.Equal(t, 0, suiteTester.TestSubtestRunCount) + + assert.Equal(t, 0, suiteTester.TearDownSubTestRunCount) + assert.Equal(t, 0, suiteTester.SetupSubTestRunCount) + + // Methods that don't match the test method identifier shouldn't + // have been run at all. + assert.Equal(t, 0, suiteTester.NonTestMethodRunCount) + + suiteSkipTester := new(SuiteSkipTester) + Run(t, suiteSkipTester) + + // All tests were skipped via skip function, so Setup/TearDownSuite should not run + assert.Equal(t, 0, suiteSkipTester.SetupSuiteRunCount) + assert.Equal(t, 0, suiteSkipTester.TearDownSuiteRunCount) + +} From 10cdced853b1fd1f0201752d5d6ed7e1e8bf7776 Mon Sep 17 00:00:00 2001 From: renzo Date: Sun, 8 Jun 2025 11:58:11 +0200 Subject: [PATCH 2/3] Change implementation to use interface instead of function + PR feedback --- suite/interfaces.go | 6 +++ suite/suite.go | 12 ++---- suite/suite_test.go | 100 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 99 insertions(+), 19 deletions(-) diff --git a/suite/interfaces.go b/suite/interfaces.go index fed037d7f..0ea083e96 100644 --- a/suite/interfaces.go +++ b/suite/interfaces.go @@ -64,3 +64,9 @@ type SetupSubTest interface { type TearDownSubTest interface { TearDownSubTest() } + +// SkipTest has a SkipTest method, which will run for every test during the +// collection phase. +type SkipTest interface { + SkipTest(testSuiteName, testName string) bool +} diff --git a/suite/suite.go b/suite/suite.go index cd1336603..7751b069c 100644 --- a/suite/suite.go +++ b/suite/suite.go @@ -123,12 +123,6 @@ type test = struct { // Run takes a testing suite and runs all of the tests attached // to it. func Run(t *testing.T, suite TestingSuite) { - RunWithSkip(t, suite, func(_, _ string) bool { return false }) -} - -// RunWithSkip takes a testing suite and a skip function allowing -// for extra filtering on tests to be run -func RunWithSkip(t *testing.T, suite TestingSuite, skip func(string, string) bool) { defer recoverAndFailOnPanic(t) suite.SetT(t) @@ -156,8 +150,10 @@ func RunWithSkip(t *testing.T, suite TestingSuite, skip func(string, string) boo continue } - if skip(suiteName, method.Name) { - continue + if skipTest, ok := suite.(SkipTest); ok { + if skipTest.SkipTest(suiteName, method.Name) { + continue + } } test := test{ diff --git a/suite/suite_test.go b/suite/suite_test.go index 66a230db7..30e4cf81e 100644 --- a/suite/suite_test.go +++ b/suite/suite_test.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "flag" + "fmt" "io" "math/rand" "os" @@ -752,17 +753,83 @@ func TestUnInitializedSuites(t *testing.T) { }) } -var register = map[string]bool{"SuiteTesterTestTwo": true} +// Test/Example of SkipTest -func skip(suiteName, testName string) bool { - return register[suiteName+testName] +type buildVersion struct { + major int + minor int +} + +func (v buildVersion) after(other buildVersion) bool { + if v.major > other.major { + return true + } + if v.major < other.major { + return false + } + if v.minor > other.minor { + return true + } + return false +} + +type testRegistration struct { + release buildVersion + features []string +} + +var testRegistrations = map[string]testRegistration{ + "SuiteWithSkipTestOne": {buildVersion{1, 2}, []string{"myOldFeature"}}, + "SuiteWithSkipTestTwo": {buildVersion{2, 0}, []string{"myNewFeature"}}, +} + +func contains(s []string, v string) bool { + for i := range s { + if v == s[i] { + return true + } + } + return false +} + +// reusing the Suite struct defined above +type SuiteWithSkip struct { + SuiteTester +} + +// SkipTest Implements the SkipTest interface +func (s *SuiteWithSkip) SkipTest(testSuiteName string, testName string) bool { + testRegister, ok := testRegistrations[testSuiteName+testName] + if !ok { + return false + } + + // runParameters. + // These could for example be defined in a file, or as cli arguments. + // In this example we define them here + currentVersion := buildVersion{1, 2} + enabledFeatures := []string{"myOldFeature", "myNewFeature"} + + if testRegister.release.after(currentVersion) { + fmt.Printf("Skipping %s, due to release", testName) + return true + } + + for _, registeredFeature := range testRegister.features { + if !contains(enabledFeatures, registeredFeature) { + fmt.Printf("Skipping %s, due to feature", testName) + return true + } + } + + return false } // TestRunSuiteWithSkip will be run by the 'go test' command, so within it, we // can run our suite using the Run(*testing.T, TestingSuite) function. func TestRunSuiteWithSkip(t *testing.T) { - suiteTester := new(SuiteTester) - RunWithSkip(t, suiteTester, skip) + suiteTester := new(SuiteWithSkip) + Run(t, suiteTester) // Normally, the test would end here. The following are simply // some assertions to ensure that the Run function is working as @@ -795,11 +862,11 @@ func TestRunSuiteWithSkip(t *testing.T) { assert.Contains(t, suiteTester.TearDownSubTestNames, "TestRunSuiteWithSkip/TestSubtest/second") for _, suiteName := range suiteTester.SuiteNameAfter { - assert.Equal(t, "SuiteTester", suiteName) + assert.Equal(t, "SuiteWithSkip", suiteName) } for _, suiteName := range suiteTester.SuiteNameBefore { - assert.Equal(t, "SuiteTester", suiteName) + assert.Equal(t, "SuiteWithSkip", suiteName) } for _, when := range suiteTester.TimeAfter { @@ -839,11 +906,22 @@ func TestRunSuiteWithSkip(t *testing.T) { } +// SuiteWithSkipAll reuses the Suite struct defined above and implements the SkipTest interface skipping all tests +type SuiteWithSkipAll struct { + SuiteTester +} + +// SkipTest Implements the SkipTest interface all tests and Setup/TeardownSuite will be skipped +func (s *SuiteWithSkipAll) SkipTest(testSuiteName string, testName string) bool { + return true +} + // TestRunSuiteWithSkip will be run by the 'go test' command, so within it, we // can run our suite using the Run(*testing.T, TestingSuite) function. + func TestRunSuiteWithSkipAll(t *testing.T) { - suiteTester := new(SuiteTester) - RunWithSkip(t, suiteTester, func(_, _ string) bool { return true }) + suiteTester := new(SuiteWithSkipAll) + Run(t, suiteTester) // Normally, the test would end here. The following are simply // some assertions to ensure that the Run function is working as @@ -876,11 +954,11 @@ func TestRunSuiteWithSkipAll(t *testing.T) { assert.NotContains(t, suiteTester.TearDownSubTestNames, "TestRunSuiteWithSkipAll/TestSubtest/second") for _, suiteName := range suiteTester.SuiteNameAfter { - assert.Equal(t, "SuiteTester", suiteName) + assert.Equal(t, "SuiteWithSkipAll", suiteName) } for _, suiteName := range suiteTester.SuiteNameBefore { - assert.Equal(t, "SuiteTester", suiteName) + assert.Equal(t, "SuiteWithSkipAll", suiteName) } for _, when := range suiteTester.TimeAfter { From e50d7c2e9bb1b4f658379b6838ed279636fe390b Mon Sep 17 00:00:00 2001 From: renzo Date: Tue, 1 Jul 2025 16:41:34 +0200 Subject: [PATCH 3/3] remove global variable as requested --- suite/suite_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/suite/suite_test.go b/suite/suite_test.go index 30e4cf81e..324462912 100644 --- a/suite/suite_test.go +++ b/suite/suite_test.go @@ -778,11 +778,6 @@ type testRegistration struct { features []string } -var testRegistrations = map[string]testRegistration{ - "SuiteWithSkipTestOne": {buildVersion{1, 2}, []string{"myOldFeature"}}, - "SuiteWithSkipTestTwo": {buildVersion{2, 0}, []string{"myNewFeature"}}, -} - func contains(s []string, v string) bool { for i := range s { if v == s[i] { @@ -799,6 +794,11 @@ type SuiteWithSkip struct { // SkipTest Implements the SkipTest interface func (s *SuiteWithSkip) SkipTest(testSuiteName string, testName string) bool { + var testRegistrations = map[string]testRegistration{ + "SuiteWithSkipTestOne": {buildVersion{1, 2}, []string{"myOldFeature"}}, + "SuiteWithSkipTestTwo": {buildVersion{2, 0}, []string{"myNewFeature"}}, + } + testRegister, ok := testRegistrations[testSuiteName+testName] if !ok { return false