Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 20 additions & 4 deletions test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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:

Expand All @@ -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):

Expand All @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions test/e2e/steps/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -42,6 +43,8 @@ type deploymentRestore struct {

type scenarioContext struct {
id string
featureName string
scenarioName string
namespace string
clusterExtensionName string
clusterObjectSetName string
Expand Down Expand Up @@ -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),
Expand Down
29 changes: 27 additions & 2 deletions test/e2e/steps/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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)
}

Expand Down