Skip to content

Commit 4418a34

Browse files
asg017claude
andcommitted
Add GitHub Actions CI workflow for fuzz testing
Runs on push to main, nightly at 2am UTC, and manual dispatch. Linux (ubuntu-22.04) and macOS (macos-14) are fully supported. Windows (windows-2022) is best-effort with continue-on-error. Crash artifacts are uploaded on failure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a61d451 commit 4418a34

1 file changed

Lines changed: 152 additions & 0 deletions

File tree

.github/workflows/fuzz.yaml

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
name: "Fuzz"
2+
on:
3+
push:
4+
branches: [main]
5+
schedule:
6+
# Nightly at 2am UTC for longer fuzzing sessions
7+
- cron: "0 2 * * *"
8+
workflow_dispatch:
9+
inputs:
10+
duration:
11+
description: "Fuzz duration per target (seconds)"
12+
default: "60"
13+
14+
permissions:
15+
contents: read
16+
17+
jobs:
18+
fuzz-linux:
19+
runs-on: ubuntu-22.04
20+
steps:
21+
- uses: actions/checkout@v4
22+
- name: Install LLVM 18
23+
run: |
24+
wget -qO- https://apt.llvm.org/llvm.sh | sudo bash -s -- 18
25+
echo "FUZZ_CC=clang-18" >> $GITHUB_ENV
26+
- run: ./scripts/vendor.sh
27+
- name: Build fuzz targets
28+
run: make -C tests/fuzz all FUZZ_CC=$FUZZ_CC FUZZ_LDFLAGS=
29+
- name: Run fuzz targets
30+
run: |
31+
DURATION=${{ github.event.inputs.duration || '60' }}
32+
EXIT_CODE=0
33+
for target in tests/fuzz/targets/*; do
34+
[ -f "$target" ] && [ -x "$target" ] || continue
35+
name=$(basename "$target")
36+
echo "::group::Fuzzing $name ($DURATION seconds)"
37+
corpus="tests/fuzz/corpus/$name"
38+
mkdir -p "$corpus"
39+
dict="tests/fuzz/${name//_/-}.dict"
40+
dict_flag=""
41+
[ -f "$dict" ] && dict_flag="-dict=$dict"
42+
if ! ASAN_OPTIONS=detect_leaks=1 "$target" $dict_flag \
43+
-max_total_time="$DURATION" "$corpus" 2>&1; then
44+
echo "::error::Fuzz target $name found a crash!"
45+
EXIT_CODE=1
46+
fi
47+
echo "::endgroup::"
48+
done
49+
exit $EXIT_CODE
50+
- name: Upload crash artifacts
51+
if: failure()
52+
uses: actions/upload-artifact@v4
53+
with:
54+
name: fuzz-crashes-linux
55+
path: |
56+
tests/fuzz/crash-*
57+
tests/fuzz/leak-*
58+
tests/fuzz/timeout-*
59+
60+
fuzz-macos:
61+
runs-on: macos-14
62+
steps:
63+
- uses: actions/checkout@v4
64+
- name: Install LLVM
65+
run: brew install llvm
66+
- run: ./scripts/vendor.sh
67+
- name: Build fuzz targets
68+
run: make -C tests/fuzz all FUZZ_CC=/opt/homebrew/opt/llvm/bin/clang
69+
- name: Run fuzz targets
70+
env:
71+
DYLD_LIBRARY_PATH: "/opt/homebrew/opt/llvm/lib/c++:${{ env.DYLD_LIBRARY_PATH }}"
72+
run: |
73+
DURATION=${{ github.event.inputs.duration || '60' }}
74+
EXIT_CODE=0
75+
for target in tests/fuzz/targets/*; do
76+
[ -f "$target" ] && [ -x "$target" ] || continue
77+
name=$(basename "$target")
78+
echo "::group::Fuzzing $name ($DURATION seconds)"
79+
corpus="tests/fuzz/corpus/$name"
80+
mkdir -p "$corpus"
81+
dict="tests/fuzz/${name//_/-}.dict"
82+
dict_flag=""
83+
[ -f "$dict" ] && dict_flag="-dict=$dict"
84+
if ! "$target" $dict_flag \
85+
-max_total_time="$DURATION" "$corpus" 2>&1; then
86+
echo "::error::Fuzz target $name found a crash!"
87+
EXIT_CODE=1
88+
fi
89+
echo "::endgroup::"
90+
done
91+
exit $EXIT_CODE
92+
- name: Upload crash artifacts
93+
if: failure()
94+
uses: actions/upload-artifact@v4
95+
with:
96+
name: fuzz-crashes-macos
97+
path: |
98+
tests/fuzz/crash-*
99+
tests/fuzz/leak-*
100+
tests/fuzz/timeout-*
101+
102+
fuzz-windows:
103+
# Best-effort: libFuzzer works on Windows via LLVM but ASAN/UBSAN
104+
# support is less reliable. Leak detection is not available.
105+
runs-on: windows-2022
106+
continue-on-error: true
107+
steps:
108+
- uses: actions/checkout@v4
109+
- name: Install LLVM 18
110+
run: choco install llvm --version=18.1.8 -y
111+
- run: bash ./scripts/vendor.sh
112+
shell: bash
113+
- name: Build fuzz targets
114+
shell: bash
115+
run: |
116+
export PATH="/c/Program Files/LLVM/bin:$PATH"
117+
cd tests/fuzz
118+
mkdir -p targets
119+
for src in *.c; do
120+
name="${src%.c}"
121+
target_name="${name//-/_}"
122+
echo "Building $target_name from $src"
123+
clang -fsanitize=address,fuzzer \
124+
-I ../../ -I ../../vendor -DSQLITE_CORE -g \
125+
../../vendor/sqlite3.c ../../sqlite-vec.c \
126+
"$src" -o "targets/${target_name}.exe" || {
127+
echo "Warning: failed to build $target_name (best-effort)"
128+
}
129+
done
130+
- name: Run fuzz targets
131+
shell: bash
132+
run: |
133+
export PATH="/c/Program Files/LLVM/bin:$PATH"
134+
DURATION=${{ github.event.inputs.duration || '60' }}
135+
for target in tests/fuzz/targets/*.exe; do
136+
[ -f "$target" ] || continue
137+
name=$(basename "$target" .exe)
138+
echo "=== Fuzzing $name ($DURATION seconds) ==="
139+
corpus="tests/fuzz/corpus/$name"
140+
mkdir -p "$corpus"
141+
"$target" -max_total_time="$DURATION" "$corpus" 2>&1 || {
142+
echo "Warning: $name found an issue or failed"
143+
}
144+
done
145+
- name: Upload crash artifacts
146+
if: failure()
147+
uses: actions/upload-artifact@v4
148+
with:
149+
name: fuzz-crashes-windows
150+
path: |
151+
tests/fuzz/crash-*
152+
tests/fuzz/leak-*

0 commit comments

Comments
 (0)