| name | add-e2e-test |
| description | Guide for writing E2E tests in tests/, covering namespace isolation, resource naming, cleanup, tags, notebook editing, and environment variables. Use when adding or modifying E2E tests. |
Add E2E Test
Guide for adding a new end-to-end test to the distributed-workloads repo.
Test structure
func TestMyFeature(t *testing.T) {
Tags(t, Tier1)
test := With(t)
namespace := test.NewTestNamespace().Name
}
Namespace isolation
Every test must operate in its own dedicated namespace. Use test.NewTestNamespace() — it creates a uniquely named namespace and registers automatic cleanup (log collection + deletion) via t.Cleanup:
namespace := test.NewTestNamespace().Name
Never use a fixed namespace name unless driven by an env var for a specific scenario (e.g., pre-upgrade/post-upgrade tests). Shared namespaces cause interference between tests.
Resource naming
All Kubernetes resources must use GenerateName instead of a fixed Name to avoid collisions:
ObjectMeta: metav1.ObjectMeta{GenerateName: "test-trainjob-"}
ObjectMeta: metav1.ObjectMeta{Name: "my-trainjob"}
Cleanup
Namespace-scoped resources are deleted automatically when the test namespace is cleaned up. Cluster-scoped resources (e.g., ClusterRole, ClusterRoleBinding) are not namespace-bound and may need to be explicitly cleaned up if the helper creating them does not already register a cleanup hook via t.T().Cleanup(...).
Tags
All tests must declare a tag -- this is mandatory. Apply it as the first statement so tests are skipped early when TEST_TIER is set:
| Tag | When to use |
|---|
Smoke | Minimal deployment verification |
Tier1–Tier3 | Progressively deeper coverage |
Gpu(accelerator) | Requires at least one GPU node |
MultiGpu(accelerator, n) | Requires n GPUs per node |
MultiNode(n) | Requires n worker nodes |
MultiNodeGpu(n, accelerator) | Requires n nodes each with at least one GPU |
MultiNodeMultiGpu(n, accelerator, gpus) | Requires n nodes each with at least gpus GPUs |
Environment variables
Declare env var constants and getter functions in tests/common/support/environment.go. Never use os.Getenv directly in test files — always go through a getter.
Editing notebooks
Test notebooks (tests/**/resources/*.ipynb) use 1-space JSON indentation with no trailing newline. When editing notebook cells, preserve the array-of-lines source format — do not collapse source arrays into single strings:
"source": [
"import os\n",
"print('hello')"
]
"source": "import os\nprint('hello')"
If a tool (e.g. NotebookEdit) converts the edited cell's source to a single string, convert it back to array-of-lines before committing. You can use a Python script:
import json
with open(path, encoding="utf-8") as f:
nb = json.load(f)
for cell in nb["cells"]:
if isinstance(cell["source"], str):
cell["source"] = cell["source"].splitlines(True)
if cell["source"] and cell["source"][-1].endswith("\n"):
cell["source"][-1] = cell["source"][-1][:-1]
with open(path, "w", encoding="utf-8") as f:
json.dump(nb, f, indent=1, ensure_ascii=False)
Key support library files
See the update-support-lib skill for the full file map. The most frequently used files when writing tests: test.go (Test interface), client.go (API clients), environment.go (env var getters), and the per-API helpers (trainjob.go, pytorchjob.go, ray.go, kueue.go).