Skip to content

test: the e2e assertions name the namespace they are about - #121

Merged
kfeofantov merged 1 commit into
mainfrom
fix/opt-out-e2e-diagnosis
Sep 16, 2026
Merged

kfeofantov merged 1 commit into
mainfrom
fix/opt-out-e2e-diagnosis

Conversation

@kfeofantov

Copy link
Copy Markdown
Contributor

checkOptOutRemovesTheRecord has been failing, and the agent was not the reason.

What was actually happening

The suite reuses one kind cluster. A run killed before its cleanup leaves its namespace behind, and the sample workload in it keeps running — same image, same workload name, same module path, and nobody annotated it. findSampleRecord matched on module_path alone, so it kept finding a pod from an abandoned run three days old and reporting that the opt-out had failed.

This also corrects a conclusion recorded in #118. The control experiment there found the test failing on both the branch and main and read that as evidence about the agent. It was evidence about the cluster: the abandoned namespace was already present for both runs.

The opt-out works

Measured directly against the cluster the killed run left in its failed state — the controller, the node and the annotated pod were all still up, so no rerun was needed:

09:24:27  annotation patched onto the pod
09:25:08  go inventory forgot departed workloads  records=1
09:26:08  coverage: go_inventory_records 3 -> 2

The retention pass of ADR 0018 evicted the record 41 seconds after the annotation. The surviving goworkload record in the payload carried namespace: runtime-agent-inv-e2e-51873 — the abandoned one.

The fix

The gap was in five payloads, not one. Every assertion about the sample workload is now scoped by the namespace under test: go_inventory, process_peaks, process_counters, listening_ports, and workload_metadata.

go_build stays keyed by image digest alone, which is correct — a build is a property of an image, not of a namespace.

Kept from the diagnosis

Two things, because they are what made the diagnosis possible at all:

  • captured_at travels with the record. The kind supersedes in place under one file name, so a reader that does not date the payload cannot tell a record that left from a file that was never rewritten.
  • The timeout says what it found. Annotation time, write time, whether the payload was written after the annotation, and two probes: whether the workload is still in workload_metadata (built from the same pod index the retention pass reads) and whether the controller ever logged an eviction.

Plus a line at the start of the run naming abandoned namespaces from earlier runs. It paid for itself immediately.

Verification

make inventory-e2e    PASS (363s, was 865s)
go test ./...         pass
golangci-lint         3 gosec findings, unchanged from main

The passing run was made with both abandoned namespaces still in the cluster — the exact condition that broke the test — and the run's own log names them:

inventory_test.go:139: abandoned namespaces from earlier runs still in this cluster:
    [runtime-agent-inv-e2e-37069 runtime-agent-inv-e2e-51873]
--- PASS: TestGoInventoryEndToEnd (363.29s)

Not fixed here

countExcluded is called from the informer's AddFunc but not from UpdateFunc, so a pod that opts out mid-flight is dropped from collection without being counted in filter.excluded_pod_annotation — the live cluster showed 0 there while the pod was excluded. The count corrects itself when the controller restarts and AddFunc replays the cache. That is a change to agent behaviour and wants its own slice.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KeZECJXTDHL6Nts95eKMJA

checkOptOutRemovesTheRecord has been failing, and the agent was not the
reason. The suite reuses one kind cluster, a run killed before its cleanup
leaves its namespace behind, and its sample workload keeps running the same
image under the same names. findSampleRecord matched on module path alone,
so it kept finding a pod from an abandoned run three days old that nobody
had annotated.

Measured against the failing cluster: the opt-out works. The annotation
landed at 09:24:27 and the controller logged "go inventory forgot departed
workloads records=1" at 09:25:08, with go_inventory_records falling 3 -> 2
on the next pass. The retention pass of ADR 0018 does what it promises.

Every sample assertion is now scoped by namespace — go_inventory, peaks,
counters, ports and workload metadata. go_build stays keyed by digest
alone, which is correct: a build is a property of an image, not of a
namespace.

Two things kept from the diagnosis, because they are what made it possible:
the payload's captured_at travels with the record, so a negative reading
can be told from a file that was never rewritten; and the timeout prints
the annotation time, the write time and whether the retention pass ever
evicted anything.

    make inventory-e2e — PASS (363s), with both abandoned namespaces
    still present in the cluster and named in the run's own log

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeZECJXTDHL6Nts95eKMJA
@kfeofantov

Copy link
Copy Markdown
Contributor Author

Note on the red lint runs above — they were a CI cache artifact, not this branch.

Two runs reported six SA5011 (possible nil pointer dereference) findings in internal/chartrender/chart_test.go, internal/collector/cadvisor_test.go and internal/nodescan/proccounters_test.go — none of which this PR touches.

What ruled the code out:

Check Result
pinned v2.11.4 locally 0 issues
same, with empty GOCACHE and GOLANGCI_LINT_CACHE 0 issues
same, cross-linted GOOS=linux GOARCH=amd64 0 issues
build tags on the three files none; ordinary files
what this PR changes two files, both //go:build e2e
build-tags in .golangci.yml absent

The last two lines are the point: without the e2e tag the linter does not analyse those files at all, so this branch is byte-identical to main as far as it is concerned. Same code, same pinned version, same config, different result.

The difference was the restored cache. Two keys existed: …dbc9038c (10 Sep), which main restores and which passes, and …0b740007 (15 Sep), which this branch restored and which fails. Deleting the latter and re-running gave a cold run — no Cache hit for: golangci line, 39s instead of 15.6s — and 0 issues.

The findings were false in any case: all three sites call t.Fatal in the nil branch before dereferencing, and t.Fatal calls runtime.Goexit(). Seeing that requires knowing testing.common.Fatalf does not return, which is exactly what a partial analysis cache loses.

Worth its own slice: the linter cache can turn a clean tree red, and nothing in the workflow guards against it.

@kfeofantov
kfeofantov merged commit bd5ad73 into main Sep 16, 2026
10 of 12 checks passed
@kfeofantov
kfeofantov deleted the fix/opt-out-e2e-diagnosis branch September 16, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant