Skip to content

Commit 0d47668

Browse files
dtfranzclaude
andcommitted
Concurrent Test Execution
Takes advantage of changes made to isolate test runs to execute as many tests in parallel as possible. For tests that must be run serially, the @serial tag has been added to the beginning of relevant feature file(s). Signed-off-by: Daniel Franz <dfranz@redhat.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent c8585e9 commit 0d47668

3 files changed

Lines changed: 104 additions & 31 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ test-experimental-e2e: COVERAGE_NAME := experimental-e2e
315315
test-experimental-e2e: export MANIFEST := $(EXPERIMENTAL_RELEASE_MANIFEST)
316316
test-experimental-e2e: export INSTALL_DEFAULT_CATALOGS := false
317317
test-experimental-e2e: PROMETHEUS_VALUES := helm/prom_experimental.yaml
318-
test-experimental-e2e: E2E_TIMEOUT := 20m
318+
test-experimental-e2e: E2E_TIMEOUT := 10m
319319
test-experimental-e2e: run-internal prometheus e2e e2e-coverage kind-clean #HELP Run experimental e2e test suite on local kind cluster
320320

321321
.PHONY: prometheus

test/e2e/features/tls.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
@Serial
12
Feature: TLS profile enforcement on metrics endpoints
23

34
Background:

test/e2e/features_test.go

Lines changed: 102 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package e2e
22

33
import (
4+
"bytes"
45
"fmt"
6+
"io"
57
"log"
68
"os"
9+
"strings"
710
"testing"
811

912
"github.com/cucumber/godog"
@@ -14,52 +17,121 @@ import (
1417
"github.com/operator-framework/operator-controller/test/e2e/steps"
1518
)
1619

17-
var opts = godog.Options{
18-
Format: "pretty",
19-
Paths: []string{"features"},
20-
Output: colors.Colored(os.Stdout),
21-
Concurrency: 1,
22-
NoColors: true,
20+
var defaultOpts = godog.Options{
21+
Format: "pretty",
22+
Paths: []string{"features"},
23+
Output: colors.Colored(os.Stdout),
24+
NoColors: true,
2325
}
2426

2527
func init() {
26-
godog.BindCommandLineFlags("godog.", &opts)
28+
godog.BindCommandLineFlags("godog.", &defaultOpts)
2729
}
2830

2931
func TestMain(m *testing.M) {
3032
// parse CLI arguments
3133
pflag.Parse()
32-
opts.Paths = pflag.Args()
34+
defaultOpts.Paths = pflag.Args()
3335

34-
// run tests
35-
sc := godog.TestSuite{
36+
// Create buffers to capture output for final summary
37+
var parallelBuf, serialBuf bytes.Buffer
38+
39+
parallelOpts := defaultOpts
40+
parallelOpts.Concurrency = 100
41+
parallelOpts.Tags = "~@Serial"
42+
parallelOpts.Randomize = -1
43+
// Write to both stdout (live) and buffer (for summary)
44+
parallelOpts.Output = io.MultiWriter(os.Stdout, &parallelBuf)
45+
// run tests concurrently
46+
scParallel := godog.TestSuite{
47+
TestSuiteInitializer: InitializeSuite,
48+
ScenarioInitializer: InitializeScenario,
49+
Options: &parallelOpts,
50+
}.Run()
51+
52+
fmt.Println("End of parallel run - beginning serial tests")
53+
54+
serialOpts := defaultOpts
55+
serialOpts.Concurrency = 1
56+
serialOpts.Tags = "@Serial"
57+
// Write to both stdout (live) and buffer (for summary)
58+
serialOpts.Output = io.MultiWriter(os.Stdout, &serialBuf)
59+
// run tests serially
60+
scSerial := godog.TestSuite{
3661
TestSuiteInitializer: InitializeSuite,
3762
ScenarioInitializer: InitializeScenario,
38-
Options: &opts,
63+
Options: &serialOpts,
3964
}.Run()
4065

41-
switch sc {
42-
// 0 - success
43-
case 0:
44-
45-
path := os.Getenv("E2E_SUMMARY_OUTPUT")
46-
if path == "" {
47-
fmt.Println("Note: E2E_SUMMARY_OUTPUT is unset; skipping summary generation")
48-
} else {
49-
if err := testutil.PrintSummary(path); err != nil {
50-
// Fail the run if alerts are found
51-
fmt.Printf("%v", err)
52-
os.Exit(1)
53-
}
66+
// TODO We re-print the output of any failed steps here for easier debugging. However, it would be
67+
// better to combine this with the E2E_SUMMARY_OUTPUT and show pass/fail + performance in one
68+
// markdown output then preserve the console output for local testing.
69+
70+
// Print aggregated summary
71+
fmt.Println("\n" + strings.Repeat("=", 80))
72+
fmt.Println("TEST EXECUTION SUMMARY")
73+
fmt.Println(strings.Repeat("=", 80))
74+
75+
fmt.Printf("\nParallel Tests Exit Code: %d\n", scParallel)
76+
if scParallel != 0 {
77+
failedSteps := extractFailedSteps(parallelBuf.String())
78+
if failedSteps != "" {
79+
fmt.Println("\nParallel Test Failures:")
80+
fmt.Println(strings.Repeat("-", 80))
81+
fmt.Println(failedSteps)
82+
}
83+
}
84+
85+
fmt.Printf("\nSerial Tests Exit Code: %d\n", scSerial)
86+
if scSerial != 0 {
87+
failedSteps := extractFailedSteps(serialBuf.String())
88+
if failedSteps != "" {
89+
fmt.Println("\nSerial Test Failures:")
90+
fmt.Println(strings.Repeat("-", 80))
91+
fmt.Println(failedSteps)
92+
}
93+
}
94+
95+
fmt.Println(strings.Repeat("=", 80))
96+
97+
if scParallel != 0 || scSerial != 0 {
98+
// 1 - failed
99+
// 2 - command line usage error
100+
// 128 - or higher, os signal related error exit codes
101+
log.Fatalf("non-zero status returned; parallel: (%d), serial: (%d), failed to run feature tests", scParallel, scSerial)
102+
}
103+
104+
path := os.Getenv("E2E_SUMMARY_OUTPUT")
105+
if path == "" {
106+
fmt.Println("Note: E2E_SUMMARY_OUTPUT is unset; skipping summary generation")
107+
} else {
108+
if err := testutil.PrintSummary(path); err != nil {
109+
// Fail the run if alerts are found
110+
fmt.Printf("%v", err)
111+
os.Exit(1)
112+
}
113+
}
114+
}
115+
116+
// extractFailedSteps extracts the "--- Failed steps:" section from godog output
117+
func extractFailedSteps(output string) string {
118+
lines := strings.Split(output, "\n")
119+
var failedSection []string
120+
capturing := false
121+
122+
for _, line := range lines {
123+
if strings.Contains(line, "--- Failed steps:") {
124+
capturing = true
125+
}
126+
if capturing {
127+
failedSection = append(failedSection, line)
54128
}
55-
return
129+
}
56130

57-
// 1 - failed
58-
// 2 - command line usage error
59-
// 128 - or higher, os signal related error exit codes
60-
default:
61-
log.Fatalf("non-zero status returned (%d), failed to run feature tests", sc)
131+
if len(failedSection) == 0 {
132+
return ""
62133
}
134+
return strings.Join(failedSection, "\n")
63135
}
64136

65137
func InitializeSuite(tc *godog.TestSuiteContext) {

0 commit comments

Comments
 (0)