Testing strategy¶
Kollect is TDD-first. Quality gates follow a six-tier test pyramid (L0–L5) defined in ADR-0706: Testing and merge-gate architecture.
Quick local loop
Before opening a PR: task lint · task coverage · task coverage:race (recommended) ·
task verify · task scrub. See Coding standards and
CONTRIBUTING.md for the full checklist.
Test pyramid (L0–L5)¶
| Tier | Scope | Blocks merge? | Typical command |
|---|---|---|---|
| L0 — Unit | Pure packages, table-driven tests, mocks | Yes | task test |
| L1 — Controller / API | envtest reconcilers, webhooks | Yes | task coverage; nightly advisory task coverage:race |
| L2 — Golden / contract | OpenAPI fragments, sample YAML, extractor goldens | Yes | task test |
| L3 — Integration | Real Postgres, Kafka, Git, S3, GCS, Redis, NATS (testcontainers) | Yes | task test-integration |
| L4 — E2E | Kind cluster: Helm install, smoke, export asserts | PR smoke (required) + nightly / extended | task test:e2e |
| L5 — Perf budget / benchmarks | In-process extractor hot path (no cluster, sinks or concurrency) | Default + nightly | task extract-budget · task bench · task perf-report |
Direction: Most tests live at L0–L2. Every new sink backend must reach L3 before merge
(NFR-EXT-3). L4 catches wiring regressions that unit tests miss. L5 is an
in-process budget, not a scale tier: it fails a >25% regression in the extractor's B/op or
allocs/op (its ns/op ceiling is only a catastrophic-regression net), and says nothing about
cluster scale. Cluster scale is the opt-in scale-envtest-10k job.
Coverage target¶
Statement coverage on ./internal/... is enforced by hack/coverage.sh via task coverage:
| Setting | Value |
|---|---|
| Target (pre-v0.10) | Hold 90% floor; further ratchet only after measured CI coverage sustains ≥90.5% (COV-90-00) |
| Current CI floor | 90% (COVERAGE_MIN in Taskfile.yml and .github/workflows/ci.yaml) |
| Codecov project target | See codecov.yml (advisory relative to CI floor) |
Regressions below the enforced floor fail CI. Raise the floor only after coverage has grown sustainably — see ADR-0706 for the ratchet policy.
task coverage # unit + envtest + floor check → coverage.out (CI path; no -race)
task coverage:race # COVERAGE_RACE=1 + CGO_ENABLED=1 (local + nightly advisory CI)
task coverage:report # go tool cover -func summary
task coverage:html # coverage.html for browser review
Integration-tagged tests (-tags=integration) and e2e packages are excluded from the default
coverage profile.
What CI runs on every PR¶
Path filters: Preflight and CodeQL run on every PR. Preflight always runs
task lint:markdown, so Markdown in code, docs-only, and mixed PRs is checked consistently.
GitHub skips CI and E2E smoke when a PR or push to main changes only documentation
paths (docs/**, mkdocs.yml, README.md, CHANGELOG.md, CONTRIBUTING.md, LICENSE, and
issue templates). Other root Markdown files still trigger both workflows.
The path-scoped Docs workflow runs the complete task docs:verify gate, then (on
main push) deploys to
platformrelay.github.io/Kollect. Any change under
api/, internal/, charts/, cmd/, config/, hack/, test/, go.mod, or
.github/workflows/ — or a mixed docs+code PR — runs the full gate below. Release tags no longer
trigger docs deploys; the site tracks main only. Docs-only PRs still need a maintainer
ruleset bypass (or admin merge) because required checks test / kind-smoke do not run when
those workflows are path-skipped.
Binding jobs in .github/workflows/ci.yaml (see ADR-0706 for the full matrix):
- Secret scan (
gitleaks), codegen drift (task verify), vulncheck, lint/format - Architecture fitness:
go-arch-lintviatask arch-lint(import boundaries in.go-arch-lint.yml) - Dependency policy: golangci-lint
depguard+gomodguard(sametask lintjob) - L0–L2:
task coveragewith coverage floor - L3:
task test-integration(Docker required) - Helm packaging (
task helm-test), image build (task docker:build) - Native Go fuzz (CEL/JSONPath extractors, content hash)
- RBAC audit (
hack/audit-rbac.sh)
E2E smoke (L4 Tier 0): .github/workflows/e2e-smoke.yaml job kind-smoke on every
non-docs PR and push to main (same paths-ignore as CI). Required for branch protection — see
coding-standards.md.
Non-blocking on PR: e2e-extended.yaml (Tier 1 matrix + webhook profile; label e2e/full or
path-filtered), task perf-report (promoted to blocking at v0.4 per ADR-0706);
SonarCloud scan (sonarcloud job — needs SONAR_TOKEN; see
tooling-setup.md).
Holistic maintainability (SonarCloud)¶
SonarCloud mirrors coverage trends and surfaces duplication / technical-debt ratios over time —
complementing point-in-time dupl and Codecov. Configured in sonar-project.properties; optional
until the maintainer adds SONAR_TOKEN. Does not replace task lint or arch-lint.
Codecov uploads run in the test job (OIDC auth, non-blocking). PR patch comments require
the maintainer to install the Codecov GitHub App once — see
tooling-setup.md § Codecov.
Scheduled and manual tiers¶
| Workflow | Tier | Purpose |
|---|---|---|
preflight.yaml |
All PRs | Markdown lint, codegen and changelog drift, module consistency |
docs.yaml |
Docs paths | Markdown lint, strict MkDocs build, GitHub Pages deploy (main only) |
e2e-smoke.yaml |
L4 Tier 0 | Mandatory kind smoke on PR + main (job kind-smoke) |
e2e-extended.yaml |
L4 Tier 1 | Optional git-export, multitenant, tenant-mode, webhook profile |
e2e-nightly.yaml |
L4 Tier 2 | Full Kind matrix + bench/perf (deduped L3) + advisory race |
test-e2e.yaml |
L4 Tier 3 | Manual full matrix (workflow_dispatch) |
release.yaml |
Supply chain | Image signing, SBOM, chart publish |
Nightly advisory race detector (HY-07 / TEST-02)¶
e2e-nightly.yaml runs a race job (continue-on-error: true) that executes
task coverage:race with CI=true, CGO_ENABLED=1, and COVERAGE_MIN=0 (racing is the
signal — the merge-gate floor stays on the non-race test job). On a WARNING: DATA RACE
finding the step summary prints an excerpt; file a GitHub issue labeled race/flake
and do not ignore it. Timeouts/compile failures are summarized distinctly from race reports.
Timeout is generous (timeout-minutes: 45) because -race is typically 2–10× slower.
Set repository variable GIT_EXPORT_TEST_REPO (Settings → Actions → Variables) to enable full
remote git SHA assert in git-export scenarios. Without it, jobs verify inventory HTTP hash only.
For local runs the variable is optional: export GIT_EXPORT_TEST_REPO to a dedicated test repo
(or file:// bare remote) when exercising push assertions; unit and envtest tiers do not require it.
Multi-node lab evidence¶
Kind L4 proves single-node wiring. Separately, published v0.16.0 was exercised on a Talos lab
with 1 control plane + 2 workers (quick+sinks, ready with conditions). Maintainer
multi-node / existing-cluster evidence sits as L4.5 beside Kind L4 and perf-budget L5 —
ADR-0707: Lab harness architecture. Publishable shape, redaction,
and an example matrix live in the
lab evidence bundle contract.
Observed (bounded)
- Two manager replicas on distinct workers; leader failover without unexpected restarts
- Inventory collection and export to in-cluster Postgres
- ClusterIP Postgres / MinIO / NATS with Helm
allowPrivateSinks: true(resolved-address policy) - Private GitHub and GitLab snapshot export (connection + push)
- Certificate scrape non-zero (partial versus live cluster certificates)
- Idle pprof captured; Wave-4 / Tier-S load not run
Not claimed from that run
- Wave-4 / Tier-S load (500–2k synthetic rows) or full pprof under churn
- 100k rows/cluster design proof (load-test runbook)
- Full Ubuntu D-suite / every sink backend / NetPol deny-path / worker drain / Argo scrape
Treat multi-node lab results as bounded evidence for a named pin, not a substitute for Kind CI or the 100k cloud gate. Raw protocols stay local-only — see the evidence bundle contract.
Local development commands¶
| Task | Purpose |
|---|---|
task docs:verify |
Tracked Markdown, truth/freshness contracts, samples, strict site build, and browser layout when Chrome is available |
task test |
Unit + envtest (no floor check; no race detector) |
task coverage |
Unit + envtest + 90% floor (CI; CGO off, no -race) |
task coverage:race |
Same as coverage with race detector (local + nightly advisory) |
task test-integration |
L3 sink/transport integration (Docker) |
task test:e2e |
L4 kind smoke (setup → smoke → teardown) |
task bench |
Micro-benchmarks on hot paths |
task extract-budget |
L5 extractor hot-path budget — >25% gate on B/op + allocs/op only (in-process; not cluster scale) |
task perf-report |
Benchmark + unit pass summary (local only, gitignored output) |
Full local setup: development/setup.md.
task docs:verify is the canonical local reproduction for the required Docs workflow. It skips
only the real-browser layout check when Chrome/Chromium is unavailable. To reproduce CI's hard
requirement exactly, run DOCS_REQUIRE_CHROME=1 task docs:verify; set CHROME_BIN when the browser
is not on PATH.
Writing chart tests (helm-unittest gotchas)¶
Two matcher behaviours produce assertions that pass unconditionally. Both were found by a review that mutated the implementation and watched the suite stay green.
contains/notContainscompare WHOLE list elements, not substrings. SonotContains: --my-flag=can never match a rendered--my-flag=60s, and the test passes whether or not the flag is emitted. When asserting that something is absent from a rendered arg list, assert the exact list instead:
# kollect-doc: ignore helm-unittest assertions, not a kollect CR
- equal:
path: spec.template.spec.containers[0].args
value: [--leader-elect, --health-probe-bind-address=:8081, ...]
containsDocumentis evaluated against EVERY rendered document. A multi-document template (e.g. a ClusterRole plus its ClusterRoleBinding) can therefore never satisfy two differentcontainsDocumentassertions at once. Assert perdocumentIndexwithisKindinstead.
Before trusting a new chart assertion, mutate the template and confirm the test goes red. A chart test that cannot fail is worse than no test — it reports coverage that does not exist.
Definition of done¶
Per-change checklist: guidelines § 6. PR workflow: CONTRIBUTING.md § Pull request process.
Further reading¶
- ADR-0706: Testing and merge-gate architecture
- ADR-0707: Lab harness architecture (L4.5 maintainer evidence)
- Engineering guidelines §4 (testing rules)
- REQUIREMENTS.md — NFR-TEST-* priorities
- operator-manual/performance.md — scale bounds and perf-report workflow
- tooling-setup.md — arch-lint, depguard, SonarCloud maintainer steps