| # TODO(jayconrod): support shared memory on more platforms. |
| [!darwin] [!linux] [!windows] 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 |
| |
| 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") |
| } |
| }) |
| } |