Skip to content

Commit e1ea222

Browse files
authored
Merge branch 'main' into fix/sidebar-show-node-and-details
2 parents 2147732 + 97ce23f commit e1ea222

23 files changed

Lines changed: 727 additions & 137 deletions

‎Makefile.cbm‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,18 @@ EDITOR_TEST_DEFINES = -DCBM_JSON_LIKE_ENABLE_TEST_API=1 \
6767
-DCBM_TOML_EDIT_ENABLE_TEST_API=1 -DCBM_YAML_ENABLE_TEST_API=1 \
6868
-DCBM_TEXT_EDIT_ENABLE_TEST_API=1 -DCBM_CLI_ENABLE_TEST_API=1 \
6969
-DCBM_DIAGNOSTICS_ENABLE_TEST_API=1
70-
CFLAGS_TEST = $(CFLAGS_COMMON) $(EDITOR_TEST_DEFINES) -g -O1 $(SANITIZE)
71-
CXXFLAGS_TEST = $(CXXFLAGS_COMMON) -g -O1 $(SANITIZE)
70+
# The build system is the single source of truth for "is this binary
71+
# instrumented": compiler-specific probes (__SANITIZE_ADDRESS__) miss
72+
# clang's feature-check spelling and every non-ASan sanitizer, so the
73+
# trap-UBSan leg once ran NATIVE timing budgets on an instrumented
74+
# binary. Any non-empty SANITIZE ⇒ sanitized budgets everywhere.
75+
ifneq ($(strip $(SANITIZE)),)
76+
SANITIZED_DEFINE = -DCBM_SANITIZED_BUILD=1
77+
else
78+
SANITIZED_DEFINE =
79+
endif
80+
CFLAGS_TEST = $(CFLAGS_COMMON) $(EDITOR_TEST_DEFINES) $(SANITIZED_DEFINE) -g -O1 $(SANITIZE)
81+
CXXFLAGS_TEST = $(CXXFLAGS_COMMON) $(SANITIZED_DEFINE) -g -O1 $(SANITIZE)
7282

7383
# TSan (can't combine with ASan)
7484
TSAN_SANITIZE = -fsanitize=thread -fno-omit-frame-pointer

