blob: cc1f98990e9e97b7abf9b1c85e94eb2255faefe1 [file] [log] [blame]
[!fuzz] skip
# Test basic fuzzing mutator behavior.
#
# fuzz_test.go has two fuzz targets (FuzzA, FuzzB) which both add a seed value.
# Each fuzz function writes the input to a log file. The coordinator and worker
# use separate log files. check_logs.go verifies that the coordinator only
# tests seed values and the worker tests mutated values on the fuzz target.
[short] skip
env GOCACHE=$WORK/cache
go test -fuzz=FuzzA -fuzztime=100x -parallel=1 -log=fuzz
go run check_logs.go fuzz fuzz.worker
# TODO(b/181800488): remove -parallel=1, here and below. For now, when a
# crash is found, all workers keep running, wasting resources and reducing
# the number of executions available to the minimizer, increasing flakiness.
# Test that the mutator is good enough to find several unique mutations.
! go test -fuzz=FuzzMutator -parallel=1 -fuzztime=100x mutator_test.go
! stdout '^ok'
stdout FAIL
stdout 'mutator found enough unique mutations'
-- go.mod --
module m
go 1.16
-- fuzz_test.go --
package fuzz_test
import (
"flag"
"fmt"
"os"
"testing"
)
var (
logPath = flag.String("log", "", "path to log file")
logFile *os.File
)
func TestMain(m *testing.M) {
flag.Parse()
var err error
logFile, err = os.OpenFile(*logPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if os.IsExist(err) {
*logPath += ".worker"
logFile, err = os.OpenFile(*logPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
}
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
os.Exit(m.Run())
}
func FuzzA(f *testing.F) {
f.Add([]byte("seed"))
f.Fuzz(func(t *testing.T, b []byte) {
fmt.Fprintf(logFile, "FuzzA %q\n", b)
})
}
func FuzzB(f *testing.F) {
f.Add([]byte("seed"))
f.Fuzz(func(t *testing.T, b []byte) {
fmt.Fprintf(logFile, "FuzzB %q\n", b)
})
}
-- check_logs.go --
// +build ignore
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"strings"
)
func main() {
coordPath, workerPath := os.Args[1], os.Args[2]
coordLog, err := os.Open(coordPath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer coordLog.Close()
if err := checkCoordLog(coordLog); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
workerLog, err := os.Open(workerPath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer workerLog.Close()
if err := checkWorkerLog(workerLog); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func checkCoordLog(r io.Reader) error {
b, err := io.ReadAll(r)
if err != nil {
return err
}
if string(bytes.TrimSpace(b)) != `FuzzB "seed"` {
return fmt.Errorf("coordinator: did not test FuzzB seed")
}
return nil
}
func checkWorkerLog(r io.Reader) error {
scan := bufio.NewScanner(r)
var sawAMutant bool
for scan.Scan() {
line := scan.Text()
if !strings.HasPrefix(line, "FuzzA ") {
return fmt.Errorf("worker: tested something other than target: %s", line)
}
if strings.TrimPrefix(line, "FuzzA ") != `"seed"` {
sawAMutant = true
}
}
if err := scan.Err(); err != nil && err != bufio.ErrTooLong {
return err
}
if !sawAMutant {
return fmt.Errorf("worker: did not test any mutants")
}
return nil
}
-- mutator_test.go --
package fuzz_test
import (
"testing"
)
// TODO(katiehockman): re-work this test once we have a better fuzzing engine
// (ie. more mutations, and compiler instrumentation)
func FuzzMutator(f *testing.F) {
// TODO(katiehockman): simplify this once we can dedupe crashes (e.g.
// replace map with calls to panic, and simply count the number of crashes
// that were added to testdata)
crashes := make(map[string]bool)
// No seed corpus initiated
f.Fuzz(func(t *testing.T, b []byte) {
crashes[string(b)] = true
if len(crashes) >= 10 {
panic("mutator found enough unique mutations")
}
})
}