summaryrefslogtreecommitdiff
path: root/src/fuzz_test.sh
blob: 131b6a005a9eaf27119e3463e69d286f2731bb81 (plain)
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/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