‎scripts/run-tests-parallel.sh‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,17 @@ stamp_windows_build_dir() {
5454
MINGW* | MSYS*) ;;
5555
*) return 0 ;;
5656
esac
57-
local runner_dir_w me stamp_out reset_out
57+
local runner_dir_w me norm_out stamp_out reset_out
5858
runner_dir_w="$(cygpath -w "$(dirname "$RUNNER")")"
5959
me="$(whoami | tr -d '\r')"
60+
# Normalize FIRST: some runner images stamp EXPLICIT (non-inherited)
61+
# Authenticated-Users ACEs onto the workspace tree, which /inheritance:r
62+
# cannot strip and /grant:r does not touch (it replaces only the granted
63+
# SIDs' own entries). /reset drops every explicit ACE and restores pure
64+
# inheritance, so the protect-and-grant below starts from a known shape
65+
# regardless of image provisioning.
66+
norm_out=$(MSYS2_ARG_CONV_EXCL='*' icacls "$runner_dir_w" /reset /Q 2>&1) ||
67+
echo "WARN: build-dir DACL normalize ($when) failed: $norm_out"
6068
stamp_out=$(MSYS2_ARG_CONV_EXCL='*' icacls "$runner_dir_w" /inheritance:r \
6169
/grant:r "${me}:(OI)(CI)F" '*S-1-5-18:(OI)(CI)F' '*S-1-5-32-544:(OI)(CI)F' \
6270
/Q 2>&1) || echo "WARN: build-dir DACL stamp ($when) failed (user=$me dir=$runner_dir_w): $stamp_out"
@@ -67,8 +75,9 @@ stamp_windows_build_dir() {
6775
# even see WHETHER it had run.
6876
if MSYS2_ARG_CONV_EXCL='*' icacls "$runner_dir_w" 2>/dev/null |
6977
grep -qE 'Authenticated Users|CREATOR OWNER'; then
70-
echo "WARN: build-dir DACL still grants cross-account mutation after $when stamp:"
78+
echo "FAIL: build-dir DACL still grants cross-account mutation after $when stamp:"
7179
MSYS2_ARG_CONV_EXCL='*' icacls "$runner_dir_w" 2>&1 | head -8
80+
exit 1
7281
else
7382
echo "build-dir DACL stamped clean ($when, $runner_dir_w, user=$me)"
7483
fi
@@ -215,6 +224,14 @@ run_one() {
215224
fi
216225
secs=$((SECONDS - t0))
217226
summary=$(grep -E '^ [0-9]+ passed' "$LOGDIR/$s.log" | tail -1)
227+
# A suite that exits 0 WITHOUT printing its completion summary ran zero
228+
# tests as far as anyone can prove (mis-parsed argv, drifted registration
229+
# macro, early return) — that is a failure, not a green with pass=0.
230+
if [ "$rc" -eq 0 ] && [ -z "$summary" ]; then
231+
rc=97
232+
echo " FAIL: suite '$s' exited 0 without a completion summary (ran nothing?)" \
233+
>> "$LOGDIR/$s.log"
234+
fi
218235
pass=$(printf '%s' "$summary" | sed -n 's/^ \([0-9]*\) passed.*/\1/p')
219236
failn=$(printf '%s' "$summary" | sed -n 's/.* \([0-9]*\) failed.*/\1/p')
220237
skip=$(printf '%s' "$summary" | sed -n 's/.* \([0-9]*\) skipped.*/\1/p')

‎scripts/security-fuzz-random.sh‎

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4+
# The fuzzer IS python3; without it this gate would run zero mutations while
5+
# still reporting green. Fail loudly instead.
6+
command -v python3 >/dev/null 2>&1 || {
7+
echo "FAIL: python3 missing — the fuzz gate cannot generate payloads" >&2
8+
exit 1
9+
}
10+
# Reproducibility: every payload derives from a logged seed, so any crash is
11+
# re-runnable by construction (CBM_FUZZ_SEED overrides for replay).
12+
FUZZ_SEED="${CBM_FUZZ_SEED:-$$}"
13+
echo "fuzz seed: $FUZZ_SEED (replay: CBM_FUZZ_SEED=$FUZZ_SEED)"
14+
415
# Fuzz testing: feeds random/mutated inputs to the MCP server and CLI
516
# to find crashes, hangs, and memory errors. Runs for a limited time.
617
#
@@ -25,11 +36,15 @@ END_TIME=$((SECONDS + DURATION))
2536

2637
# Portable timeout
2738
run_with_timeout() {
39+
# PRESERVES the child's exit status: the whole gate exists to observe
40+
# crash codes (139/134/136), and an `|| true` here once made EC
41+
# unconditionally 0 — the crash checks were dead code and a SIGSEGV
42+
# read green. Timeout reports 124, which the checks correctly ignore.
2843
local secs="$1"; shift
2944
if command -v timeout &>/dev/null; then
30-
timeout "$secs" "$@" || true
45+
timeout "$secs" "$@"
3146
else
32-
perl -e "alarm($secs); exec @ARGV" -- "$@" || true
47+
perl -e "alarm($secs); exec @ARGV" -- "$@"
3348
fi
3449
}
3550

@@ -43,6 +58,7 @@ while [ $SECONDS -lt $END_TIME ]; do
4358
# Generate a random mutated JSON-RPC payload
4459
PAYLOAD=$(python3 -c "
4560
import random, string, json
61+
random.seed($FUZZ_SEED + $ITERATIONS)
4662
4763
# Base valid request
4864
base = {
@@ -111,8 +127,8 @@ except:
111127
printf '%s\n%s\n' "$INIT" "$PAYLOAD" > "$FUZZ_TMPDIR/input.jsonl"
112128

113129
# Run and check for crashes (not exit code — we expect errors, just not crashes)
114-
run_with_timeout 5 "$BINARY" < "$FUZZ_TMPDIR/input.jsonl" > /dev/null 2>&1
115-
EC=$?
130+
EC=0
131+
run_with_timeout 5 "$BINARY" < "$FUZZ_TMPDIR/input.jsonl" > /dev/null 2>&1 || EC=$?
116132

117133
# 139 = SIGSEGV, 134 = SIGABRT, 136 = SIGFPE — real crashes
118134
if [ $EC -eq 139 ] || [ $EC -eq 134 ] || [ $EC -eq 136 ]; then
@@ -135,7 +151,8 @@ while [ $SECONDS -lt $CLI_END ]; do
135151
CLI_ITERATIONS=$((CLI_ITERATIONS + 1))
136152

137153
QUERY=$(python3 -c "
138-
import random, string
154+
import random
155+
random.seed($FUZZ_SEED + 100000 + $CLI_ITERATIONS), string
139156
parts = ['MATCH', 'WHERE', 'RETURN', '(', ')', '-[', ']->', '<-[', ']-',
140157
'n', 'r', '.name', '.label', '=', '\"', \"'\", ';', '--', 'DROP',
141158
'DELETE', 'ATTACH', 'DETACH', '*', 'COUNT', 'LIMIT', 'ORDER BY']
@@ -146,7 +163,8 @@ if random.random() > 0.5:
146163
print(q)
147164
" 2>/dev/null || echo "MATCH (n) RETURN n")
148165

149-
run_with_timeout 3 "$BINARY" cli query_graph "{\"query\":\"$QUERY\"}" > /dev/null 2>&1
166+
EC=0
167+
run_with_timeout 3 "$BINARY" cli query_graph "{\"query\":\"$QUERY\"}" > /dev/null 2>&1 || EC=$?
150168
EC=$?
151169

152170
if [ $EC -eq 139 ] || [ $EC -eq 134 ] || [ $EC -eq 136 ]; then

‎scripts/smoke-test.sh‎

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ retire_account_daemon() {
8282
fi
8383
}
8484

85+
# Tolerate ERRORS, never CRASHES: the adversarial phases assert "doesn't
86+
# crash", and a bare `|| true` discards exactly the crash exit the claim is
87+
# about. rc >= 128 means killed by signal / fatal status (SIGSEGV=139,
88+
# SIGABRT=134 — ASan aborts land here too).
89+
run_no_crash() {
90+
local label="$1"; shift
91+
local rc=0
92+
"$@" > /dev/null 2>&1 || rc=$?
93+
if [ "$rc" -ge 128 ]; then
94+
echo "FAIL $label: crashed (rc=$rc)"
95+
exit 1
96+
fi
97+
return 0
98+
}
99+
85100
TMPDIR=$(smoke_mktemp_dir)
86101
DRYRUN_HOME=""
87102
# On MSYS2/Windows, convert POSIX path to native Windows path for the binary
@@ -2596,7 +2611,12 @@ echo "--- Phase 9b: adversarial install/uninstall tests ---"
25962611
# completes without crash and prints "Detected agents:" line.
25972612
EMPTY_HOME=$(smoke_mktemp_dir)
25982613
mkdir -p "$EMPTY_HOME/.local/bin"
2599-
INSTALL_OUT=$(HOME="$EMPTY_HOME" LOCALAPPDATA="$EMPTY_HOME/AppData/Local" "$BINARY" install -y 2>&1) || true
2614+
INSTALL_RC=0
2615+
INSTALL_OUT=$(HOME="$EMPTY_HOME" LOCALAPPDATA="$EMPTY_HOME/AppData/Local" "$BINARY" install -y 2>&1) || INSTALL_RC=$?
2616+
if [ "$INSTALL_RC" -ge 128 ]; then
2617+
echo "FAIL 9b-1: install crashed (rc=$INSTALL_RC)"
2618+
exit 1
2619+
fi
26002620
if ! echo "$INSTALL_OUT" | grep -qi 'detected agents'; then
26012621
echo "FAIL 9b-1: install output missing 'Detected agents' line"
26022622
exit 1
@@ -2609,12 +2629,12 @@ rm -rf "$EMPTY_HOME"
26092629
IDEM_HOME=$(smoke_mktemp_dir)
26102630
mkdir -p "$IDEM_HOME/.claude" "$IDEM_HOME/.local/bin"
26112631
copy_smoke_binary "$IDEM_HOME/.local/bin/codebase-memory-mcp"
2612-
HOME="$IDEM_HOME" LOCALAPPDATA="$IDEM_HOME/AppData/Local" "$BINARY" install -y 2>&1 > /dev/null || true
2632+
run_no_crash 9b-2 env HOME="$IDEM_HOME" LOCALAPPDATA="$IDEM_HOME/AppData/Local" "$BINARY" install -y
26132633
IDEM_INSTALLER="$BINARY"
26142634
if [[ "$BINARY" == *.exe ]]; then
26152635
IDEM_INSTALLER="$IDEM_HOME/.local/bin/codebase-memory-mcp.exe"
26162636
fi
2617-
HOME="$IDEM_HOME" LOCALAPPDATA="$IDEM_HOME/AppData/Local" "$IDEM_INSTALLER" install -y 2>&1 > /dev/null || true
2637+
run_no_crash 9b-2-second env HOME="$IDEM_HOME" LOCALAPPDATA="$IDEM_HOME/AppData/Local" "$IDEM_INSTALLER" install -y
26182638
# Count MCP entries — should be exactly 1
26192639
COUNT=$(cat "$IDEM_HOME/.claude.json" 2>/dev/null | python3 -c "
26202640
import json, sys
@@ -2632,7 +2652,12 @@ rm -rf "$IDEM_HOME"
26322652
# 9b-3: Uninstall without prior install
26332653
CLEAN_HOME=$(smoke_mktemp_dir)
26342654
mkdir -p "$CLEAN_HOME/.claude" "$CLEAN_HOME/.local/bin"
2635-
UNINSTALL_OUT=$(HOME="$CLEAN_HOME" "$BINARY" uninstall -y -n 2>&1) || true
2655+
UNINSTALL_RC=0
2656+
UNINSTALL_OUT=$(HOME="$CLEAN_HOME" "$BINARY" uninstall -y -n 2>&1) || UNINSTALL_RC=$?
2657+
if [ "$UNINSTALL_RC" -ge 128 ]; then
2658+
echo "FAIL 9b-3: uninstall crashed (rc=$UNINSTALL_RC)"
2659+
exit 1
2660+
fi
26362661
echo "OK 9b-3: uninstall without install doesn't crash"
26372662
retire_account_daemon "9b-3-cleanup"
26382663
rm -rf "$CLEAN_HOME"
@@ -2642,7 +2667,7 @@ CORRUPT_HOME=$(smoke_mktemp_dir)
26422667
mkdir -p "$CORRUPT_HOME/.claude" "$CORRUPT_HOME/.local/bin"
26432668
copy_smoke_binary "$CORRUPT_HOME/.local/bin/codebase-memory-mcp"
26442669
echo '{invalid json here' > "$CORRUPT_HOME/.claude.json"
2645-
HOME="$CORRUPT_HOME" "$BINARY" install -y 2>&1 > /dev/null || true
2670+
run_no_crash 9b-4 env HOME="$CORRUPT_HOME" "$BINARY" install -y
26462671
# Should either fix it or handle gracefully — not crash
26472672
echo "OK 9b-4: install over corrupt JSON doesn't crash"
26482673
retire_account_daemon "9b-4-cleanup"
@@ -2652,13 +2677,13 @@ rm -rf "$CORRUPT_HOME"
26522677
DBL_HOME=$(smoke_mktemp_dir)
26532678
mkdir -p "$DBL_HOME/.claude" "$DBL_HOME/.local/bin"
26542679
copy_smoke_binary "$DBL_HOME/.local/bin/codebase-memory-mcp"
2655-
HOME="$DBL_HOME" "$BINARY" install -y 2>&1 > /dev/null || true
2680+
run_no_crash 9b-8-install env HOME="$DBL_HOME" "$BINARY" install -y
26562681
DBL_UNINSTALLER="$BINARY"
26572682
if [[ "$BINARY" == *.exe ]]; then
26582683
DBL_UNINSTALLER="$DBL_HOME/.local/bin/codebase-memory-mcp.exe"
26592684
fi
2660-
HOME="$DBL_HOME" "$DBL_UNINSTALLER" uninstall -y -n 2>&1 > /dev/null || true
2661-
HOME="$DBL_HOME" "$BINARY" uninstall -y -n 2>&1 > /dev/null || true
2685+
run_no_crash 9b-8-first env HOME="$DBL_HOME" "$DBL_UNINSTALLER" uninstall -y -n
2686+
run_no_crash 9b-8-second env HOME="$DBL_HOME" "$BINARY" uninstall -y -n
26622687
echo "OK 9b-8: double uninstall doesn't crash"
26632688
retire_account_daemon "9b-8-cleanup"
26642689
rm -rf "$DBL_HOME"
@@ -2783,7 +2808,8 @@ echo "=== Phase 11: process kill E2E ==="
27832808
# Start MCP server in background
27842809
MCP_KILL_INPUT=$(smoke_mktemp_file)
27852810
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"kill-test","version":"1.0"}}}' > "$MCP_KILL_INPUT"
2786-
"$BINARY" < "$MCP_KILL_INPUT" > /dev/null 2>&1 &
2811+
KILL_OUT=$(smoke_mktemp_file)
2812+
"$BINARY" < "$MCP_KILL_INPUT" > "$KILL_OUT" 2>&1 &
27872813
KILL_PID=$!
27882814
sleep 1
27892815

@@ -2798,8 +2824,20 @@ if kill -0 "$KILL_PID" 2>/dev/null; then
27982824
fi
27992825
echo "OK 11c-d: process killed successfully"
28002826
else
2801-
echo "OK 11: MCP server already exited (clean shutdown on EOF)"
2827+
# The server ended before the probe: distinguish clean shutdown-on-EOF
2828+
# from an instant crash — the old branch treated both as OK with the
2829+
# output discarded, so a startup segfault read as a clean pass on any
2830+
# fast machine. A clean run must have exited 0 AND produced the
2831+
# initialize response.
2832+
KILL_RC=0
2833+
wait "$KILL_PID" 2>/dev/null || KILL_RC=$?
2834+
if [ "$KILL_RC" -ge 128 ] || ! grep -q '"jsonrpc"' "$KILL_OUT"; then
2835+
echo "FAIL 11: server exited before probe with rc=$KILL_RC and $(wc -c < "$KILL_OUT") bytes of output — crash, not clean EOF shutdown"
2836+
exit 1
2837+
fi
2838+
echo "OK 11: MCP server already exited (clean shutdown on EOF, initialize answered)"
28022839
fi
2840+
rm -f "$KILL_OUT"
28032841

28042842
rm -f "$MCP_KILL_INPUT"
28052843

@@ -3228,13 +3266,24 @@ fi
32283266
echo ""
32293267
echo "=== Phase 15: UI HTTP server ==="
32303268

3231-
UI_PORT=19876
3269+
# A kernel-assigned free port instead of a fixed one: a squatter on a fixed
3270+
# port made the whole phase read as SKIP. The tiny TOCTOU window between
3271+
# probe and bind is the residual risk, not the common case.
3272+
UI_PORT=$(python3 -c 'import socket; s = socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()')
32323273
UI_INPUT=$(smoke_mktemp_file)
32333274
"$BINARY" --port "$UI_PORT" < "$UI_INPUT" > /dev/null 2>&1 &
32343275
UI_PID=$!
3235-
sleep 1
3276+
# Readiness poll instead of a fixed sleep: SKIP is legitimate ONLY when the
3277+
# process exited (the documented no-embedded-assets case); a slow start on a
3278+
# loaded runner must not masquerade as it.
3279+
UI_READY=0
3280+
for _ in $(seq 1 100); do
3281+
if ! kill -0 "$UI_PID" 2>/dev/null; then break; fi
3282+
if curl -sf "http://127.0.0.1:$UI_PORT/" -o /dev/null 2>/dev/null; then UI_READY=1; break; fi
3283+
sleep 0.1
3284+
done
32363285

3237-
if kill -0 "$UI_PID" 2>/dev/null; then
3286+
if [ "$UI_READY" -eq 1 ] || kill -0 "$UI_PID" 2>/dev/null; then
32383287
# 15a: GET / returns 200 with HTML content
32393288
UI_BODY=$(curl -sf "http://127.0.0.1:$UI_PORT/" 2>/dev/null || echo "")
32403289
if echo "$UI_BODY" | grep -qi "<html"; then
@@ -3258,6 +3307,8 @@ if kill -0 "$UI_PID" 2>/dev/null; then
32583307
echo "SKIP 15b: /rpc not reachable"
32593308
else
32603309
echo "FAIL 15b: /rpc did not return JSON-RPC"
3310+
kill "$UI_PID" 2>/dev/null || true
3311+
exit 1
32613312
fi
32623313

32633314
kill "$UI_PID" 2>/dev/null || true

‎scripts/soak-test.sh‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,19 @@ mcp_call() {
340340
MCP_LAST_RESPONSE="$resp"
341341
if json_rpc_response_ok "$id" "$resp"; then
342342
exit_code=0
343+
else
344+
# A stale (previous, late) response desyncs every later
345+
# round-trip: drain queued lines until the matching id shows up
346+
# or the stream runs dry, so one slow reply costs one failed op
347+
# instead of corrupting the whole run's verdicts.
348+
local drain=""
349+
while read -r -t 2 drain <&4 2>/dev/null; do
350+
MCP_LAST_RESPONSE="$drain"
351+
if json_rpc_response_ok "$id" "$drain"; then
352+
exit_code=0
353+
break
354+
fi
355+
done
343356
fi
344357
fi
345358
local t1
@@ -426,7 +439,9 @@ wait_for_diagnostics_snapshot() {
426439
return 1
427440
}
428441

442+
SNAPSHOT_ATTEMPTS=0
429443
collect_snapshot() {
444+
SNAPSHOT_ATTEMPTS=$((SNAPSHOT_ATTEMPTS + 1))
430445
refresh_diagnostics_paths || return 0
431446
if [ -f "$DIAG_FILE" ]; then
432447
python3 -c "
@@ -681,6 +696,16 @@ fi
681696

682697
# ── Analysis ─────────────────────────────────────────────────────
683698

699+
# Check 0: the analysis must have DATA. Snapshot collection silently
700+
# tolerates transient misses, so a run where diagnostics never materialized
701+
# used to sail through every check vacuously — the leak/FD/latency verdicts
702+
# below are only meaningful if most sampling attempts actually landed.
703+
SNAPSHOT_ROWS=$(($(wc -l < "$METRICS_CSV") - 1))
704+
if [ "$SNAPSHOT_ATTEMPTS" -gt 0 ] && [ $((SNAPSHOT_ROWS * 10)) -lt $((SNAPSHOT_ATTEMPTS * 6)) ]; then
705+
echo "FAIL: only ${SNAPSHOT_ROWS}/${SNAPSHOT_ATTEMPTS} snapshots collected — analysis would be vacuous" | tee -a "$SUMMARY"
706+
PASS=false
707+
fi
708+
684709
# Check 1: Memory leak detection via RSS trend
685710
# This is the primary leak detector on ALL platforms (including Windows
686711
# where LeakSanitizer is unavailable). Catches both linear leaks (slope)

‎scripts/test-windows.ps1‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,16 @@ foreach ($t in $guards) {
227227
} elseif ($code -eq 1 -or $t -eq "tests\windows\test_windows_launcher.py") {
228228
Write-Host "RED ($t) - REGRESSION: a fixed Windows bug is broken again" -ForegroundColor Red
229229
$guardFailures += $t
230-
} else {
231-
Write-Host "PRECONDITION ($t) exit=$code - skipped (see message above)" -ForegroundColor Yellow
230+
} elseif ($code -eq 2) {
231+
# Exit 2 is the guards' DOCUMENTED precondition-skip contract; every
232+
# other unexpected code (a crashed python, an access-violation status,
233+
# a mistyped guard) is a FAILURE - an uncontracted exit once let a
234+
# crashing guard read as an invisible skip under a green banner.
235+
Write-Host "PRECONDITION ($t) exit=2 - skipped (see message above)" -ForegroundColor Yellow
232236
$guardSkips += $t
237+
} else {
238+
Write-Host "FAILED ($t) exit=$code - crashed or exited outside the guard contract 0/1/2" -ForegroundColor Red
239+
$guardFailures += $t
233240
}
234241
}
235242

@@ -266,6 +273,10 @@ if ($guardSkips.Count -gt 0) {
266273
if ($fixedKeepers.Count -gt 0) {
267274
Write-Host ("Known-red repros that are now GREEN (promote to guards): {0}" -f ($fixedKeepers -join ", ")) -ForegroundColor Green
268275
}
276+
if ($guardSkips.Count -eq $guards.Count -and $guards.Count -gt 0) {
277+
Write-Host "FAIL: every guard skipped - nothing was actually verified" -ForegroundColor Red
278+
exit 1
279+
}
269280
if ($guardFailures.Count -gt 0) {
270281
Write-Host ("REGRESSION: {0} green guard(s) went red: {1}" -f $guardFailures.Count, ($guardFailures -join ", ")) -ForegroundColor Red
271282
Write-Host "A previously-fixed Windows bug is broken again (see the guard's docstring and its referenced issue)." -ForegroundColor Red

0 commit comments

Comments
 (0)