wait-on: WAIT_FROM start marker, GONE line for pid form, regex validation, executable bit
F1: log form searches only bytes after WAIT_FROM, so a stale done line in a reused log is ignored (also makes the per-check cost independent of log size). F2: pid form prints GONE (no completion evidence). F4: invalid ERE is exit 3. Mode 100755 recorded for wait-on.sh; other tools/ scripts are still 100644. F5, F6, F7 recorded as known limitations in the header. Fixtures added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
df590aa2c7
commit
30e199cf55
3 changed files with 57 additions and 6 deletions
|
|
@ -9,7 +9,7 @@ gets a Soul pass before merge.
|
|||
| script | purpose | consumer | named by |
|
||||
|---|---|---|---|
|
||||
| `tools/stopgap/ygg-verify.sh` | Pushes an exact revision to a mirror on Yggdrasil and runs one command in a capped, niced container (`RUN_ON_HOST=1`: run a host-side command in the same slot, clone and watchdog instead; the watchdog kills every container carrying the job label). `SUMMARY=<log>` keeps the full log and prints a bounded summary (verdict, counts, ZERO TESTS RAN, failing tests); `wait <log>` blocks at most 270 s and returns the summary once; call it with a Bash tool `timeout` of at least 280000 ms. Dies with the stopgap when Idunn's verify transaction lands. | Hands, Soul, Imagination probes | `references/briefs.md` (Hands long jobs and verification, Soul long jobs), `SKILL.md` (Yggdrasil) |
|
||||
| `tools/wait-on.sh` | One general waiter for anything that is not a Yggdrasil job: `pid <pid> [<log>]`, `log <log> <done-regex>` (with `WAIT_PID=<pid>` a dead process with no match is exit 98), `file <path>`. Same contract as `ygg-verify.sh wait`: blocks at most 270 s, exit 0 done with a tail of at most 60 log lines, `RUNNING <n>s` exit 99, exit 98 gone with no completion evidence; call it with a Bash tool `timeout` of at least 280000 ms. Replaces hand-rolled poll loops and idle `true`/`echo`/`sleep` calls. | Hands, Soul, Imagination probes | `references/briefs.md` (Hands long jobs, Soul long jobs, detached scripts) |
|
||||
| `tools/wait-on.sh` | One general waiter for anything that is not a Yggdrasil job: `pid <pid> [<log>]`, `log <log> <done-regex>` (with `WAIT_PID=<pid>` a dead process with no match is exit 98; `WAIT_FROM=<byte offset>` ignores a reused log's stale lines; an invalid ERE is exit 3), `file <path>`. The `pid` form prints `GONE (no completion evidence)`: prefer `log` whenever the job writes a done line. Same contract as `ygg-verify.sh wait`: blocks at most 270 s, exit 0 done with a tail of at most 60 log lines, `RUNNING <n>s` exit 99, exit 98 gone with no completion evidence; call it with a Bash tool `timeout` of at least 280000 ms. Replaces hand-rolled poll loops and idle `true`/`echo`/`sleep` calls. | Hands, Soul, Imagination probes | `references/briefs.md` (Hands long jobs, Soul long jobs, detached scripts) |
|
||||
| `tools/mutation-table.sh` | One verify job that runs a list of hand mutants (file, before, after, test) against a revision and returns a kill table: CAUGHT with the killing test, SURVIVED, UNVIABLE, TIMEOUT, NOAPPLY, ERROR. Refuses a red or zero-test baseline and a red BUILD; rows print live. Isolation is Docker's: the baselines run once in a container that holds the revision, that container is committed to a throwaway image, and every mutant runs in a fresh `--rm` container from it, so nothing a run writes (tracked, untracked or ignored, such as `target/`) reaches the next mutant; the image is removed when the job ends, killed or not. The shared registry and sccache volumes and the network are not isolated. A file-level load failure (vitest `FAIL <file> [ <file> ]`, jest `FAIL <file>` plus `Test suite failed to run`) reads UNVIABLE. Replaces per-agent runmut/mutrun/strykrun scripts. | Soul, Hands (scoped run) | `references/briefs.md` (Hands verification, Soul reruns) |
|
||||
| `tools/testparse.sh` | Sourced library: failing test names and pass/fail/skip counts from cargo, dotnet, vitest, jest and pytest logs, and test files that failed to load (`LOADFAIL`). Shared by the summary and the kill table. | the two scripts above | not named by a brief; it lives and dies with them |
|
||||
| `tools/test-tools.sh` | Local fixture check of the parsers and the summary over `tools/fixtures/logs` (real cargo and mutation logs, plus hand-written dotnet, vitest, jest, pytest, exit-0-but-failed and no-verdict logs), and of the mutation runner's host driver and container half under a mock docker. Run it before merging a change to the scripts above. | whoever changes those scripts | `SKILL.md` (Shared tools) |
|
||||
|
|
|
|||
|
|
@ -216,6 +216,25 @@ 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"
|
||||
# F1: a stale done line in a reused log is ignored from WAIT_FROM on; a new one is seen
|
||||
printf 'old run
|
||||
ALL DONE
|
||||
' >"$T/r.log"; off=$(wc -c <"$T/r.log")
|
||||
r=$(WAIT_FROM=$off WM=2 wo log "$T/r.log" 'ALL DONE'); wchk "stale done before WAIT_FROM ignored" 99 "$r"
|
||||
printf 'new run
|
||||
ALL DONE
|
||||
' >>"$T/r.log"
|
||||
r=$(WAIT_FROM=$off WM=4 wo log "$T/r.log" 'ALL DONE'); wchk "new done after WAIT_FROM seen" 0 "$r"
|
||||
printf 'short
|
||||
' >"$T/r2.log"
|
||||
r=$(WAIT_FROM=9999 WM=2 wo log "$T/r2.log" 'short'); wchk "truncated log searched whole" 0 "$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
|
||||
exit $fail
|
||||
|
|
|
|||
42
tools/wait-on.sh
Normal file → Executable file
42
tools/wait-on.sh
Normal file → Executable file
|
|
@ -5,16 +5,35 @@
|
|||
# 0 done; prints a bounded tail (at most 60 lines) of the log when one is given
|
||||
# 99 still going; prints `RUNNING <n>s (...)`: call it again
|
||||
# 98 the thing waited on is gone with no completion evidence
|
||||
# 3 the done regex is not a valid ERE (usage error, nothing was waited on)
|
||||
# 2 bad usage
|
||||
# Call it with a Bash tool `timeout` of at least 280000 ms.
|
||||
#
|
||||
# Forms:
|
||||
# wait-on.sh pid <pid> [<log>] done when the process is gone (exit 0)
|
||||
# wait-on.sh pid <pid> [<log>] process gone: prints `GONE (no completion evidence)`, exit 0
|
||||
# wait-on.sh log <log> <done-regex> done when a log line matches the ERE
|
||||
# wait-on.sh file <path> done when the file exists and is non-empty
|
||||
# `log` accepts WAIT_PID=<pid>: if that process dies and the regex never matched
|
||||
# (checked once more after the death), exit 98. Without WAIT_PID a log that does
|
||||
# not exist after 30 s is exit 98 (never started). `pid` cannot tell a finished
|
||||
# process from one that never existed: both are exit 0, so pass a log and read it.
|
||||
# process from one that never existed or crashed: exit 0 there means only "gone",
|
||||
# never "succeeded". Prefer `log <log> <regex>` with WAIT_PID whenever the job
|
||||
# writes a done line.
|
||||
#
|
||||
# Reused log paths: `log` accepts WAIT_FROM=<byte offset>; only bytes after it are
|
||||
# searched, so a stale done line from an earlier job on the same path is ignored.
|
||||
# Capture it at launch: `WAIT_FROM=$(wc -c < "$log" 2>/dev/null || echo 0)`. If the
|
||||
# file is now shorter than WAIT_FROM (truncated and rewritten) the whole file is
|
||||
# searched. Reading only from the offset also keeps each 2 s check cheap on a
|
||||
# multi-GB log. Without WAIT_FROM the whole log is searched.
|
||||
#
|
||||
# Known limitations (Soul pass 1, PR #7; not fixed):
|
||||
# F5 WAIT_PID naming a launcher that exits while its child keeps writing gives a
|
||||
# false 98; pass the pid that writes the log, or none.
|
||||
# F6 `file` is done at the first byte; a writer that is still writing is not
|
||||
# waited out. Have the job write the file last (rename into place).
|
||||
# F7 `pid <pid> <log>` prints a tail only; without WAIT_FROM the `log` form
|
||||
# greps the whole log each 2 s, which is slow on multi-GB logs.
|
||||
#
|
||||
# PIDs: on Git Bash a Windows PID (PowerShell Start-Process .Id, a task manager
|
||||
# number) is seen by `tasklist`, but `kill -0` does not know it; Git Bash's own
|
||||
|
|
@ -35,6 +54,17 @@ alive() {
|
|||
esac
|
||||
return 1
|
||||
}
|
||||
# scan <log> <re>: does the log (from WAIT_FROM, when set and not past EOF) match?
|
||||
scan() {
|
||||
[ -f "$1" ] || return 1
|
||||
local from=${WAIT_FROM:-0}
|
||||
case "$from" in ''|*[!0-9]*) from=0 ;; esac
|
||||
if [ "$from" -gt 0 ] && [ "$(wc -c <"$1")" -ge "$from" ]; then
|
||||
tail -c +"$((from + 1))" "$1" 2>/dev/null | grep -Eq -- "$2"
|
||||
else
|
||||
grep -Eq -- "$2" "$1" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
tail60() { [ -n "${1:-}" ] && [ -f "$1" ] && tail -n 60 "$1" | cut -c1-400; return 0; }
|
||||
running() { echo "RUNNING $(( $(date +%s) - t0 ))s ($1)"; exit 99; }
|
||||
elapsed() { echo $(( $(date +%s) - t0 )); }
|
||||
|
|
@ -43,18 +73,20 @@ case "$mode" in
|
|||
pid)
|
||||
pid=${2:-}; log=${3:-}; [ -n "$pid" ] || usage
|
||||
while :; do
|
||||
if ! alive "$pid"; then tail60 "$log"; exit 0; fi
|
||||
if ! alive "$pid"; then echo "GONE (no completion evidence)"; tail60 "$log"; exit 0; fi
|
||||
[ "$(elapsed)" -lt "$cap" ] || running "pid $pid alive; last log line: $(tail -n 1 "$log" 2>/dev/null | cut -c1-160)"
|
||||
sleep 2
|
||||
done ;;
|
||||
log)
|
||||
log=${2:-}; re=${3:-}; [ -n "$log" ] && [ -n "$re" ] || usage
|
||||
wp=${WAIT_PID:-}
|
||||
rc=0; printf '' | grep -Eq -- "$re" 2>/dev/null || rc=$?
|
||||
[ "$rc" -le 1 ] || { echo "wait-on: usage - /$re/ is not a valid extended regex" >&2; exit 3; }
|
||||
while :; do
|
||||
if [ -f "$log" ] && grep -Eq -- "$re" "$log" 2>/dev/null; then tail60 "$log"; exit 0; fi
|
||||
if scan "$log" "$re"; then tail60 "$log"; exit 0; fi
|
||||
if [ -n "$wp" ] && ! alive "$wp"; then
|
||||
sleep 1
|
||||
if [ -f "$log" ] && grep -Eq -- "$re" "$log" 2>/dev/null; then tail60 "$log"; exit 0; fi
|
||||
if scan "$log" "$re"; then tail60 "$log"; exit 0; fi
|
||||
echo "wait-on: DEAD - pid $wp is gone and /$re/ never matched in $log"; tail60 "$log"; exit 98
|
||||
fi
|
||||
if [ -z "$wp" ] && [ ! -f "$log" ] && [ "$(elapsed)" -ge 30 ]; then
|
||||
|
|
|
|||
Loading…
Reference in a new issue