fix(tracking): keep the test suite off the user's real database - #3758
Open
KuSh wants to merge 1 commit into
Open
fix(tracking): keep the test suite off the user's real database#3758KuSh wants to merge 1 commit into
KuSh wants to merge 1 commit into
Conversation
`cargo test` wrote into `~/.local/share/rtk/history.db`, the contributor's own tracking database. Two independent routes, both now closed. **In-process.** The tracking tests call `Tracker::new()`, which resolves `RTK_DB_PATH` → config → platform default like any real invocation, so they inserted synthetic rows (`rtk cmd1_test_<pid>`, …) into real savings history and never cleaned up. `rtk gain` counted them. On this machine the database already held 3069 such rows, the oldest dated 2026-04-04. `Tracker::new()` now resolves through `resolve_db_path`, which under `cfg(test)` redirects to a per-process file in the temp dir unless the test set `RTK_DB_PATH` itself. Structural rather than advisory: no test can reach the real database by accident, whatever it calls, and the 18 existing `Tracker::new()` call sites did not have to change. `Tracker::open(path)` is added for tests wanting an explicit database; `get_db_path` keeps its own semantics so its unit test still exercises the real resolution order. **A spawned rtk child.** Eight integration test files ran the binary directly, and a child is built without `cfg(test)`, so it resolves the real path. Nothing in the child can tell that its parent is a test, so the fix has to be at the call site: `tests/common/rtk_command()` builds the `Command` with `RTK_DB_PATH` already pointed at a temp file, and all eleven spawn sites use it. Beyond fabricating rows, this could destroy the database outright: a test that spawns many rtk children in parallel left a 35 MB history.db malformed (`2nd reference to page 5617`, `wrong # of entries in index idx_pf_timestamp`), after which `rtk gain` fails and the tracking tests themselves fail with `database disk image is malformed` — so the suite cannot pass again until the user repairs it by hand. Recovery via `.recover` cost 57 of 55828 rows. `core::db_isolation_tests` enforces both halves: it scans `src/` and `tests/` for code that spawns the binary without `RTK_DB_PATH`, and asserts a default `Tracker` in a test build resolves under the temp dir. Verified end to end — the real database's mtime, row count and integrity are unchanged across a full `cargo test --all`, which now creates temp databases instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
cargo testwrites into~/.local/share/rtk/history.db— the contributor's own tracking database. Two independent routes.1. In-process. Several tracking tests call
Tracker::new(), which resolvesRTK_DB_PATH→ config → platform default exactly like a real invocation (src/core/tracking.rs,get_db_path). They insert synthetic rows (rtk cmd1_test_<pid>,rtk git status test_<pid>, …) and never clean up, sortk gaincounts them as real usage.This has been happening for a long time. The database on my machine held 3069 rows matching
%_test_%, the oldest dated2026-04-04.2. A spawned rtk child. Eight integration test files run the binary directly via
Command::new(env!("CARGO_BIN_EXE_rtk")). The child is built withoutcfg(test), so it resolves the real path like any other invocation — and nothing in the child can detect that its parent is a test.Why it matters beyond polluted stats
A test that spawns many rtk children in parallel can destroy the database. I hit exactly this while writing an unrelated test:
After that the failure is self-sustaining:
rtk gainstops working, and the tracking tests themselves fail withError code 11: The database disk image is malformed, so the suite cannot pass again until the user repairs their database by hand. Recovery viasqlite3 old.db ".recover" | sqlite3 new.dbworked but lost 57 of 55828commandsrows.Fix
Each route needs a different mechanism, because
cfg(test)reaches only one of them.In-process — structural.
Tracker::new()now goes throughresolve_db_path(), which undercfg(test)returns a per-process file in the temp dir unless the test setRTK_DB_PATHitself:No test can reach the real database by accident, whatever it calls — as opposed to every test remembering to set
RTK_DB_PATH, which is what silently failed. The 18 existingTracker::new()call sites are unchanged.Tracker::open(path)is added for tests that want an explicit database.get_db_pathkeeps its own semantics, sotest_db_path_env_and_defaultstill exercises the real resolution order.Spawned children — at the call site.
tests/common/rtk_command()builds theCommandwithRTK_DB_PATHalready pointed at a temp file; all eleven spawn sites across the eight files now use it.Enforcement.
core::db_isolation_testscovers both halves:src/andtests/for code that spawns the binary withoutRTK_DB_PATH, naming the offending files;Trackerin a test build resolves under the temp dir.The scan is what found the eight integration files — I had identified three by reading.
Verification
Real database is untouched across a full run:
cargo fmt --all --check,cargo clippy --all-targets(zero warnings) andcargo test --allall pass.Note for maintainers
Existing databases still carry the accumulated rows. They're identifiable by the
_test_<pid>shape inrtk_cmd, so a cleanup is:Worth deciding whether that belongs in a migration or is left to users — this PR does not touch existing data.