From 68cfdfb21238439a3134c462c47c19187bf0e6ac Mon Sep 17 00:00:00 2001 From: Predrag Knezevic Date: Wed, 13 May 2026 16:15:18 +0200 Subject: [PATCH] Add test-identifying annotations to e2e resources Inject e2e.olm.operatorframework.io/feature and e2e.olm.operatorframework.io/scenario annotations into every resource applied during an e2e scenario. This makes it possible to identify which feature file and scenario produced a given resource when debugging test failures on a cluster. Co-Authored-By: Claude Opus 4.6 --- AGENTS.md | 2 +- test/e2e/README.md | 24 ++++++++++++++++++++---- test/e2e/steps/hooks.go | 5 +++++ test/e2e/steps/steps.go | 29 +++++++++++++++++++++++++++-- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e71ab54da1..27fb4d5c1c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -167,7 +167,7 @@ make generate │ │ └── values.yaml # Default values │ └── prometheus/ # Prometheus monitoring ├── test/ # Test suites -│ ├── e2e/ # End-to-end tests +│ ├── e2e/ # End-to-end tests (see test/e2e/README.md) │ ├── extension-developer-e2e/ # Extension developer tests │ ├── upgrade-e2e/ # Upgrade tests │ └── regression/ # Regression tests diff --git a/test/e2e/README.md b/test/e2e/README.md index 98b99b9caa..2bb364293f 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -288,7 +288,23 @@ Each scenario runs in its own namespace with unique resource names, ensuring com - Namespace: `ns-{scenario-id}` - ClusterExtension: `ce-{scenario-id}` -### 2. Automatic Cleanup +### 2. Test-Identifying Annotations + +Every resource applied during a scenario is automatically annotated with the feature file name and scenario name: + +- `e2e.olm.operatorframework.io/feature`: derived from the feature file path (e.g., `install`, `update`, `recover`) +- `e2e.olm.operatorframework.io/scenario`: the scenario name (e.g., `Install latest available version`) + +These annotations are added to all resources created within a scenario. + +These annotations make it possible to identify which test scenario produced a given resource when debugging failures on a +cluster: + +```bash +kubectl get clusterextension -o json | jq '.items[] | {name: .metadata.name, feature: .metadata.annotations["e2e.olm.operatorframework.io/feature"], scenario: .metadata.annotations["e2e.olm.operatorframework.io/scenario"]}' +``` + +### 3. Automatic Cleanup The `ScenarioCleanup` hook ensures all resources are deleted after each scenario: @@ -297,7 +313,7 @@ The `ScenarioCleanup` hook ensures all resources are deleted after each scenario - Deletes namespaces - Deletes added resources -### 3. Declarative Resource Management +### 4. Declarative Resource Management Resources are managed declaratively using YAML templates embedded in feature files as docstrings: @@ -313,7 +329,7 @@ When ClusterExtension is applied """ ``` -### 4. Polling with Timeouts +### 5. Polling with Timeouts All asynchronous operations use `waitFor` with consistent timeout (300s) and tick (1s): @@ -324,7 +340,7 @@ waitFor(ctx, func() bool { }) ``` -### 5. Feature Gate Detection +### 6. Feature Gate Detection Tests automatically detect enabled feature gates from the running controller and skip scenarios that require disabled features. diff --git a/test/e2e/steps/hooks.go b/test/e2e/steps/hooks.go index d34ccd31e2..0aec64daec 100644 --- a/test/e2e/steps/hooks.go +++ b/test/e2e/steps/hooks.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "os/exec" + "path/filepath" "regexp" "strconv" "strings" @@ -42,6 +43,8 @@ type deploymentRestore struct { type scenarioContext struct { id string + featureName string + scenarioName string namespace string clusterExtensionName string clusterObjectSetName string @@ -209,6 +212,8 @@ func CheckFeatureTags(ctx context.Context, sc *godog.Scenario) (context.Context, func CreateScenarioContext(ctx context.Context, sc *godog.Scenario) (context.Context, error) { scCtx := &scenarioContext{ id: sc.Id, + featureName: strings.TrimSuffix(filepath.Base(sc.Uri), filepath.Ext(sc.Uri)), + scenarioName: sc.Name, namespace: fmt.Sprintf("ns-%s", sc.Id), clusterExtensionName: fmt.Sprintf("ce-%s", sc.Id), clusterObjectSetName: fmt.Sprintf("cos-%s", sc.Id), diff --git a/test/e2e/steps/steps.go b/test/e2e/steps/steps.go index 0a9bf21157..a0892f4bd0 100644 --- a/test/e2e/steps/steps.go +++ b/test/e2e/steps/steps.go @@ -321,6 +321,16 @@ func toUnstructured(yamlContent string) (*unstructured.Unstructured, error) { return &unstructured.Unstructured{Object: u}, nil } +func injectTestAnnotations(obj *unstructured.Unstructured, sc *scenarioContext) { + annotations := obj.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + annotations["e2e.olm.operatorframework.io/feature"] = sc.featureName + annotations["e2e.olm.operatorframework.io/scenario"] = sc.scenarioName + obj.SetAnnotations(annotations) +} + func substituteScenarioVars(content string, sc *scenarioContext) string { vars := map[string]string{ "TEST_NAMESPACE": sc.namespace, @@ -415,7 +425,12 @@ func ResourceIsApplied(ctx context.Context, yamlTemplate *godog.DocString) error if err != nil { return fmt.Errorf("failed to parse resource yaml: %v", err) } - out, err := k8scliWithInput(yamlContent, "apply", "-f", "-") + injectTestAnnotations(res, sc) + annotatedYAML, err := yaml.Marshal(res.Object) + if err != nil { + return fmt.Errorf("failed to marshal resource yaml: %w", err) + } + out, err := k8scliWithInput(string(annotatedYAML), "apply", "-f", "-") if err != nil { return fmt.Errorf("failed to apply resource %v; err: %w; stderr: %s", out, err, stderrOutput(err)) } @@ -1683,7 +1698,17 @@ spec: ref: %s `, result.CatalogName, result.CatalogImageRef) - if _, err := k8scliWithInput(catalogYAML, "apply", "-f", "-"); err != nil { + catalogObj, err := toUnstructured(catalogYAML) + if err != nil { + return fmt.Errorf("failed to parse catalog YAML: %w", err) + } + injectTestAnnotations(catalogObj, sc) + annotatedYAML, err := yaml.Marshal(catalogObj.Object) + if err != nil { + return fmt.Errorf("failed to marshal catalog YAML: %w", err) + } + + if _, err := k8scliWithInput(string(annotatedYAML), "apply", "-f", "-"); err != nil { return fmt.Errorf("failed to apply ClusterCatalog: %w", err) }