Skip to content

Commit 8bda126

Browse files
authored
Refactor e2e tests to support feature-gate aware skipping (#2293)
Consolidates experimental-e2e tests into the main e2e test suite and implements automatic feature-gate detection from OLM deployments. Changes: - Merges experimental-e2e tests into test/e2e directory - Adds new feature_gates.go helper that queries OLM deployments to detect enabled/disabled feature gates - Updates tests to skip automatically when required feature gates are disabled (SingleOwnNamespaceInstallSupport, WebhookProviderCertManager) - Removes separate experimental-e2e Makefile target - Falls back to programmatic defaults when feature gates aren't explicitly configured This allows the test suite to adapt to different cluster configurations without manual test selection. Post-commit improvements (with AI assistance): - Fixed typo: processFeatuteGate -> processFeatureGate - Added sync.Once for thread-safe feature gate caching - Improved error messages for feature gate validation - Added package and function documentation Assisted-by: Claude code Signed-off-by: Todd Short <[email protected]>
1 parent 760855f commit 8bda126

File tree

4 files changed

+116
-41
lines changed

4 files changed

+116
-41
lines changed

Makefile

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,6 @@ test: manifests generate fmt lint test-unit test-e2e test-regression #HELP Run a
213213
e2e: #EXHELP Run the e2e tests.
214214
go test -count=1 -v ./test/e2e/...
215215

216-
.PHONY: experimental-e2e
217-
experimental-e2e: #EXHELP Run the experimental e2e tests.
218-
go test -count=1 -v ./test/experimental-e2e/...
219-
220216
E2E_REGISTRY_NAME := docker-registry
221217
E2E_REGISTRY_NAMESPACE := operator-controller-e2e
222218

@@ -285,7 +281,7 @@ test-experimental-e2e: KIND_CLUSTER_NAME := operator-controller-e2e
285281
test-experimental-e2e: GO_BUILD_EXTRA_FLAGS := -cover
286282
test-experimental-e2e: COVERAGE_NAME := experimental-e2e
287283
test-experimental-e2e: export MANIFEST := $(EXPERIMENTAL_RELEASE_MANIFEST)
288-
test-experimental-e2e: run-internal image-registry prometheus experimental-e2e e2e e2e-coverage kind-clean #HELP Run experimental e2e test suite on local kind cluster
284+
test-experimental-e2e: run-internal image-registry prometheus e2e e2e-coverage kind-clean #HELP Run experimental e2e test suite on local kind cluster
289285

290286
.PHONY: prometheus
291287
prometheus: PROMETHEUS_NAMESPACE := olmv1-system

test/experimental-e2e/single_namespace_support_test.go renamed to test/e2e/single_namespace_support_test.go

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
package experimental_e2e
1+
package e2e
22

33
import (
44
"context"
55
"fmt"
66
"os"
77
"testing"
8-
"time"
98

109
"github.com/stretchr/testify/assert"
1110
"github.com/stretchr/testify/require"
@@ -16,46 +15,19 @@ import (
1615
apimeta "k8s.io/apimachinery/pkg/api/meta"
1716
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1817
"k8s.io/apimachinery/pkg/types"
19-
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
20-
"k8s.io/client-go/rest"
2118
"k8s.io/utils/ptr"
22-
ctrl "sigs.k8s.io/controller-runtime"
23-
"sigs.k8s.io/controller-runtime/pkg/client"
2419

2520
ocv1 "github.com/operator-framework/operator-controller/api/v1"
26-
"github.com/operator-framework/operator-controller/internal/operator-controller/scheme"
2721
utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils"
2822
. "github.com/operator-framework/operator-controller/test/helpers"
2923
)
3024

3125
const (
32-
artifactName = "operator-controller-experimental-e2e"
33-
pollDuration = time.Minute
34-
pollInterval = time.Second
26+
soNsFlag = "SingleOwnNamespaceInstallSupport"
3527
)
3628

37-
var (
38-
cfg *rest.Config
39-
c client.Client
40-
)
41-
42-
func TestMain(m *testing.M) {
43-
cfg = ctrl.GetConfigOrDie()
44-
45-
var err error
46-
utilruntime.Must(apiextensionsv1.AddToScheme(scheme.Scheme))
47-
c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
48-
utilruntime.Must(err)
49-
50-
os.Exit(m.Run())
51-
}
52-
53-
func TestNoop(t *testing.T) {
54-
t.Log("Running experimental-e2e tests")
55-
defer utils.CollectTestArtifacts(t, artifactName, c, cfg)
56-
}
57-
5829
func TestClusterExtensionSingleNamespaceSupport(t *testing.T) {
30+
SkipIfFeatureGateDisabled(t, soNsFlag)
5931
t.Log("Test support for cluster extension config")
6032
defer utils.CollectTestArtifacts(t, artifactName, c, cfg)
6133

@@ -213,6 +185,7 @@ func TestClusterExtensionSingleNamespaceSupport(t *testing.T) {
213185
}
214186

215187
func TestClusterExtensionOwnNamespaceSupport(t *testing.T) {
188+
SkipIfFeatureGateDisabled(t, soNsFlag)
216189
t.Log("Test support for cluster extension with OwnNamespace install mode support")
217190
defer utils.CollectTestArtifacts(t, artifactName, c, cfg)
218191

@@ -382,6 +355,7 @@ func TestClusterExtensionOwnNamespaceSupport(t *testing.T) {
382355
}
383356

384357
func TestClusterExtensionVersionUpdate(t *testing.T) {
358+
SkipIfFeatureGateDisabled(t, soNsFlag)
385359
t.Log("When a cluster extension is installed from a catalog")
386360
t.Log("When resolving upgrade edges")
387361

test/e2e/webhook_support_test.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,13 @@ import (
2121

2222
ocv1 "github.com/operator-framework/operator-controller/api/v1"
2323
utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils"
24+
. "github.com/operator-framework/operator-controller/test/helpers"
2425
)
2526

2627
var dynamicClient dynamic.Interface
2728

28-
func TestNoop(t *testing.T) {
29-
t.Log("Running experimental-e2e tests")
30-
defer utils.CollectTestArtifacts(t, artifactName, c, cfg)
31-
}
32-
3329
func TestWebhookSupport(t *testing.T) {
30+
SkipIfFeatureGateDisabled(t, "WebhookProviderCertManager")
3431
t.Log("Test support for bundles with webhooks")
3532
defer utils.CollectTestArtifacts(t, artifactName, c, cfg)
3633

test/helpers/feature_gates.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Package utils provides helper functions for e2e tests, including
2+
// feature gate detection and validation utilities.
3+
package utils
4+
5+
import (
6+
"strings"
7+
"sync"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
appsv1 "k8s.io/api/apps/v1"
12+
"k8s.io/component-base/featuregate"
13+
"sigs.k8s.io/controller-runtime/pkg/client"
14+
15+
catdfeatures "github.com/operator-framework/operator-controller/internal/catalogd/features"
16+
opconfeatures "github.com/operator-framework/operator-controller/internal/operator-controller/features"
17+
)
18+
19+
var (
20+
featureGateStatus map[string]bool
21+
featureGateStatusOnce sync.Once
22+
)
23+
24+
const (
25+
fgPrefix = "--feature-gates="
26+
)
27+
28+
// SkipIfFeatureGateDisabled skips the test if the specified feature gate is disabled.
29+
// It queries the OLM deployments to detect feature gate settings and falls back to
30+
// programmatic defaults if the feature gate is not explicitly configured.
31+
func SkipIfFeatureGateDisabled(t *testing.T, fg string) {
32+
if !isFeatureGateEnabled(t, fg) {
33+
t.Skipf("Feature-gate %q disabled", fg)
34+
}
35+
}
36+
37+
func isFeatureGateEnabled(t *testing.T, fg string) bool {
38+
gatherFeatureGates(t)
39+
enabled, ok := featureGateStatus[fg]
40+
if ok {
41+
return enabled
42+
}
43+
44+
// Not found (i.e. not explicitly set), so we need to find the programmed default.
45+
// Because feature-gates are organized by catd/opcon, we need to check each individually.
46+
// To avoid a panic, we need to check if it's a known gate first.
47+
mfgs := []featuregate.MutableFeatureGate{
48+
catdfeatures.CatalogdFeatureGate,
49+
opconfeatures.OperatorControllerFeatureGate,
50+
}
51+
f := featuregate.Feature(fg)
52+
for _, mfg := range mfgs {
53+
known := mfg.GetAll()
54+
if _, ok := known[f]; ok {
55+
e := mfg.Enabled(f)
56+
t.Logf("Feature-gate %q not found in arguments, defaulting to %v", fg, e)
57+
return e
58+
}
59+
}
60+
61+
t.Fatalf("Unknown feature-gate: %q", fg)
62+
return false // unreachable, but required for compilation
63+
}
64+
65+
func processFeatureGate(t *testing.T, featureGateValue string) {
66+
fgvs := strings.Split(featureGateValue, ",")
67+
for _, fg := range fgvs {
68+
v := strings.Split(fg, "=")
69+
require.Len(t, v, 2, "invalid feature-gate format: %q (expected name=value)", fg)
70+
switch v[1] {
71+
case "true":
72+
featureGateStatus[v[0]] = true
73+
t.Logf("Feature-gate %q enabled", v[0])
74+
case "false":
75+
featureGateStatus[v[0]] = false
76+
t.Logf("Feature-gate %q disabled", v[0])
77+
default:
78+
t.Fatalf("invalid feature-gate value: %q (expected true or false)", fg)
79+
}
80+
}
81+
}
82+
83+
func gatherFeatureGatesFromDeployment(t *testing.T, dep *appsv1.Deployment) {
84+
for _, con := range dep.Spec.Template.Spec.Containers {
85+
for _, arg := range con.Args {
86+
if strings.HasPrefix(arg, fgPrefix) {
87+
processFeatureGate(t, strings.TrimPrefix(arg, fgPrefix))
88+
}
89+
}
90+
}
91+
}
92+
93+
func gatherFeatureGates(t *testing.T) {
94+
featureGateStatusOnce.Do(func() {
95+
featureGateStatus = make(map[string]bool)
96+
97+
depList := &appsv1.DeploymentList{}
98+
err := c.List(t.Context(), depList, client.MatchingLabels{
99+
"app.kubernetes.io/part-of": "olm",
100+
})
101+
require.NoError(t, err)
102+
require.Len(t, depList.Items, 2)
103+
104+
for _, d := range depList.Items {
105+
gatherFeatureGatesFromDeployment(t, &d)
106+
}
107+
})
108+
}

0 commit comments

Comments
 (0)