Skip to content

Commit 68cfdfb

Browse files
pedjakclaude
andcommitted
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 <noreply@anthropic.com>
1 parent 10ea84d commit 68cfdfb

4 files changed

Lines changed: 53 additions & 7 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ make generate
167167
│ │ └── values.yaml # Default values
168168
│ └── prometheus/ # Prometheus monitoring
169169
├── test/ # Test suites
170-
│ ├── e2e/ # End-to-end tests
170+
│ ├── e2e/ # End-to-end tests (see test/e2e/README.md)
171171
│ ├── extension-developer-e2e/ # Extension developer tests
172172
│ ├── upgrade-e2e/ # Upgrade tests
173173
│ └── regression/ # Regression tests

test/e2e/README.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,23 @@ Each scenario runs in its own namespace with unique resource names, ensuring com
288288
- Namespace: `ns-{scenario-id}`
289289
- ClusterExtension: `ce-{scenario-id}`
290290

291-
### 2. Automatic Cleanup
291+
### 2. Test-Identifying Annotations
292+
293+
Every resource applied during a scenario is automatically annotated with the feature file name and scenario name:
294+
295+
- `e2e.olm.operatorframework.io/feature`: derived from the feature file path (e.g., `install`, `update`, `recover`)
296+
- `e2e.olm.operatorframework.io/scenario`: the scenario name (e.g., `Install latest available version`)
297+
298+
These annotations are added to all resources created within a scenario.
299+
300+
These annotations make it possible to identify which test scenario produced a given resource when debugging failures on a
301+
cluster:
302+
303+
```bash
304+
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"]}'
305+
```
306+
307+
### 3. Automatic Cleanup
292308

293309
The `ScenarioCleanup` hook ensures all resources are deleted after each scenario:
294310

@@ -297,7 +313,7 @@ The `ScenarioCleanup` hook ensures all resources are deleted after each scenario
297313
- Deletes namespaces
298314
- Deletes added resources
299315

300-
### 3. Declarative Resource Management
316+
### 4. Declarative Resource Management
301317

302318
Resources are managed declaratively using YAML templates embedded in feature files as docstrings:
303319

@@ -313,7 +329,7 @@ When ClusterExtension is applied
313329
"""
314330
```
315331

316-
### 4. Polling with Timeouts
332+
### 5. Polling with Timeouts
317333

318334
All asynchronous operations use `waitFor` with consistent timeout (300s) and tick (1s):
319335

@@ -324,7 +340,7 @@ waitFor(ctx, func() bool {
324340
})
325341
```
326342

327-
### 5. Feature Gate Detection
343+
### 6. Feature Gate Detection
328344

329345
Tests automatically detect enabled feature gates from the running controller and skip scenarios that require disabled
330346
features.

test/e2e/steps/hooks.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"os/exec"
9+
"path/filepath"
910
"regexp"
1011
"strconv"
1112
"strings"
@@ -42,6 +43,8 @@ type deploymentRestore struct {
4243

4344
type scenarioContext struct {
4445
id string
46+
featureName string
47+
scenarioName string
4548
namespace string
4649
clusterExtensionName string
4750
clusterObjectSetName string
@@ -209,6 +212,8 @@ func CheckFeatureTags(ctx context.Context, sc *godog.Scenario) (context.Context,
209212
func CreateScenarioContext(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
210213
scCtx := &scenarioContext{
211214
id: sc.Id,
215+
featureName: strings.TrimSuffix(filepath.Base(sc.Uri), filepath.Ext(sc.Uri)),
216+
scenarioName: sc.Name,
212217
namespace: fmt.Sprintf("ns-%s", sc.Id),
213218
clusterExtensionName: fmt.Sprintf("ce-%s", sc.Id),
214219
clusterObjectSetName: fmt.Sprintf("cos-%s", sc.Id),

test/e2e/steps/steps.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,16 @@ func toUnstructured(yamlContent string) (*unstructured.Unstructured, error) {
321321
return &unstructured.Unstructured{Object: u}, nil
322322
}
323323

324+
func injectTestAnnotations(obj *unstructured.Unstructured, sc *scenarioContext) {
325+
annotations := obj.GetAnnotations()
326+
if annotations == nil {
327+
annotations = make(map[string]string)
328+
}
329+
annotations["e2e.olm.operatorframework.io/feature"] = sc.featureName
330+
annotations["e2e.olm.operatorframework.io/scenario"] = sc.scenarioName
331+
obj.SetAnnotations(annotations)
332+
}
333+
324334
func substituteScenarioVars(content string, sc *scenarioContext) string {
325335
vars := map[string]string{
326336
"TEST_NAMESPACE": sc.namespace,
@@ -415,7 +425,12 @@ func ResourceIsApplied(ctx context.Context, yamlTemplate *godog.DocString) error
415425
if err != nil {
416426
return fmt.Errorf("failed to parse resource yaml: %v", err)
417427
}
418-
out, err := k8scliWithInput(yamlContent, "apply", "-f", "-")
428+
injectTestAnnotations(res, sc)
429+
annotatedYAML, err := yaml.Marshal(res.Object)
430+
if err != nil {
431+
return fmt.Errorf("failed to marshal resource yaml: %w", err)
432+
}
433+
out, err := k8scliWithInput(string(annotatedYAML), "apply", "-f", "-")
419434
if err != nil {
420435
return fmt.Errorf("failed to apply resource %v; err: %w; stderr: %s", out, err, stderrOutput(err))
421436
}
@@ -1683,7 +1698,17 @@ spec:
16831698
ref: %s
16841699
`, result.CatalogName, result.CatalogImageRef)
16851700

1686-
if _, err := k8scliWithInput(catalogYAML, "apply", "-f", "-"); err != nil {
1701+
catalogObj, err := toUnstructured(catalogYAML)
1702+
if err != nil {
1703+
return fmt.Errorf("failed to parse catalog YAML: %w", err)
1704+
}
1705+
injectTestAnnotations(catalogObj, sc)
1706+
annotatedYAML, err := yaml.Marshal(catalogObj.Object)
1707+
if err != nil {
1708+
return fmt.Errorf("failed to marshal catalog YAML: %w", err)
1709+
}
1710+
1711+
if _, err := k8scliWithInput(string(annotatedYAML), "apply", "-f", "-"); err != nil {
16871712
return fmt.Errorf("failed to apply ClusterCatalog: %w", err)
16881713
}
16891714

0 commit comments

Comments
 (0)