|
| 1 | +# OpenShift Tests Extension (OTE) |
| 2 | + |
| 3 | +This directory contains a separate Go module that builds the OTE binary |
| 4 | +(`cluster-capi-operator-tests-ext`). The binary is embedded gzipped in the |
| 5 | +operator image at `/usr/bin/cluster-capi-operator-tests-ext.gz` and used by |
| 6 | +`openshift-tests` to discover and run e2e tests as part of the OpenShift test |
| 7 | +infrastructure. |
| 8 | + |
| 9 | +## Adding a test to OTE |
| 10 | + |
| 11 | +Whether a test file is visible to OTE depends entirely on its file suffix: |
| 12 | + |
| 13 | +| File | `make e2e` | OTE (`list tests`) | |
| 14 | +|---|---|---| |
| 15 | +| `e2e/my_test.go` | ✅ | ❌ | |
| 16 | +| `e2e/my.go` | ✅ | ✅ | |
| 17 | +| `e2e/my.go` + `//go:build !e2e` | ❌ | ✅ | |
| 18 | + |
| 19 | +**To make a test discoverable by OTE:** write it in a regular `.go` file (not |
| 20 | +`_test.go`). No changes to the extension binary are needed — ginkgo |
| 21 | +auto-discovery picks it up on the next build. |
| 22 | + |
| 23 | +**To keep a test out of OTE:** use the standard `_test.go` suffix. It still |
| 24 | +runs via `make e2e`. |
| 25 | + |
| 26 | +**To make a test run via OTE only (not `make e2e`):** use a regular `.go` file |
| 27 | +with `//go:build !e2e`. `make e2e` passes `--tags=e2e` internally which causes |
| 28 | +`!e2e` files to be excluded from compilation. The OTE binary is built without |
| 29 | +that tag, so the file is included. |
| 30 | + |
| 31 | +## Test labels and suite routing |
| 32 | + |
| 33 | +Labels on a test control which OTE suite it lands in, and therefore which |
| 34 | +nightly conformance job picks it up via the `Parents` field: |
| 35 | + |
| 36 | +| Label on test | OTE suite | Parent suite | |
| 37 | +|---|---|---| |
| 38 | +| none (default) | `capio/parallel` | `openshift/conformance/parallel` | |
| 39 | +| `Label("Serial")` | `capio/serial` | `openshift/conformance/serial` | |
| 40 | +| `Label("Disruptive")` | `capio/disruptive` | `openshift/disruptive-longrunning` | |
| 41 | + |
| 42 | +A test is included in a periodic run when **both** of these are true: |
| 43 | +1. The cluster has the feature set that enables the test's feature gate (e.g. `TechPreviewNoUpgrade`) |
| 44 | +2. The job runs the matching parent suite (e.g. `openshift/conformance/serial`) |
| 45 | + |
| 46 | +Example — a serial test that lands in `capio/serial → openshift/conformance/serial`: |
| 47 | + |
| 48 | +```go |
| 49 | +// e2e/aws_machineset.go (regular .go, not _test.go — visible to OTE) |
| 50 | +var _ = Describe("[sig-cluster-lifecycle][OCPFeatureGate:ClusterAPIMachineManagementAWS] Cluster API AWS", func() { |
| 51 | + It("should scale a MachineSet", Label("Serial"), func() { |
| 52 | + // test body |
| 53 | + }) |
| 54 | +}) |
| 55 | +``` |
| 56 | + |
| 57 | +Example — a parallel test (no label) that lands in `capio/parallel → openshift/conformance/parallel`: |
| 58 | + |
| 59 | +```go |
| 60 | +var _ = Describe("[sig-cluster-lifecycle][OCPFeatureGate:ClusterAPIMachineManagementAWS] Cluster API AWS", func() { |
| 61 | + It("should have a running cluster", func() { |
| 62 | + // test body |
| 63 | + }) |
| 64 | +}) |
| 65 | +``` |
| 66 | + |
| 67 | +## Feature gate labeling |
| 68 | + |
| 69 | +Every e2e test must carry the appropriate `[OCPFeatureGate:FeatureName]` tag in |
| 70 | +its `Describe` block name. This tells `openshift-tests` to skip the test |
| 71 | +automatically on clusters where the feature gate is not enabled: |
| 72 | + |
| 73 | +```go |
| 74 | +var _ = Describe("[sig-cluster-lifecycle][OCPFeatureGate:ClusterAPIMachineManagementAWS] Cluster API AWS", func() { |
| 75 | + It("should run a machine", func() { ... }) |
| 76 | +}) |
| 77 | +``` |
| 78 | + |
| 79 | +Without this tag, a test runs on every cluster regardless of feature gate |
| 80 | +status, which will cause failures on clusters where the feature is not active. |
| 81 | + |
| 82 | +## Blocking vs informing lifecycle |
| 83 | + |
| 84 | +By default every test is `blocking` — a failure causes the suite exit code to |
| 85 | +be non-zero. To mark a test as `informing` (failure recorded but non-blocking): |
| 86 | + |
| 87 | +```go |
| 88 | +import g "github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo" |
| 89 | + |
| 90 | +It("should be stable but not yet required", g.Informing(), func() { ... }) |
| 91 | +``` |
| 92 | + |
| 93 | +Informing failures appear in the JSON output and Sippy dashboards but do not |
| 94 | +gate merges or promotions. Use this temporarily to gather stability data before |
| 95 | +promoting a test to blocking. Tests must not remain informing indefinitely. |
| 96 | + |
| 97 | +**Important:** `g.Informing()` is only honored when the test runs through the |
| 98 | +OTE binary (`run-suite`, `run-test`). When running via `make e2e`, ginkgo has |
| 99 | +no concept of informing lifecycle — a failing informing test will still cause |
| 100 | +the `make e2e` run to exit with a non-zero code. |
| 101 | + |
| 102 | + |
| 103 | +## Running locally |
| 104 | + |
| 105 | +`list tests` and `list suites` work without a cluster. `run-suite` and |
| 106 | +`run-test` require a reachable OCP cluster because `InitCommonVariables()` |
| 107 | +fetches the `cluster` Infrastructure object. |
| 108 | + |
| 109 | +```bash |
| 110 | +# Build the binary |
| 111 | +make bin/cluster-capi-operator-tests-ext |
| 112 | + |
| 113 | +# List all discovered tests (no cluster needed) |
| 114 | +./bin/cluster-capi-operator-tests-ext list tests |
| 115 | + |
| 116 | +# Run a specific suite against a cluster |
| 117 | +./bin/cluster-capi-operator-tests-ext run-suite capio/serial |
| 118 | + |
| 119 | +# Run a single test by exact name |
| 120 | +./bin/cluster-capi-operator-tests-ext run-test "[sig-cluster-lifecycle]... test name" |
| 121 | +``` |
| 122 | + |
| 123 | +The `KUBECONFIG` env var is read directly by `config.GetConfig()` — it must |
| 124 | +point to a reachable OCP cluster, not just whatever `kubectl` has as |
| 125 | +current-context. |
0 commit comments