Run the E2E test suite
Helvilette’s end-to-end suite is the recommended way to verify a change, because it exercises the real loop — a Git server, the control plane, and an agent, all in containers — rather than a mocked approximation.
Prerequisites
Section titled “Prerequisites”- Docker, running
- Go 1.25 or newer
You do not need to install Ginkgo. make e2e invokes it through
go run github.com/onsi/ginkgo/v2/ginkgo, so the runner comes from the version
pinned in go.mod on whatever machine you are on.
Run the suite
Section titled “Run the suite”make e2eWhat it does
Section titled “What it does”The suite is built on Ginkgo and Testcontainers-Go, and it manages its own infrastructure:
- Builds a lightweight
git-daemoncontainer serving test playbooks overgit://. - Builds the
othelaandagentimages directly from the local Dockerfiles, so you are testing your working tree, not a published image. - Asserts the state and outputs of the GitOps reconciliation loop programmatically.
- Tears down every container and network it created.
Because it builds images from local Dockerfiles, the first run is slow and subsequent runs benefit from Docker’s layer cache.
The rest of the development loop
Section titled “The rest of the development loop”Unit tests and end-to-end tests are deliberately separate. Unit tests need no Docker and finish in about a second; the suite above needs a running stack and takes minutes.
| Target | What it does |
|---|---|
make test | Unit tests over ./cmd/... and ./pkg/.... No Docker |
make fmt-check | Verifies gofmt without rewriting files. The same check CI runs |
make e2e | The end-to-end suite |
make clean-e2e | Tears down the stack and deletes the state it wrote |
make test is scoped rather than pointed at ./... because ./... pulls in
the Ginkgo suite, which hangs when no stack is running.
Cleaning up
Section titled “Cleaning up”The stack writes runtime state to tests/e2e/data and data/:
make clean-e2eOthela now runs as your own UID and keeps its database in a named volume, so a
current stack leaves nothing root-owned behind. If you ran an older one,
tests/e2e/data/playbooks/server may still be owned by root and unreadable,
which makes go vet ./... fail with permission denied before it compiles
anything. make clean-e2e clears that using a throwaway container, so no
sudo is needed.
When to use this instead of the manual loop
Section titled “When to use this instead of the manual loop”The quickstart runs both binaries with
go run against localhost. That is quick to iterate on, but it does not
exercise container packaging, network boundaries, or cloning over a real Git
transport.
Use the manual loop while writing code. Use the E2E suite before opening a pull request, and whenever you touch the job dispatch path, the clone logic, or the Ansible invocation.
Containers stop being enough where the agent meets the operating system. It manages systemd units and applies playbooks with real package and service tasks, and systemd inside a container is either absent or crippled. When what you are testing is that behaviour, use the Vagrant environment, which gives you two real VMs.