Skip to content

Commit cef3375

Browse files
authored
Update Ginkgo E2E test doc (#922)
Signed-off-by: Jonathan West <[email protected]>
1 parent 46c43ce commit cef3375

File tree

1 file changed

+206
-1
lines changed

1 file changed

+206
-1
lines changed

test/openshift/e2e/README.md

Lines changed: 206 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ These tests are written with the [Ginkgo/Gomega test frameworks](https://github.
103103
### Tests are currently grouped as follows:
104104
- `sequential`: Tests that are not safe to run in parallel with other tests.
105105
- A test is NOT safe to run in parallel with other tests if:
106-
- It modifies resources in `openshift-gitops`
106+
- It modifies resources in `openshift-gitops` or `openshift-gitops-operators` (or similar)
107107
- It modifies the GitOps operator `Subscription`
108108
- It modifies cluster-scoped resources, such as `ClusterRoles`/`ClusterRoleBindings`, or `Namespaces` that are shared between tests
109109
- More generally, if it writes to a K8s resource that is used by another test.
@@ -112,6 +112,7 @@ These tests are written with the [Ginkgo/Gomega test frameworks](https://github.
112112
- It is fine for a parallel test to read cluster-scoped resources (such as resources in openshift-gitops namespace)
113113
- A parallel test should NEVER write to resources that may be shared with other tests (`Subscriptions`, some cluster-scoped resources, etc.)
114114

115+
*Guidance*: Look at the list of restrictions for sequential. If your test is doing any of those things, it needs to run sequential. Otherwise parallel is fine.
115116

116117

117118
### Test fixture:
@@ -127,6 +128,201 @@ These tests are written with the [Ginkgo/Gomega test frameworks](https://github.
127128
- The goal of this test fixture is to make it easy to write tests, and to ensure it is easy to understand and maintain existing tests.
128129
- See existing k8s tests for usage examples.
129130

131+
132+
## Writing new tests
133+
134+
Ginkgo tests are read from left to right. For example:
135+
- `Expect(k8sClient.Create(ctx, argoCD)).To(Succeed())`
136+
- Can be read as: Expect create of argo cd CR to suceeed.
137+
- `Eventually(appControllerPod, "3m", "5s").Should(k8sFixture.ExistByName())`
138+
- Can be read as: Eventually the `(argo cd application controller pod)` should exist (within 3 minute, chekcing every 5 seconds.)
139+
- `fixture.Update(argoCD, func(){ (...)})`
140+
- Can be reas ad: Update Argo CD CR using the given function
141+
142+
143+
The E2E tests we use within this repo uses the standard controller-runtime k8s go API to interact with kubernetes (controller-runtime). This API is very familiar to anyone already writing go operator/controller code (such as developers of this project).
144+
145+
The best way to learn how to write a new test (or matcher/fixture), is just to copy an existing one!
146+
- There are 150+ existing tests you can 'steal' from, which provide examples of nearly anything you could want.
147+
148+
### Standard patterns you can use
149+
150+
#### To verify a K8s resource has an expected status/spec:
151+
- `fixture` packages
152+
- Fixture packages contain utility functions which exists for (nearly) all resources (described in detail elsewhere)
153+
- Most often, a function in a `fixture` will already exist for what you are looking for.
154+
- For example, use `argocdFixture` to check if Argo CD is available:
155+
- `Eventually(argoCDbeta1, "5m", "5s").Should(argocdFixture.BeAvailable())`
156+
- Consider adding new functions to fixtures, so that tests can use them as well.
157+
- If no fixture package function exists, just use a function that returns bool
158+
- Example: `1-005_validate_route_tls_test.go`
159+
160+
#### To create an object:
161+
- `Expect(k8sClient.Create(ctx, (object))).Should(Succeed())`
162+
163+
#### To update an object, use `fixture.Update`
164+
- `fixture.Update(object, func(){})` function
165+
- Test will automatically retry the update if update fails.
166+
- This avoids a common issue in k8s tests, where update fails which causes the test to fail.
167+
168+
#### To delete a k8s object
169+
- `Expect(k8sClient.Delete(ctx, (object))).Should(Succeed())`
170+
- Where `(object)` is any k8s resource
171+
172+
173+
### Parallel vs Sequential
174+
175+
When to include a test in 'parallel' package, vs when to include a test in 'sequential' package? See elsewhere in this document for the exact criteria for when to include a test in parallel, and when to include it in sequential.
176+
177+
*General Guidance*: Look at the list of restrictions for sequential/parallel above.
178+
- If your test is performing any restricted behaviours, it needs to run sequential. Otherwise parallel is fine.
179+
- For example: if your test modifies ANYTHING in `openshift-gitops` Namespace, it's not safe to run in parallel. Include it in the `sequential` tests package.
180+
181+
182+
#### When writing sequential tests, ensure you:
183+
184+
A) Call EnsureSequentialCleanSlate before each test:
185+
```go
186+
BeforeEach(func() {
187+
fixture.EnsureSequentialCleanSlate()
188+
}
189+
```
190+
191+
Unlike with parallel tests, you don't need to clean up namespace after each test. Sequential will automatically cleanup namespaces created via the `fixture.Create(...)Namespace` API. (But if you want to delete it using `defer`, it doesn't hurt).
192+
193+
194+
#### When writing parallel tests, ensure you:
195+
196+
A) Call EnsureParallelCleanSlate before each test
197+
```go
198+
BeforeEach(func() {
199+
fixture.EnsureParallelCleanSlate()
200+
})
201+
```
202+
203+
B) Clean up any namespaces (or any cluster-scoped resources you created) using `defer`:
204+
```go
205+
// Create a namespace to use for the duration of the test, and then automatically clean it up after.
206+
ns, cleanupFunc := fixture.CreateRandomE2ETestNamespaceWithCleanupFunc()
207+
defer cleanupFunc()
208+
```
209+
210+
211+
212+
213+
### General Tips
214+
- DON'T ADD SLEEP STATEMENTS TO TESTS (unless it's absolutely necessary, but it rarely is!)
215+
- Use `Eventually`/`Consistently` with a condition, instead.
216+
- Use `By("")` to document each step for what the test is doing.
217+
- This is very helpful for other team members that need to maintain your test after you wrote it.
218+
- Also all `By("")`s are included in test output as `Step: (...)`, which makes it easy to tell what the test is doing when the test is running.
219+
220+
221+
222+
## Translating from Kuttl to Ginkgo
223+
224+
### `01-create-or-update-resource.yaml`
225+
226+
Example:
227+
In kuttl, this would create (or modify an existing) `ArgoCD` CR to have dex sso provider using openShiftOAuth.
228+
```yaml
229+
apiVersion: argoproj.io/v1alpha1
230+
kind: ArgoCD
231+
metadata:
232+
name: argocd
233+
spec:
234+
sso:
235+
provider: dex
236+
dex:
237+
openShiftOAuth: true
238+
```
239+
240+
Equivalent in Ginkgo - to create:
241+
```go
242+
argocdObj := &argov1beta1api.ArgoCD{
243+
ObjectMeta: metav1.ObjectMeta{
244+
Name: "argocd",
245+
Namespace: "(namespace)",
246+
},
247+
Spec: argov1beta1api.ArgoCDSpec{
248+
SSO: &argov1beta1api.ArgoCDSSOSpec{
249+
Provider: argov1beta1api.SSOProviderTypeDex,
250+
Dex: &argov1beta1api.ArgoCDDexSpec{
251+
OpenShiftOAuth: true,
252+
},
253+
},
254+
},
255+
}
256+
Expect(k8sClient.Create(ctx, argocdObj)).To(Succeed())
257+
```
258+
259+
Equivalent in Ginkgo - to update:
260+
```go
261+
argocdObj := &argov1beta1api.ArgoCD{
262+
ObjectMeta: metav1.ObjectMeta{
263+
Name: "argocd",
264+
Namespace: "(namespace)",
265+
},
266+
}
267+
argocdFixture.Update(argocdObj, func(ac *argov1beta1api.ArgoCD) {
268+
ac.Spec.SSO = &argov1beta1api.ArgoCDSSOSpec{
269+
Provider: argov1beta1api.SSOProviderTypeDex,
270+
Dex: &argov1beta1api.ArgoCDDexSpec{
271+
OpenShiftOAuth: true,
272+
},
273+
}
274+
})
275+
```
276+
277+
### `01-assert.yaml`
278+
279+
Example:
280+
```yaml
281+
apiVersion: argoproj.io/v1beta1
282+
kind: ArgoCD
283+
metadata:
284+
name: argocd
285+
status:
286+
phase: Available
287+
sso: Running
288+
```
289+
290+
The equivalent here is `Eventually`.
291+
292+
Equivalent in Ginkgo:
293+
```go
294+
Eventually(argoCDObject).Should(argocdFixture.BeAvailable())
295+
Eventually(argoCDObject).Should(argocdFixture.HaveSSOStatus("Running"))
296+
```
297+
298+
### `02-errors.yaml`
299+
300+
The close equivalent to an `errors.yaml` is Eventually with a Not, then a Consistently with a Not
301+
302+
Example:
303+
```yaml
304+
apiVersion: argoproj.io/v1beta1
305+
kind: ArgoCD
306+
metadata:
307+
name: argocd
308+
status:
309+
phase: Pending
310+
sso: Failed
311+
```
312+
313+
Equivalent in Ginkgo:
314+
```go
315+
Eventually(argoCDObject).ShouldNot(argocdFixture.HavePhase("Pending"))
316+
Consistently(argoCDObject).ShouldNot(argocdFixture.HavePhase("Pending"))
317+
318+
Eventually(argoCDObject).ShouldNot(argocdFixture.HaveSSOStatus("Failed"))
319+
Consistently(argoCDObject).ShouldNot(argocdFixture.HaveSSOStatus("Failed"))
320+
```
321+
322+
323+
324+
325+
130326
## Tips for debugging tests
131327

132328
### If you are debugging tests in CI
@@ -147,3 +343,12 @@ Example:
147343
```bash
148344
E2E_DEBUG_SKIP_CLEANUP=true ./bin/ginkgo -v -focus "1-099_validate_server_autoscale" -r ./test/openshift/e2e/ginkgo/parallel
149345
```
346+
347+
348+
## External Documentation
349+
350+
[**Ginkgo/Gomega docs**](https://onsi.github.io/gomega/): The Ginkgo/Gomega docs are great! they are very detailed, with lots of good examples. There are also plenty of other examples of Ginkgo/Gomega you can find via searching.
351+
352+
**Ask an LLM (Gemini/Cursor/etc)**: Ginkgo/gomega are popular enough that LLMs are able to answer questions and write code for them.
353+
- For example, I performed the following Gemini Pro query, and got an excellent answer:
354+
- `With Ginkgo/Gomega (https://onsi.github.io/gomega) and Go lang, how do I create a matcher which checks whether a Kubernetes Deployment (via Deployment go object) has ready replicas of 1`

0 commit comments

Comments
 (0)