From d965f9f43e581bfddb34113cca5468dcb3ca5506 Mon Sep 17 00:00:00 2001 From: Gregory Petrosyan Date: Mon, 4 Jul 2022 18:56:10 +0300 Subject: [PATCH] Integrated fuzzing support Fixes #32. --- TODO.md | 1 - engine.go | 47 +++++++++++++++++++++++++++++++++++++++++ engine_fuzz_test.go | 51 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 engine_fuzz_test.go diff --git a/TODO.md b/TODO.md index de3c5f9..874f532 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,6 @@ - explicit examples support (based on `refDraws` shrink test machinery) - explicit settings support (to not depend on global environment) -- [go-fuzz](https://github.com/golang/proposal/blob/master/design/draft-fuzzing.md) integration ## Generators diff --git a/engine.go b/engine.go index dc6d540..5c09e0d 100644 --- a/engine.go +++ b/engine.go @@ -8,6 +8,7 @@ package rapid import ( "bytes" + "encoding/binary" "flag" "fmt" "log" @@ -113,6 +114,46 @@ func MakeCheck(prop func(*T)) func(*testing.T) { } } +// MakeFuzz creates a fuzz target for [*testing.F.Fuzz]: +// +// func FuzzFoo(f *testing.F) { +// f.Fuzz(rapid.MakeFuzz(func(t *rapid.T) { +// // test code +// })) +// } +func MakeFuzz(prop func(*T)) func(*testing.T, []byte) { + return func(t *testing.T, input []byte) { + t.Helper() + checkFuzz(t, prop, input) + } +} + +func checkFuzz(tb tb, prop func(*T), input []byte) { + tb.Helper() + + var buf []uint64 + for len(input) > 0 { + var tmp [8]byte + n := copy(tmp[:], input) + buf = append(buf, binary.LittleEndian.Uint64(tmp[:])) + input = input[n:] + } + + t := newT(tb, newBufBitStream(buf, false), flags.verbose, nil) + err := checkOnce(t, prop) + + switch { + case err == nil: + // do nothing + case err.isInvalidData(): + tb.SkipNow() + case err.isStopTest(): + tb.Fatalf("[rapid] failed: %v", err) + default: + tb.Fatalf("[rapid] panic: %v\nTraceback:\n%v", err, traceback(err)) + } +} + func checkTB(tb tb, prop func(*T)) { tb.Helper() @@ -373,6 +414,9 @@ type TB interface { Name() string Logf(format string, args ...any) Log(args ...any) + Skipf(format string, args ...any) + Skip(args ...any) + SkipNow() Errorf(format string, args ...any) Error(args ...any) Fatalf(format string, args ...any) @@ -388,6 +432,9 @@ type tb interface { Name() string Logf(format string, args ...any) Log(args ...any) + Skipf(format string, args ...any) + Skip(args ...any) + SkipNow() Errorf(format string, args ...any) Error(args ...any) Fatalf(format string, args ...any) diff --git a/engine_fuzz_test.go b/engine_fuzz_test.go new file mode 100644 index 0000000..73db2ed --- /dev/null +++ b/engine_fuzz_test.go @@ -0,0 +1,51 @@ +// Copyright 2022 Gregory Petrosyan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package rapid_test + +import ( + "testing" + + . "pgregory.net/rapid" +) + +func checkInt(t *T) { + answer := Int().Draw(t, "answer") + if answer == 42 { + t.Fatalf("fuzzing works") + } +} + +func checkSlice(t *T) { + slice := SliceOfN(Int(), 5, 5).Draw(t, "slice") + if slice[0] < slice[1] && slice[1] < slice[2] && slice[2] < slice[3] && slice[3] < slice[4] { + t.Fatalf("fuzzing works") + } +} + +func checkString(t *T) { + hello := String().Draw(t, "hello") + if hello == "world" { + t.Fatalf("fuzzing works") + } +} + +func TestRapidInt(t *testing.T) { + t.Skip() + Check(t, checkInt) +} +func TestRapidSlice(t *testing.T) { + t.Skip() + Check(t, checkSlice) +} +func TestRapidString(t *testing.T) { + t.Skip() + Check(t, checkString) +} + +func FuzzInt(f *testing.F) { f.Fuzz(MakeFuzz(checkInt)) } +func FuzzSlice(f *testing.F) { f.Fuzz(MakeFuzz(checkSlice)) } +func FuzzString(f *testing.F) { f.Fuzz(MakeFuzz(checkString)) }