Day 048 — Fuzzing (testing.F) & Examples¶
Month 2 · Week 3 · ⬅ Day 047 · Day 049 ➡ · Journal index
🎯 Learning Objective¶
Write a native Go fuzz test with testing.F that asserts invariants over generated inputs, and write runnable, output-verified Example functions that double as documentation.
📚 Topics¶
func FuzzXxx(f *testing.F)·f.Addseed corpus ·f.Fuzz(func(t, ...))- Property/invariant thinking (round-trips, idempotence) · the corpus on disk
func ExampleXxx()with// Output:comments
📖 Reading / Sources¶
- Go — Fuzzing tutorial
-
testing— Fuzzing -
testing— Examples - Go Blog — Go fuzzing is in the std toolchain (1.18)
📝 Notes¶
- A fuzz test seeds with
f.Add(...)and runsf.Fuzz(func(t *testing.T, in T){...}). Fuzzable arg types are limited:[]byte,string, numeric types,bool,rune. The seed args and thef.Fuzzcallback params must match in type and order. → [[fuzzing]] go test(no flags) runs the fuzz target against only the seed corpus — fast, deterministic, part of CI.go test -fuzz=FuzzXxxenters the mutation engine, generating new inputs until a failure or you stop it (-fuzztime=30sto bound it).- Fuzzing checks properties/invariants, not exact outputs, because you don't know the answer for a random input. Classics: round-trip (
Decode(Encode(x)) == x), idempotence (f(f(x)) == f(x)), "never panics", "output stays valid UTF-8". - When the engine finds a failing input it writes it to
testdata/fuzz/FuzzXxx/<hash>. That file becomes a permanent regression seed — commit it. Examplefunctions live in_test.go, are compiled, and if they end with a// Output:comment,go testruns them and checks stdout matches. No// Output:→ compiled but not run. → [[examples-as-tests]]- Naming wires examples into godoc:
ExampleReversedocumentsReverse,ExampleReverse_unicodeis a named variant.// Unordered output:matches lines in any order (good for map ranges). - Examples are the rare test that's also rendered documentation on pkg.go.dev — keep them clean and realistic.
💻 Code Examples¶
func FuzzReverse(f *testing.F) {
for _, s := range []string{"", "a", "Hello", "héllo", "go🚀"} {
f.Add(s) // seed corpus
}
f.Fuzz(func(t *testing.T, s string) {
got := Reverse(s)
if Reverse(got) != s { // invariant 1: round trip
t.Errorf("Reverse(Reverse(%q)) != %q", s, s)
}
if utf8.ValidString(s) && !utf8.ValidString(got) { // invariant 2
t.Errorf("Reverse(%q) produced invalid UTF-8", s)
}
})
}
func ExampleReverse() {
fmt.Println(Reverse("Hello"))
// Output: olleH
}
Runnable property-check demo (shows fuzzing's mindset & a UTF-8 bug):
examples/month-02/fuzz-roundtrip/· Run:go run ./examples/month-02/fuzz-roundtripRealFuzzReverse:exercises/month-02/week-3/reverse/· Fuzz:go test -fuzz=FuzzReverse -fuzztime=10s ./exercises/month-02/week-3/reverse
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
FuzzReverse round-trip + UTF-8 invariants |
✅ | exercises/month-02/week-3/reverse |
| Watch a byte-reverse fail the invariant | ✅ | examples/month-02/fuzz-roundtrip |
🐛 Mistakes Made¶
- Mismatched seed types:
f.Add(0)(int) withf.Fuzz(func(t, s string))— fuzzing panics if the corpus types don't match the callback. Made them line up. - Asserted an exact output in a fuzz body — impossible for random input; switched to an invariant.
❓ Open Questions¶
- How does the engine minimize a failing input to the smallest reproducer? (Shrinking — it trims/mutates toward a minimal case before saving to
testdata.)
🧠 Active Recall (answer without looking)¶
- Q: What does plain
go testdo with aFuzzXxxtarget?A
Runs it only against the seed corpus (f.Add values + any saved testdata cases) — deterministic, no new inputs. Use -fuzz=FuzzXxx to actually mutate/generate.
2. Q: Why must fuzz tests assert properties, not exact outputs? A
The inputs are generated/unknown, so you can't precompute the answer. You assert invariants (round-trip, idempotence, never-panics, stays-valid) that must hold for any input.
🪶 Feynman Reflection¶
Fuzzing is a robot that keeps inventing weird inputs trying to break a rule you stated must always hold — like "reversing twice gives me back the original." When it finds a breaker, it saves the exact input forever so the bug can't sneak back. Example functions are tests whose printed output is checked and shown as documentation.
🕳️ Knowledge Gaps¶
- Fuzzing structured inputs (structs, multiple args) and coverage-guided corpus growth internals.
✅ Summary¶
I can write a property-based fuzz test that runs seeds in CI and mutates under -fuzz, and write Example functions that verify output and document the API.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 049: week review + closed-book recall of the whole testing toolkit.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦🟦⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: test(week-3): testing.F fuzzing and example functions (day 048)