e2etest/newe2e_suite_manager.go (100 lines of code) (raw):

package e2etest import ( "reflect" "strings" "testing" ) type SuiteManager struct { testingT *testing.T Suites map[string]any EarlyRunSuites map[string]any } var suiteManager = &SuiteManager{Suites: make(map[string]any), EarlyRunSuites: make(map[string]any)} func (sm *SuiteManager) RegisterSuite(Suite any) { suiteName := reflect.ValueOf(Suite).Elem().Type().Name() sm.Suites[suiteName] = Suite } // Early runs do not run in parallel, and run before anything else. func (sm *SuiteManager) RegisterEarlyRunSuite(Suite any) { suiteName := reflect.ValueOf(Suite).Elem().Type().Name() sm.EarlyRunSuites[suiteName] = Suite } func (sm *SuiteManager) RunSuites(t *testing.T) { defer func() { NewFrameworkAsserter(t).AssertNow("Suite runner panicked (fatal)", IsNil{}, recover()) }() sm.testingT = t tgt := sm.EarlyRunSuites early := true runAllSuites: for sName, v := range tgt { sVal := reflect.ValueOf(v) sTyp := reflect.TypeOf(v) mCount := sVal.NumMethod() setupIdx := -1 teardownIdx := -1 testIdxs := make(map[string]int) for idx := 0; idx < mCount; idx++ { method := sTyp.Method(idx) mName := method.Name // in (self, asserter) out () if method.Type.NumIn() != 2 || method.Type.NumOut() != 0 { continue } // check that the first input is actually an asserter inName := method.Type.In(1).String() if inName != "*e2etest.ScenarioVariationManager" && inName != "e2etest.Asserter" { continue } switch { case strings.EqualFold(mName, "SetupSuite"): setupIdx = idx case strings.EqualFold(mName, "TeardownSuite"): teardownIdx = idx case strings.HasPrefix(mName, "Scenario_"): testIdxs[mName] = idx } } t.Run(sName, func(t *testing.T) { if !early { // Early runners must run now. t.Parallel() // todo: env var } if setupIdx != -1 { // todo: call setup with suite manager defer func() { NewFrameworkAsserter(t).AssertNow("Scenario setup panicked (recovered)", NoError{stackTrace: true}, recover()) }() sVal.Method(setupIdx).Call([]reflect.Value{reflect.ValueOf(&ScenarioVariationManager{t: t})}) } if !t.Failed() { for scenarioName, scenarioIdx := range testIdxs { scenarioName := scenarioName // create intermediate values. scenarioIdx := scenarioIdx // This bug is technically fixed in newer versions of Go, but the fix must be manually applied. t.Run(scenarioName, func(t *testing.T) { defer func() { NewFrameworkAsserter(t).AssertNow("Scenario runner panicked (recovered)", NoError{stackTrace: true}, recover()) }() if !early { t.Parallel() } sm := NewScenarioManager(t, sVal.Method(scenarioIdx)) sm.runNow = early sm.RunScenario() _ = scenarioIdx }) } } else { (&FrameworkAsserter{t: t, SuiteName: sName}).Log("Skipping tests, failed suite setup...") } if teardownIdx != -1 { t.Cleanup(func() { defer func() { NewFrameworkAsserter(t).AssertNow("Scenario teardown panicked (recovered; you probably need to manually clean up resources)", NoError{stackTrace: true}, recover()) }() sVal.Method(teardownIdx).Call([]reflect.Value{reflect.ValueOf(&FrameworkAsserter{t: t, SuiteName: sName, ScenarioName: "Teardown"})}) }) } }) } if early { // Jump back and run the remaining suites. early = false tgt = sm.Suites goto runAllSuites } }