test: isolate tests from ambient XDG and GOG_* path variables - #997
Conversation
Tests isolate storage via per-test HOME/t.TempDir sandboxes, but the
layout resolver (internal/config/layout.go) honors GOG_HOME, the
GOG_{CONFIG,DATA,STATE,CACHE}_DIR overrides, and the XDG base directory
variables ahead of HOME-derived defaults. On machines that export any
of them, tests resolve the developer's real gogcli directories: with
XDG_DATA_HOME/XDG_STATE_HOME exported, 19 failures across internal/cmd,
internal/config, and internal/secrets from cross-test contamination
(the exact split depends on preexisting state and platform); with
GOG_HOME exported, 77+ failures. In every case test fixtures
(service-account stubs, tracking.json, gmail-watch state, file-keyring
entries) leak into the real directories, clobbering any real
file-keyring, tracking, or watch state. CI never sees this because
GitHub runners export none of these variables.
Unset the GOG_* path overrides plus XDG data/state/cache in
internal/cmd's TestMain (which already redirects HOME and
XDG_CONFIG_HOME to a temp root), add equivalent TestMains to
internal/secrets and internal/config, and clear the GOG_* overrides in
the internal/googleapi test that deliberately writes to the ambient
layout (with GOG_HOME exported it previously stayed green while
writing into the real directory). Unsetting rather than redirecting
matters: a single shared override directory still cross-contaminates
tests; unsetting lets each test's own sandbox take effect, matching CI
behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs maintainer review before merge. Reviewed August 14, 2026, 8:23 PM ET / August 15, 2026, 00:23 UTC. ClawSweeper reviewWhat this changesThe PR updates four Go test files so ambient GOG and XDG path overrides cannot direct test fixtures into a developer’s persistent gogcli directories. Merge readiness✅ Ready for maintainer review Current main still lets documented GOG and XDG overrides bypass test homes, while this focused test-only patch clears those inputs before affected tests resolve storage paths; it remains a useful, mergeable fix. Priority: P2 Review scores
Verification
How this fits togethergogcli’s layout resolver turns command options, GOG environment variables, XDG variables, and platform defaults into config, data, state, and cache locations. Tests use those locations for credentials, keyring entries, and Gmail state, so test setup must ensure the resolver selects temporary paths. flowchart LR
A[Ambient GOG and XDG variables] --> B[Layout resolver]
C[Test setup] --> B
B --> D[Storage-path precedence]
D --> E[Temporary test directories]
D --> F[Developer persistent directories]
E --> G[Test fixtures]
F --> H[Unwanted fixture writes]
Before mergeNone. Agent review detailsSecurityNone. Review metrics
Technical reviewBest possible solution: Merge the narrow test-process isolation while retaining individual tests’ ability to set an explicit path variable for resolver coverage. Do we have a high-confidence way to reproduce the issue? Yes, from current source: export GOG_HOME or a relevant XDG path variable, then run the affected package tests; resolver precedence directs their system-layout fixture writes outside HOME-based test sandboxes. Is this the best way to solve the issue? Yes. Clearing ambient overrides once before each affected package runs preserves existing per-test overrides and avoids creating another shared fixture directory. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 45b5d766e137. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
|
What
Makes the test suite immune to ambient path-environment variables. The layout resolver (
internal/config/layout.go) honorsGOG_HOME,GOG_{CONFIG,DATA,STATE,CACHE}_DIR, and the XDG base directories ahead of HOME-derived defaults, but tests isolate themselves with per-testt.Setenv("HOME", t.TempDir())sandboxes. On any machine that exports one of these documented variables,go test ./...today both fails (cross-test contamination through the shared real directory) and writes test fixtures into the developer's real gogcli data — including live file-keyring entries,tracking.json, and gmail-watch state.Four test-only changes, no runtime code touched:
internal/cmd/testmain_test.go— the existingTestMain(which already redirectsHOMEandXDG_CONFIG_HOMEto a temp root, from 2ca93e9) now also unsets the fiveGOG_*path overrides plusXDG_DATA_HOME/XDG_STATE_HOME/XDG_CACHE_HOME, restoring saved values afterward.internal/config/testmain_test.go,internal/secrets/testmain_test.go(new) — minimalTestMains unsetting all nine path variables; these packages' tests were exposed the same way.internal/googleapi/service_account_test.go—TestTokenSourceForServiceAccountScopesUsesInjectedStoredeliberately writes an "ambient" fixture through the real resolver to prove the injected store wins. It already pinsHOME/XDG_CONFIG_HOME/XDG_DATA_HOMEper test but notGOG_*, so withGOG_HOMEexported it wrote<GOG_HOME>/data/sa-YUBiLmNvbQ.json(contents:ambient) into the real directory while reportingok— silently clobbering any real stored service-account key for that address. It now clears theGOG_*overrides too.Unsetting rather than redirecting is deliberate: we tried redirecting the variables at a single shared package-level directory, and tests still cross-contaminate through it — the failures need no preexisting content, because writer tests fill the shared directory mid-run and reader tests then see their state (preexisting junk only changes which package the failures land in). That is also the precise reason CI has never seen this: GitHub runners export none of these variables, so every test falls back to its own
t.Setenv("HOME", …)sandbox — had a runner exportedXDG_DATA_HOME, even a pristine one, the same failures would appear. Unsetting reproduces that environment everywhere. Per-testt.Setenvof any of these variables keeps working (TestMainruns beforem.Run), and the build-tagged integration suites that intentionally target the real layout are untouched.Why
Measured at current main (45b5d76), on macOS (the resolver branches involved are not platform-gated, so Linux with the same variables exported is equally exposed):
XDG_DATA_HOME/XDG_STATE_HOMEexported → 19 failing tests acrossinternal/cmd,internal/config,internal/secrets(the split varies with what's already in the shared directory), plus service-account stubs, a file keyring,tracking.json, and gmail-watch state written into the real$XDG_DATA_HOME/gogcliand$XDG_STATE_HOME/gogcli.GOG_HOMEexported → 77 failing tests, same mechanism, higher resolver precedence — andGOG_HOMEis gogcli's own documented relocation knob, so the population most at risk is gogcli developers who also use gogcli.internal/googleapileak above stays green while overwriting real data.This came out of a real diagnosis: on a Nix-managed dev machine (XDG variables exported globally), 19 tests failed on a clean checkout of main, and the real
~/.local/share/gogcli/~/.local/state/gogclihad been silently accumulating test fixtures since June. VISION.md counts reliability improvements around keyring and credentials as wanted work; this protects contributors' actual credentials/state fromgo test.Behavior changes (complete ledger)
_test.gofiles.GOG_HOME,GOG_{CONFIG,DATA,STATE,CACHE}_DIR,XDG_DATA_HOME,XDG_STATE_HOME,XDG_CACHE_HOME(and, ininternal/config/internal/secrets,XDG_CONFIG_HOME). Tests that set these per test are unaffected.XDG_CACHE_HOMEunset, thego buildsubprocess ininternal/cmd's slides-assets test derives its build cache under the sandboxed HOME on Linux (cold cache per run). No measurable runtime change on darwin; the unset is still wanted because gogcli genuinely resolves the cache path (internal/cmd/backup_gmail.go).Proof
Self-contained TAP script, no credentials required — it runs the matrix against whatever checkout it's started from, so the same script demonstrates the bug on main and its absence here. It pins
GOFLAGSand starts each scenario from all nine path variables unset (setting only that scenario's), so ambient environment on the machine running it cannot skew or vacuously pass the checks.proof-isolation.sh(bash, stdlib only)At current main (45b5d76):
The silent case in isolation, at main — the package reports
okwhile writing through the real resolver (sa-YUBiLmNvbQ.jsonis the service-account stub fora@b.com, base64url-encoded):On this branch:
Scope notes: the proof exercises the four affected packages; a full
go test ./...under each of the three environments also passes on this branch (that is how the affected set was established — no other package resolves the system layout outside build-tagged integration tests, which intentionally use the real one). Windows CI runs with none of these variables set, so it sees pure CI-parity behavior. One adjacent observation, deliberately out of scope for this PR: CI cannot detect removal of theseTestMains (runners never export the variables), so the isolation is convention-guarded only. (The keyring-selection variables —GOG_KEYRING_BACKENDand friends — were audited separately and need no scrubbing here: every test that opens a secrets store already pins the backend tofileper test, on main and on this branch alike.)🤖 Generated with Claude Code