test(cargo-cache): cover the stale-reader-marker sweep and its bound

The staleness path decides whether a publisher may reclaim disk, so getting it
wrong means an abandoned marker pins a snapshot generation forever — the exact
outcome the bound exists to prevent. It was previously covered only by
analogy to prune-cache.sh's .ci-lock-* staleness, which is not the bar.

New publish-snapshot-selftest.sh scenario 8 asserts both directions against
the SAME backdated marker, which is what separates "honours the bound" from
"ignores anything that looks old":

* under CACHE_READ_STALE_SECONDS=86400 a three-hour-old marker is left alone
  and still defers reclamation, exactly as a live reader does;
* under the 7200s default the same marker is swept, reported as swept, and the
  generation it was pinning — plus the one deferred by the first half — is
  reclaimed.

Backdated with `touch -d`, not slept for; the suite stays fast.

Red-proven by mutation rather than against the pre-fix scripts, since the
whole mechanism is new there and "it does not exist yet" proves nothing about
the threshold logic. Mutating live_reader_count's bound test to `true` (never
sweep) fails scenario 8:

    ASSERTION FAILED: the stale marker was not reported as swept

and to `false` (sweep everything, bound ignored) fails scenario 6 instead,
which is the right blast radius — ignoring the bound means unlinking under a
LIVE reader:

    ASSERTION FAILED: the previous generation was unlinked while a reader
    still held it

Also documents the entry-count check's measured cost in the README: on ext4
with a warm cache over 78,554 entries, 44 ms per metadata walk against 3,126 ms
for the `cp -al` it guards — about 2.8%. Not a perf-claiming change; the number
is there so the next reader does not have to wonder.

The reviewer's other nit — scenarios 8/9 of the seed suite exercising
publish-snapshot.sh's interlock — was already covered by the cross-reference
in this file's header, so no move.

Verification: `bash scripts/selftest.sh` — 5 suites, exit 0, 88 assertions
(82 before this commit, 63 at baseline). shellcheck: no new findings.

Refs: daniel/gitdan#11, zemyna#911

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sqh2vscfzisk83VuPVQX9L
This commit is contained in:
2026-08-23 16:52:53 -05:00
co-authored by Claude Opus 5
parent 719475831b
commit 3b2dec6a50
2 changed files with 49 additions and 1 deletions
+3 -1
View File
@@ -161,7 +161,9 @@ mechanisms, both required:
count (the only signal for a subtree unlinked before its parent was listed —
there is no error to read). A tree that fails any of the three is deleted
and the clone retried; one that fails the last attempt fails the job. A
partial tree never reaches the final name.
partial tree never reaches the final name. The two counts cost one metadata
walk each: measured on ext4 with a warm cache over a 78,554-entry tree, 44 ms
per walk against 3,126 ms for the `cp -al` they guard — about 2.8%.
**What this does and does not guarantee.** Three separate claims, deliberately
not collapsed into one: