#!/usr/bin/env bash # Local, cheap checks of the verdict-bearing text parsers: tools/testparse.sh and # the summary and wait modes of tools/stopgap/ygg-verify.sh, over # tools/fixtures/logs. cargo-*.log (the tiny-crate fixture) and mutation-table-nonce.log (the soul2 # reproducer) are real Yggdrasil runs, 2026-10-01; soul-s5/*.log are real # dotnet, pytest, vitest and cargo runs of Soul's probe repo (several runs per # log, split by "=== MARK " lines); the rest are hand-written. # The mutation runners tools/mutation-table.sh and tools/cargo-mutants.sh (launcher, host driver # and container halves) run under a mock docker and, for cargo-mutants, a fake cargo and flock # (fixtures/mutation-mock/fakebin), with real slot locks. # Run: bash tools/test-tools.sh (no network, no builds) set -uo pipefail here=$(cd "$(dirname "$0")" && pwd) L=$here/fixtures/logs; V=$here/stopgap/ygg-verify.sh T=$(mktemp -d); trap 'rm -rf "$T"' EXIT fail=0 NONCE='' # seg -> a file holding just that run of a multi-run log seg() { local o="$T/$(basename "$1").$2"; awk -v m="=== MARK $2" '/^=== MARK /{on=($0==m);next} on' "$L/$1" >"$o"; echo "$o"; } # check [] check() { local p=$1; [ -f "$p" ] || p=$L/$1 out=$(bash "$V" summarize "$p" "$2" ${NONCE:+"$NONCE"}) printf '%s\n' "$out" | grep -qF -- "$3" || { echo "FAIL $1: missing: $3"; fail=1; return; } if [ -n "${4:-}" ] && printf '%s\n' "$out" | grep -qF -- "$4"; then echo "FAIL $1: must not contain: $4"; fail=1; return; fi [ "$(printf '%s\n' "$out" | wc -l)" -le 81 ] || { echo "FAIL $1: summary over 81 lines"; fail=1; return; } echo "ok $(basename "$1"): $3" } check cargo-green.log 0 "exit 0 GREEN" "RED" check cargo-green.log 0 "FRAMEWORK cargo passed=3 failed=0" check cargo-red.log 101 "exit 101 RED" check cargo-red.log 101 "FAILED tests::add_adds" check cargo-red.log 101 "left: 5" check cargo-zero-tests.log 0 "ZERO TESTS RAN" NONCE=474d380d662e71a5 # a real run: the soul2 reproducer through the snapshot runner, 2026-10-01 check mutation-table-nonce.log 1 "untested-equiv | SURVIVED" check mutation-table-nonce.log 1 "stamp2-bad | CAUGHT | tests::d_cache" check mutation-table-nonce.log 1 "add-minus | CAUGHT | tests::c_add" check mutation-table-nonce.log 1 "caught=2 survived=1 unviable=0 timeout=0 noapply=0 error=0" "no test counts recognised" NONCE= check dotnet-red.log 1 "FRAMEWORK dotnet passed=11 failed=1 skipped=2" check dotnet-red.log 1 "FAILED MyNs.AddTests.Subtracts" check vitest-red.log 1 "FRAMEWORK vitest passed=10 failed=1 skipped=1" check vitest-red.log 1 "FAILED src/add.test.ts > add > carries" check pytest-red.log 1 "FRAMEWORK pytest passed=5 failed=1 skipped=1" check pytest-red.log 1 "FAILED tests/test_add.py::test_carries" check exit0-but-failed.log 0 "MISMATCH: exit 0 but the log shows" check no-verdict.log 97 "NO VERDICT" check no-verdict.log 97 "RED" "GREEN" # ---- tools/mutation-table.sh, host driver plus container half, against the mock in # fixtures/mutation-mock: shell scripts that print cargo/vitest/jest-shaped output, run # from the subdirectory pkg/ (a workdir below the repo root), under a mock docker # (fixtures/mutation-mock/fakebin) where a container is a directory and `commit` copies # it, so every mutant really runs on a copy of the baselined snapshot. # One case per finding of the 2026-10-01 Soul passes; each fails if it regresses. mt() { # mt [BUILD]: sets mt_out, mt_rc, mt_repo, mt_fd local root; root=$(mktemp -d); mt_repo=$root/repo; mt_fd=$root/fd; mkdir -p "$mt_repo/pkg" "$root/mt" "$mt_fd" cp -r "$here/fixtures/mutation-mock/." "$mt_repo/pkg/"; rm -rf "$mt_repo/pkg/fakebin"; cp "$L/jest-real.log" "$mt_repo/pkg/" git -C "$mt_repo" init -q && git -C "$mt_repo" add -A && git -C "$mt_repo" -c user.name=t -c user.email=t@t commit -qm mock cp "$here/testparse.sh" "$here/mutation-table.sh" "$root/mt/"; cp "$here/fixtures/mutation-mock/$1" "$root/mt/mutants.txt" mt_out=$(PATH="$here/fixtures/mutation-mock/fakebin:$PATH" FAKE_DOCKER=$mt_fd YGG_WORK=$mt_repo YGG_IMAGE=base YGG_CNAME=job1 YGG_LABEL=eureka-verify-job=job1 \ YGG_CPUS=1 YGG_MEM=1g YGG_KILLED=${MT_KILLED:-} MT_BUNDLE=$root/mt WORKDIR=pkg TEST=$2 BUILD=${3:-} MUT_TIMEOUT=${MT_TO:-15} \ timeout "${MT_WALL:-120}" bash "$here/mutation-table.sh" --host 2>&1) mt_rc=$? } mtcheck() { # mtcheck ... local name=$1 s; shift for s in "$@"; do case $s in '!'*) if printf '%s\n' "$mt_out" | grep -qF -- "${s#!}"; then echo "FAIL mutation $name: must not contain: ${s#!}"; fail=1; return; fi ;; *) printf '%s\n' "$mt_out" | grep -qF -- "$s" || { echo "FAIL mutation $name: missing: $s"; printf '%s\n' "$mt_out" | tail -n 12; fail=1; return; } ;; esac done echo "ok mutation $name" } # nothing the job made may outlive it: no container directory, no snapshot image, no bundle mtclean() { # mtclean [images]: a killed mock container is a still-running process, so E checks only the image and bundle local left dirs=("$mt_fd/c" "$mt_fd/i"); [ -z "${2:-}" ] || dirs=("$mt_fd/i") left=$(ls "${dirs[@]}" 2>/dev/null | grep -v '^$' | grep -v ':$' | paste -sd, -) if [ -z "$left" ] && [ ! -e "$(dirname "$mt_repo")/mt" ]; then echo "ok mutation $1: containers, snapshot image and bundle removed" else echo "FAIL mutation $1: left behind: ${left:-the bundle dir}"; fail=1; fi } # A: a mistyped BUILD must abort, not read as a fully killed suite mt mutants-a.txt 'bash test.sh' 'bash bulid.sh' [ "$mt_rc" = 2 ] || { echo "FAIL mutation A: exit $mt_rc, want 2"; fail=1; } mtcheck A-build-baseline "BASELINE RED: BUILD failed" "!UNVIABLE" "!SNAPSHOT" mtclean A # B: a label that spells a kill must not forge one mt mutants-b.txt 'bash test.sh' [ "$mt_rc" = 1 ] || { echo "FAIL mutation B: exit $mt_rc, want 1"; fail=1; } mtcheck B-label-forge "caught=0 survived=1" 'forge \| CAUGHT \| label | SURVIVED |' mtclean B # the nonce form ygg-verify.sh carries, and the plain form when no nonce is set YGG_NONCE=n0nce1 mt mutants-b.txt 'bash test.sh' mtcheck B-nonce "YGG-SUMMARY/n0nce1: [1/1] " "!YGG-SUMMARY: " # C: a test that rewrites a tracked file, leaks an untracked one or keeps state in an # IGNORED file (cache.txt: what `git status` cannot see) must not decide the next mutant mt mutants-c.txt 'bash test.sh' mtcheck C-pollution "stamp-bad | CAUGHT | tests::b_write" "untested-equiv | SURVIVED" "!untested-equiv | CAUGHT" "caught=1 survived=1" [ -z "$(git -C "$mt_repo" status --porcelain --ignored)" ] || { echo "FAIL mutation C: the seed repository changed"; fail=1; } mtclean C # D: a test file that fails to load is not a kill; a named failing test is mt mutants-d.txt 'bash vtest.sh' mtcheck D-import-failure "syntax-break | UNVIABLE" "wrong-result | CAUGHT | add.js > adds" "caught=1 survived=0 unviable=1" # D2 (jest, real output): "FAIL " with "Test suite failed to run" is a load failure; the "●" block names the # test; a test run that calls process.exit prints a "●" line under no FAIL header, which names nothing (ERROR, not CAUGHT) mt mutants-h.txt 'bash jtest.sh' mtcheck D2-jest "jest-load | UNVIABLE" "jest-wrong | CAUGHT | add › adds" "jest-exit | ERROR | exit 1, no failing test parsed" "!process.exit called | CAUGHT" "caught=1 survived=0 unviable=1 timeout=0 noapply=0 error=1" # E: rows print as mutants finish, so a job timeout keeps them; the TERM the timeout sends still cleans up MT_WALL=8 mt mutants-e.txt 'bash test.sh' mtcheck E-live-rows "[1/2] quick | CAUGHT" "!MUTATION" sleep 1; mtclean E images # E2: a watchdog kill (the flag the stopgap sets) stops the fan-out after the baseline and cleans up mt_k=$(mktemp); MT_KILLED=$mt_k mt mutants-c.txt 'bash test.sh'; rm -f "$mt_k" [ "$mt_rc" = 124 ] || { echo "FAIL mutation E2: exit $mt_rc, want 124"; fail=1; } mtcheck E2-killed "JOB KILLED by the watchdog before mutant 1 of 2" "!CAUGHT" mtclean E2 # E3 (Soul, F1): a test that cannot run twice is green in the baseline and red in every mutant's # container; the control run of the unmutated snapshot must abort the job, not report a killed suite mt mutants-i.txt 'bash oncetest.sh' [ "$mt_rc" = 2 ] || { echo "FAIL mutation E3: exit $mt_rc, want 2"; fail=1; } mtcheck E3-snapshot-red "SNAPSHOT RED (exit 101) for: bash oncetest.sh" "no rows counted" "!CAUGHT" "!MUTATION" mtclean E3 # F: LF before-text matches a CRLF file mt mutants-f.txt 'bash test.sh' mtcheck F-crlf "crlf-multiline | CAUGHT | tests::d_win" # a real BUILD failure on a mutant is still UNVIABLE mt mutants-g.txt 'bash test.sh' 'bash build.sh' mtcheck build-unviable "build-break | UNVIABLE | build exit 1" "unviable=1" # Soul 2026-10-01, finding 1: a run that selected zero tests is not a pass. check "$(seg soul-s5/pdotnet.log zero)" 0 "ZERO TESTS RAN" "no test counts recognised" check "$(seg soul-s5/ppy.log zero)" 0 "ZERO TESTS RAN" "no test counts recognised" check "$(seg soul-s5/ppy.log zerodefault)" 0 "ZERO TESTS RAN" "no test counts recognised" check "$(seg soul-s5/ppy.log q)" 1 "FRAMEWORK pytest passed=2 failed=1 skipped=1" # -q: no === bars # finding 2: dotnet names with spaces, arguments, sub-millisecond tails; none dropped. d=$(seg soul-s5/pdotnet.log full) check "$d" 1 "FAILED display name with spaces" "NAMES MISSING" check "$d" 1 'FAILED PNs.T.TheoryFails(x: 1, s: "a b")' check "$d" 1 "FAILED PNs.T.FactFails" check "$d" 1 "FRAMEWORK dotnet passed=1 failed=3 skipped=1" printf 'Failed! - Failed: 2, Passed: 0, Skipped: 0, Total: 2, Duration: 1 ms - X.dll (net10.0)\n Failed A.One [1 ms]\n' >"$T/missing.log" check "$T/missing.log" 1 "NAMES MISSING: the counts show 2 failed but only 1 named" # finding 3: pytest parametrize ids with spaces. out=$(bash "$V" summarize "$(seg soul-s5/ppy.log default)" 1) if printf "%s " "$out" | grep -qxF "FAILED test_p.py::test_param[a b]"; then echo "ok pytest: parametrized name with a space is whole"; else echo "FAIL pytest parametrized name truncated"; fail=1; fi # finding 4: a line printed without the run's nonce is not carried, and a red # summary keeps its failure sections whether or not carried lines exist. check soul-s5/prust.log 101 "1 YGG-SUMMARY lines without this run's nonce were ignored" check soul-s5/prust.log 101 "FAILED noisy_fail" "forged | CAUGHT" NONCE=feedbeef { cat "$L/cargo-red.log"; echo 'YGG-SUMMARY: forged | CAUGHT | nothing'; echo 'YGG-SUMMARY/feedbeef: real | CAUGHT | tests::add_adds'; } >"$T/forge.log" check "$T/forge.log" 101 "real | CAUGHT | tests::add_adds" "forged | CAUGHT" check "$T/forge.log" 101 "left: 5" check "$T/forge.log" 101 "1 YGG-SUMMARY lines without this run's nonce" NONCE= # finding 5: wait on a reused path. Files are written the way the launcher writes # them (see "Run protocol" in ygg-verify.sh): .run is the commit point, .done is # " ". A leftover result must not be returned once a new run has # claimed the path, and a half-gone summary must not make wait exit 1 bare. W=$T/wait.log echo old >"$W.run"; echo "old 0" >"$W.done"; echo "OLD RESULT" >"$W.summary" ( sleep 1; echo $$ >"$W.pid"; echo new >"$W.run"; rm -f "$W.done" "$W.summary" sleep 3; echo "NEW RESULT" >"$W.summary"; echo "new 7" >"$W.done" ) & bg=$! out=$(SETTLE=2 WAIT_MAX=30 bash "$V" wait "$W"); st=$? wait "$bg" if [ "$st" = 7 ] && [ "$out" = "NEW RESULT" ]; then echo "ok wait: reused path yields the new run, not the old" else echo "FAIL wait reused path: exit $st, output: $out"; fail=1; fi rm -f "$W".* echo r1 >"$W.run"; echo "r1 3" >"$W.done" # done, summary file gone out=$(WAIT_MAX=10 SETTLE=0 bash "$V" wait "$W"); st=$? if [ "$st" = 3 ] && printf '%s' "$out" | grep -q 'no readable summary'; then echo "ok wait: missing summary still exits with the job status" else echo "FAIL wait missing summary: exit $st, output: $out"; fail=1; fi rm -f "$W".* echo r1 >"$W.run"; echo "r0 3" >"$W.done"; echo $$ >"$W.pid" # done from another run, launcher alive out=$(WAIT_MAX=3 SETTLE=0 bash "$V" wait "$W"); st=$? if [ "$st" = 99 ]; then echo "ok wait: a done from another run is not a result" else echo "FAIL wait foreign done: exit $st, output: $out"; fail=1; fi # N2 (Soul, 2026-10-01): file-level failures are not named test failures, whichever runner # prints them. Vitest with a project prefix and with spaces in the path; jest's file header. check vitest-project-load-failure.log 1 "FAILED TO LOAD |unit| src/add.test.ts" "FAILED |unit|" check vitest-spaces-load-failure.log 1 "FAILED TO LOAD src/my tests/add.test.ts" "FAILED src/my" check vitest-project-red.log 1 "FAILED |unit| src/add.test.ts > add > carries" "FAILED TO LOAD" # jest, real output (jest-real.log): one space after FAIL, no TTY. Names come from the "●" lines under a FAIL header only. check "$(seg jest-real.log green)" 0 "FRAMEWORK jest passed=2 failed=0 skipped=0" "no test counts recognised" check "$(seg jest-real.log wrong)" 1 "FRAMEWORK jest passed=1 failed=1 skipped=0" check "$(seg jest-real.log wrong)" 1 "FAILED add › adds" "FAILED TO LOAD" check "$(seg jest-real.log load)" 1 "FAILED TO LOAD jt/add.test.js" "FAILED jt/add.test.js" check "$(seg jest-real.log load)" 1 "FRAMEWORK jest passed=0 failed=0 skipped=0" "FAILED Test suite" # F3: process.exit(1) in a test run prints a "●" line under no FAIL header; it is a crash, not a named kill check "$(seg jest-real.log exit)" 1 "exit non-zero with no failing test parsed" "FAILED process.exit" # F4: the "nothing recognised" line names every framework the parser reads check "$(seg jest-real.log exit)" 1 "no test counts recognised in the log (cargo, dotnet, vitest, jest, pytest)" # ---- tools/cargo-mutants.sh: the launcher, host driver and container halves, through the mock docker # and fixtures/mutation-mock/fakebin/cargo (a fake `cargo mutants` driven by a table of mutant -> # outcome under a filtered run and under the full suite). The launcher runs for real: its bundle, # quoting and the environment it hands the driver are the production path; only ygg-verify.sh is # stubbed, by a script that does what RUN_ON_HOST=1 does (run the command inside the checked-out # revision). Slot locks are real flock(2) locks on files, taken by the driver and by the test. CMB=$here/fixtures/mutation-mock/fakebin cmrepo() { # cmrepo A|B|C: sets cm_repo and cm_wd. A: a workspace at the repo root, workdir crates/x. B: a standalone package pkg/ under the repo root. C: B touching only the two files that have tests. local r f c; r=$(mktemp -d); cm_repo=$r/repo; mkdir -p "$cm_repo"; git -C "$cm_repo" init -q c="git -C $cm_repo -c user.name=t -c user.email=t@t" if [ "$1" = A ]; then cm_wd=crates/x; mkdir -p "$cm_repo/crates/x/src/alpha" "$cm_repo/crates/x/src/bin" printf '[workspace]\nmembers = ["crates/x"]\n' >"$cm_repo/Cargo.toml"; printf '[package]\nname = "x"\n' >"$cm_repo/crates/x/Cargo.toml" : >"$cm_repo/crates/x/src/lib.rs"; $c add -A 2>/dev/null; $c commit -qm c1 for f in src/lib.rs src/main.rs src/gamma.rs src/beta.rs src/alpha/mod.rs src/alpha/inner.rs src/bin/tool.rs build.rs; do echo "// $f" >"$cm_repo/crates/x/$f"; done else cm_wd=pkg; mkdir -p "$cm_repo/pkg/src/a" "$cm_repo/pkg/src/bin" printf '[package]\nname = "p"\n' >"$cm_repo/pkg/Cargo.toml"; : >"$cm_repo/pkg/src/lib.rs"; $c add -A 2>/dev/null; $c commit -qm c1 for f in src/a/mod.rs src/a/b.rs; do echo "// $f" >"$cm_repo/pkg/$f"; done [ "$1" = C ] || for f in src/lib.rs src/main.rs src/bin/x.rs build.rs; do echo "// $f" >"$cm_repo/pkg/$f"; done fi $c add -A 2>/dev/null; $c commit -qm c2 } cmtable() { # cmtable A|B|C: sets cm_table (name outcome under a filtered run outcome under the full suite) cm_table=$(mktemp) if [ "$1" = A ]; then local p=crates/x/src printf '%s\n' \ "$p/alpha/mod.rs:2:5: replace add -> i32 with 0 caught caught" \ "$p/alpha/mod.rs:7:7: replace * with + in double missed caught" \ "$p/alpha/mod.rs:9:9: replace && with || in check missed missed" \ "$p/alpha/mod.rs:12:5: replace f(x) -> Vec with vec![1] caught caught" \ "$p/alpha/mod.rs:15:5: replace id -> Option with None unviable unviable" \ "$p/alpha/inner.rs:3:5: replace slow -> u32 with 0 timeout caught" \ "$p/alpha/inner.rs:6:5: replace spin -> u32 with 1 missed timeout" \ "$p/beta.rs:2:5: replace b1 -> i32 with 0 missed caught" \ "$p/beta.rs:3:5: replace b2 -> i32 with 0 missed missed" \ "$p/beta.rs:4:5: replace b3 -> i32 with 0 missed unviable" \ "$p/gamma.rs:2:5: replace g -> i32 with 0 caught caught" \ "$p/lib.rs:2:5: replace l1 -> i32 with 0 missed caught" \ "$p/lib.rs:3:5: replace f(x) -> Vec with vec![1] missed missed" \ "$p/main.rs:2:5: replace m -> i32 with 0 missed caught" \ "crates/x/build.rs:2:5: replace bu -> i32 with 0 missed caught" \ "$p/bin/tool.rs:2:5: replace t -> i32 with 0 missed caught" >"$cm_table" elif [ "$1" = C ]; then printf '%s\n' "src/a/mod.rs:2:5: replace a -> i32 with 0 caught caught" "src/a/b.rs:2:5: replace a -> i32 with 0 caught caught" >"$cm_table" else printf '%s\n' "src/lib.rs:2:5: replace a -> i32 with 0 caught caught" "src/main.rs:2:5: replace a -> i32 with 0 caught caught" \ "src/bin/x.rs:2:5: replace a -> i32 with 0 caught caught" "build.rs:2:5: replace a -> i32 with 0 caught caught" \ "src/a/mod.rs:2:5: replace a -> i32 with 0 caught caught" "src/a/b.rs:2:5: replace a -> i32 with 0 caught caught" >"$cm_table" fi } # cm [VAR=value]...: runs the launcher; sets cm_out, cm_rc, cm_root, cm_fd, cm_log. HOLD_OWN=1 holds the job's own slot as # ygg-verify does (a descriptor the driver inherits); HOLD_OTHERS= holds n further slots as other jobs would (separate processes); PSI= is the io pressure file's full avg60. cm() { local lay=$1; shift cmrepo "$lay"; cmtable "$lay" cm_root=$(dirname "$cm_repo"); cm_fd=$cm_root/fd; cm_log=$cm_root/cargo.log mkdir -p "$cm_fd" "$cm_root/tools/stopgap" "$cm_root/ygg" "$cm_root/tmp" cp "$here/cargo-mutants.sh" "$cm_root/tools/" printf '#!/usr/bin/env bash\n# test stub of ygg-verify.sh RUN_ON_HOST=1: ; runs cmd as the host half inside the revision\ncd "$YGG_WORK" && exec bash -o pipefail -c "$4"\n' >"$cm_root/tools/stopgap/ygg-verify.sh" local holders=() i n own=0 others=0 psi=0.00 kv env=() for kv in "$@"; do case $kv in HOLD_OWN=*) own=${kv#*=} ;; HOLD_OTHERS=*) others=${kv#*=} ;; PSI=*) psi=${kv#*=} ;; *) env+=("$kv") ;; esac; done for i in $(seq 1 "$others"); do perl -e 'use Fcntl ":flock"; open(F, ">>", $ARGV[0]) or die; flock(F, LOCK_EX) or die; open(R, ">", "$ARGV[0].ready"); close R; sleep 120' "$cm_root/ygg/slot-$((i + 1)).lock" & holders+=($!) for n in $(seq 1 50); do [ -e "$cm_root/ygg/slot-$((i + 1)).lock.ready" ] && break; sleep 0.1; done done cm_out=$( ( if [ "$own" = 1 ]; then exec {ownfd}>"$cm_root/ygg/slot-1.lock"; PATH=$CMB:$PATH flock -n "$ownfd"; fi export PATH=$CMB:$PATH FAKE_DOCKER=$cm_fd FAKE_CM_TABLE=$cm_table FAKE_CM_LOG=$cm_log FAKE_CM_TESTS=$cm_root/tests.tsv \ FAKE_WSROOT=$([ "$lay" = A ] || echo pkg/) YGG_WORK=$cm_repo YGG_IMAGE=rust YGG_CNAME=job1 YGG_LABEL=eureka-verify-job=job1 YGG_CPUS=1 YGG_MEM=1g \ YGG_SLOTS=5 YGG_ROOT=$cm_root/ygg YGG_NONCE=n0nce1 CM_PSI=$cm_root/psi TMPDIR=$cm_root/tmp printf 'beta::\t0\n' >"$cm_root/tests.tsv" printf 'some avg10=0.00 avg60=0.00 avg300=0.00 total=0\nfull avg10=0.00 avg60=%s avg300=0.00 total=0\n' "$psi" >"$cm_root/psi" for kv in "${env[@]}"; do export "${kv?}"; done timeout 240 bash "$cm_root/tools/cargo-mutants.sh" "$cm_repo" HEAD HEAD~1 "$cm_wd" 2>&1 ) ) cm_rc=$? for i in "${holders[@]}"; do kill "$i" 2>/dev/null; done; wait 2>/dev/null } cmcheck() { # cmcheck ... local name=$1 s; shift for s in "$@"; do case $s in '!'*) if printf '%s\n' "$cm_out" | grep -qF -- "${s#!}"; then echo "FAIL cargo-mutants $name: must not contain: ${s#!}"; printf '%s\n' "$cm_out" | tail -n 12; fail=1; return; fi ;; *) printf '%s\n' "$cm_out" | grep -qF -- "$s" || { echo "FAIL cargo-mutants $name: missing: $s"; printf '%s\n' "$cm_out" | tail -n 14; fail=1; return; } ;; esac done echo "ok cargo-mutants $name" } cmrc() { [ "$cm_rc" = "$2" ] || { echo "FAIL cargo-mutants $1: exit $cm_rc, want $2"; printf '%s\n' "$cm_out" | tail -n 8; fail=1; }; } cmclean() { # nothing the job made may outlive it: no container directory, no bundle, no staging directory local left; left=$(ls "$cm_fd/c" "$cm_root/tmp" 2>/dev/null | grep -v '^$' | grep -v ':$' | paste -sd, -) if [ -z "$left" ]; then echo "ok cargo-mutants $1: containers, bundle and staging removed"; else echo "FAIL cargo-mutants $1: left behind: $left"; fail=1; fi } cmlocks() { # cmlocks : after the driver exits, every slot lock can be taken again by a fresh process local i bad='' for i in 1 2 3 4 5; do bash -c "exec {fd}>'$cm_root/ygg/slot-$i.lock'; PATH=$CMB:\$PATH flock -n \$fd" || bad="$bad $i"; done if [ -z "$bad" ]; then echo "ok cargo-mutants $1: every slot lock is free again"; else echo "FAIL cargo-mutants $1: slots still locked:$bad"; fail=1; fi } # filter derivation, stage-1 survivors and timeouts confirmed, zero-test filter, no raw stage-1 lines, exit 2 and 3 normal cm A TEST_ARGS='--skip flaky' cmrc A 0 cmcheck A-filters \ 'FILTER crates/x/src/alpha/mod.rs alpha:: tests=1 mutants=5' 'FILTER crates/x/src/alpha/inner.rs alpha::inner:: tests=1 mutants=2' \ 'FILTER crates/x/src/gamma.rs gamma:: tests=1 mutants=1' \ '!FILTER crates/x/src/lib.rs' '!FILTER crates/x/src/main.rs' '!FILTER crates/x/src/bin/tool.rs' '!FILTER crates/x/build.rs' cmcheck A-zero-test-filter 'FILTER crates/x/src/beta.rs beta:: tests=0 mutants=3' 'caught=10 missed=3 timeout=1 unviable=2' cmcheck A-confirmed-and-counted 'MUTANTS tested=16 caught=10 missed=3 timeout=1 unviable=2 | stage1 filtered=8 survivors=4 confirmed_caught=2 | containers=3 |' \ '!replace * with + in double' '!replace slow -> u32 with 0' '!replace add -> i32' \ 'MISSED crates/x/src/alpha/mod.rs:9:9: replace && with || in check' 'MISSED crates/x/src/beta.rs:3:5: replace b2' 'MISSED crates/x/src/lib.rs:3:5: replace f(x) -> Vec with vec![1]' \ 'TIMEOUT crates/x/src/alpha/inner.rs:6:5: replace spin' if [ "$(printf '%s\n' "$cm_out" | grep -c "^YGG-SUMMARY/n0nce1: MISSED ")" = 3 ]; then echo "ok cargo-mutants A-missed-once: 3 final MISSED lines"; else echo "FAIL cargo-mutants A-missed-once"; fail=1; fi if printf '%s\n' "$cm_out" | grep -qE '^(MISSED|TIMEOUT) '; then echo "FAIL cargo-mutants A-no-raw-lines: a MISSED or TIMEOUT line outside the nonce-tagged lines"; fail=1; else echo "ok cargo-mutants A-no-raw-lines"; fi if grep -qF 'stage=1 filter=alpha:: harness=--skip flaky' "$cm_log" && grep -qF 'stage=1 filter=alpha::inner:: harness=--skip flaky' "$cm_log" && grep -qF 'stage=2 filter= harness=--skip flaky' "$cm_log" then echo "ok cargo-mutants A-harness-args: TEST_ARGS follow the filter in stage 1 and stand alone in stage 2"; else echo "FAIL cargo-mutants A-harness-args"; sort -u "$cm_log"; fail=1; fi if [ "$(grep -c '^stage=2' "$cm_log")" = 3 ]; then echo "ok cargo-mutants A-stage2-sharded: 3 full-suite shards"; else echo "FAIL cargo-mutants A-stage2-sharded"; fail=1; fi # a file whose filter selects 0 tests is found at plan time and never pays a stage-1 pass; its names run in stage 2 if grep -qF 'count filter=beta:: harness=--skip flaky' "$cm_log" && ! grep -q '^stage=1 filter=beta::' "$cm_log" then echo "ok cargo-mutants A-zero-test-plan: counted at plan time, no stage-1 run for it"; else echo "FAIL cargo-mutants A-zero-test-plan"; sort -u "$cm_log"; fail=1; fi # the stage-2 timeout bound is the generous default with a floor, not a tight multiple of a quiet-host baseline if [ "$(grep -c '^stage=2 .* mult=5 min=20$' "$cm_log")" = 3 ] && ! grep '^stage=2' "$cm_log" | grep -qv 'mult=5 min=20$' then echo "ok cargo-mutants A-timeout-bound: stage 2 runs at 5x with a 20 s floor"; else echo "FAIL cargo-mutants A-timeout-bound"; grep '^stage=2' "$cm_log"; fail=1; fi cmclean A; cmlocks A # derivation on a standalone package under the repo root: the diff is taken at the workspace root (--relative) cm B cmrc B 0 cmcheck B-derivation 'FILTER src/a/mod.rs a:: tests=1 mutants=1' 'FILTER src/a/b.rs a::b:: tests=1 mutants=1' \ '!FILTER src/lib.rs' '!FILTER src/main.rs' '!FILTER src/bin/x.rs' '!FILTER build.rs' 'MUTANTS tested=6 caught=6 missed=0' # FILTERS overrides the derived filter; '-' forces the full suite cm B 'FILTERS=src/a/b.rs=-,src/lib.rs=special::' cmcheck B-override 'FILTER src/lib.rs special:: tests=1 mutants=1' 'FILTER src/a/mod.rs a:: ' '!FILTER src/a/b.rs' 'tested=6 caught=6' # stage 1 settles every mutant: the full-suite baseline still runs (stage 2 is never skipped), and a red one is a red job cm C cmrc C-settled 0 cmcheck C-settled 'MUTANTS tested=2 caught=2 missed=0 timeout=0 unviable=0 | stage1 filtered=2 survivors=0 confirmed_caught=0' if [ "$(grep -c '^stage=2' "$cm_log")" = 1 ]; then echo "ok cargo-mutants C-baseline-runs: one full-suite run although stage 1 settled every mutant"; else echo "FAIL cargo-mutants C-baseline-runs"; sort -u "$cm_log"; fail=1; fi cm C FAKE_CM_RED=2 [ "$cm_rc" != 0 ] || { echo "FAIL cargo-mutants C-red-baseline: exit 0"; fail=1; } cmcheck C-red-baseline 'ERROR stage 2 (shard 1 of 1)' 'container exited 4' '!MUTANTS' cmclean C-red-baseline # CONFIRM_TIMEOUT_MULT and MIN_TEST_TIMEOUT reach the stage-2 run cm A CONFIRM_TIMEOUT_MULT=9 MIN_TEST_TIMEOUT=33 if grep '^stage=2' "$cm_log" | grep -q 'mult=9 min=33$' && ! grep '^stage=2' "$cm_log" | grep -qv 'mult=9 min=33$'; then echo "ok cargo-mutants timeout-overrides: 9x with a 33 s floor in stage 2"; else echo "FAIL cargo-mutants timeout-overrides"; grep '^stage=2' "$cm_log"; fail=1; fi # slot lease: the job's own slot is held by ygg-verify (an inherited descriptor), others by other jobs cm A HOLD_OWN=1 HOLD_OTHERS=1; cmcheck lease-free 'containers=3 |'; cmlocks lease-free cm A HOLD_OWN=1 HOLD_OTHERS=3; cmcheck lease-one-free 'containers=2 |' cm A HOLD_OWN=1 HOLD_OTHERS=4; cmcheck lease-none-free 'containers=1 |' 'tested=16'; cmlocks lease-none-free open_none=$(grep -o 'openfds=[0-9]*' "$cm_log" | sort -u | paste -sd, -) cm A HOLD_OWN=1 HOLD_OTHERS=1; open_leased=$(grep -o 'openfds=[0-9]*' "$cm_log" | sort -u | paste -sd, -) if [ "$open_none" = "$open_leased" ]; then echo "ok cargo-mutants lease-not-inherited: containers see the same descriptors with 2 leases held as with none ($open_none)"; else echo "FAIL cargo-mutants lease-not-inherited: $open_none vs $open_leased"; fail=1; fi cm A PSI=41.00; cmcheck lease-io-pressure 'containers=1 |' 'tested=16' # a red baseline, in stage 1 or in stage 2, is not a clean run cm A FAKE_CM_RED=1 [ "$cm_rc" != 0 ] || { echo "FAIL cargo-mutants red-stage1: exit 0"; fail=1; } cmcheck red-stage1 'ERROR stage 1 (' 'container exited 4' '!MUTANTS' '!MISSED' '!TIMEOUT' cm A FAKE_CM_RED=2 [ "$cm_rc" != 0 ] || { echo "FAIL cargo-mutants red-stage2: exit 0"; fail=1; } cmcheck red-stage2 'ERROR stage 2 (shard' 'container exited 4' '!MUTANTS' '!MISSED' '!TIMEOUT' cmclean red-stage2 # NAMES rerun: no stage-1 container, only the listed names, on the full suite cm_names=$(mktemp) printf '%s\n' 'crates/x/src/alpha/mod.rs:9:9: replace && with || in check' 'crates/x/src/lib.rs:3:5: replace f(x) -> Vec with vec![1]' 'crates/x/src/beta.rs:2:5: replace b1 -> i32 with 0' >"$cm_names" cm A "NAMES=$cm_names" cmrc names 0 cmcheck names 'MUTANTS tested=3 caught=1 missed=2 timeout=0 unviable=0 | stage1 filtered=0 survivors=0 confirmed_caught=0' 'MISSED crates/x/src/alpha/mod.rs:9:9' '!FILTER' if grep -q 'run job1-s1' "$cm_fd/log" || grep -q '^stage=1' "$cm_log"; then echo "FAIL cargo-mutants names: a stage-1 container or a filtered run"; fail=1; else echo "ok cargo-mutants names: no stage-1 container"; fi # a name the range does not produce is an error that does not echo the name printf '%s\n' 'crates/x/src/lib.rs:99:9: replace no_such_secret_name -> i32 with 0' >"$cm_names" cm A "NAMES=$cm_names" [ "$cm_rc" != 0 ] || { echo "FAIL cargo-mutants names-unknown: exit 0"; fail=1; } cmcheck names-unknown 'ERROR 1 mutants selected' '!no_such_secret_name' '!MUTANTS' rm -f "$cm_names" # killed: a job the watchdog already killed starts nothing and leaves nothing cm_k=$(mktemp); cm A "YGG_KILLED=$cm_k"; rm -f "$cm_k" cmrc killed-before 124 if grep -q '^run ' "$cm_fd/log" 2>/dev/null; then echo "FAIL cargo-mutants killed-before: a container started"; fail=1; else echo "ok cargo-mutants killed-before: no container started"; fi cmclean killed-before # the flag appears while stage 1 runs: stage 2 never starts cm_k=$(mktemp -u); cm A "YGG_KILLED=$cm_k" "FAKE_CM_KILLS=$cm_k"; rm -f "$cm_k" cmrc killed-during 124 cmcheck killed-during 'JOB KILLED' '!MUTANTS' if grep -q 'run job1-s2' "$cm_fd/log"; then echo "FAIL cargo-mutants killed-during: a stage-2 container started"; fail=1; else echo "ok cargo-mutants killed-during: no stage-2 container"; fi cmclean killed-during # the launcher refuses bad input before any job starts, and never echoes the value it refused cmrepo A for v in 'SHARDS=0' 'SHARDS=x;SECRET' 'FILTERS=nopairSECRET' 'CONFIRM_TIMEOUT_MULT=2;SECRET'; do lo=$(env "$v" bash "$here/cargo-mutants.sh" "$cm_repo" HEAD HEAD~1 "$cm_wd" 2>&1); lrc=$? if [ "$lrc" = 2 ] && ! printf '%s' "$lo" | grep -q SECRET; then echo "ok cargo-mutants launcher refuses ${v%%=*}"; else echo "FAIL cargo-mutants launcher ${v%%=*}: exit $lrc, output: $lo"; fail=1; fi done lo=$(bash "$here/cargo-mutants.sh" "$cm_repo" HEAD~1 HEAD "$cm_wd" 2>&1); lrc=$? if [ "$lrc" = 2 ] && printf '%s' "$lo" | grep -q 'not an ancestor'; then echo "ok cargo-mutants launcher refuses a base that is not an ancestor"; else echo "FAIL cargo-mutants launcher base: exit $lrc, output: $lo"; fail=1; fi # the -F escaper: every regex metacharacter, the pipe included, is escaped; nothing else is touched esc_bad='' for c in '\' . + '*' '?' '(' ')' '|' '[' ']' '{' '}' '^' '$'; do got=$(printf 'a%sb\n' "$c" | bash "$here/cargo-mutants.sh" --escape) [ "$got" = '^a'\\"$c"'b$' ] || esc_bad="$esc_bad [$c]" done got=$(printf 'replace x -> Vec with vec![1] & 2 - 3 #\n' | bash "$here/cargo-mutants.sh" --escape) [ "$got" = '^replace x -> Vec with vec!\[1\] & 2 - 3 #$' ] || esc_bad="$esc_bad [plain]" if [ -z "$esc_bad" ]; then echo "ok cargo-mutants escaper: all 14 metacharacters escaped, others literal"; else echo "FAIL cargo-mutants escaper:$esc_bad"; fail=1; fi # wait-on.sh: pid, log and file forms, and the vanished-process exit W=$here/wait-on.sh wo() { local rc; WAIT_MAX=${WM:-4} bash "$W" "$@" >"$T/wo.out" 2>&1; rc=$?; echo "$rc"; } wchk() { if [ "$3" = "$2" ]; then echo "ok wait-on $1: exit $3"; else echo "FAIL wait-on $1: exit $3, want $2"; fail=1; fi; } sleep 3 & sp=$!; printf 'one\ntwo\n' >"$T/w.log" wchk "pid finishes" 0 "$(WM=10 wo pid $sp "$T/w.log")" sleep 20 & sp=$! r=$(WM=2 wo pid $sp); wchk "pid still running" 99 "$r" grep -q '^RUNNING ' "$T/wo.out" || { echo "FAIL wait-on: no RUNNING line"; fail=1; } kill $sp 2>/dev/null; wait $sp 2>/dev/null r=$(WM=10 wo pid $sp "$T/w.log"); wchk "killed pid is done" 0 "$r" grep -q two "$T/wo.out" || { echo "FAIL wait-on pid: no log tail"; fail=1; } : >"$T/l.log"; ( sleep 2; echo "step 1" >>"$T/l.log"; echo "all done ok" >>"$T/l.log" ) & r=$(WM=10 wo log "$T/l.log" 'done ok'); wchk "log gains its done line" 0 "$r" grep -q 'all done ok' "$T/wo.out" || { echo "FAIL wait-on log: no tail"; fail=1; } r=$(WM=2 wo log "$T/l.log" 'never-appears'); wchk "log never matches" 99 "$r" printf 'partial\n' >"$T/v.log"; sleep 1 & vp=$!; wait $vp r=$(WAIT_PID=$vp WM=10 wo log "$T/v.log" 'FINISHED'); wchk "vanished pid, no completion" 98 "$r" printf 'FINISHED\n' >>"$T/v.log" r=$(WAIT_PID=$vp WM=10 wo log "$T/v.log" 'FINISHED'); wchk "vanished pid, completion logged" 0 "$r" r=$(WM=2 wo log "$T/none.log" 'x'); wchk "log missing, no pid, short cap" 99 "$r" ( sleep 2; echo ready >"$T/f.txt" ) & r=$(WM=10 wo file "$T/f.txt"); wchk "file appears" 0 "$r" : >"$T/e.txt"; r=$(WM=2 wo file "$T/e.txt"); wchk "file empty is not done" 99 "$r" # F2: the pid form reports GONE, never anything that reads as success sleep 1 & gp=$!; wait $gp r=$(WM=4 wo pid $gp); wchk "pid gone" 0 "$r" grep -q '^GONE (no completion evidence)' "$T/wo.out" || { echo "FAIL wait-on pid: no GONE line"; fail=1; } # F4: an invalid ERE is a usage error, not a forever RUNNING r=$(WM=2 wo log "$T/l.log" '(unclosed'); wchk "invalid regex" 3 "$r" grep -q 'not a valid extended regex' "$T/wo.out" || { echo "FAIL wait-on: no regex message"; fail=1; } r=$(wo bogus); wchk "bad usage" 2 "$r" wait # context-pack.py: a spec's anchors carried from its base to the worktree (no network) CP=$here/context-pack.py; CF=$here/fixtures/context-pack if py -3 -c pass >/dev/null 2>&1; then PY="py -3"; elif python3 -c pass >/dev/null 2>&1; then PY=python3; else PY=python; fi wp() { cygpath -m "$1" 2>/dev/null || echo "$1"; } R=$T/cp; mkdir -p "$R"; Rw=$(wp "$R") g() { git -C "$R" -c user.name=t -c user.email=t@t -c core.autocrlf=false "$@" >/dev/null 2>&1; } seqf() { local i; for i in $(seq 1 "$2"); do echo "$1$i"; done; } g init -q seqf a 40 >"$R/a.txt"; seqf b 40 >"$R/b.txt"; seqf c 10 >"$R/c.txt"; seqf d 20 >"$R/d.txt" seqf e 10 >"$R/e.txt"; seqf f 20 >"$R/f.txt"; seqf g 300 >"$R/big.txt"; seqf h 5 | sed 's/$/ /' >"$R/h.txt"; seqf m 40 >"$R/m.txt" printf 's1\ns2\ns3\ns4\ndef needle_three\ns6\ns7 needle_many\ns8\nfoo::bar\ns10\n' >"$R/sym.txt" printf 'needle_three call 1\nneedle_three call 2\nneedle_three call 3\nneedle_threefold is another word\n' >"$R/zcallers.txt" echo 'bound_fifty bound_fiftyone spread_sym long_symbol q.zq sym_bad twenty_sym' >"$R/anchors.txt" seqf "bound_fifty x" 50 >"$R/bound50.txt"; seqf "bound_fiftyone x" 51 >"$R/bound51.txt"; seqf "q.zq x" 51 >"$R/dotted.txt" printf 'long_symbol %s%s\r' "$(printf '%0150d' 0 | tr 0 z)" "$(printf '%0100d' 0 | tr 0 y)" >"$R/longline.txt"; printf '\n' >>"$R/longline.txt" printf 'sym_bad \377 endX\r\n' >"$R/bad.txt" mkdir -p "$R/manyf"; for i in $(seq -w 1 21); do seqf "spread_sym $i-" 3 >"$R/manyf/f$i.txt"; done; echo "spread_sym extra" >>"$R/manyf/f21.txt" mkdir -p "$R/twf"; for i in $(seq -w 1 20); do seqf "twenty_sym $i-" 3 >"$R/twf/f$i.txt"; done seqf "needle_many x." 30 >"$R/many1.txt"; seqf "needle_many y." 30 >"$R/many2.txt" g add -A; g commit -q -m base; BASE=$(git -C "$R" rev-parse HEAD) { sed -n 1,5p "$R/a.txt"; printf 'ins1\nins2\nins3\n'; sed -n '6,$p' "$R/a.txt"; } >"$R/a.new"; mv "$R/a.new" "$R/a.txt" sed -e '2,3d' -e 's/^b10$/b10-EDITED/' "$R/b.txt" >"$R/b.new"; mv "$R/b.new" "$R/b.txt" sed -e '10,14c\X1\nX2' -e '30,31d' "$R/m.txt" >"$R/m.new"; mv "$R/m.new" "$R/m.txt" g rm -q c.txt; g mv d.txt d2.txt; g add -A; g commit -q -m second sed -i 's/^f5$/f5-UNCOMMITTED/' "$R/f.txt"; printf 'n1\nn2\nn3\n' >"$R/n.txt" sed "s/\"BASE\"/\"$BASE\"/" "$CF/spec.json" >"$T/cp-spec.json"; SP=$(wp "$T/cp-spec.json") cpo() { $PY "$CP" t:cut_spec:cut-x.r1 "$Rw" "$@" >"$T/cp.out" 2>"$T/cp.err"; echo $?; } cchk() { if [ "$3" = "$2" ]; then echo "ok context-pack $1: exit $3"; else echo "FAIL context-pack $1: exit $3, want $2"; fail=1; fi; } cin() { grep -qF -- "$2" "$T/cp.out" && echo "ok context-pack $1" || { echo "FAIL context-pack $1: missing: $2"; fail=1; }; } cnot() { if grep -qF -- "$2" "$T/cp.out" "$T/cp.err"; then echo "FAIL context-pack $1: must not contain: $2"; fail=1; else echo "ok context-pack $1"; fi; } r=$(cpo --spec-json "$SP"); cchk "fixture (GONE and OUT_OF_RANGE)" 3 "$r" cin "header" "context-pack t:cut_spec:cut-x.r1 base ${BASE:0:12} HEAD" cin "header counts modified files" " modified 6" cin "MOVED(+3)" "[1] a.txt:20-22 MOVED(+3) -> a.txt:23-25" cin "insertion at the last line is outside" "[2] a.txt:3-5 SAME -> a.txt:3-5" cin "insertion before the range shifts it" "[3] a.txt:6-7 MOVED(+3) -> a.txt:9-10" cin "insertion inside widens" "[4] a.txt:4-8 TOUCHED(+3/-0 inside) -> a.txt:4-11" cin "delete above, edit inside" "[5] b.txt:8-12 TOUCHED(+1/-1 inside) -> b.txt:6-10" cin "MOVED(-2)" "[6] b.txt:30-31 MOVED(-2) -> b.txt:28-29" cin "GONE" "[7] c.txt:2-3 GONE (not named by the spec's deletes)" cin "RENAMED" "[8] d.txt:4-5 RENAMED -> d2.txt SAME -> d2.txt:4-5" cin "NEW with basename candidates" "[9] sub/a.txt:1-2 NEW (at base: a.txt)" cin "OUT_OF_RANGE" "[10] e.txt:5-50 OUT_OF_RANGE (base file has 10 lines)" cin "uncommitted edit is TOUCHED" "[11] f.txt:5-6 TOUCHED(+1/-1 inside)" cin "last line of the file is in range" "[12] e.txt:10-10 SAME" cin "one past the last line is out of range" "[13] e.txt:10-11 OUT_OF_RANGE (base file has 10 lines)" cin "CRLF file reads SAME" "[14] h.txt:2-3 SAME" cin "hunk straddling the head clamps to the new block" "[15] m.txt:12-13 TOUCHED(+2/-5 inside) -> m.txt:10-11" cin "hunk starting inside and ending past the range" "[16] m.txt:8-11 TOUCHED(+2/-5 inside) -> m.txt:8-11" cin "range just after a replaced block shifts" "[17] m.txt:15-16 MOVED(-3) -> m.txt:12-13" cin "deletion after the range moves nothing else" "[18] m.txt:28-28 MOVED(-3) -> m.txt:25-25" cin "deletion at the next line" "[19] m.txt:29-29 MOVED(-3) -> m.txt:26-26" cin "deletion ending just before the range" "[20] m.txt:32-32 MOVED(-5) -> m.txt:27-27" cin "range after both hunks" "[21] m.txt:33-33 MOVED(-5) -> m.txt:28-28" cin "insertion at the first line is inside" "[22] a.txt:5-7 TOUCHED(+3/-0 inside) -> a.txt:5-10" cin "hunk ending just before the range shifts it" "[23] b.txt:4-5 MOVED(-2) -> b.txt:2-3" cin "NEW file present in the worktree gets a window" "== [24] n.txt:1-2" cin "NEW window shows the file" " 2 n2" cin "CRLF window text" " 2 h2" { tr -d ' ' <"$T/cp.out" | cmp -s - "$T/cp.out"; } && echo "ok context-pack CRLF is stripped from windows" || { echo "FAIL context-pack CR in output"; fail=1; } cin "window numbers are worktree lines" " 6 b8" cin "window shows the edit" "b10-EDITED" cin "GONE window is labelled AT BASE" "== [7] c.txt:2-3 AT BASE" cin "GONE window shows base text" " 2 c2" cin "renamed window reads the new path" "== [8] d2.txt:4-5" [ "$(grep -c '^\[' "$T/cp.out")" = 24 ] && echo "ok context-pack table lists every anchor" || { echo "FAIL context-pack table count"; fail=1; } { sed -n 2p "$T/cp.out" | grep -q '^\[7\]' && sed -n 6p "$T/cp.out" | grep -q '^\[24\]' && sed -n 7p "$T/cp.out" | grep -q '^\[1\]'; } && echo "ok context-pack problem anchors first" || { echo "FAIL context-pack problem anchors first"; fail=1; } # deletes naming the file, and a valid range, make the same spec exit 0 sed -e 's/"deletes": \[\]/"deletes": [{"path": "c.txt"}]/' -e 's/"end_line": 50/"end_line": 6/' -e 's/"end_line": 11/"end_line": 10/' "$T/cp-spec.json" >"$T/cp-spec0.json" r=$(cpo --spec-json "$(wp "$T/cp-spec0.json")"); cchk "GONE named by deletes, no OUT_OF_RANGE" 0 "$r" cin "GONE in deletes has no qualifier" "[7] c.txt:2-3 GONE #" # unreachable base sed "s/\"$BASE\"/\"deadbeef\"/" "$T/cp-spec.json" >"$T/cp-specb.json" r=$(cpo --spec-json "$(wp "$T/cp-specb.json")"); cchk "unreachable base" 3 "$r" cin "UNRESOLVED hint" "UNRESOLVED base not a commit here: git fetch origin deadbeef" [ "$(grep -c 'UNRESOLVED' "$T/cp.out")" = 24 ] && echo "ok context-pack every anchor UNRESOLVED" || { echo "FAIL context-pack UNRESOLVED count"; fail=1; } cnot "UNRESOLVED prints no window" "== [" # --since: every anchor stays in the table; only the edited one has a window r=$(cpo --spec-json "$SP" --since HEAD); cchk "--since" 3 "$r" cin "--since keeps the table" "[1] a.txt:20-22 MOVED(+3)" cin "--since prints the edited window" "== [11] f.txt:5-6" cin "--since window shows the edit" "f5-UNCOMMITTED" cnot "--since omits untouched windows" "== [1] " cnot "--since omits the GONE window" "== [7] " r=$(cpo --spec-json "$SP" --since "$BASE"); cin "--since an older commit widens" "== [4] a.txt:4-11" cnot "--since an older commit still omits untouched windows" "== [2] " r=$(cpo --spec-json "$SP" --since "$BASE" --only 15,16,17,18,19,20,21) for k in 15 16 19 20; do cin "--since window $k overlaps a hunk" "== [$k] "; done for k in 17 18 21; do cnot "--since window $k does not overlap" "== [$k] "; done r=$(cpo --spec-json "$SP" --only 2,11); cin "--only prints the named window" "== [2] " cnot "--only omits the others" "== [1] " r=$(cpo --spec-json "$SP" --only 99); cchk "--only outside file_changes" 2 "$r"; grep -qF "context-pack: --only: index outside file_changes" "$T/cp.err" && echo "ok context-pack names --only" || { echo "FAIL context-pack names --only"; fail=1; } r=$(cpo --spec-json "$SP" --since feedface); cchk "--since not a commit" 2 "$r"; grep -qF "context-pack: --since: not a commit in this worktree" "$T/cp.err" && echo "ok context-pack names --since" || { echo "FAIL context-pack names --since"; fail=1; } Z70=$(printf 'z%.0s' $(seq 1 70)) echo '{"base": "'$BASE'", "deletes": [], "file_changes": [ {"change": "up", "location": {"path": "../a.txt", "line": 1, "end_line": 1}}, {"change": "abs", "location": {"path": "/a.txt", "line": 1, "end_line": 1}}, {"change": "drive", "location": {"path": "c:a.txt", "line": 1, "end_line": 1}}, {"change": "'$Z70'zzzz", "location": {"path": "a.txt", "line": 1, "end_line": 1}}]}' >"$T/cp-specu.json" r=$(cpo --spec-json "$(wp "$T/cp-specu.json")"); cchk "unsafe paths" 3 "$r" [ "$(grep -c 'UNRESOLVED path or git diff unusable' "$T/cp.out")" = 3 ] && echo "ok context-pack unsafe paths are UNRESOLVED" || { echo "FAIL context-pack unsafe paths"; fail=1; } cin "a long change is cut at 70 characters" "# $Z70" cnot "a long change is cut at 70 characters (not 71)" "${Z70}z" # caps { echo '{"base": "'$BASE'", "deletes": [], "file_changes": [' for i in $(seq 1 30); do echo '{"change": "c'$i'", "location": {"path": "big.txt", "line": '$((i*5))', "end_line": '$((i*5+9))'}}'$([ $i -lt 30 ] && echo ,); done echo ']}'; } >"$T/cp-spec30.json" r=$(cpo --spec-json "$(wp "$T/cp-spec30.json")" --max-lines 200); cchk "30 anchors" 0 "$r" [ "$(wc -l <"$T/cp.out")" -le 200 ] && echo "ok context-pack output within --max-lines" || { echo "FAIL context-pack over 200 lines"; fail=1; } [ "$(grep -c '^\[' "$T/cp.out")" = 30 ] && echo "ok context-pack capped output keeps the full table" || { echo "FAIL context-pack capped table"; fail=1; } [ "$(grep -c '^== \[' "$T/cp.out")" = 9 ] && echo "ok context-pack expands 9 windows of 17 lines" || { echo "FAIL context-pack window count: $(grep -c '^== \[' "$T/cp.out")"; fail=1; } cin "rest listed as not expanded" "not expanded: --only 10,11,12" echo '{"base": "'$BASE'", "deletes": [], "file_changes": [{"change": "long", "location": {"path": "big.txt", "line": 20, "end_line": 169}}]}' >"$T/cp-spec1.json" S1=$(wp "$T/cp-spec1.json") r=$(cpo --spec-json "$S1"); cchk "150-line range" 0 "$r" cin "window cut at 100 lines" "56 more lines: --only 1 --max-lines 160" [ "$(grep -c '^ *[0-9]* g' "$T/cp.out")" = 100 ] && echo "ok context-pack long window prints 100 lines" || { echo "FAIL context-pack long window"; fail=1; } r=$(cpo --spec-json "$S1" --only 1 --max-lines 160); cchk "--only with the suggested --max-lines" 0 "$r" cnot "full window needs no more-lines note" "more lines" [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 160 ] && echo "ok context-pack suggested --max-lines is exact" || { echo "FAIL context-pack suggested max: $(wc -l <"$T/cp.out")"; fail=1; } # reads, symbols and caller lists sed "s/\"BASE\"/\"$BASE\"/" "$CF/spec-reads.json" >"$T/cp-spec-reads.json"; SR=$(wp "$T/cp-spec-reads.json") r=$(cpo --spec-json "$SR"); cchk "reads and symbols" 3 "$r" cin "a read that moved" "[r1] a.txt:20-22 MOVED(+3) -> a.txt:23-25" cin "a read on a deleted path is GONE" "[r2] c.txt:2-3 GONE (not named by the spec's deletes)" cin "a read absent at base is MISSING" "[r3] sub/a.txt:1-2 MISSING (read path absent at base)" cin "a present symbol adds no suffix" "[1] a.txt:20-22 MOVED(+3) -> a.txt:23-25" cin "a missing symbol is named" "[2] b.txt:30-31 MOVED(-2) SYMBOL_MISSING b99" cin "an identifier matches whole words only" "MOVED(+3) SYMBOL_MISSING a2 -> a.txt:23-25" cin "a non-identifier symbol is a literal substring" "[4] sym.txt:9-9 SAME" cnot "the substring symbol is not missing" "SYMBOL_MISSING ::ba" cin "the read caption carries its why" "== READ r1: moved below the insertion (a.txt:23-25 MOVED(+3))" cin "a GONE read prints its window at base" "== READ r2: deleted path (c.txt:2-3 AT BASE" [ "$(grep -c '^\[' "$T/cp.out")" = 9 ] && echo "ok context-pack the table lists edits and reads" || { echo "FAIL context-pack table rows with reads"; fail=1; } [ "$(grep -n '^== READ r1' "$T/cp.out" | cut -d: -f1)" -gt "$(grep -n '^== \[4\]' "$T/cp.out" | cut -d: -f1)" ] && echo "ok context-pack read windows follow the edit windows" || { echo "FAIL context-pack read window order"; fail=1; } [ "$(grep -n '^CALLERS' "$T/cp.out" | head -1 | cut -d: -f1)" -gt "$(grep -n '^== READ r5' "$T/cp.out" | cut -d: -f1)" ] && echo "ok context-pack caller lists follow the read windows" || { echo "FAIL context-pack caller list order"; fail=1; } cin "3 callers are all listed" "CALLERS needle_three: 3 hits in 1 files" cin "caller hit format" "zcallers.txt:2: needle_three call 2" cnot "the hit inside the symbol's own window is dropped" "sym.txt:5: def needle_three" cnot "a longer word is no caller" "needle_threefold" cin "60 hits print per-file counts" "CALLERS needle_many: 60 hits in 2 files" cin "per-file count line" " 30 many1.txt" cin "the narrowing command" "narrow: git grep -n -I -w -F -e needle_many -- " cnot "60 hits print no hit lines" "many1.txt:1:" r=$(cpo --spec-json "$SR" --only r1); cchk "--only r1 keeps the exit of all anchors" 3 "$r" cin "--only r1 prints the read" "== READ r1: moved below the insertion" cnot "--only r1 prints no other read" "== READ r2"; cnot "--only r1 prints no edit window" "== [1]" r=$(cpo --spec-json "$SR" --only r9); cchk "--only past the reads" 2 "$r"; grep -qF "context-pack: --only: index outside reads" "$T/cp.err" && echo "ok context-pack names the reads bound" || { echo "FAIL context-pack reads bound"; fail=1; } r=$(cpo --spec-json "$SR" --only 1,r4); cin "--only mixes edit and read labels" "CALLERS needle_many: 60 hits"; cnot "--only lists callers of the selected anchors only" "CALLERS b99" r=$(cpo --spec-json "$SR" --since "$(git -C "$R" rev-parse HEAD)"); cnot "--since HEAD prints no read window" "== READ"; cnot "--since HEAD prints no caller list" "CALLERS" r=$(cpo --spec-json "$SR" --max-lines 3); cchk "--max-lines below the table with reads" 3 "$r" [ "$(grep -c '^\[' "$T/cp.out")" = 9 ] && echo "ok context-pack a small budget keeps every edit and read row" || { echo "FAIL context-pack small budget with reads"; fail=1; } cnot "a small budget prints no window" "== "; cnot "a small budget prints no caller list" "CALLERS" r=$(cpo --spec-json "$SR" --max-lines 60); cchk "caller lists cut by the budget" 3 "$r" [ "$(wc -l <"$T/cp.out" | tr -d ' ')" -le 60 ] && echo "ok context-pack caller lists stay within --max-lines" || { echo "FAIL context-pack caller lists over budget"; fail=1; } cin "the cut caller lists are named" "callers not printed: " SUGG=$(grep -o '^callers not printed: .*--max-lines [0-9]*' "$T/cp.out" | grep -o '[0-9]*$') r=$(cpo --spec-json "$SR" --max-lines "$SUGG"); cnot "the suggested --max-lines prints every caller list" "callers not printed" [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = "$SUGG" ] && echo "ok context-pack suggested caller --max-lines is exact" || { echo "FAIL context-pack suggested caller max: $(wc -l <"$T/cp.out") vs $SUGG"; fail=1; } { echo '{"base": "'$BASE'", "deletes": [], "file_changes": [' for i in 1 2 3 4 5 6 7 8 9; do echo '{"change": "s'$i'", "location": {"path": "big.txt", "line": '$i', "end_line": '$i', "symbol": "g'$i'"}}'$([ $i -lt 9 ] && echo ,); done echo ']}'; } >"$T/cp-spec9sym.json" r=$(cpo --spec-json "$(wp "$T/cp-spec9sym.json")" --max-lines 400); cchk "nine symbols" 0 "$r" [ "$(grep -c '^CALLERS' "$T/cp.out")" = 8 ] && echo "ok context-pack at most 8 caller lists" || { echo "FAIL context-pack caller list count"; fail=1; } cin "the ninth symbol is named" "callers not listed: g9" echo '{"base": "'$BASE'", "deletes": [], "file_changes": [], "reads": [{"why": "w", "location": {"path": "a.txt", "line": 20, "end_line": 22, "symbol": "a21"}}]}' >"$T/cp-spec-r0.json" r=$(cpo --spec-json "$(wp "$T/cp-spec-r0.json")"); cchk "a spec of one read, symbol present" 0 "$r" cin "a read with a symbol present: no suffix" "[r1] a.txt:20-22 MOVED(+3) -> a.txt:23-25 # w" # caller-list bounds: 50 hits list, 51 count; 20 files; a hit's text is cut at 160; CR and bad bytes { echo '{"base": "'$BASE'", "deletes": [], "file_changes": [' n=0; for sy in bound_fifty bound_fiftyone spread_sym long_symbol q.zq sym_bad twenty_sym; do n=$((n+1)) echo '{"change": "'$sy'", "location": {"path": "anchors.txt", "line": 1, "end_line": 1, "symbol": "'$sy'"}}'$([ $n -lt 7 ] && echo ,); done echo ']}'; } >"$T/cp-specb.json" r=$(cpo --spec-json "$(wp "$T/cp-specb.json")" --max-lines 400); cchk "caller list bounds" 0 "$r" cin "exactly 50 hits are listed" "CALLERS bound_fifty: 50 hits in 1 files" [ "$(grep -c '^bound50.txt:' "$T/cp.out")" = 50 ] && echo "ok context-pack 50 hits print as lines" || { echo "FAIL context-pack 50 hit lines"; fail=1; } cin "51 hits are counted" "CALLERS bound_fiftyone: 51 hits in 1 files" cin "51 hits: the per-file count" " 51 bound51.txt" cnot "51 hits print no hit lines" "bound51.txt:1:" grep -qx "(1 more files)" "$T/cp.out" && echo "ok context-pack 21 files: the 21st is named" || { echo "FAIL context-pack 21st file line"; fail=1; } grep -qx "narrow: git grep -n -I -F -e q.zq -- " "$T/cp.out" && echo "ok context-pack the narrowing line is exact" || { echo "FAIL context-pack narrowing line"; fail=1; } cin "exactly 20 files name no more" "CALLERS twenty_sym: 60 hits in 20 files" [ "$(grep -c '(0 more files)' "$T/cp.out")" = 0 ] && echo "ok context-pack 20 files print no more-files line" || { echo "FAIL context-pack 20 files"; fail=1; } [ "$(grep -c '^ *[34] manyf/' "$T/cp.out")" = 20 ] && echo "ok context-pack at most 20 files are counted" || { echo "FAIL context-pack file cap: $(grep -c '^ *[34] manyf/' "$T/cp.out")"; fail=1; } cin "files are ranked by hits, largest first" " 4 manyf/f21.txt" [ "$(grep -n 'manyf/f21.txt' "$T/cp.out" | cut -d: -f1)" -lt "$(grep -n 'manyf/f01.txt' "$T/cp.out" | cut -d: -f1)" ] && echo "ok context-pack the largest file prints first" || { echo "FAIL context-pack file order"; fail=1; } cin "a non-identifier narrowing command has no -w" "narrow: git grep -n -I -F -e q.zq -- " LL=$(grep '^longline.txt:1: ' "$T/cp.out"); [ "${#LL}" = 176 ] && echo "ok context-pack a hit's text is cut at 160 characters" || { echo "FAIL context-pack hit text length ${#LL}"; fail=1; } cnot "a CR in a hit is stripped" "$(printf '\r')" cin "a hit with invalid UTF-8 still prints" "bad.txt:1: sym_bad" cin "a hit keeps a trailing X and loses its CR" "endX" grep -qx "bad.txt:1: sym_bad .* endX" "$T/cp.out" && echo "ok context-pack a hit ends at its last character" || { echo "FAIL context-pack hit end"; fail=1; } r=$(cpo --spec-json "$SR" --only 5); cchk "--only past the last edit anchor" 2 "$r"; grep -qxF "context-pack: --only: index outside file_changes" "$T/cp.err" && echo "ok context-pack --only 5 is outside four anchors" || { echo "FAIL context-pack --only 5"; fail=1; } r=$(cpo --spec-json "$SR" --only 4); cchk "--only the last edit anchor" 3 "$r"; cin "--only 4 prints that window" "== [4] sym.txt:9-9" r=$(cpo --spec-json "$SR" --only r6); cchk "--only past the last read" 2 "$r"; grep -qxF "context-pack: --only: index outside reads" "$T/cp.err" && echo "ok context-pack --only r6 is outside five reads" || { echo "FAIL context-pack --only r6"; fail=1; } r=$(cpo --spec-json "$SR" --only r5); cchk "--only the last read" 3 "$r"; cin "--only r5 prints that window" "== READ r5:" sed -n 3p "$T/cp.out" | grep -q '^\[r3\]' && echo "ok context-pack a MISSING read sorts with the problem anchors" || { echo "FAIL context-pack MISSING read order"; fail=1; } cin "a GONE read checks its symbol at base" "GONE (not named by the spec's deletes) SYMBOL_MISSING c9" { echo '{"base": "'$BASE'", "deletes": [], "file_changes": [{"location": {"path": "a.txt", "line": 1, "end_line": 2, "symbol": "zz9"}}],' echo '"reads": [{"location": {"path": "a.txt", "line": 1, "end_line": 2}}, {"why": "'$(printf '%0250d' 0 | tr 0 w)'", "location": {"path": "a.txt", "line": 3, "end_line": 3}}]}'; } >"$T/cp-specw.json" r=$(cpo --spec-json "$(wp "$T/cp-specw.json")"); cchk "a missing symbol alone fails the run" 3 "$r" cin "a symbol absent from an unmoved window" "[1] a.txt:1-2 SAME SYMBOL_MISSING zz9" cnot "an anchor without a change or why gets no filler" "XXXX" W200=$(printf '%0200d' 0 | tr 0 w); cin "the read caption is cut at 200" "== READ r2: $W200 (a.txt:3-3" # the caller lists obey --max-lines at every budget, and the suggested value prints them all { echo '{"base": "'$BASE'", "deletes": [], "file_changes": [' for i in 1 2 3 4 5 6 7 8 9 10; do echo '{"change": "s'$i'", "location": {"path": "big.txt", "line": '$i', "end_line": '$i', "symbol": "g'$i'"}}'$([ $i -lt 10 ] && echo ,); done echo ']}'; } >"$T/cp-spec10sym.json"; S10=$(wp "$T/cp-spec10sym.json") r=$(cpo --spec-json "$S10" --max-lines 400); cin "two symbols past the eighth are named together" "callers not listed: g9,g10" FULLN=$(wc -l <"$T/cp.out" | tr -d ' ') for b in $((FULLN-1)) $((FULLN-3)) $((FULLN-6)) $((FULLN-9)) $((FULLN-30)); do r=$(cpo --spec-json "$S10" --max-lines "$b") [ "$(wc -l <"$T/cp.out" | tr -d ' ')" -le "$b" ] && echo "ok context-pack budget $b holds" || { echo "FAIL context-pack budget $b exceeded: $(wc -l <"$T/cp.out")"; fail=1; } SG=$(grep -o '^callers not printed: .*--max-lines [0-9]*' "$T/cp.out" | grep -o '[0-9]*$') if [ -n "$SG" ]; then r=$(cpo --spec-json "$S10" --max-lines "$SG"); [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = "$FULLN" ] && ! grep -q 'callers not printed\|not expanded' "$T/cp.out" && echo "ok context-pack budget $b: the suggestion prints everything" || { echo "FAIL context-pack budget $b: suggestion $SG gave $(wc -l <"$T/cp.out") of $FULLN"; fail=1; }; fi done r=$(cpo --spec-json "$S10" --max-lines $((FULLN-1))); [ "$(grep -c '^CALLERS' "$T/cp.out")" = 6 ] && echo "ok context-pack one line short drops two lists for the note" || { echo "FAIL context-pack one line short: $(grep -c '^CALLERS' "$T/cp.out") lists"; fail=1; } cin "the dropped lists are named" "callers not printed: g7,g8: --max-lines $FULLN" # window budgets: the exact thresholds, and what the caller lists leave of them r=$(cpo --spec-json "$S1" --max-lines 104); cnot "104 lines expand no 100-line window" "== ["; cin "104 lines skip a 100-line window" "not expanded: --only 1" r=$(cpo --spec-json "$S1" --max-lines 105); [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 105 ] && grep -qx "56 more lines: --only 1 --max-lines 160" "$T/cp.out" && echo "ok context-pack 105 lines hold a capped window and its note" || { echo "FAIL context-pack 105-line budget"; fail=1; } S30=$(wp "$T/cp-spec30.json") r=$(cpo --spec-json "$S30" --max-lines 186); [ "$(grep -c '^== ' "$T/cp.out")" = 9 ] && [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 186 ] && echo "ok context-pack 186 lines hold 9 windows and the note" || { echo "FAIL context-pack 186-line budget"; fail=1; } r=$(cpo --spec-json "$S30" --max-lines 185); [ "$(grep -c '^== ' "$T/cp.out")" = 8 ] && echo "ok context-pack 185 lines hold 8 windows" || { echo "FAIL context-pack 185-line budget"; fail=1; } r=$(cpo --spec-json "$S30" --max-lines 542); [ "$(grep -c '^== ' "$T/cp.out")" = 30 ] && echo "ok context-pack 542 lines hold all 30 windows" || { echo "FAIL context-pack 542-line budget"; fail=1; }; cnot "542 lines need no not-expanded note" "not expanded" for b in 530 531 532 533 534 535 536 537 538 539 540 541; do r=$(cpo --spec-json "$S30" --max-lines $b); [ "$(wc -l <"$T/cp.out" | tr -d ' ')" -le $b ] && grep -q '^not expanded: --only 30$' "$T/cp.out" && echo "ok context-pack budget $b skips only the last window" || { echo "FAIL context-pack budget $b: $(wc -l <"$T/cp.out") lines"; fail=1; } done for b in 37 38 39 40 41 42 43 44 45 46 47; do r=$(cpo --spec-json "$S30" --only 1,2 --max-lines $b); [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = $b ] && [ "$(grep -c '^== ' "$T/cp.out")" = 1 ] && grep -q 'more lines: --only 1 ' "$T/cp.out" && echo "ok context-pack --only 1,2 at $b lines truncates window 1 to fit" || { echo "FAIL context-pack --only 1,2 at $b: $(wc -l <"$T/cp.out") lines"; fail=1; } done r=$(cpo --spec-json "$S30" --only 1,2 --max-lines 36); cnot "--only 1,2 at 36 lines expands nothing" "== [" r=$(cpo --spec-json "$S10" --max-lines 94); [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 94 ] && [ "$(grep -c '^== ' "$T/cp.out")" = 10 ] && [ "$(grep -c '^CALLERS' "$T/cp.out")" = 6 ] && echo "ok context-pack one line short keeps every window and 6 lists" || { echo "FAIL context-pack 94-line S10"; fail=1; } r=$(cpo --spec-json "$S10" --max-lines 86); [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 86 ] && [ "$(grep -c '^== ' "$T/cp.out")" = 9 ] && [ "$(grep -c '^CALLERS' "$T/cp.out")" = 5 ] && echo "ok context-pack 86 lines: 9 windows, 5 lists, every note" || { echo "FAIL context-pack 86-line S10"; fail=1; } r=$(cpo --spec-json "$S10" --max-lines 65); [ "$(grep -c '^== ' "$T/cp.out")" = 7 ] && [ "$(grep -c '^CALLERS' "$T/cp.out")" = 0 ] && grep -q '^callers not printed: ' "$T/cp.out" && grep -q '^callers not listed: g9,g10' "$T/cp.out" && echo "ok context-pack windows outrank caller lists" || { echo "FAIL context-pack 65-line S10"; fail=1; } r=$(cpo --spec-json "$SR" --max-lines 40); [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 40 ] && [ "$(grep -c '^== ' "$T/cp.out")" = 3 ] && [ "$(grep -c '^CALLERS' "$T/cp.out")" = 2 ] && echo "ok context-pack 40 lines: 3 windows, 2 lists, no line unused" || { echo "FAIL context-pack 40-line SR"; fail=1; } r=$(cpo --spec-json "$SR" --max-lines 44); [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 44 ] && [ "$(grep -c '^CALLERS' "$T/cp.out")" = 3 ] && echo "ok context-pack 44 lines: 3 lists" || { echo "FAIL context-pack 44-line SR"; fail=1; } r=$(cpo --spec-json "$SR" --max-lines 62); [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 61 ] && [ "$(grep -c '^CALLERS' "$T/cp.out")" = 4 ] && echo "ok context-pack 62 lines: a list that needs 2 lines waits" || { echo "FAIL context-pack 62-line SR"; fail=1; } # usage and unreadable spec; no error echoes an input value r=$(cpo --spec-json "$SP" --max-lines CANARY-usage); cchk "bad --max-lines" 2 "$r"; cnot "usage error echoes no value" "CANARY-usage" r=$(cpo --spec-json "$T/CANARY-nofile.json"); cchk "unreadable --spec-json" 4 "$r"; grep -qF "context-pack: --spec-json: unreadable; --spec-json reads a saved view" "$T/cp.err" && echo "ok context-pack names the unreadable --spec-json" || { echo "FAIL context-pack names the unreadable --spec-json"; fail=1; }; cnot "unreadable --spec-json echoes no path" "CANARY-nofile" grep -qF -- "--spec-json" "$T/cp.err" && echo "ok context-pack names --spec-json" || { echo "FAIL context-pack error does not name --spec-json"; fail=1; } r=$($PY "$CP" CANARY-id "$Rw" >"$T/cp.out" 2>"$T/cp.err"; echo $?); cchk "spec id that is no cut_spec id" 2 "$r"; grep -qF "context-pack: spec id: not a cut_spec id" "$T/cp.err" && echo "ok context-pack names the spec id field" || { echo "FAIL context-pack names the spec id field"; fail=1; }; cnot "bad id echoes no value" "CANARY-id" r=$($PY "$CP" t:cut_spec:cut-x.r1 "$T/CANARY-notrepo" --spec-json "$SP" >"$T/cp.out" 2>"$T/cp.err"; echo $?); cchk "worktree is not a repo" 2 "$r"; grep -qF "context-pack: worktree: not a git worktree" "$T/cp.err" && echo "ok context-pack names the worktree field" || { echo "FAIL context-pack names the worktree field"; fail=1; }; cnot "worktree error echoes no path" "CANARY-notrepo" # the spec by id through the stdio MCP binding (a fake eureka-state), retried once on Unavailable H=$T/cphome; mkdir -p "$H"; FK=$(wp "$CF/fake-eureka-state.py") $PY - "$(wp "$H")/.claude.json" "$($PY -c 'import sys;print(sys.executable)')" "$FK" <<'PYE' import json, sys json.dump({"mcpServers": {"eureka-state": {"type": "stdio", "command": sys.argv[2], "args": [sys.argv[3]], "env": {"EUREKA_INSTANCE": "t", "HUGINN_ENDPOINT": "rudp://binding"}}}}, open(sys.argv[1], "w")) PYE Hw=$(wp "$H") mcp() { HOME="$Hw" USERPROFILE="$Hw" FAKE_WANT=t:cut_spec:cut-x.r1 FAKE_SPEC="$SP" FAKE_COUNT="$(wp "$T/cp.count")" "$@"; } $PY "$CP" t:cut_spec:cut-x.r1 "$Rw" --spec-json "$SP" >"$T/cp.want" 2>/dev/null : >"$T/cp.count" mcp $PY "$CP" t:cut_spec:cut-x.r1 "$Rw" >"$T/cp.out" 2>"$T/cp.err"; r=$? cchk "spec by id through the binding" 3 "$r" cmp -s "$T/cp.out" "$T/cp.want" && echo "ok context-pack by id equals --spec-json" || { echo "FAIL context-pack by id differs from --spec-json"; fail=1; } : >"$T/cp.count" mcp env FAKE_MODE=unavailable HUGINN_ENDPOINT=rudp://CANARY-endpoint $PY "$CP" t:cut_spec:cut-x.r1 "$Rw" >"$T/cp.out" 2>"$T/cp.err"; r=$? cchk "Unavailable twice" 4 "$r"; cnot "Unavailable echoes no endpoint" "CANARY-endpoint" [ "$(wc -l <"$T/cp.count" | tr -d ' ')" = 2 ] && echo "ok context-pack retried Unavailable exactly once" || { echo "FAIL context-pack view attempts: $(wc -l <"$T/cp.count")"; fail=1; } grep -qF "context-pack: view: Unavailable; --spec-json reads a saved view" "$T/cp.err" && echo "ok context-pack Unavailable names --spec-json" || { echo "FAIL context-pack Unavailable hint"; fail=1; } mcp env EUREKA_STATE_EXE="$T/CANARY-exe" $PY "$CP" t:cut_spec:cut-x.r1 "$Rw" >"$T/cp.out" 2>"$T/cp.err"; r=$? cchk "unspawnable EUREKA_STATE_EXE" 4 "$r"; grep -qF "context-pack: transport: Unavailable; --spec-json reads a saved view" "$T/cp.err" && echo "ok context-pack names the transport failure" || { echo "FAIL context-pack names the transport failure"; fail=1; }; cnot "spawn error echoes no path" "CANARY-exe" : >"$T/cp.count" mcp env FAKE_MODE=invalid HUGINN_ENDPOINT=rudp://CANARY-endpoint2 $PY "$CP" t:cut_spec:cut-x.r1 "$Rw" >"$T/cp.out" 2>"$T/cp.err"; r=$? cchk "a refusal that is not Unavailable" 4 "$r"; [ "$(wc -l <"$T/cp.count" | tr -d " ")" = 1 ] && echo "ok context-pack does not retry a refusal" || { echo "FAIL context-pack retried a refusal"; fail=1; }; cnot "refusal echoes no endpoint" "CANARY-endpoint2" grep -qF "context-pack: view: InvalidInput; --spec-json reads a saved view" "$T/cp.err" && echo "ok context-pack names the refusal code" || { echo "FAIL context-pack refusal code"; fail=1; } for code in Rejected Misconfigured TooLarge Unencodable Internal; do mcp env FAKE_MODE=invalid FAKE_CODE=$code $PY "$CP" t:cut_spec:cut-x.r1 "$Rw" >"$T/cp.out" 2>"$T/cp.err"; r=$? grep -qF "context-pack: view: $code; --spec-json reads a saved view" "$T/cp.err" && [ $r = 4 ] && echo "ok context-pack names the code $code" || { echo "FAIL context-pack code $code"; fail=1; } done mcp env FAKE_MODE=garbage $PY "$CP" t:cut_spec:cut-x.r1 "$Rw" >"$T/cp.out" 2>"$T/cp.err"; r=$? cchk "unparsable answer" 4 "$r"; grep -qF "context-pack: view: unparsable answer; --spec-json reads a saved view" "$T/cp.err" && echo "ok context-pack names the unparsable answer" || { echo "FAIL context-pack unparsable message"; fail=1; } mcp env FAKE_MODE=null $PY "$CP" t:cut_spec:cut-x.r1 "$Rw" >"$T/cp.out" 2>"$T/cp.err"; r=$? cchk "no such cut_spec" 4 "$r"; grep -qF "context-pack: view: no such cut_spec; --spec-json reads a saved view" "$T/cp.err" && echo "ok context-pack names the missing spec" || { echo "FAIL context-pack missing spec message"; fail=1; } mcp env HOME="$(wp "$T")/nohome" USERPROFILE="$(wp "$T")/nohome" $PY "$CP" t:cut_spec:cut-x.r1 "$Rw" >"$T/cp.out" 2>"$T/cp.err"; r=$? cchk "no binding and no EUREKA_STATE_EXE" 4 "$r"; grep -qF "context-pack: binding: eureka-state is not configured; --spec-json reads a saved view" "$T/cp.err" && echo "ok context-pack names the missing binding" || { echo "FAIL context-pack binding message"; fail=1; } echo '{}' >"$T/cp-empty.json"; r=$(cpo --spec-json "$(wp "$T/cp-empty.json")"); cchk "spec-json that is no cut_spec" 4 "$r" grep -qF "context-pack: spec: not a cut_spec value; --spec-json reads a saved view" "$T/cp.err" && echo "ok context-pack names the spec shape" || { echo "FAIL context-pack spec shape message"; fail=1; } grep -qF "usage: " "$T/cp.err" || echo "ok context-pack spec error prints no usage text" r=$(cpo --max-lines 0); cchk "usage: no spec source" 2 "$r" grep -qF "context-pack: usage error" "$T/cp.err" && echo "ok context-pack usage error text" || { echo "FAIL context-pack usage text"; fail=1; } # glob characters in a spec path name that file only; straddling hunks; --since boundaries R2=$T/cp2; mkdir -p "$R2/src/[a]" "$R2/src/a"; R2w=$(wp "$R2") g2() { git -C "$R2" -c user.name=t -c user.email=t@t -c core.autocrlf=false "$@" >/dev/null 2>&1; } g2 init -q seqf x 30 >"$R2/src/[a]/f.txt"; seqf x 30 >"$R2/src/a/f.txt" for n in s t u v; do seqf x 30 >"$R2/$n.txt"; done; seqf w 12 >"$R2/w.txt"; seqf g 200 >"$R2/big.txt"; echo q1 >"$R2/one.txt" g2 add -A; g2 commit -q -m base; B2=$(git -C "$R2" rev-parse HEAD) { printf 'new1\nnew2\n'; cat "$R2/src/a/f.txt"; } >"$R2/src/a/f.new"; mv "$R2/src/a/f.new" "$R2/src/a/f.txt" sed -i 8,12d "$R2/s.txt"; sed -i 15,25d "$R2/t.txt" sed -i '8,12c\NEW' "$R2/u.txt"; sed -i '15,25c\NEW' "$R2/v.txt" g2 add -A; g2 commit -q -m second; S2=$(git -C "$R2" rev-parse HEAD) sed -i 's/^w5$/w5-EDITED/' "$R2/w.txt" echo '{"base": "'$B2'", "deletes": [], "file_changes": [ {"change": "glob path", "location": {"path": "src/[a]/f.txt", "line": 10, "end_line": 12}}, {"change": "pure deletion above the start", "location": {"path": "s.txt", "line": 10, "end_line": 20}}, {"change": "pure deletion past the end", "location": {"path": "t.txt", "line": 10, "end_line": 20}}, {"change": "one-line replacement above the start", "location": {"path": "u.txt", "line": 10, "end_line": 20}}, {"change": "one-line replacement past the end", "location": {"path": "v.txt", "line": 10, "end_line": 20}}, {"change": "edit on the last line", "location": {"path": "w.txt", "line": 3, "end_line": 5}}, {"change": "edit on the first line", "location": {"path": "w.txt", "line": 5, "end_line": 7}}, {"change": "after the edit", "location": {"path": "w.txt", "line": 6, "end_line": 8}}, {"change": "before the edit", "location": {"path": "w.txt", "line": 1, "end_line": 4}}]}' >"$T/cp-spec2.json" SP2=$(wp "$T/cp-spec2.json") cp2() { $PY "$CP" t:cut_spec:cut-x.r1 "$R2w" "$@" >"$T/cp.out" 2>"$T/cp.err"; echo $?; } r=$(cp2 --spec-json "$SP2"); cchk "straddle fixture" 0 "$r" cin "a glob path reads its own file: SAME" "[1] src/[a]/f.txt:10-12 SAME -> src/[a]/f.txt:10-12" cin "pure deletion starting above the anchor starts at the first survivor" "[2] s.txt:10-20 TOUCHED(+0/-5 inside) -> s.txt:8-15" cin "pure deletion ending past the anchor ends at the last survivor" "[3] t.txt:10-20 TOUCHED(+0/-11 inside) -> t.txt:10-14" cin "one-line replacement above the start begins at the replacement" "[4] u.txt:10-20 TOUCHED(+1/-5 inside) -> u.txt:8-16" cin "one-line replacement past the end ends at the replacement" "[5] v.txt:10-20 TOUCHED(+1/-11 inside) -> v.txt:10-15" cin "straddle window starts at the mapped range" "== [2] s.txt:8-15" r=$(cp2 --spec-json "$SP2" --since "$S2" --only 6,7,8,9) cin "--since: an edit on the anchor's last line is inside" "== [6] w.txt:3-5" cin "--since: an edit on the anchor's first line is inside" "== [7] w.txt:5-7" cnot "--since: an anchor after the edit has no window" "== [8] " cnot "--since: an anchor before the edit has no window" "== [9] " cin "--since keeps every anchor in the table" "[9] w.txt:1-4 SAME" # a --since commit whose tree is unreadable: git diff fails, so no anchor is claimed edited BL=$(echo only | git -C "$R2" hash-object -w --stdin); TR=$(printf '100644 blob %s\tonly.txt\n' "$BL" | git -C "$R2" mktree) CM=$(git -C "$R2" -c user.name=t -c user.email=t@t commit-tree "$TR" -m orphan) chmod u+w "$R2/.git/objects/${TR:0:2}/${TR:2}" 2>/dev/null; rm -f "$R2/.git/objects/${TR:0:2}/${TR:2}" r=$(cp2 --spec-json "$SP2" --since "$CM"); cchk "--since with an unreadable tree" 0 "$r" cnot "--since: a failed git diff claims no edited window" "== [" cin "--since: a failed git diff keeps the table" "[6] w.txt:3-5" # a spec value of the wrong type is a typed refusal naming the field, never a traceback or the value $PY - "$T" "$SP" <<'PYE' import copy, json, sys t, sp = sys.argv[1], sys.argv[2] spec = json.load(open(sp, encoding="utf-8")) cases = { "line": (lambda v: v["file_changes"][1]["location"].update(line="CANARY-line"), "file_changes[2].location.line"), "line-bool": (lambda v: v["file_changes"][0]["location"].update(line=True), "file_changes[1].location.line"), "end_line": (lambda v: v["file_changes"][2]["location"].update(end_line="CANARY-end"), "file_changes[3].location.end_line"), "path": (lambda v: v["file_changes"][0]["location"].update(path=["CANARY-path"]), "file_changes[1].location.path"), "nolocation": (lambda v: v["file_changes"][3].pop("location"), "file_changes[4].location"), "location-str": (lambda v: v["file_changes"][0].update(location="CANARY-loc"), "file_changes[1].location"), "entry-str": (lambda v: v["file_changes"].__setitem__(0, "CANARY-str"), "file_changes[1]"), "change": (lambda v: v["file_changes"][0].update(change={"CANARY": 1}), "file_changes[1].change"), "fcs-str": (lambda v: v.update(file_changes="CANARY-fcs"), "file_changes"), "base": (lambda v: v.update(base=12345), "base"), "deletes-str": (lambda v: v.update(deletes="CANARY-del"), "deletes"), "deletes-nopath": (lambda v: v.update(deletes=[{"note": "CANARY-note"}]), "deletes[1].path"), "deletes-entry-str": (lambda v: v.update(deletes=["CANARY-d"]), "deletes[1].path"), "reads-str": (lambda v: v.update(reads="CANARY-reads"), "reads"), "read-entry-str": (lambda v: v.update(reads=["CANARY-read"]), "reads[1]"), "read-nolocation": (lambda v: v.update(reads=[{"why": "w"}]), "reads[1].location"), "read-path": (lambda v: v.update(reads=[{"why": "w", "location": {"path": ["CANARY-p"], "line": 1}}]), "reads[1].location.path"), "read-line": (lambda v: v.update(reads=[{"why": "w", "location": {"path": "a.txt", "line": "CANARY-l"}}]), "reads[1].location.line"), "read-why": (lambda v: v.update(reads=[{"why": {"CANARY": 1}, "location": {"path": "a.txt", "line": 1}}]), "reads[1].why"), "read-symbol": (lambda v: v.update(reads=[{"why": "w", "location": {"path": "a.txt", "line": 1, "symbol": 7}}]), "reads[1].location.symbol"), "edit-symbol": (lambda v: v["file_changes"][0]["location"].update(symbol=["CANARY-s"]), "file_changes[1].location.symbol"), } names = [] for name, (edit, field) in cases.items(): d = copy.deepcopy(spec) edit(d["document"]["value"]) json.dump(d, open("%s/cp-bad-%s.json" % (t, name), "w")) names.append("%s %s" % (name, field)) open(t + "/cp-bad.list", "w", newline=chr(10)).write("\n".join(names) + "\n") PYE while read -r nm fld; do r=$(cpo --spec-json "$(wp "$T/cp-bad-$nm.json")"); cchk "wrong-typed spec: $nm" 4 "$r" grep -qF "context-pack: spec: $fld: InvalidInput; --spec-json reads a saved view" "$T/cp.err" && echo "ok context-pack names $fld" || { echo "FAIL context-pack message for $nm: $(cat "$T/cp.err")"; fail=1; } cnot "wrong-typed spec $nm: no value" "CANARY"; cnot "wrong-typed spec $nm: no traceback" "Traceback" done <"$T/cp-bad.list" echo '{"base": "CANARY-base", "deletes": [], "file_changes": [{"change": "c", "location": {"path": "a.txt", "line": 1, "end_line": 2}}]}' >"$T/cp-specnb.json" r=$(cpo --spec-json "$(wp "$T/cp-specnb.json")"); cchk "a base that is no sha" 3 "$r" cin "a base that is no sha reads UNRESOLVED" "UNRESOLVED base not a commit here" cin "a base that is no sha is not echoed in the header" "base unresolvable HEAD" echo '{"file_changes": [{"change": "c", "location": {"path": "a.txt", "line": 1}}]}' >"$T/cp-spec-nobase.json" r=$(cpo --spec-json "$(wp "$T/cp-spec-nobase.json")"); cchk "a spec value without a base" 4 "$r" grep -qF "context-pack: spec: not a cut_spec value; --spec-json reads a saved view" "$T/cp.err" && echo "ok context-pack a missing base is a typed refusal" || { echo "FAIL context-pack missing base: $(cat "$T/cp.err")"; fail=1; } # a --max-lines below the table: the table always lists every anchor and no window prints r=$(cpo --spec-json "$(wp "$T/cp-spec30.json")" --max-lines 5); cchk "--max-lines below the table" 0 "$r" [ "$(grep -c '^\[' "$T/cp.out")" = 30 ] && echo "ok context-pack a small budget keeps the full table" || { echo "FAIL context-pack small budget table"; fail=1; } cnot "a small budget prints no window" "== [" [ -z "$(sed -n 32p "$T/cp.out")" ] && echo "ok context-pack a blank line follows the table" || { echo "FAIL context-pack blank line after the table"; fail=1; } cin "a small budget lists every window as not expanded" "not expanded: --only 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30" # --only truncates a window to the budget and says how to see the rest r=$(cpo --spec-json "$S1" --only 1 --max-lines 50); cchk "--only truncation" 0 "$r" [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 50 ] && echo "ok context-pack --only fills the budget exactly" || { echo "FAIL context-pack --only budget: $(wc -l <"$T/cp.out")"; fail=1; } [ "$(grep -c '^ *[0-9]* g' "$T/cp.out")" = 45 ] && echo "ok context-pack --only truncated window has 45 lines" || { echo "FAIL context-pack --only window lines"; fail=1; } cin "--only truncation note counts the rest" "111 more lines: --only 1 --max-lines 160" r=$(cpo --spec-json "$S1" --only 1 --max-lines 7); cchk "--only at the smallest truncatable budget" 0 "$r" [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 7 ] && [ "$(grep -c '^ *[0-9]* g' "$T/cp.out")" = 2 ] && echo "ok context-pack --only budget 7 prints 2 window lines" || { echo "FAIL context-pack --only budget 7"; fail=1; } r=$(cpo --spec-json "$S1" --only 1 --max-lines 6); cin "--only below the smallest truncation is not expanded" "not expanded: --only 1" cnot "--only below the smallest truncation prints no window" "== [" echo '{"base": "'$BASE'", "deletes": [], "file_changes": [ {"change": "long", "location": {"path": "big.txt", "line": 20, "end_line": 169}}, {"change": "short", "location": {"path": "big.txt", "line": 200, "end_line": 210}}]}' >"$T/cp-spec2w.json" r=$(cpo --spec-json "$(wp "$T/cp-spec2w.json")" --only 1,2 --max-lines 50); cchk "--only two windows" 0 "$r" [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 50 ] && echo "ok context-pack --only keeps room for the not-expanded line" || { echo "FAIL context-pack --only reserve: $(wc -l <"$T/cp.out")"; fail=1; } cin "--only: the window that no longer fits is listed" "not expanded: --only 2" r=$(cpo --spec-json "$(wp "$T/cp-spec2w.json")" --only 1,2 --max-lines 161); cchk "--only first window needs exactly the remaining budget" 0 "$r" cin "--only: a window that fills the budget minus the reserve is cut, not skipped" "== [1] big.txt:20-169" [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 161 ] && echo "ok context-pack --only cut window keeps the reserve line" || { echo "FAIL context-pack --only exact need: $(wc -l <"$T/cp.out")"; fail=1; } r=$(cpo --spec-json "$(wp "$T/cp-spec2w.json")" --only 1,2 --max-lines 8); cchk "--only two windows, too small to cut" 0 "$r" cnot "--only: below four lines after the reserve nothing is cut" "== [" cin "--only: both are listed as not expanded" "not expanded: --only 1,2" echo '{"base": "'$B2'", "deletes": [], "file_changes": [ {"change": "long", "location": {"path": "big.txt", "line": 20, "end_line": 169}}, {"change": "tiny", "location": {"path": "one.txt", "line": 1, "end_line": 1}}]}' >"$T/cp-spec3.json" r=$(cp2 --spec-json "$(wp "$T/cp-spec3.json")" --only 1,2 --max-lines 100); cchk "--only cut window then a tiny one" 0 "$r" [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 100 ] && echo "ok context-pack a cut window is charged what it prints" || { echo "FAIL context-pack cut window charge: $(wc -l <"$T/cp.out")"; fail=1; } cin "a cut window leaves no room for the next one" "not expanded: --only 2" r=$(cpo --spec-json "$S1" --max-lines 104); cin "a 100-line cut window needs 102 lines of budget" "not expanded: --only 1" r=$(cpo --spec-json "$S1" --max-lines 105); cin "a 100-line cut window fits in 105 lines" "56 more lines: --only 1 --max-lines 160" [ "$(wc -l <"$T/cp.out" | tr -d ' ')" = 105 ] && echo "ok context-pack a 100-line cut window fills 105 lines" || { echo "FAIL context-pack cut window at 105: $(wc -l <"$T/cp.out")"; fail=1; } grep -qxF "56 more lines: --only 1 --max-lines 160" "$T/cp.out" && echo "ok context-pack the more-lines note is its own line" || { echo "FAIL context-pack more-lines note line"; fail=1; } r=$(cpo --spec-json "$(wp "$T/cp-spec30.json")" --max-lines 202); cchk "reserve line at the budget edge" 0 "$r" [ "$(grep -c '^== \[' "$T/cp.out")" = 9 ] && [ "$(wc -l <"$T/cp.out" | tr -d ' ')" -le 202 ] && echo "ok context-pack a window that would crowd out the not-expanded line is skipped" || { echo "FAIL context-pack reserve at 202: $(wc -l <"$T/cp.out")"; fail=1; } # the binding env block reaches the server; the caller environment overrides it mcp env -u HUGINN_ENDPOINT -u EUREKA_INSTANCE FAKE_MODE=needenv FAKE_NEED=rudp://binding $PY "$CP" t:cut_spec:cut-x.r1 "$Rw" >"$T/cp.out" 2>"$T/cp.err"; r=$? cchk "env from the binding reaches the server" 3 "$r"; cmp -s "$T/cp.out" "$T/cp.want" && echo "ok context-pack binding env supplies the server environment" || { echo "FAIL context-pack binding env not applied"; fail=1; } mcp env -u EUREKA_INSTANCE HUGINN_ENDPOINT=rudp://caller FAKE_MODE=needenv FAKE_NEED=rudp://caller $PY "$CP" t:cut_spec:cut-x.r1 "$Rw" >"$T/cp.out" 2>"$T/cp.err"; r=$? cchk "the caller environment overrides the binding" 3 "$r"; cmp -s "$T/cp.out" "$T/cp.want" && echo "ok context-pack caller env wins over binding env" || { echo "FAIL context-pack caller env lost"; fail=1; } # standard library plus git only if grep -qE '^import (requests|yaml|tree_sitter)|^from (requests|yaml|tree_sitter)|ctags|tree-sitter|"rg"' "$CP"; then echo "FAIL context-pack: a dependency beyond git"; fail=1; else echo "ok context-pack: standard library and git only"; fi # recall.py: the UserPromptSubmit hook that pushes the nearest in-force rulings, questions and follow-ups # into a prompt. Driven against the fake binding; R1-R7 are the rules the cut_spec names. RC=$here/recall.py rcl() { mcp env FAKE_ARGS="$(wp "$T/rc.args")" FAKE_ITEMS="$(wp "$T/rc.items")" "$@"; } # rrun [VAR=val ...]: one hook run; sets rc.out (stdout), rr (exit status), rms (elapsed ms) rrun() { local in=$1; shift; : >"$T/cp.count"; : >"$T/rc.args" local a; a=$(date +%s%N) rcl env "$@" $PY "$RC" prompt <"$in" >"$T/rc.out" 2>"$T/rc.err"; rr=$? rms=$(( ($(date +%s%N) - a) / 1000000 )) } rcalls() { grep -c '^query$' "$T/cp.count"; } rchk() { if [ "$2" = 0 ] && [ ! -s "$T/rc.out" ] && [ ! -s "$T/rc.err" ]; then echo "ok recall $1"; else echo "FAIL recall $1: exit $2, stdout $(wc -c <"$T/rc.out") bytes, stderr $(wc -c <"$T/rc.err") bytes"; fail=1; fi; } rctx() { $PY -c 'import json,sys;sys.stdout.write(json.load(open(sys.argv[1]))["hookSpecificOutput"]["additionalContext"])' "$(wp "$T/rc.out")"; } $PY - "$(wp "$T")" <<'PYE' import json, sys t = sys.argv[1] def doc(kind, label, value): return {"id": {"id": "t:%s:%s" % (kind, label), "kind": kind.title().replace("_", "")}, "document": {"kind": kind, "value": value}, "status": "InForce"} items = [doc("ruling", "alpha", {"title": "Retrieval is pushed", "operator_quote": "pop into context"}), doc("question", "beta", {"title": "Which fork", "operator_quote": None}), doc("follow_up", "gamma", {"item": "f" * 500, "source": {}}), doc("ruling", "delta", {"title": "Edge in", "operator_quote": "q" * 300}), doc("ruling", "eps", {"title": "Edge out", "operator_quote": "q" * 301})] json.dump(items, open(t + "/rc.items", "w")) json.dump([items[0], doc("ruling", "big", {"title": "B" * 190, "operator_quote": None}), items[1]], open(t + "/rc.items2", "w")) json.dump([], open(t + "/rc.items0", "w")) def hook(name, prompt): json.dump({"hook_event_name": "UserPromptSubmit", "session_id": "s", "prompt": prompt}, open(t + "/" + name, "w")) hook("rc.in", "who is on the first batch") hook("rc.in2", "two words") hook("rc.in0", "") hook("rc.inmb", "a " + "日 " * 300) hook("rc.in3", "one two three") open(t + "/rc.inbad", "w").write("not json") json.dump({"hook_event_name": "UserPromptSubmit"}, open(t + "/rc.innoprompt", "w")) PYE RIN=$T/rc.in # the happy path: one hook JSON; a header, then one line per item in rank order rrun "$RIN"; [ $rr = 0 ] && [ ! -s "$T/rc.err" ] && echo "ok recall exits 0 with a hook JSON" || { echo "FAIL recall happy path: exit $rr"; fail=1; } $PY - "$(wp "$T/rc.out")" <<'PYE' && echo "ok recall output is one UserPromptSubmit hook JSON" || { echo "FAIL recall hook JSON shape"; fail=1; } import json, sys o = json.load(open(sys.argv[1])) assert list(o) == ["hookSpecificOutput"] assert o["hookSpecificOutput"]["hookEventName"] == "UserPromptSubmit" assert list(o["hookSpecificOutput"]) == ["hookEventName", "additionalContext"] PYE rctx >"$T/rc.full" $PY - "$(wp "$T/rc.full")" <<'PYE' && echo "ok recall lists id and title, a short operator quote, a follow_up cut to 240, no quote over 300" || { echo "FAIL recall line format"; fail=1; } import sys L = open(sys.argv[1], encoding="utf-8").read().split("\n") assert "possibly irrelevant" in L[0] and "view" in L[0] and "ranked" in L[0] assert L[1] == '- {"id":"t:ruling:alpha","title":"Retrieval is pushed","operator_quote":"pop into context"}', L[1] assert L[2] == '- {"id":"t:question:beta","title":"Which fork"}', L[2] assert L[3] == '- {"id":"t:follow_up:gamma","title":"' + "f" * 240 + '"}', L[3] assert L[4] == '- {"id":"t:ruling:delta","title":"Edge in","operator_quote":"%s"}' % ("q" * 300), L[4] assert L[5] == '- {"id":"t:ruling:eps","title":"Edge out"}', L[5] assert len(L) == 6 PYE # R4: the selection is exactly the three kinds, in force, across every root, as documents; the text is the prompt rrun "$RIN" RECALL_TOP_K=7 $PY - "$(wp "$T/rc.args")" "$(wp "$T/rc.in")" <<'PYE' && echo "ok recall R4 selection is the three kinds, in force, document projection, top_k from RECALL_TOP_K" || { echo "FAIL recall R4 selection"; fail=1; } import json, sys a = [json.loads(l) for l in open(sys.argv[1])] assert len(a) == 1 a = a[0] assert a["selection"] == {"schemas": ["epiphany.pipeline.ruling.v2", "epiphany.pipeline.question.v2", "epiphany.pipeline.follow_up.v2"], "projection": "document", "fields": [{"index": "in_force", "op": "any_of", "values": ["true"]}]}, a["selection"] assert a["semantic"] == {"text": json.load(open(sys.argv[2]))["prompt"], "top_k": 7}, a["semantic"] PYE rrun "$RIN"; [ "$($PY -c 'import json,sys;print(json.loads(open(sys.argv[1]).readline())["semantic"]["top_k"])' "$(wp "$T/rc.args")")" = 5 ] && echo "ok recall top_k defaults to 5" || { echo "FAIL recall default top_k"; fail=1; } # R3: the query text is the prompt cut to at most 1000 bytes at a character boundary rrun "$T/rc.inmb" $PY - "$(wp "$T/rc.args")" "$(wp "$T/rc.inmb")" <<'PYE' && echo "ok recall R3 a multibyte prompt is cut to a valid UTF-8 prefix of at most 1000 bytes" || { echo "FAIL recall R3 byte bound"; fail=1; } import json, sys sent = json.loads(open(sys.argv[1], encoding="utf-8").readline())["semantic"]["text"] full = json.load(open(sys.argv[2], encoding="utf-8"))["prompt"] n = len(sent.encode("utf-8")) assert len(full.encode("utf-8")) > 1200 and full.startswith(sent) and n == 998, n PYE $PY -c 'import json,sys;json.dump({"prompt": "ab " * 400}, open(sys.argv[1], "w"))' "$(wp "$T/rc.inasc")" rrun "$T/rc.inasc"; [ "$($PY -c 'import json,sys;print(len(json.loads(open(sys.argv[1]).readline())["semantic"]["text"].encode()))' "$(wp "$T/rc.args")")" = 1000 ] && echo "ok recall R3 an ASCII prompt is cut to exactly 1000 bytes" || { echo "FAIL recall R3 ASCII cut is not 1000 bytes"; fail=1; } # R6: fewer than 3 words is not queried; R7: one attempt rrun "$T/rc.in2"; rchk "R6 a two-word prompt prints nothing" $rr; [ "$(rcalls)" = 0 ] && echo "ok recall R6 a two-word prompt makes zero calls" || { echo "FAIL recall R6 queried a two-word prompt"; fail=1; } rrun "$T/rc.in0"; [ "$(rcalls)" = 0 ] && echo "ok recall R6 an empty prompt makes zero calls" || { echo "FAIL recall R6 queried an empty prompt"; fail=1; } rrun "$T/rc.in3"; [ "$(rcalls)" = 1 ] && echo "ok recall R6 a three-word prompt is queried" || { echo "FAIL recall R6 three words not queried"; fail=1; } rrun "$RIN" FAKE_MODE=unavailable; rchk "R1 Unavailable prints nothing" $rr; [ "$(rcalls)" = 1 ] && echo "ok recall R7 Unavailable is one attempt, no retry" || { echo "FAIL recall R7 attempts: $(rcalls)"; fail=1; } # R1: every failure is exit 0 and empty stdout rrun "$RIN" FAKE_MODE=garbage; rchk "R1 an unparsable answer" $rr rrun "$RIN" HOME="$(wp "$T")/nohome" USERPROFILE="$(wp "$T")/nohome"; rchk "R1 no binding" $rr rrun "$RIN" EUREKA_STATE_EXE="$T/CANARY-exe"; rchk "R1 an unspawnable binding" $rr rrun "$T/rc.inbad"; rchk "R1 malformed stdin" $rr; [ "$(rcalls)" = 0 ] && echo "ok recall malformed stdin makes no call" || { echo "FAIL recall called on malformed stdin"; fail=1; } rrun "$T/rc.innoprompt"; rchk "R1 stdin with no prompt" $rr : >"$T/cp.count"; rcl $PY "$RC" <"$RIN" >"$T/rc.out" 2>"$T/rc.err"; rchk "R1 no subcommand" $? # R5: the budget drops items whole from the tail and never cuts a line $PY - "$(wp "$T/rc.full")" "$(wp "$T")" <<'PYE' import sys full = open(sys.argv[1], encoding="utf-8").read() L = full.split("\n") one = len(L[0]) + 1 + len(L[1]) open(sys.argv[2] + "/rc.b", "w").write(" ".join(str(n) for n in (len(full), len(full) - 1, one, one - 1, len(L[0]) + 4))) PYE set -- $(cat "$T/rc.b") rrun "$RIN" RECALL_BUDGET=$1; rctx >"$T/rc.ctx"; cmp -s "$T/rc.ctx" "$T/rc.full" && echo "ok recall R5 a budget equal to the output keeps every item" || { echo "FAIL recall R5 exact budget"; fail=1; } rrun "$RIN" RECALL_BUDGET=$2; rctx >"$T/rc.ctx" $PY - "$(wp "$T/rc.ctx")" "$(wp "$T/rc.full")" "$2" <<'PYE' && echo "ok recall R5 one character short drops the last item whole" || { echo "FAIL recall R5 budget minus one"; fail=1; } import sys got = open(sys.argv[1], encoding="utf-8").read() want = "\n".join(open(sys.argv[2], encoding="utf-8").read().split("\n")[:-1]) assert got == want and len(got) <= int(sys.argv[3]) PYE rrun "$RIN" RECALL_BUDGET=$3; rctx >"$T/rc.ctx"; [ "$(wc -l <"$T/rc.ctx" | tr -d ' ')" = 1 ] && echo "ok recall R5 the header and one item fit their exact budget" || { echo "FAIL recall R5 one-item budget"; fail=1; } rrun "$RIN" RECALL_BUDGET=$4; rchk "R5 a budget below the header and one item prints nothing" $rr rrun "$RIN" RECALL_BUDGET=$5; rchk "R5 a budget with no room for an item prints nothing" $rr # an item that does not fit ends the list, even when a later one would fit rcl env FAKE_ITEMS="$(wp "$T/rc.items2")" $PY "$RC" prompt <"$RIN" >"$T/rc.out" 2>"$T/rc.err"; rctx >"$T/rc.full2" $PY - "$(wp "$T/rc.full2")" "$(wp "$T")" <<'PYE' import sys L = open(sys.argv[1], encoding="utf-8").read().split("\n") assert len(L) == 4 and len(L[2]) > len(L[3]), L open(sys.argv[2] + "/rc.b2", "w").write(str(len(L[0]) + len(L[1]) + len(L[3]) + 2)) PYE rcl env FAKE_ITEMS="$(wp "$T/rc.items2")" RECALL_BUDGET="$(cat "$T/rc.b2")" $PY "$RC" prompt <"$RIN" >"$T/rc.out" 2>"$T/rc.err"; rctx >"$T/rc.ctx" [ "$(wc -l <"$T/rc.ctx" | tr -d ' ')" = 1 ] && echo "ok recall R5 items drop from the tail: a big item ends the list" || { echo "FAIL recall R5 skipped over a big item"; fail=1; } rcl env FAKE_ITEMS="$(wp "$T/rc.items0")" $PY "$RC" prompt <"$RIN" >"$T/rc.out" 2>"$T/rc.err"; rchk "R5 zero items prints nothing" $? # R2: the deadline is a function of RECALL_DEADLINE_MS, and the child is killed. # r2ms [VAR=val ...]: the faster of two runs, timed by Python around the interpreter itself # (sys.executable): no bash, `env` or `py` launcher in the measurement, whose spawn cost on Windows is 100 to # 300 ms and is not the script's. The script's clock starts at process creation on Windows, so what is timed is # the deadline less the reserve that fits the kill and exit inside it. Sets rms, and rr nonzero if a run printed # anything or exited nonzero. r2ms() { local dl=$1; shift rms=$(rcl env ${dl:+RECALL_DEADLINE_MS=$dl} FAKE_SLEEP=6 "$@" $PY - "$RC" "$(wp "$RIN")" <<'PYE' import subprocess, sys, time best = None for _ in range(2): t = time.perf_counter() p = subprocess.Popen([sys.executable, sys.argv[1], "prompt"], stdin=open(sys.argv[2], "rb"), stdout=subprocess.PIPE) out = p.stdout.read(); rc = p.wait() ms = int((time.perf_counter() - t) * 1000) assert out == b"" and rc == 0, (out, rc) best = ms if best is None else min(best, ms) print(best) PYE ); rr=$? } r2band() { #