test(prune-cache): build scenario 14's aside the way the pass builds one

Review finding on #2. Swapping the settle window's `stat -c %Z` for `%Y` —
the exact substitution the comment beside it calls the wrong signal — left
the suite green at 37/37. The scenario built its aside with `mkdir`, so the
fixture's mtime was also ~now and the two timestamps agreed; a fixture whose
clocks agree cannot tell them apart.

Every real aside is the opposite shape: a cache last written days ago,
renamed a moment ago. Under `%Y` the window would never fire for one, the
sweeper would silently return to reclaiming asides another pass is still
deciding about, and nothing would say so — the mechanism guarded by a comment
again, which is what the previous finding was about.

So the fixture is now built the way the pass builds one: an old directory
`mv`d into the aside name. `%Z` -> `%Y` now fails scenario 14, as does
deleting the guard outright.

Also sharpens why the PID alternative was rejected: the `$$` in an aside's
name was that pass's PID inside its own job container, so testing it from
another one is not unreliable, it is meaningless.

No change to prune-cache.sh's behaviour; the assertions are untouched.
This commit is contained in:
2026-08-23 23:46:03 -05:00
parent 65f0782233
commit dcd73dd82d
2 changed files with 17 additions and 6 deletions
+13 -3
View File
@@ -43,7 +43,9 @@
# 14. AND NOT WHILE ANOTHER PASS MAY STILL BE EVICTING IT. An aside with no
# readers is indistinguishable from one a concurrent pass has just
# renamed and not yet decided about; reclaiming that one lets `rm -rf`
# empty a tree its owner may still restore under a live cache name.
# empty a tree its owner may still restore under a live cache name. Its
# fixture is an OLD directory renamed a moment ago — production's shape,
# and the only shape that can tell the two timestamps apart.
set -euo pipefail
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
. "$script_dir/cache-lib.sh"
@@ -264,13 +266,21 @@ echo
echo "=== 14: an aside another pass may still be evicting is left alone ==="
reset_cache
aside="$root/.evicting-target-$DEAD-9999"
mkdir -p "$aside"; head -c 4096 /dev/zero > "$aside/blob"
# Built the way the pass builds one — an old cache directory renamed a moment
# ago — because the two ages that describe it disagree, and which of them the
# window reads is the whole mechanism. `mkdir`-ing the aside directly would
# give it a fresh mtime as well as a fresh ctime, and a fixture whose two
# clocks agree cannot tell %Z from %Y: the settle window would read the wrong
# one, never fire for any real aside, and this scenario would not notice.
victim="$root/target-$DEAD-victim"
mkdir -p "$victim"; head -c 4096 /dev/zero > "$victim/blob"
touch -d '2020-01-01' "$victim" # an old cache, which is every cache
mv -T "$victim" "$aside" # set aside a moment ago
# Deliberately no reader marker: the reader gate would pass this straight
# through, which is the whole point. An aside with no readers is exactly what
# a pass that has just renamed one aside and not yet decided about it looks
# like, and `rm -rf` traverses fd-relative — so reclaiming it out from under
# that pass lets it republish a half-emptied tree under a live cache name.
# Nothing here fakes an age: the directory really was set aside a moment ago.
run_prune "1000000 900000"
assert_kept "$aside" "an aside younger than the settle window is not reclaimed"
assert_kept "$aside/blob" "and is left intact, not part-way emptied"