commit e9bbba2b0ac78b989cca2d0bf6ffd66d12a5221a
parent 6557073b7d3c01a99639b5ec31d102bbf0fa77b6
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 9 May 2026 11:17:08 -0700
test-ar-driver
Diffstat:
9 files changed, 205 insertions(+), 2 deletions(-)
diff --git a/test/ar/cases/01-verbose-create-list-extract.expected b/test/ar/cases/01-verbose-create-list-extract.expected
@@ -0,0 +1,15 @@
+== cv ==
+a - a.o
+a - b.o
+a - c.o
+== tv ==
+4 a.o
+8 b.o
+12 c.o
+== xv ==
+x - a.o
+x - b.o
+x - c.o
+a.o
+b.o
+c.o
diff --git a/test/ar/cases/01-verbose-create-list-extract.sh b/test/ar/cases/01-verbose-create-list-extract.sh
@@ -0,0 +1,15 @@
+# cv → tv → xv: covers verbose write, verbose list, verbose extract.
+printf 'aaaa' > a.o
+printf 'bbbbbbbb' > b.o
+printf 'cccccccccccc' > c.o
+
+echo "== cv =="
+"$CFREE" ar cv lib.a a.o b.o c.o
+
+echo "== tv =="
+"$CFREE" ar tv lib.a
+
+echo "== xv =="
+mkdir extracted
+( cd extracted && "$CFREE" ar xv ../lib.a )
+ls extracted | sort
diff --git a/test/ar/cases/02-replace-preserves-others.actual b/test/ar/cases/02-replace-preserves-others.actual
@@ -0,0 +1,13 @@
+== rv (replace b, add c) ==
+r - b.o
+a - c.o
+== tv after rv ==
+6 b.o
+8 b.o
+12 c.o
+== tv after second r ==
+6 c.o
+8 c.o
+12 c.o
+2 a.o
+2 d.o
diff --git a/test/ar/cases/02-replace-preserves-others.expected b/test/ar/cases/02-replace-preserves-others.expected
@@ -0,0 +1,12 @@
+== rv (replace b, add c) ==
+r - b.o
+a - c.o
+== tv after rv ==
+4 a.o
+6 b.o
+12 c.o
+== tv after second r ==
+2 a.o
+6 b.o
+12 c.o
+2 d.o
diff --git a/test/ar/cases/02-replace-preserves-others.sh b/test/ar/cases/02-replace-preserves-others.sh
@@ -0,0 +1,26 @@
+# Regression: `r` must preserve unlisted members in order, replace
+# matching basenames in place, and append truly-new members. Each
+# old member's name comes from the iterator's single shared name
+# buffer, so the driver must copy it into stable storage before the
+# next iter step (otherwise old members collapse to one name).
+
+printf 'aaaa' > a.o
+printf 'bbbbbbbb' > b.o
+"$CFREE" ar c lib.a a.o b.o
+
+# Replace b.o (shorter content) and add c.o.
+printf 'BBBBBB' > b.o
+printf 'cccccccccccc' > c.o
+echo "== rv (replace b, add c) =="
+"$CFREE" ar rv lib.a b.o c.o
+
+echo "== tv after rv =="
+"$CFREE" ar tv lib.a
+
+# Add a member that already exists alongside one that doesn't, in
+# r mode (no v) — exercises the same code path silently.
+printf 'AA' > a.o
+printf 'dd' > d.o
+"$CFREE" ar r lib.a a.o d.o
+echo "== tv after second r =="
+"$CFREE" ar tv lib.a
diff --git a/test/ar/cases/03-print-header-order.expected b/test/ar/cases/03-print-header-order.expected
@@ -0,0 +1,13 @@
+== p single (no header) ==
+aaaa
+== pv single (header) ==
+lib.a(a.o):
+aaaa
+== p multi (header) ==
+lib.a(a.o):
+aaaalib.a(c.o):
+cccccccccccc
+== pv multi (header) ==
+lib.a(a.o):
+aaaalib.a(c.o):
+cccccccccccc
diff --git a/test/ar/cases/03-print-header-order.sh b/test/ar/cases/03-print-header-order.sh
@@ -0,0 +1,24 @@
+# Regression: `pv` (and `p` with multiple members) prints a per-member
+# header via driver_printf and the payload via the stdout writer. Both
+# must land in the right order — driver_stdout_writer routes through
+# stdio so it shares libc's buffer with printf.
+
+printf 'aaaa' > a.o
+printf 'cccccccccccc' > c.o
+"$CFREE" ar c lib.a a.o c.o
+
+echo "== p single (no header) =="
+"$CFREE" ar p lib.a a.o
+echo
+
+echo "== pv single (header) =="
+"$CFREE" ar pv lib.a a.o
+echo
+
+echo "== p multi (header) =="
+"$CFREE" ar p lib.a a.o c.o
+echo
+
+echo "== pv multi (header) =="
+"$CFREE" ar pv lib.a a.o c.o
+echo
diff --git a/test/ar/run.sh b/test/ar/run.sh
@@ -0,0 +1,79 @@
+#!/bin/sh
+# Driver-level `cfree ar` test harness.
+#
+# Each test/ar/cases/*.sh is a scenario script. The harness allocates a
+# fresh sandbox dir per case, cd's in, sources the script with $CFREE
+# exported, captures stdout, and diffs against the matching .expected.
+# Stderr is forwarded straight through so write/read failures still
+# surface.
+#
+# Honors $CFREE for the binary path; defaults to build/cfree relative
+# to the repo root.
+
+set -u
+
+script_dir=$(cd "$(dirname "$0")" && pwd)
+repo_root=$(cd "$script_dir/../.." && pwd)
+cases_dir="$script_dir/cases"
+
+CFREE="${CFREE:-$repo_root/build/cfree}"
+export CFREE
+
+if [ ! -x "$CFREE" ]; then
+ echo "ar-driver: cfree binary not found at $CFREE" >&2
+ exit 2
+fi
+
+work_root=$(mktemp -d "${TMPDIR:-/tmp}/cfree-ar-test.XXXXXX")
+trap 'rm -rf "$work_root"' EXIT
+
+pass=0
+fail=0
+failures=
+
+for sh in "$cases_dir"/*.sh; do
+ [ -e "$sh" ] || continue
+ name=$(basename "${sh%.sh}")
+ expected="${sh%.sh}.expected"
+ actual="$work_root/$name.actual"
+
+ if [ ! -e "$expected" ]; then
+ printf 'FAIL %s (missing %s)\n' "$name" "$(basename "$expected")"
+ fail=$((fail + 1))
+ failures="$failures $name"
+ continue
+ fi
+
+ sandbox="$work_root/$name"
+ mkdir -p "$sandbox"
+ ( cd "$sandbox" && sh "$sh" ) > "$actual" 2>&1
+ case_rc=$?
+
+ if [ "$case_rc" -ne 0 ]; then
+ printf 'FAIL %s (script exit=%d)\n' "$name" "$case_rc"
+ diff -u "$expected" "$actual" || true
+ fail=$((fail + 1))
+ failures="$failures $name"
+ continue
+ fi
+
+ if diff -u "$expected" "$actual" >/dev/null 2>&1; then
+ printf 'PASS %s\n' "$name"
+ pass=$((pass + 1))
+ else
+ printf 'FAIL %s\n' "$name"
+ diff -u "$expected" "$actual" || true
+ # Keep .actual on failure for debugging by copying out of the
+ # sandbox before it's cleaned up.
+ cp "$actual" "$cases_dir/$name.actual" 2>/dev/null || true
+ fail=$((fail + 1))
+ failures="$failures $name"
+ fi
+done
+
+total=$((pass + fail))
+printf '\nar-driver: %d/%d passed\n' "$pass" "$total"
+if [ "$fail" -gt 0 ]; then
+ printf 'ar-driver: failures:%s\n' "$failures"
+ exit 1
+fi
diff --git a/test/test.mk b/test/test.mk
@@ -9,6 +9,9 @@
# libcfree.a. Set CFREE_AR_TEST_HOST=1 to also dump produced bytes
# to /tmp and run the host's `ar t` / `nm --print-armap` as a
# cross-check.
+# - test-ar-driver: scenario-driven CLI harness for `cfree ar`. Each
+# case under test/ar/cases/ runs a small script and diffs stdout.
+# Depends on the cfree driver binary.
# - test-link: linker + JIT behavioral harness in test/link/; three paths
# per case (roundtrip R, ELF exec E, JIT J). Depends only on libcfree.a.
# Set CFREE_TEST_ALLOW_SKIP=1 to allow skipped layers.
@@ -16,9 +19,9 @@
# four paths per case (D direct-JIT, R roundtrip, E exec, J jit-via-file).
# Depends only on libcfree.a; reuses test/link harness binaries.
-.PHONY: test test-lex test-pp test-pp-err test-elf test-ar test-link test-cg test-lib-deps
+.PHONY: test test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg test-lib-deps
-test: test-lex test-pp test-pp-err test-elf test-ar test-link test-cg test-lib-deps
+test: test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg test-lib-deps
test-lex: bin
@CFREE=$(abspath $(BIN)) test/lex/run.sh
@@ -46,6 +49,9 @@ $(AR_TEST_BIN): test/ar_test.c $(LIB_AR)
@mkdir -p $(dir $@)
$(CC) $(DRIVER_CFLAGS) test/ar_test.c $(LIB_AR) -o $@
+test-ar-driver: bin
+ @CFREE=$(abspath $(BIN)) test/ar/run.sh
+
# Test harness binaries shared by test-elf, test-link, and test-cg.
# Declared as Make targets (not built by the run.sh scripts) so they pick
# up libcfree.a changes deterministically.