#!/usr/bin/env bash
# Exercise the pruner only against a fresh fixture; never a configured build root.
set -euo pipefail

REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
PRUNER="$REPO/modules/buildbox/user-config/bin/buildbox-build-cache-prune.sh"
# The pruner measures with `du --one-file-system`. Under an overlay or bind-mounted
# TMPDIR the fixture's subdirectories sit on a different filesystem than its root, so
# du stops at the boundary and reports the root inode alone. Anchor the fixture beside
# the repo, which is always one filesystem.
WORK="$(mktemp -d "$REPO/.build-cache-prune-fixture.XXXXXX")"
trap 'rm -rf "$WORK"' EXIT
fail() { printf 'FAIL %s\n' "$1" >&2; exit 1; }

make_entry() {
  local name="$1" age_days="$2" bytes="$3"
  mkdir -p "$WORK/builds/$name"
  truncate -s "$bytes" "$WORK/builds/$name/payload"
  touch -d "${age_days} days ago" "$WORK/builds/$name"
}

# Under cap: even a very old entry must be retained. This is the no-op branch.
mkdir -p "$WORK/builds"
make_entry retained 30 1024
under=$(BUILDBOX_BUILDS_ROOT="$WORK/builds" BUILDBOX_CACHE_MAX_BYTES=10485760 \
  BUILDBOX_CACHE_TARGET_BYTES=5242880 BUILDBOX_CACHE_MIN_AGE_DAYS=7 bash "$PRUNER")
[ -d "$WORK/builds/retained" ] || fail "under-cap cache deleted an entry"
case "$under" in *'build-cache no-op'*) ;; *) fail "under-cap cache did not report no-op";; esac

# Over cap: oldest aged entries go first until target. A fresh entry is never a candidate.
rm -rf "$WORK/builds"; mkdir -p "$WORK/builds"
make_entry old-a 30 4096
make_entry old-b 20 4096
make_entry fresh 1 4096
over=$(BUILDBOX_BUILDS_ROOT="$WORK/builds" BUILDBOX_CACHE_MAX_BYTES=12000 \
  BUILDBOX_CACHE_TARGET_BYTES=10000 BUILDBOX_CACHE_MIN_AGE_DAYS=7 bash "$PRUNER")
[ ! -e "$WORK/builds/old-a" ] || fail "over-cap cache retained oldest eligible entry"
[ ! -e "$WORK/builds/old-b" ] || fail "over-cap cache did not prune to target"
[ -d "$WORK/builds/fresh" ] || fail "over-cap cache pruned fresh entry"
case "$over" in *'build-cache pruned='*) ;; *) fail "over-cap cache did not report pruning";; esac

# Hidden entries are stashed data, never candidates — even old, even over cap.
rm -rf "$WORK/builds"; mkdir -p "$WORK/builds"
make_entry .preserved-runs 30 8192
make_entry old-mirror 30 8192
hidden=$(BUILDBOX_BUILDS_ROOT="$WORK/builds" BUILDBOX_CACHE_MAX_BYTES=12000 \
  BUILDBOX_CACHE_TARGET_BYTES=10000 BUILDBOX_CACHE_MIN_AGE_DAYS=7 bash "$PRUNER") || true
[ -d "$WORK/builds/.preserved-runs" ] || fail "over-cap cache pruned a hidden entry"
[ ! -e "$WORK/builds/old-mirror" ] || fail "over-cap cache retained the visible aged entry"

# An entry carrying a directory the pruner cannot read (jailed session state on the
# boxes) must not disable the cap: measurement excludes it, eviction continues.
rm -rf "$WORK/builds"; mkdir -p "$WORK/builds"
make_entry readable-old 30 8192
mkdir -p "$WORK/builds/blocked-old/inner"
truncate -s 8192 "$WORK/builds/blocked-old/inner/payload"
chmod 000 "$WORK/builds/blocked-old/inner"
touch -d '30 days ago' "$WORK/builds/blocked-old"
unreadable=$(BUILDBOX_BUILDS_ROOT="$WORK/builds" BUILDBOX_CACHE_MAX_BYTES=12000 \
  BUILDBOX_CACHE_TARGET_BYTES=10000 BUILDBOX_CACHE_MIN_AGE_DAYS=7 bash "$PRUNER" 2>&1) || true
case "$unreadable" in *'cannot measure'*) fail "an unreadable subtree disabled the cap";; esac
[ ! -e "$WORK/builds/readable-old" ] || fail "eviction stopped at the unreadable entry"
chmod -R u+rwX "$WORK/builds" 2>/dev/null

printf 'ok   build cache pruner no-ops under cap and prunes aged entries over cap\n'
