]> git.hungrycats.org Git - bees/commitdiff
test: renumber eof-tail-no-churn from 21 to 24
authorZygo Blaxell <bees@furryterror.org>
Fri, 24 Jul 2026 16:10:18 +0000 (12:10 -0400)
committerZygo Blaxell <bees@furryterror.org>
Sat, 5 Sep 2026 04:04:14 +0000 (00:04 -0400)
The case landed as 21-eof-tail-no-churn but 21 was already taken by
21-scan-first-transid, so two cases shared the number.  Move it to the
next free slot (22 and 23 exist) and update IT_TEST_NAME to match.

Assisted-by: Claude-Code:claude-opus-4-8
test/integration/cases/21-eof-tail-no-churn.sh [deleted file]
test/integration/cases/24-eof-tail-no-churn.sh [new file with mode: 0755]

diff --git a/test/integration/cases/21-eof-tail-no-churn.sh b/test/integration/cases/21-eof-tail-no-churn.sh
deleted file mode 100755 (executable)
index df75940..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-#!/bin/bash
-# Test 21: an EOF-tail extent is rewritten only when it frees a WHOLE block
-#
-# Regression guard for the phantom-unreachable-block bug (cf3959bc4) and a
-# runtime exercise of the past-EOF temp elision (f04f91a14).  Runs at the
-# DEFAULT free-min = 50%.
-#
-# MECHANISM
-#   btrfs does not partially free a data extent: truncating a file within an
-#   extent leaves the whole physical extent allocated and only lowers
-#   i_size, so whole blocks past EOF become allocated-but-unreachable.  bees
-#   reclaims them by COPYING the reachable data to a compact extent (a
-#   cleanup plan -- no dedupe, viable with no candidates).  The gain is the
-#   number of COMPLETE unreachable blocks; a sub-block past-EOF tail shares
-#   its block with reachable data and frees nothing.  That count is the
-#   floor() in scan_next_region_block_count.
-#
-#   For an extent of E blocks with reachable data D blocks (= bad, copied)
-#   and U complete unreachable blocks (= good, reclaimed), U = E - D, and
-#   free-min at 50% passes iff good >= bad, i.e. U >= D, i.e. retained data
-#   is at most half the extent.
-#
-# THE BUG: the unreachable region was rounded UP, so a sub-block-only tail
-#   counted as 1 phantom freeable block.  On a SINGLE-block extent that
-#   phantom is decisive: good_pre = 1 = bad clears free-min at exactly 50%
-#   and the negative debt rewrites the extent for zero net gain (pure ref
-#   churn).  Post-fix the tail rounds DOWN to 0 -> free-min rejects and
-#   do-nothing wins.  (Only E=1 flips at 50%; for E>=2 the phantom never
-#   cleared free-min, so the multi-block cases here test that REAL gains
-#   still fire and the executor handles the past-EOF tail, not the flip.)
-#
-# WHY A COPY, NOT A DEDUPE: each file is independent random data, so there
-#   is no duplicate to reference -- the only plan is the cleanup copy.  A
-#   dedupe would correctly have negative debt and is not what regressed.
-#
-# CASES (all at default free-min = 50%):
-#   A  4096 -> 2990   E=1 D=1 U=0   NOT rewritten (post) / rewritten (pre)  <- guard
-#   B  8192 -> 2990   E=2 D=1 U=1   rewritten (1-block gain, floor of mixed block)
-#   C 16384 -> 6000   E=4 D=2 U=2   rewritten (2-block gain, floor of mixed block)
-#
-# Prerequisites: testlib.sh, a configured host.
-
-set -euo pipefail
-
-SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
-source "$SCRIPT_DIR/../lib/testlib.sh"
-
-IT_TEST_NAME="21-eof-tail-no-churn"
-
-it_load_config
-it_setup_trap
-
-BEESHOME="$TEST_MOUNT/.beeshome"
-IT_BEES_LOGFILE="$TEST_LOG_DIR/${IT_RUN_ID}.log"
-
-echo "=== $IT_TEST_NAME ==="
-it_deploy
-
-it_ssh_script <<REMOTE
-set -euo pipefail
-
-disk_bytenr() {
-    btrfs-search-metadata file "\$1" 2>/dev/null | \
-        awk '/disk_bytenr/ {for (i=1;i<=NF;i++) if (\$i=="disk_bytenr") {print \$(i+1); exit}}'
-}
-one_extent() { [ "\$(filefrag "\$1" | grep -oE '[0-9]+ extent' | grep -oE '^[0-9]+')" = 1 ]; }
-# Physical allocation length = the EXTENT_ITEM key's offset field (the
-# extent's item-header offset), read from the extent tree.  Independent of
-# any file's i_size / reference length.  dump-tree prints it as
-#   item N key (<bytenr> EXTENT_ITEM <length>) ...
-extent_len() {  # bytenr
-    btrfs inspect-internal dump-tree -t extent '${TEST_SCRATCH_DEV}' 2>/dev/null | \
-        grep -F "(\$1 EXTENT_ITEM " | head -1 | \
-        sed -E 's/.*EXTENT_ITEM ([0-9]+)\).*/\1/'
-}
-
-# ── Fresh fs ─────────────────────────────────────────────────────────────────
-umount -l '${TEST_MOUNT}' 2>/dev/null || true
-mkfs.btrfs -f '${TEST_SCRATCH_DEV}'
-mkdir -p '${TEST_MOUNT}'
-mount -o subvolid=5 '${TEST_SCRATCH_DEV}' '${TEST_MOUNT}'
-cd '${TEST_MOUNT}'
-
-# One file per case, DISTINCT random content (no cross-file dedupe), each a
-# single extent.  write -> commit -> truncate -> commit.
-make_case() {  # name  write_bytes  truncate_to
-    head -c "\$2" /dev/urandom > "\$1"
-    btrfs filesystem sync '${TEST_MOUNT}'
-    one_extent "\$1" || { echo "[FAIL] \$1 write fragmented; premise broken" >&2; exit 1; }
-    truncate -s "\$3" "\$1"
-    btrfs filesystem sync '${TEST_MOUNT}'
-    # PREMISE: btrfs never partially frees a data extent, so the physical
-    # allocation (EXTENT_ITEM key length) stays at the written size even
-    # though i_size shrank -- that leftover is the unreachable space bees
-    # reclaims.  This is thoroughly baked into current btrfs on-disk design;
-    # only a format change (e.g. extent_tree_v2) could relax it, and that
-    # would require significant bees planner work throughout.  So a failure
-    # HERE is a canary for a changed btrfs, not a flaky test -- assert it at
-    # setup rather than let cases B/C silently stop reclaiming.
-    local bn plen
-    bn="\$(disk_bytenr "\$1")"
-    plen="\$(extent_len "\$bn")"
-    [ "\$plen" = "\$2" ] || { echo "[FAIL] \$1 extent shrank on truncate: EXTENT_ITEM len \$plen != written \$2 -- btrfs partial-free premise broken" >&2; exit 1; }
-    echo "[premise] \$1: i_size \$3, physical extent still \$plen bytes"
-}
-make_case a_regression  4096  2990    # E=1 D=1 U=0 -> 0 complete unreachable blocks
-make_case b_one_block   8192  2990    # E=2 D=1 U=1 -> 1 complete unreachable block
-make_case c_two_block  16384  6000    # E=4 D=2 U=2 -> 2 complete unreachable blocks
-
-echo "A_PRE=\$(disk_bytenr a_regression)" >  /tmp/pre.env
-echo "B_PRE=\$(disk_bytenr b_one_block)"  >> /tmp/pre.env
-echo "C_PRE=\$(disk_bytenr c_two_block)"  >> /tmp/pre.env
-cat /tmp/pre.env
-
-# ── bees config: DEFAULT free-min (50%), one deterministic pass ──────────────
-mkdir -p '${BEESHOME}' '${TEST_LOG_DIR}'
-cat > '${BEESHOME}/bees.conf' <<'CONF'
-[log.plan]
-level = 8
-[loop]
-exit-one-pass = yes
-[thread]
-thread-max = 1
-[scan.extent.*]
-method = 1
-filter = ACCEPT
-[rewrite]
-free-min = 50%
-CONF
-# free-min is pinned to 50% (the schema default and the production regime
-# in which the regression occurred) so the test is hermetic: the host may
-# carry a global /etc/bees rewrite.free-min (e.g. 128K) that bees merges
-# under this per-run config, and at 128K the 'cap threshold at extent size'
-# clause would require freeing 100% of any sub-128K extent -- masking both
-# the bug and the real-gain cases.  At 50%, B/C (free exactly half) pass
-# and A (frees nothing) is declined.
-
-'${REMOTE_BEES_BIN}/bees' --config '${BEESHOME}/bees.conf' '${TEST_MOUNT}' \
-    > '${IT_BEES_LOGFILE}' 2>&1 &
-wait \$! || true
-
-btrfs filesystem sync '${TEST_MOUNT}'
-{
-    echo "A_POST=\$(disk_bytenr a_regression)"
-    echo "B_POST=\$(disk_bytenr b_one_block)"
-    echo "C_POST=\$(disk_bytenr c_two_block)"
-} > '${TEST_LOG_DIR}/${IT_RUN_ID}.results'
-cat '${TEST_LOG_DIR}/${IT_RUN_ID}.results'
-REMOTE
-
-# ── Assertions ───────────────────────────────────────────────────────────────
-eval "$(it_ssh "cat /tmp/pre.env; cat '${TEST_LOG_DIR}/${IT_RUN_ID}.results'")"
-
-it_assert_no_errors "run: no exceptions / Plan incomplete / crash"
-it_assert_bees_log_grep "run: planner evaluated extents" "scan_next_log_plan start bytenr"
-
-# THE FIX: a 0-whole-block EOF tail is declined even at 50% free-min.
-it_assert "A: 0-block EOF tail NOT rewritten (bytenr stable)" \
-    test "$A_PRE" = "$A_POST"
-
-# Real gains still fire at 50% free-min -- proves bees was active (so A's
-# stability is a genuine decline) and drives the executor's bare-past-EOF
-# tail path (f04f91a14); it_assert_no_errors catches any Plan-incomplete.
-it_assert "B: 1-block gain rewritten" test "$B_PRE" != "$B_POST"
-it_assert "C: 2-block gain rewritten" test "$C_PRE" != "$C_POST"
-
-# Strongest form (optional): re-deploy cf3959bc4^ and assert A IS rewritten
-# there -- proving the fixture bites pre-fix.  A is the only case that flips
-# at 50% free-min (see header), so it is the sole differential signal.
-
-it_summary
diff --git a/test/integration/cases/24-eof-tail-no-churn.sh b/test/integration/cases/24-eof-tail-no-churn.sh
new file mode 100755 (executable)
index 0000000..48dd47b
--- /dev/null
@@ -0,0 +1,171 @@
+#!/bin/bash
+# Test 21: an EOF-tail extent is rewritten only when it frees a WHOLE block
+#
+# Regression guard for the phantom-unreachable-block bug (cf3959bc4) and a
+# runtime exercise of the past-EOF temp elision (f04f91a14).  Runs at the
+# DEFAULT free-min = 50%.
+#
+# MECHANISM
+#   btrfs does not partially free a data extent: truncating a file within an
+#   extent leaves the whole physical extent allocated and only lowers
+#   i_size, so whole blocks past EOF become allocated-but-unreachable.  bees
+#   reclaims them by COPYING the reachable data to a compact extent (a
+#   cleanup plan -- no dedupe, viable with no candidates).  The gain is the
+#   number of COMPLETE unreachable blocks; a sub-block past-EOF tail shares
+#   its block with reachable data and frees nothing.  That count is the
+#   floor() in scan_next_region_block_count.
+#
+#   For an extent of E blocks with reachable data D blocks (= bad, copied)
+#   and U complete unreachable blocks (= good, reclaimed), U = E - D, and
+#   free-min at 50% passes iff good >= bad, i.e. U >= D, i.e. retained data
+#   is at most half the extent.
+#
+# THE BUG: the unreachable region was rounded UP, so a sub-block-only tail
+#   counted as 1 phantom freeable block.  On a SINGLE-block extent that
+#   phantom is decisive: good_pre = 1 = bad clears free-min at exactly 50%
+#   and the negative debt rewrites the extent for zero net gain (pure ref
+#   churn).  Post-fix the tail rounds DOWN to 0 -> free-min rejects and
+#   do-nothing wins.  (Only E=1 flips at 50%; for E>=2 the phantom never
+#   cleared free-min, so the multi-block cases here test that REAL gains
+#   still fire and the executor handles the past-EOF tail, not the flip.)
+#
+# WHY A COPY, NOT A DEDUPE: each file is independent random data, so there
+#   is no duplicate to reference -- the only plan is the cleanup copy.  A
+#   dedupe would correctly have negative debt and is not what regressed.
+#
+# CASES (all at default free-min = 50%):
+#   A  4096 -> 2990   E=1 D=1 U=0   NOT rewritten (post) / rewritten (pre)  <- guard
+#   B  8192 -> 2990   E=2 D=1 U=1   rewritten (1-block gain, floor of mixed block)
+#   C 16384 -> 6000   E=4 D=2 U=2   rewritten (2-block gain, floor of mixed block)
+#
+# Prerequisites: testlib.sh, a configured host.
+
+set -euo pipefail
+
+SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
+source "$SCRIPT_DIR/../lib/testlib.sh"
+
+IT_TEST_NAME="24-eof-tail-no-churn"
+
+it_load_config
+it_setup_trap
+
+BEESHOME="$TEST_MOUNT/.beeshome"
+IT_BEES_LOGFILE="$TEST_LOG_DIR/${IT_RUN_ID}.log"
+
+echo "=== $IT_TEST_NAME ==="
+it_deploy
+
+it_ssh_script <<REMOTE
+set -euo pipefail
+
+disk_bytenr() {
+    btrfs-search-metadata file "\$1" 2>/dev/null | \
+        awk '/disk_bytenr/ {for (i=1;i<=NF;i++) if (\$i=="disk_bytenr") {print \$(i+1); exit}}'
+}
+one_extent() { [ "\$(filefrag "\$1" | grep -oE '[0-9]+ extent' | grep -oE '^[0-9]+')" = 1 ]; }
+# Physical allocation length = the EXTENT_ITEM key's offset field (the
+# extent's item-header offset), read from the extent tree.  Independent of
+# any file's i_size / reference length.  dump-tree prints it as
+#   item N key (<bytenr> EXTENT_ITEM <length>) ...
+extent_len() {  # bytenr
+    btrfs inspect-internal dump-tree -t extent '${TEST_SCRATCH_DEV}' 2>/dev/null | \
+        grep -F "(\$1 EXTENT_ITEM " | head -1 | \
+        sed -E 's/.*EXTENT_ITEM ([0-9]+)\).*/\1/'
+}
+
+# ── Fresh fs ─────────────────────────────────────────────────────────────────
+umount -l '${TEST_MOUNT}' 2>/dev/null || true
+mkfs.btrfs -f '${TEST_SCRATCH_DEV}'
+mkdir -p '${TEST_MOUNT}'
+mount -o subvolid=5 '${TEST_SCRATCH_DEV}' '${TEST_MOUNT}'
+cd '${TEST_MOUNT}'
+
+# One file per case, DISTINCT random content (no cross-file dedupe), each a
+# single extent.  write -> commit -> truncate -> commit.
+make_case() {  # name  write_bytes  truncate_to
+    head -c "\$2" /dev/urandom > "\$1"
+    btrfs filesystem sync '${TEST_MOUNT}'
+    one_extent "\$1" || { echo "[FAIL] \$1 write fragmented; premise broken" >&2; exit 1; }
+    truncate -s "\$3" "\$1"
+    btrfs filesystem sync '${TEST_MOUNT}'
+    # PREMISE: btrfs never partially frees a data extent, so the physical
+    # allocation (EXTENT_ITEM key length) stays at the written size even
+    # though i_size shrank -- that leftover is the unreachable space bees
+    # reclaims.  This is thoroughly baked into current btrfs on-disk design;
+    # only a format change (e.g. extent_tree_v2) could relax it, and that
+    # would require significant bees planner work throughout.  So a failure
+    # HERE is a canary for a changed btrfs, not a flaky test -- assert it at
+    # setup rather than let cases B/C silently stop reclaiming.
+    local bn plen
+    bn="\$(disk_bytenr "\$1")"
+    plen="\$(extent_len "\$bn")"
+    [ "\$plen" = "\$2" ] || { echo "[FAIL] \$1 extent shrank on truncate: EXTENT_ITEM len \$plen != written \$2 -- btrfs partial-free premise broken" >&2; exit 1; }
+    echo "[premise] \$1: i_size \$3, physical extent still \$plen bytes"
+}
+make_case a_regression  4096  2990    # E=1 D=1 U=0 -> 0 complete unreachable blocks
+make_case b_one_block   8192  2990    # E=2 D=1 U=1 -> 1 complete unreachable block
+make_case c_two_block  16384  6000    # E=4 D=2 U=2 -> 2 complete unreachable blocks
+
+echo "A_PRE=\$(disk_bytenr a_regression)" >  /tmp/pre.env
+echo "B_PRE=\$(disk_bytenr b_one_block)"  >> /tmp/pre.env
+echo "C_PRE=\$(disk_bytenr c_two_block)"  >> /tmp/pre.env
+cat /tmp/pre.env
+
+# ── bees config: DEFAULT free-min (50%), one deterministic pass ──────────────
+mkdir -p '${BEESHOME}' '${TEST_LOG_DIR}'
+cat > '${BEESHOME}/bees.conf' <<'CONF'
+[log.plan]
+level = 8
+[loop]
+exit-one-pass = yes
+[thread]
+thread-max = 1
+[scan.extent.*]
+method = 1
+filter = ACCEPT
+[rewrite]
+free-min = 50%
+CONF
+# free-min is pinned to 50% (the schema default and the production regime
+# in which the regression occurred) so the test is hermetic: the host may
+# carry a global /etc/bees rewrite.free-min (e.g. 128K) that bees merges
+# under this per-run config, and at 128K the 'cap threshold at extent size'
+# clause would require freeing 100% of any sub-128K extent -- masking both
+# the bug and the real-gain cases.  At 50%, B/C (free exactly half) pass
+# and A (frees nothing) is declined.
+
+'${REMOTE_BEES_BIN}/bees' --config '${BEESHOME}/bees.conf' '${TEST_MOUNT}' \
+    > '${IT_BEES_LOGFILE}' 2>&1 &
+wait \$! || true
+
+btrfs filesystem sync '${TEST_MOUNT}'
+{
+    echo "A_POST=\$(disk_bytenr a_regression)"
+    echo "B_POST=\$(disk_bytenr b_one_block)"
+    echo "C_POST=\$(disk_bytenr c_two_block)"
+} > '${TEST_LOG_DIR}/${IT_RUN_ID}.results'
+cat '${TEST_LOG_DIR}/${IT_RUN_ID}.results'
+REMOTE
+
+# ── Assertions ───────────────────────────────────────────────────────────────
+eval "$(it_ssh "cat /tmp/pre.env; cat '${TEST_LOG_DIR}/${IT_RUN_ID}.results'")"
+
+it_assert_no_errors "run: no exceptions / Plan incomplete / crash"
+it_assert_bees_log_grep "run: planner evaluated extents" "scan_next_log_plan start bytenr"
+
+# THE FIX: a 0-whole-block EOF tail is declined even at 50% free-min.
+it_assert "A: 0-block EOF tail NOT rewritten (bytenr stable)" \
+    test "$A_PRE" = "$A_POST"
+
+# Real gains still fire at 50% free-min -- proves bees was active (so A's
+# stability is a genuine decline) and drives the executor's bare-past-EOF
+# tail path (f04f91a14); it_assert_no_errors catches any Plan-incomplete.
+it_assert "B: 1-block gain rewritten" test "$B_PRE" != "$B_POST"
+it_assert "C: 2-block gain rewritten" test "$C_PRE" != "$C_POST"
+
+# Strongest form (optional): re-deploy cf3959bc4^ and assert A IS rewritten
+# there -- proving the fixture bites pre-fix.  A is the only case that flips
+# at 50% free-min (see header), so it is the sole differential signal.
+
+it_summary