From qa-fuzz-testing
Author and run Go's native fuzzing (Go 1.18+) - coverage-guided fuzzing built into the standard testing package via FuzzXxx functions. Covers f.Add seed-corpus declaration, f.Fuzz callback signature with typed parameters, testdata/fuzz/<FuzzXxx>/ directory layout for seeds + regression cases, the -fuzz flag for `go test`, and CI integration via short smoke runs. Use for fuzz testing Go libraries - Go's native approach integrates seamlessly with standard `go test` rather than requiring a separate toolchain like AFL++.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-fuzz-testing:go-native-fuzzingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [go.dev/doc/security/fuzz](https://go.dev/doc/security/fuzz),
Per go.dev/doc/security/fuzz, unlike libFuzzer / AFL++, Go's native fuzzer is:
f.Fuzz(func(t *testing.T, s string, n int) { ... })For sanitiser pairing: Go uses the race detector (-race) as
its TSan-equivalent; ASan-equivalent comes via gcflags (limited).
For corpus discipline see
corpus-management-reference.
go test.For binary-level fuzzing of Go programs, AFL++ in -Q mode also
works.
In any _test.go file alongside your unit tests:
package parser
import "testing"
func FuzzParseQuery(f *testing.F) {
f.Add("SELECT * FROM users WHERE id = 1")
f.Add("INSERT INTO foo VALUES (1, 'bar')")
f.Add("")
f.Fuzz(func(t *testing.T, q string) {
result, err := ParseQuery(q)
if err != nil {
return
}
if result == nil {
t.Fatalf("ParseQuery returned nil result with no error for %q", q)
}
})
}
Fuzz*testing.Ff.Add(seed1, seed2, ...) adds seed inputsf.Fuzz(fn) registers the fuzz callback; fn takes
*testing.T followed by typed parametersGo's fuzzer supports these types as fuzz parameters:
| Type | Notes |
|---|---|
[]byte | Variable-length byte slices |
string | Variable-length strings |
bool | Single byte |
byte, rune | Integers |
int, int8, int16, int32, int64 | Signed integers |
uint, uint8, uint16, uint32, uint64 | Unsigned integers |
float32, float64 | Floats |
Multi-parameter functions are fuzzed jointly:
f.Fuzz(func(t *testing.T, port int, host string, body []byte) {
handleRequest(host, port, body)
})
The fuzzer mutates all parameters together.
Two sources for seeds:
f.Add(...) calls - versioned in code, executed
on every test run.testdata/fuzz/FuzzXxx/ - versioned text files,
one per seed, in the same package.The seed file format (per Go docs):
go test fuzz v1
string("SELECT * FROM users WHERE id = 1")
int(42)
For multi-parameter targets, each line corresponds to a parameter in order.
When go test -fuzz=Xxx finds a failure, it writes the failing
input to testdata/fuzz/FuzzXxx/<sha256>. On the next go test
run, this file becomes a regular regression test that must pass -
no more -fuzz flag needed.
This is the unique strength of Go's approach: failing inputs become permanent regression coverage as part of the test fixture.
# Run the unit tests + seeds (no exploration)
go test ./parser/
# Fuzz a specific target for 30 seconds
go test -fuzz=FuzzParseQuery -fuzztime=30s ./parser/
# Fuzz indefinitely (CI long-running)
go test -fuzz=FuzzParseQuery ./parser/
| Flag | Effect |
|---|---|
-fuzz=NAME | Run the fuzz target with the given name |
-fuzztime=DURATION | Stop after duration (e.g., 30s, 1h) or -1 for indefinite |
-fuzzminimizetime=DURATION | Time spent minimising failures (default 1m) |
-fuzzcachedir=PATH | Where to cache mutations (default $GOCACHE/fuzz/) |
-parallel=N | Concurrent workers |
-race | Enable race detector (TSan-equivalent) |
Pair fuzzing with the race detector for thread-safety bugs:
go test -race -fuzz=FuzzConcurrentAccess -fuzztime=10m ./...
When a failure occurs, Go prints:
--- FAIL: FuzzParseQuery (3.45s)
--- FAIL: FuzzParseQuery/c1d4e1...
fuzz: minimizing 50-byte failing input file
--- FAIL: FuzzParseQuery (0.00s)
parser_test.go:18: ParseQuery returned nil result with no error for "..."
Failing input written to testdata/fuzz/FuzzParseQuery/c1d4e1abc...
To re-run:
go test -run=FuzzParseQuery/c1d4e1abc... ./parser/
Per the Go docs, the failing input lives at
testdata/fuzz/FuzzXxx/<hash> - commit it as part of the fix
to lock in regression coverage.
go test -run=FuzzParseQuery/c1d4e1abc... ./parser/
This treats the saved file as a regular t.Run sub-test, no
fuzzing.
- uses: actions/setup-go@v5
with: { go-version: '1.22' }
- name: Run tests (incl. seeds)
run: go test -race ./...
- name: Smoke fuzz (3 min per target)
run: |
for target in $(grep -rh "^func Fuzz" --include="*_test.go" | \
awk '{print $2}' | sed 's/(.*//'); do
echo "Fuzzing $target"
go test -fuzz=$target -fuzztime=180s ./... || true
done
- name: Commit any new regression fixtures
if: always()
run: |
if git diff --quiet testdata/; then exit 0; fi
git config user.name "fuzz-bot"
git config user.email "[email protected]"
git add testdata/
git commit -m "Add fuzz failure fixtures"
# PR or push — per team convention
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Missing f.Add calls | Fuzzer starts from empty corpus; slow path discovery | Add 3-10 representative seeds |
Skipping t.Fatalf for invariant violations | Fuzzer can't detect logical bugs | Assert invariants explicitly |
Not committing testdata/fuzz/ | Lose regression coverage on next run | Commit alongside the fix |
-fuzztime=10s in CI | Too short to find anything new | Use 1-5 min smoke; long campaigns separate |
| One huge fuzz target | Slow iteration; unclear coverage attribution | Split per function |
Fuzz target without -race | Misses concurrency bugs in concurrent code | go test -race -fuzz=... |
Ignoring testdata/ after CI fuzz | New regression fixtures lost | Commit + PR them automatically |
[]byte → struct mutation beyond
the supported types; complex inputs need manual unmarshalling
in the fuzz body.-fuzz is single-target. Can't fuzz multiple FuzzXxx
simultaneously in one go test invocation; loop or use -jobs.corpus-management-reference.libfuzzer-cpp,
afl-plus-plus,
cargo-fuzz-rust,
atheris-python-fuzzing,
jazzer-jvm-fuzzing,
ossfuzz-integration.fuzz-toolkit-dispatcher.npx claudepluginhub testland/qa --plugin qa-fuzz-testingProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.