-
Notifications
You must be signed in to change notification settings - Fork 72
✨ Concurrent E2E Test Execution #2675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| @Serial | ||
| Feature: TLS profile enforcement on metrics endpoints | ||
|
|
||
| Background: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,12 @@ | ||
| package e2e | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "fmt" | ||
| "io" | ||
| "log" | ||
| "os" | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/cucumber/godog" | ||
|
|
@@ -14,52 +17,145 @@ import ( | |
| "github.com/operator-framework/operator-controller/test/e2e/steps" | ||
| ) | ||
|
|
||
| var opts = godog.Options{ | ||
| var cliOpts = godog.Options{ | ||
| Concurrency: 1, | ||
| Format: "pretty", | ||
| Paths: []string{"features"}, | ||
| Output: colors.Colored(os.Stdout), | ||
| Concurrency: 1, | ||
| NoColors: true, | ||
| } | ||
|
|
||
| func init() { | ||
| godog.BindCommandLineFlags("godog.", &opts) | ||
| godog.BindCommandLineFlags("godog.", &cliOpts) | ||
| } | ||
|
|
||
| func TestMain(m *testing.M) { | ||
| // parse CLI arguments | ||
| pflag.Parse() | ||
| opts.Paths = pflag.Args() | ||
| cliOpts.Paths = pflag.Args() | ||
|
|
||
| if cliOpts.Tags != "" { | ||
| fmt.Println("Note: Custom feature tags provided - disabling automatic test parallelization") | ||
| // run tests explicitly as requested by CLI | ||
| sc := godog.TestSuite{ | ||
| TestSuiteInitializer: InitializeSuite, | ||
| ScenarioInitializer: InitializeScenario, | ||
| Options: &cliOpts, | ||
| }.Run() | ||
|
|
||
| if sc != 0 { | ||
| // 1 - failed | ||
| // 2 - command line usage error | ||
| // 128 - or higher, os signal related error exit codes | ||
| log.Fatalf("non-zero status returned: (%d), failed to run feature tests", sc) | ||
| } | ||
| } else { | ||
| executeTestsParallel() | ||
| } | ||
|
|
||
| path := os.Getenv("E2E_SUMMARY_OUTPUT") | ||
| if path == "" { | ||
| fmt.Println("Note: E2E_SUMMARY_OUTPUT is unset; skipping summary generation") | ||
| } else { | ||
| if err := testutil.PrintSummary(path); err != nil { | ||
| // Fail the run if alerts are found | ||
| fmt.Printf("%v", err) | ||
| os.Exit(1) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func executeTestsParallel() { | ||
| // Create buffers to capture output for final summary | ||
| var parallelBuf, serialBuf bytes.Buffer | ||
|
|
||
| parallelOpts := cliOpts | ||
| if !pflag.Lookup("godog.concurrency").Changed { | ||
| // Override default concurrency value with 100; otherwise use whatever was provided by CLI | ||
| parallelOpts.Concurrency = 100 | ||
| } | ||
| parallelOpts.Tags = "~@Serial" | ||
| // Write to both specified output (live to stdout, by default) and buffer (for summary) | ||
| parallelOpts.Output = io.MultiWriter(parallelOpts.Output, ¶llelBuf) | ||
| // run tests concurrently | ||
|
Comment on lines
+69
to
+80
|
||
| scParallel := godog.TestSuite{ | ||
| TestSuiteInitializer: InitializeSuite, | ||
| ScenarioInitializer: InitializeScenario, | ||
| Options: ¶llelOpts, | ||
| }.Run() | ||
|
|
||
| // run tests | ||
| sc := godog.TestSuite{ | ||
| fmt.Println("End of parallel run - beginning serial tests") | ||
|
|
||
| serialOpts := cliOpts | ||
| serialOpts.Concurrency = 1 | ||
| serialOpts.Tags = "@Serial" | ||
| // Write to both specified output (live to stdout, by default) and buffer (for summary) | ||
| serialOpts.Output = io.MultiWriter(serialOpts.Output, &serialBuf) | ||
| // run tests serially | ||
| scSerial := godog.TestSuite{ | ||
| TestSuiteInitializer: InitializeSuite, | ||
| ScenarioInitializer: InitializeScenario, | ||
| Options: &opts, | ||
| Options: &serialOpts, | ||
| }.Run() | ||
|
|
||
| switch sc { | ||
| // 0 - success | ||
| case 0: | ||
|
|
||
| path := os.Getenv("E2E_SUMMARY_OUTPUT") | ||
| if path == "" { | ||
| fmt.Println("Note: E2E_SUMMARY_OUTPUT is unset; skipping summary generation") | ||
| } else { | ||
| if err := testutil.PrintSummary(path); err != nil { | ||
| // Fail the run if alerts are found | ||
| fmt.Printf("%v", err) | ||
| os.Exit(1) | ||
| } | ||
| // TODO We re-print the output of any failed steps here for easier debugging. However, it would be | ||
| // better to combine this with the E2E_SUMMARY_OUTPUT and show pass/fail + performance in one | ||
| // markdown output then preserve the console output for local testing. | ||
|
|
||
| // Print aggregated summary | ||
| fmt.Println("\n" + strings.Repeat("=", 80)) | ||
| fmt.Println("TEST EXECUTION SUMMARY") | ||
| fmt.Println(strings.Repeat("=", 80)) | ||
|
|
||
| fmt.Printf("\nParallel Tests Exit Code: %d\n", scParallel) | ||
| if scParallel != 0 { | ||
| failedSteps := extractFailedSteps(parallelBuf.String()) | ||
| if failedSteps != "" { | ||
| fmt.Println("\nParallel Test Failures:") | ||
| fmt.Println(strings.Repeat("-", 80)) | ||
| fmt.Println(failedSteps) | ||
| } | ||
| } | ||
|
|
||
| fmt.Printf("\nSerial Tests Exit Code: %d\n", scSerial) | ||
| if scSerial != 0 { | ||
| failedSteps := extractFailedSteps(serialBuf.String()) | ||
| if failedSteps != "" { | ||
| fmt.Println("\nSerial Test Failures:") | ||
| fmt.Println(strings.Repeat("-", 80)) | ||
| fmt.Println(failedSteps) | ||
| } | ||
| } | ||
|
|
||
| fmt.Println(strings.Repeat("=", 80)) | ||
|
|
||
| if scParallel != 0 || scSerial != 0 { | ||
| // 1 - failed | ||
| // 2 - command line usage error | ||
| // 128 - or higher, os signal related error exit codes | ||
| log.Fatalf("non-zero status returned; parallel: (%d), serial: (%d), failed to run feature tests", scParallel, scSerial) | ||
| } | ||
| } | ||
|
|
||
| // extractFailedSteps extracts the "--- Failed steps:" section from godog output | ||
| func extractFailedSteps(output string) string { | ||
| lines := strings.Split(output, "\n") | ||
| var failedSection []string | ||
| capturing := false | ||
|
|
||
| for _, line := range lines { | ||
| if strings.Contains(line, "--- Failed steps:") { | ||
| capturing = true | ||
| } | ||
| return | ||
| if capturing { | ||
| failedSection = append(failedSection, line) | ||
| } | ||
| } | ||
|
|
||
| // 1 - failed | ||
| // 2 - command line usage error | ||
| // 128 - or higher, os signal related error exit codes | ||
| default: | ||
| log.Fatalf("non-zero status returned (%d), failed to run feature tests", sc) | ||
| if len(failedSection) == 0 { | ||
| return "" | ||
| } | ||
| return strings.Join(failedSection, "\n") | ||
| } | ||
|
|
||
| func InitializeSuite(tc *godog.TestSuiteContext) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.