-
Notifications
You must be signed in to change notification settings - Fork 0
chore: update pull request #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,6 +24,10 @@ | |||||||||||||||||||||||||
| # Aggregation (no suite execution): | ||||||||||||||||||||||||||
| # fm-test-run.sh --aggregate-json <out.json> <lane.json> [more lane.json...] | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Run control (no suite execution): | ||||||||||||||||||||||||||
| # fm-test-run.sh --list-runs | ||||||||||||||||||||||||||
| # fm-test-run.sh --stop [<run-id>] | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Options: | ||||||||||||||||||||||||||
| # --json <path> write a deterministic timing artifact after the run | ||||||||||||||||||||||||||
| # --list print selected script paths (one per line) and exit 0 | ||||||||||||||||||||||||||
|
|
@@ -42,6 +46,15 @@ | |||||||||||||||||||||||||
| # selected script is in the proven-isolated set | ||||||||||||||||||||||||||
| # (bin/fm-test-isolation-proof.sh --list). Cap is 8. Stateful | ||||||||||||||||||||||||||
| # families never schedule under --jobs. | ||||||||||||||||||||||||||
| # --list-runs print one FM_TEST_RUN record per registered live run and exit | ||||||||||||||||||||||||||
| # --stop [<run-id>] | ||||||||||||||||||||||||||
| # stop registered runs by identity, never by process-name | ||||||||||||||||||||||||||
| # matching. With a run id, exactly that run. With no run id, | ||||||||||||||||||||||||||
| # only the runs registered from THIS repository root, so a | ||||||||||||||||||||||||||
| # concurrent agent's suite in another worktree is never a | ||||||||||||||||||||||||||
| # candidate. Broad `pkill -f fm-test-run.sh` is what this | ||||||||||||||||||||||||||
| # verb exists to replace: concurrent runs share a near | ||||||||||||||||||||||||||
| # identical command line, so no pattern can tell them apart. | ||||||||||||||||||||||||||
| # -h, --help print this header | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Per-script machine-parseable markers (stdout): | ||||||||||||||||||||||||||
|
|
@@ -53,9 +66,22 @@ | |||||||||||||||||||||||||
| # FM_TEST_SUMMARY_FAMILY family=<name> count=<n> duration_ms=<n> failed=<n> | ||||||||||||||||||||||||||
| # FM_TEST_SLOWEST rank=<k> script=<path> duration_ms=<n> | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Instead of that summary, an interrupted run prints exactly one terminator: | ||||||||||||||||||||||||||
| # FM_TEST_ABORTED <iso8601> run_id=<id> signal=<name> completed=<n> selected=<n> | ||||||||||||||||||||||||||
| # A killed run used to end mid-line, so a truncated log was indistinguishable | ||||||||||||||||||||||||||
| # from a short clean one and a reader counting failures off it concluded the | ||||||||||||||||||||||||||
| # suite passed. Treat a log with neither FM_TEST_SUMMARY nor FM_TEST_ABORTED as | ||||||||||||||||||||||||||
| # lost output, never as a pass. | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Exit status is non-zero if any selected script exits non-zero or a configured | ||||||||||||||||||||||||||
| # --fail-on-gate-skip token appears. Other gate skips (first meaningful line | ||||||||||||||||||||||||||
| # matching ^skip:) remain successful and are counted as skipped_gate. | ||||||||||||||||||||||||||
| # An aborted run exits 128+<signal> (143 for TERM, 130 for INT, 129 for HUP). | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Every executing run registers itself in a per-user registry directory | ||||||||||||||||||||||||||
| # (FM_TEST_RUN_REGISTRY, default $TMPDIR/fm-test-run-registry) and removes that | ||||||||||||||||||||||||||
| # record on exit. Records are pruned when their pid is gone, so a crashed run | ||||||||||||||||||||||||||
| # leaves no stale entry that a later --stop could act on. | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Family labels, the changed-file map, and production portable-shard composition | ||||||||||||||||||||||||||
| # live in this script only (one owner). The proven-isolated candidate set remains | ||||||||||||||||||||||||||
|
|
@@ -88,6 +114,19 @@ EXCLUDE_FAMILIES=() | |||||||||||||||||||||||||
| FAIL_ON_GATE_SKIP= | ||||||||||||||||||||||||||
| JOBS=1 | ||||||||||||||||||||||||||
| JOBS_MAX=8 | ||||||||||||||||||||||||||
| LIST_RUNS=0 | ||||||||||||||||||||||||||
| STOP_REQUESTED=0 | ||||||||||||||||||||||||||
| STOP_RUN_ID= | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Where executing runs register their identity so a concurrent agent can stop | ||||||||||||||||||||||||||
| # one exactly instead of pattern-matching a shared command line. Per-user by | ||||||||||||||||||||||||||
| # default because TMPDIR is per-user on macOS; FM_TEST_RUN_REGISTRY overrides it | ||||||||||||||||||||||||||
| # for tests and for anyone deliberately partitioning runs further. | ||||||||||||||||||||||||||
| RUN_REGISTRY="${FM_TEST_RUN_REGISTRY:-${TMPDIR:-/tmp}/fm-test-run-registry}" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Seconds to wait for a signalled run to write its terminator and exit before | ||||||||||||||||||||||||||
| # escalating to SIGKILL on whatever is left. | ||||||||||||||||||||||||||
| STOP_GRACE_SECONDS="${FM_TEST_RUN_STOP_GRACE:-10}" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # How many separate-runner shards the portable serial remainder splits into. | ||||||||||||||||||||||||||
| # One owner: CI lane names carry this count and are refused when they disagree. | ||||||||||||||||||||||||||
|
|
@@ -128,6 +167,211 @@ now_ms() { | |||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||
| # Run registry: identity-scoped stop, so nobody has to reach for pkill. | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Concurrent agents invoke this runner with near-identical command lines | ||||||||||||||||||||||||||
| # ("--changed --base origin/main"), so `pkill -f` cannot distinguish one run | ||||||||||||||||||||||||||
| # from another and has killed a sibling agent's suite mid-flight. Each | ||||||||||||||||||||||||||
| # executing run therefore records its own identity here and --stop acts only on | ||||||||||||||||||||||||||
| # those records: by exact run id, or by this repository root. | ||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| run_registry_file() { | ||||||||||||||||||||||||||
| printf '%s/%s.run\n' "$RUN_REGISTRY" "$1" | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # One field out of a registry record. Fields are "key=value", one per line, and | ||||||||||||||||||||||||||
| # values may contain "=" so only the first is a separator. | ||||||||||||||||||||||||||
| run_record_field() { | ||||||||||||||||||||||||||
| local file=$1 key=$2 line | ||||||||||||||||||||||||||
| [ -f "$file" ] || return 1 | ||||||||||||||||||||||||||
| while IFS= read -r line; do | ||||||||||||||||||||||||||
| case "$line" in | ||||||||||||||||||||||||||
| "$key="*) printf '%s\n' "${line#*=}"; return 0 ;; | ||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||
| done <"$file" | ||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| pid_is_alive() { | ||||||||||||||||||||||||||
| kill -0 "$1" 2>/dev/null | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Direct and transitive children of a pid, deepest last. Used so a stop reaches | ||||||||||||||||||||||||||
| # the test script the run is currently executing (and any --jobs workers) | ||||||||||||||||||||||||||
| # without ever touching a process the run does not own. | ||||||||||||||||||||||||||
| descendant_pids() { | ||||||||||||||||||||||||||
| local root=$1 snapshot | ||||||||||||||||||||||||||
| snapshot=$(ps -eo pid=,ppid= 2>/dev/null || true) | ||||||||||||||||||||||||||
| [ -n "$snapshot" ] || return 0 | ||||||||||||||||||||||||||
| # One breadth-first pass per level, driven off a single ps snapshot so the | ||||||||||||||||||||||||||
| # tree cannot shift underneath a walk that re-reads the process table. | ||||||||||||||||||||||||||
| printf '%s\n' "$snapshot" | awk -v root="$root" ' | ||||||||||||||||||||||||||
| { child[NR] = $1; parent[NR] = $2; rows = NR } | ||||||||||||||||||||||||||
| END { | ||||||||||||||||||||||||||
| want[root] = 1 | ||||||||||||||||||||||||||
| # Bounded by tree depth; each pass adopts children of everything marked | ||||||||||||||||||||||||||
| # so far, and stops as soon as a pass marks nothing new. | ||||||||||||||||||||||||||
| do { | ||||||||||||||||||||||||||
| added = 0 | ||||||||||||||||||||||||||
| for (i = 1; i <= rows; i++) { | ||||||||||||||||||||||||||
| if (want[parent[i]] && !want[child[i]]) { | ||||||||||||||||||||||||||
| want[child[i]] = 1 | ||||||||||||||||||||||||||
| print child[i] | ||||||||||||||||||||||||||
| added = 1 | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } while (added) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| ' | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Drop records whose process is gone. A crashed run must not leave an entry | ||||||||||||||||||||||||||
| # that a later --stop could act on, and a recycled pid must not inherit one. | ||||||||||||||||||||||||||
| prune_run_registry() { | ||||||||||||||||||||||||||
| local file pid | ||||||||||||||||||||||||||
| [ -d "$RUN_REGISTRY" ] || return 0 | ||||||||||||||||||||||||||
| for file in "$RUN_REGISTRY"/*.run; do | ||||||||||||||||||||||||||
| [ -f "$file" ] || continue | ||||||||||||||||||||||||||
| pid=$(run_record_field "$file" pid || true) | ||||||||||||||||||||||||||
| if [ -z "$pid" ] || ! pid_is_alive "$pid"; then | ||||||||||||||||||||||||||
| rm -f "$file" | ||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+232
to
+242
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Write registry records atomically.
The window is small, but concurrent runs are the stated use case: every registering run runs Write to a temporary file in the same directory, then rename it. 🛠️ Proposed fix file=$(run_registry_file "$RUN_ID")
+ local tmp_file="$file.tmp.$$"
{
printf 'run_id=%s\n' "$RUN_ID"
printf 'pid=%s\n' "$$"
printf 'root=%s\n' "$ROOT"
printf 'fm_home=%s\n' "${FM_HOME:--}"
printf 'json=%s\n' "${JSON_PATH:--}"
printf 'selection=%s\n' "$SELECTION_DESC"
printf 'started=%s\n' "$RUN_STARTED_ISO"
- } >"$file" || die "could not write run record: $file"
+ } >"$tmp_file" || die "could not write run record: $file"
+ mv -f "$tmp_file" "$file" || die "could not publish run record: $file"
RUN_REGISTERED_FILE=$fileAlso applies to: 249-258 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| register_run() { | ||||||||||||||||||||||||||
| local file | ||||||||||||||||||||||||||
| mkdir -p "$RUN_REGISTRY" || die "could not create run registry: $RUN_REGISTRY" | ||||||||||||||||||||||||||
| chmod 0700 "$RUN_REGISTRY" 2>/dev/null || true | ||||||||||||||||||||||||||
| file=$(run_registry_file "$RUN_ID") | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| printf 'run_id=%s\n' "$RUN_ID" | ||||||||||||||||||||||||||
| printf 'pid=%s\n' "$$" | ||||||||||||||||||||||||||
| printf 'root=%s\n' "$ROOT" | ||||||||||||||||||||||||||
| printf 'fm_home=%s\n' "${FM_HOME:--}" | ||||||||||||||||||||||||||
| printf 'json=%s\n' "${JSON_PATH:--}" | ||||||||||||||||||||||||||
| printf 'selection=%s\n' "$SELECTION_DESC" | ||||||||||||||||||||||||||
| printf 'started=%s\n' "$RUN_STARTED_ISO" | ||||||||||||||||||||||||||
| } >"$file" || die "could not write run record: $file" | ||||||||||||||||||||||||||
| RUN_REGISTERED_FILE=$file | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| list_runs() { | ||||||||||||||||||||||||||
| local file run_id pid root started selection printed=0 | ||||||||||||||||||||||||||
| prune_run_registry | ||||||||||||||||||||||||||
| for file in "$RUN_REGISTRY"/*.run; do | ||||||||||||||||||||||||||
| [ -f "$file" ] || continue | ||||||||||||||||||||||||||
| run_id=$(run_record_field "$file" run_id || echo unknown) | ||||||||||||||||||||||||||
| pid=$(run_record_field "$file" pid || echo unknown) | ||||||||||||||||||||||||||
| root=$(run_record_field "$file" root || echo unknown) | ||||||||||||||||||||||||||
| started=$(run_record_field "$file" started || echo unknown) | ||||||||||||||||||||||||||
| selection=$(run_record_field "$file" selection || echo unknown) | ||||||||||||||||||||||||||
| printf 'FM_TEST_RUN run_id=%s pid=%s started=%s root=%s selection=%s\n' \ | ||||||||||||||||||||||||||
| "$run_id" "$pid" "$started" "$root" "$selection" | ||||||||||||||||||||||||||
| printed=1 | ||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||
| [ "$printed" -eq 1 ] || log "no live runs registered in $RUN_REGISTRY" | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Signal one registered run and, after its grace period, whatever it left | ||||||||||||||||||||||||||
| # behind. The run's own handler writes FM_TEST_ABORTED, so its log identifies | ||||||||||||||||||||||||||
| # itself as truncated rather than looking like a short clean run. | ||||||||||||||||||||||||||
| stop_one_run() { | ||||||||||||||||||||||||||
| local file=$1 run_id pid waited child | ||||||||||||||||||||||||||
| run_id=$(run_record_field "$file" run_id || echo unknown) | ||||||||||||||||||||||||||
| pid=$(run_record_field "$file" pid || true) | ||||||||||||||||||||||||||
| if [ -z "$pid" ] || ! pid_is_alive "$pid"; then | ||||||||||||||||||||||||||
| rm -f "$file" | ||||||||||||||||||||||||||
| log "run $run_id was already gone" | ||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| log "stopping run $run_id (pid $pid)" | ||||||||||||||||||||||||||
| # The run's currently-executing test script is signalled alongside the run | ||||||||||||||||||||||||||
| # itself. Bash defers a trap until the foreground command returns, so TERMing | ||||||||||||||||||||||||||
| # only the run would leave it blocked in the middle of a long test and its | ||||||||||||||||||||||||||
| # terminator unwritten until the grace period expired and killed it silently. | ||||||||||||||||||||||||||
| for child in $(descendant_pids "$pid"); do | ||||||||||||||||||||||||||
| kill -TERM "$child" 2>/dev/null || true | ||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||
| kill -TERM "$pid" 2>/dev/null || true | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| waited=0 | ||||||||||||||||||||||||||
| while [ "$waited" -lt "$STOP_GRACE_SECONDS" ] && pid_is_alive "$pid"; do | ||||||||||||||||||||||||||
| sleep 1 | ||||||||||||||||||||||||||
| waited=$((waited + 1)) | ||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||
|
Comment on lines
+301
to
+305
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Validate
Reject a non-numeric value at configuration time. 🛡️ Proposed fix at line 129 STOP_GRACE_SECONDS="${FM_TEST_RUN_STOP_GRACE:-10}"
+case "$STOP_GRACE_SECONDS" in
+ ''|*[!0-9]*) die "FM_TEST_RUN_STOP_GRACE must be a whole number of seconds: $STOP_GRACE_SECONDS" ;;
+esac🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Anything still alive is the run's own subtree; the run itself already had | ||||||||||||||||||||||||||
| # its chance to exit cleanly. Children are collected before the final kill so | ||||||||||||||||||||||||||
| # a test script that ignored TERM cannot outlive its run. | ||||||||||||||||||||||||||
| for child in $(descendant_pids "$pid"); do | ||||||||||||||||||||||||||
| kill -KILL "$child" 2>/dev/null || true | ||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||
| if pid_is_alive "$pid"; then | ||||||||||||||||||||||||||
| kill -KILL "$pid" 2>/dev/null || true | ||||||||||||||||||||||||||
| log "run $run_id did not exit within ${STOP_GRACE_SECONDS}s; killed" | ||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||
| rm -f "$file" | ||||||||||||||||||||||||||
| printf 'FM_TEST_STOPPED run_id=%s pid=%s\n' "$run_id" "$pid" | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # With a run id, stop exactly that run. Without one, stop only the runs this | ||||||||||||||||||||||||||
| # repository root started - never a sibling worktree's run, which is the exact | ||||||||||||||||||||||||||
| # collateral damage a broad pkill causes. | ||||||||||||||||||||||||||
| stop_runs() { | ||||||||||||||||||||||||||
| local want=$1 file root matched=0 | ||||||||||||||||||||||||||
| prune_run_registry | ||||||||||||||||||||||||||
| if [ -n "$want" ]; then | ||||||||||||||||||||||||||
| # Run ids are minted by this script as fm-test-run-<ms>-<pid>. Validating | ||||||||||||||||||||||||||
| # the shape keeps a caller-supplied id from resolving to a path outside the | ||||||||||||||||||||||||||
| # registry, since the id is used to build a file this function rm -f's. | ||||||||||||||||||||||||||
| case "$want" in | ||||||||||||||||||||||||||
| */*) die "not a run id: $want (expected fm-test-run-<ms>-<pid>; see --list-runs)" ;; | ||||||||||||||||||||||||||
| fm-test-run-[0-9]*-[0-9]*) ;; | ||||||||||||||||||||||||||
| *) die "not a run id: $want (expected fm-test-run-<ms>-<pid>; see --list-runs)" ;; | ||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||
| file=$(run_registry_file "$want") | ||||||||||||||||||||||||||
| [ -f "$file" ] || die "no live run registered with id: $want (see --list-runs)" | ||||||||||||||||||||||||||
| stop_one_run "$file" | ||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||
| [ -d "$RUN_REGISTRY" ] || die "no live run registered from this root: $ROOT" | ||||||||||||||||||||||||||
| for file in "$RUN_REGISTRY"/*.run; do | ||||||||||||||||||||||||||
| [ -f "$file" ] || continue | ||||||||||||||||||||||||||
| root=$(run_record_field "$file" root || true) | ||||||||||||||||||||||||||
| [ "$root" = "$ROOT" ] || continue | ||||||||||||||||||||||||||
| stop_one_run "$file" | ||||||||||||||||||||||||||
| matched=$((matched + 1)) | ||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||
| [ "$matched" -gt 0 ] \ | ||||||||||||||||||||||||||
| || die "no live run registered from this root: $ROOT (see --list-runs)" | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Terminator for an interrupted run. Printed exactly once, to stdout, in place | ||||||||||||||||||||||||||
| # of FM_TEST_SUMMARY, so a truncated log is self-identifying. | ||||||||||||||||||||||||||
| # shellcheck disable=SC2329 # Invoked indirectly by the signal traps below. | ||||||||||||||||||||||||||
| abort_run() { | ||||||||||||||||||||||||||
| local signal=$1 status=$2 child | ||||||||||||||||||||||||||
| trap - TERM INT HUP | ||||||||||||||||||||||||||
| [ "${RUN_ABORTED:-0}" -eq 0 ] || exit "$status" | ||||||||||||||||||||||||||
| RUN_ABORTED=1 | ||||||||||||||||||||||||||
| for child in $(descendant_pids "$$"); do | ||||||||||||||||||||||||||
| kill -TERM "$child" 2>/dev/null || true | ||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||
| printf 'FM_TEST_ABORTED %s run_id=%s signal=%s completed=%s selected=%s\n' \ | ||||||||||||||||||||||||||
| "$(now_iso)" "${RUN_ID:-unknown}" "$signal" "${TOTAL:-0}" "${SELECTED_TOTAL:-0}" | ||||||||||||||||||||||||||
| exit "$status" | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # shellcheck disable=SC2329 # Invoked indirectly by the EXIT trap below. | ||||||||||||||||||||||||||
| cleanup_run() { | ||||||||||||||||||||||||||
| [ -z "${RUN_REGISTERED_FILE:-}" ] || rm -f "$RUN_REGISTERED_FILE" | ||||||||||||||||||||||||||
| [ -z "${RUN_TMP:-}" ] || rm -rf "$RUN_TMP" | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Primary family for one tests/*.test.sh basename. Unmapped scripts are | ||||||||||||||||||||||||||
| # unclassified so new tests are still runnable and visible in summaries. | ||||||||||||||||||||||||||
| family_for_basename() { | ||||||||||||||||||||||||||
|
|
@@ -1270,6 +1514,26 @@ while [ "$#" -gt 0 ]; do | |||||||||||||||||||||||||
| CHECK_COVERAGE=1 | ||||||||||||||||||||||||||
| shift | ||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||
| --list-runs) | ||||||||||||||||||||||||||
| LIST_RUNS=1 | ||||||||||||||||||||||||||
| shift | ||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||
| --stop) | ||||||||||||||||||||||||||
| STOP_REQUESTED=1 | ||||||||||||||||||||||||||
| shift | ||||||||||||||||||||||||||
| # An optional bare run id; anything starting with "-" is a later option. | ||||||||||||||||||||||||||
| if [ "$#" -gt 0 ]; then | ||||||||||||||||||||||||||
| case "$1" in | ||||||||||||||||||||||||||
| -*) ;; | ||||||||||||||||||||||||||
| *) STOP_RUN_ID=$1; shift ;; | ||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||
| --stop=*) | ||||||||||||||||||||||||||
| STOP_REQUESTED=1 | ||||||||||||||||||||||||||
| STOP_RUN_ID=${1#--stop=} | ||||||||||||||||||||||||||
| shift | ||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||
|
Comment on lines
+1532
to
+1536
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Reject an empty
🛠️ Proposed fix --stop=*)
STOP_REQUESTED=1
STOP_RUN_ID=${1#--stop=}
+ [ -n "$STOP_RUN_ID" ] \
+ || die "--stop= needs a run id (use bare --stop for this root; see --list-runs)"
shift
;;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| --aggregate-json) | ||||||||||||||||||||||||||
| [ "$#" -gt 1 ] || die "--aggregate-json requires an output path" | ||||||||||||||||||||||||||
| AGGREGATE_OUT=$2 | ||||||||||||||||||||||||||
|
|
@@ -1334,6 +1598,16 @@ if [ "$LIST_LANES" -eq 1 ]; then | |||||||||||||||||||||||||
| exit 0 | ||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if [ "$LIST_RUNS" -eq 1 ]; then | ||||||||||||||||||||||||||
| list_runs | ||||||||||||||||||||||||||
| exit 0 | ||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if [ "$STOP_REQUESTED" -eq 1 ]; then | ||||||||||||||||||||||||||
| stop_runs "$STOP_RUN_ID" | ||||||||||||||||||||||||||
| exit 0 | ||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if [ "$CHECK_COVERAGE" -eq 1 ]; then | ||||||||||||||||||||||||||
| run_coverage_guard | ||||||||||||||||||||||||||
| exit $? | ||||||||||||||||||||||||||
|
|
@@ -1443,16 +1717,26 @@ RUN_TMP=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run.XXXXXX") | |||||||||||||||||||||||||
| RECORDS="$RUN_TMP/records.tsv" | ||||||||||||||||||||||||||
| FAMILIES_TSV="$RUN_TMP/families.tsv" | ||||||||||||||||||||||||||
| : >"$RECORDS" | ||||||||||||||||||||||||||
| trap 'rm -rf "$RUN_TMP"' EXIT | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| RUN_STARTED_ISO=$(now_iso) | ||||||||||||||||||||||||||
| RUN_STARTED_MS=$(now_ms) | ||||||||||||||||||||||||||
| RUN_ID="fm-test-run-${RUN_STARTED_MS}-$$" | ||||||||||||||||||||||||||
| RUN_REGISTERED_FILE= | ||||||||||||||||||||||||||
| RUN_ABORTED=0 | ||||||||||||||||||||||||||
| TOTAL=0 | ||||||||||||||||||||||||||
| SELECTED_TOTAL=${#SCRIPTS[@]} | ||||||||||||||||||||||||||
| FAILED=0 | ||||||||||||||||||||||||||
| SKIPPED_GATE=0 | ||||||||||||||||||||||||||
| AGG_RC=0 | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Registered before the first script so a stop issued at any point during the | ||||||||||||||||||||||||||
| # run finds this run's identity, and torn down on every exit path. | ||||||||||||||||||||||||||
| trap 'cleanup_run' EXIT | ||||||||||||||||||||||||||
| trap 'abort_run TERM 143' TERM | ||||||||||||||||||||||||||
| trap 'abort_run INT 130' INT | ||||||||||||||||||||||||||
| trap 'abort_run HUP 129' HUP | ||||||||||||||||||||||||||
| register_run | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Family accumulators as TSV lines updated in-memory via temp files. | ||||||||||||||||||||||||||
| # family -> count, duration_ms, failed | ||||||||||||||||||||||||||
| family_bump() { | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Make the default registry path user-specific.
The comment states the registry is per-user, but that only holds where
TMPDIRis per-user. On Linux,TMPDIRis often unset, so the default resolves to the shared/tmp/fm-test-run-registry. Two consequences follow:mkdir -psucceeds,chmod 0700fails, and the|| trueinregister_runhides the failure. Records with repository paths become readable by other users.pidthatstop_one_runsignals.--stopthen sendsTERMandKILLto a pid this runner never started.Add the user id to the default path so runs from different users never share a directory.
🔒 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents