Building and Testing

The host needs no Go toolchain. All build and test commands run inside a containerized dev shell defined by dev/Containerfile and driven by ilo. The .ilo.rc at the repo root supplies the shell arguments, so the short form is always available:
ilo bash -c '<command>'
The dev shell pre-installs the Go toolchain, all static-analysis tools, controller-gen, and the envtest asset bundle. The Go module cache is mounted from ${XDG_CACHE_HOME}/go so repeated builds do not re-download dependencies.
Building
ilo bash -c 'go build -o jaas .'
The Dockerfile builds the production image. It accepts VERSION and COMMIT build args:
docker build -t ghcr.io/metio/jaas:dev .
docker build --build-arg VERSION=v1 --build-arg COMMIT=abc123 -t ghcr.io/metio/jaas:dev .
Regenerating generated code
CRD manifests and the zz_generated.deepcopy.go file are generated by controller-gen. Run this after touching api/v1/ types:
# Regenerate deep-copy functions
ilo bash -c 'controller-gen object paths=./api/v1/...'
# Regenerate CRD manifests under config/crd/bases/
ilo bash -c 'controller-gen crd paths=./api/v1/... output:crd:dir=./config/crd/bases'
Static analysis
golangci-lint is not used. The standalone tools below run directly, both in CI and locally inside the dev shell:
ilo bash -c 'go vet ./...'
ilo bash -c 'staticcheck ./...' # config: staticcheck.conf, checks = ["all"]
ilo bash -c 'gofumpt -l .' # empty output means clean; any output is a failure
ilo bash -c 'gosec ./...' # inline #nosec justifications silence false positives
ilo bash -c 'govulncheck ./...' # reachable-from-code advisories only
ilo bash -c 'arch-go' # architecture rules; config: arch-go.yml
Test layers
Pure unit tests
Table-driven tests with no external state. They live next to the code they cover across internal/... and api/v1/. Several act as drift gates: conditions_test.go verifies that every Reason* constant has a matching docs/runbooks/<reason>.md, and TestErrorResponse_StableCodeValues pins the wire-level ErrCode* strings against accidental rename.
ilo bash -c 'go test -count=1 -race -cover ./...'
To run a single test by name:
ilo bash -c 'go test -count=1 -v -run TestName ./internal/handler/'
Envtest-backed operator tests
Files named envtest_*_test.go (in internal/operator/, internal/webhook/selfsigned/, and main_envtest_test.go at the repo root) boot a real kube-apiserver and etcd via controller-runtime’s envtest package and run the reconciler, webhook, and full run(...) function against them.
The tests share one apiserver instance per test binary, guarded by a sync.Once, so the startup cost is paid once. Each test t.Skips when KUBEBUILDER_ASSETS is unset — there is no build tag. The dev shell pre-stages the envtest asset bundle via setup-envtest (pinned ENVTEST_K8S_VERSION) and exports KUBEBUILDER_ASSETS, so these tests run by default inside ilo bash. On a host without the bundle they silently skip.
The envtest harness sets Config.SkipImpersonation (the only place that setting is allowed) and defaults MetricsBindAddress to "0" so parallel test cases do not fight over the metrics port.
ilo bash -c 'go test -count=1 -race -cover ./...'
Golden / example end-to-end tests
examples_test.go boots the full binary via runInBackground and asserts HTTP responses against golden files under testdata/golden/. Comparison is semantic — both sides are parsed as JSON and compared on the parsed values, so whitespace and key ordering are irrelevant.
After changing an example or adding a new one, regenerate the golden files:
ilo bash -c 'go test -update ./...'
Inspect and commit the diff in testdata/golden/.
Fuzz tests
Fuzz targets in internal/handler/, internal/sources/, and internal/urlguard/ harden the request path, the tar/gzip artifact unpacker, and the SSRF URL/IP parser against adversarial input. CI exercises their seed corpus as ordinary unit tests. To fuzz interactively:
ilo bash -c 'go test -fuzz=FuzzName -fuzztime=30s ./internal/urlguard/'
Benchmarks
Throughput benchmarks in internal/eval/, internal/storage/, and internal/operator/ cover reconcile throughput, watch mapping, and the tenant-client cache. They are baselines, not merge gates. The reconcile benchmark is envtest-backed and skips without KUBEBUILDER_ASSETS.
ilo bash -c 'go test -bench=. -benchmem -run=^$ ./internal/operator/'
Kind operator smoke tests
The cluster-level layer runs outside go test. Pure-kubectl bash scenarios in hack/smoke/ run against a real kind cluster via .github/workflows/kind-smoke.yml. To run a scenario locally against any reachable cluster, deploy JaaS and invoke the scenario scripts directly:
hack/smoke/scenario-inline-files.sh
See CI and releases for how the smoke layer fits into the two-angle end-to-end strategy.