#!/bin/sh # Long-running fuzz loop per TESTING_PLAN.md's "Fuzz testing" section: # repeatedly draw a fresh random batch of setboard/go records from the full # 200k-position tests/cross_sectional_200k.ep_ pool, run them through a # DEBUG=1 build under gdb (so any assert/SIGTRAP/SIGSEGV leaves a full # backtrace instead of just a bare crash), shallow-depth, small/no hash # (setboard memsets the hash table as a side effect, so a large --hash # wastes time on every single record), --cpus 2 to exercise split.c's # parallel search path too. Runs until stopped (^C) or a crash is found. # # Usage: ./fuzz_test.sh [binary] [depth] [batch_size] [cpus] BIN="${1:-./_typhoon}" DEPTH="${2:-6}" BATCH="${3:-200}" CPUS="${4:-2}" SUITE=../tests/cross_sectional_200k.ep_ LOGDIR=/usr/local/tmp/typhoon/fuzz mkdir -p "$LOGDIR" if [ ! -x "$BIN" ]; then echo "ERROR: $BIN not found/executable (build first: gmake clean; gmake DEBUG=1 MP=1)" exit 1 fi if [ ! -f "$SUITE" ]; then echo "ERROR: $SUITE not found (run from src/)" exit 1 fi # Copy the binary out to a stable, private path rather than exec'ing $BIN # in place. A concurrent "gmake clean" (e.g. update_head_reference.sh, # precommit_check.sh) unlinks ./_typhoon/./typhoon out from under a loop # that's mid-run, silently breaking every subsequent iteration with # "not found/executable" -- observed live running this alongside # update_head_reference.sh. Copying once up front means later rebuilds in # the working tree can't touch what this loop is actually running. FUZZ_BIN="$LOGDIR/fuzz_bin_$$" cp "$BIN" "$FUZZ_BIN" chmod +x "$FUZZ_BIN" trap 'rm -f "$FUZZ_BIN"' EXIT BIN="$FUZZ_BIN" TOTAL_RECORDS=$(grep -c '^go' "$SUITE") ITER=0 while :; do ITER=$((ITER + 1)) SAMPLE="$LOGDIR/batch_${ITER}.ep_" awk -v k="$BATCH" -v n="$TOTAL_RECORDS" 'BEGIN { srand() } { sub(/\r$/, "") } /^setboard/ { rec = $0; next } /^go[ \t]*$/ { rec = rec "\n" $0; c++; recs[c] = rec; rec = ""; next } rec != "" { rec = rec "\n" $0 } END { picked = 0 while (picked < k && picked < c) { r = int(rand() * c) + 1 if (!(r in used)) { used[r] = 1; print recs[r]; picked++ } } }' "$SUITE" > "$SAMPLE" STAMP=$(date +%Y%m%d_%H%M%S) LOG="$LOGDIR/run_${ITER}_${STAMP}.log" GDBOUT="$LOGDIR/run_${ITER}_${STAMP}.gdb.txt" echo "=== iter $ITER: $BATCH positions from $SUITE (sd $DEPTH, cpus $CPUS) -> $GDBOUT ===" gdb -q -batch \ -ex "set pagination off" \ -ex run \ -ex "bt full" \ -ex "info registers" \ --args "$BIN" --cpus "$CPUS" --hash 1m --logfile "$LOG" \ --batch --command "force; book name /nonexistent.book.bin; sd $DEPTH; script $SAMPLE" \ > "$GDBOUT" 2>&1 STATUS=$? if grep -qE 'Program terminated with signal (SIGSEGV|SIGABRT|SIGBUS|SIGILL|SIGFPE)|Assertion failure' "$GDBOUT"; then echo "*** CRASH/ASSERT DETECTED on iter $ITER -- see $GDBOUT ***" echo " positions: $SAMPLE" echo " engine log: $LOG" exit 1 fi echo " iter $ITER clean (exit=$STATUS)" done