| // Copyright 2021 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package fuzz |
| |
| import ( |
| "context" |
| "errors" |
| "flag" |
| "fmt" |
| "internal/race" |
| "io" |
| "os" |
| "os/signal" |
| "reflect" |
| "strconv" |
| "testing" |
| "time" |
| ) |
| |
| var benchmarkWorkerFlag = flag.Bool("benchmarkworker", false, "") |
| |
| func TestMain(m *testing.M) { |
| flag.Parse() |
| if *benchmarkWorkerFlag { |
| runBenchmarkWorker() |
| return |
| } |
| os.Exit(m.Run()) |
| } |
| |
| func BenchmarkWorkerFuzzOverhead(b *testing.B) { |
| if race.Enabled { |
| b.Skip("TODO(48504): fix and re-enable") |
| } |
| origEnv := os.Getenv("GODEBUG") |
| defer func() { os.Setenv("GODEBUG", origEnv) }() |
| os.Setenv("GODEBUG", fmt.Sprintf("%s,fuzzseed=123", origEnv)) |
| |
| ws := &workerServer{ |
| fuzzFn: func(_ CorpusEntry) (time.Duration, error) { return time.Second, nil }, |
| workerComm: workerComm{memMu: make(chan *sharedMem, 1)}, |
| } |
| |
| mem, err := sharedMemTempFile(workerSharedMemSize) |
| if err != nil { |
| b.Fatalf("failed to create temporary shared memory file: %s", err) |
| } |
| defer func() { |
| if err := mem.Close(); err != nil { |
| b.Error(err) |
| } |
| }() |
| |
| initialVal := []any{make([]byte, 32)} |
| encodedVals := marshalCorpusFile(initialVal...) |
| mem.setValue(encodedVals) |
| |
| ws.memMu <- mem |
| |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| ws.m = newMutator() |
| mem.setValue(encodedVals) |
| mem.header().count = 0 |
| |
| ws.fuzz(context.Background(), fuzzArgs{Limit: 1}) |
| } |
| } |
| |
| // BenchmarkWorkerPing acts as the coordinator and measures the time it takes |
| // a worker to respond to N pings. This is a rough measure of our RPC latency. |
| func BenchmarkWorkerPing(b *testing.B) { |
| if race.Enabled { |
| b.Skip("TODO(48504): fix and re-enable") |
| } |
| b.SetParallelism(1) |
| w := newWorkerForTest(b) |
| for i := 0; i < b.N; i++ { |
| if err := w.client.ping(context.Background()); err != nil { |
| b.Fatal(err) |
| } |
| } |
| } |
| |
| // BenchmarkWorkerFuzz acts as the coordinator and measures the time it takes |
| // a worker to mutate a given input and call a trivial fuzz function N times. |
| func BenchmarkWorkerFuzz(b *testing.B) { |
| if race.Enabled { |
| b.Skip("TODO(48504): fix and re-enable") |
| } |
| b.SetParallelism(1) |
| w := newWorkerForTest(b) |
| entry := CorpusEntry{Values: []any{[]byte(nil)}} |
| entry.Data = marshalCorpusFile(entry.Values...) |
| for i := int64(0); i < int64(b.N); { |
| args := fuzzArgs{ |
| Limit: int64(b.N) - i, |
| Timeout: workerFuzzDuration, |
| } |
| _, resp, _, err := w.client.fuzz(context.Background(), entry, args) |
| if err != nil { |
| b.Fatal(err) |
| } |
| if resp.Err != "" { |
| b.Fatal(resp.Err) |
| } |
| if resp.Count == 0 { |
| b.Fatal("worker did not make progress") |
| } |
| i += resp.Count |
| } |
| } |
| |
| // newWorkerForTest creates and starts a worker process for testing or |
| // benchmarking. The worker process calls RunFuzzWorker, which responds to |
| // RPC messages until it's stopped. The process is stopped and cleaned up |
| // automatically when the test is done. |
| func newWorkerForTest(tb testing.TB) *worker { |
| tb.Helper() |
| c, err := newCoordinator(CoordinateFuzzingOpts{ |
| Types: []reflect.Type{reflect.TypeOf([]byte(nil))}, |
| Log: io.Discard, |
| }) |
| if err != nil { |
| tb.Fatal(err) |
| } |
| dir := "" // same as self |
| binPath := os.Args[0] // same as self |
| args := append(os.Args[1:], "-benchmarkworker") |
| env := os.Environ() // same as self |
| w, err := newWorker(c, dir, binPath, args, env) |
| if err != nil { |
| tb.Fatal(err) |
| } |
| tb.Cleanup(func() { |
| if err := w.cleanup(); err != nil { |
| tb.Error(err) |
| } |
| }) |
| if err := w.startAndPing(context.Background()); err != nil { |
| tb.Fatal(err) |
| } |
| tb.Cleanup(func() { |
| if err := w.stop(); err != nil { |
| tb.Error(err) |
| } |
| }) |
| return w |
| } |
| |
| func runBenchmarkWorker() { |
| ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) |
| defer cancel() |
| fn := func(CorpusEntry) error { return nil } |
| if err := RunFuzzWorker(ctx, fn); err != nil && err != ctx.Err() { |
| panic(err) |
| } |
| } |
| |
| func BenchmarkWorkerMinimize(b *testing.B) { |
| if race.Enabled { |
| b.Skip("TODO(48504): fix and re-enable") |
| } |
| |
| ws := &workerServer{ |
| workerComm: workerComm{memMu: make(chan *sharedMem, 1)}, |
| } |
| |
| mem, err := sharedMemTempFile(workerSharedMemSize) |
| if err != nil { |
| b.Fatalf("failed to create temporary shared memory file: %s", err) |
| } |
| defer func() { |
| if err := mem.Close(); err != nil { |
| b.Error(err) |
| } |
| }() |
| ws.memMu <- mem |
| |
| bytes := make([]byte, 1024) |
| ctx := context.Background() |
| for sz := 1; sz <= len(bytes); sz <<= 1 { |
| sz := sz |
| input := []any{bytes[:sz]} |
| encodedVals := marshalCorpusFile(input...) |
| mem = <-ws.memMu |
| mem.setValue(encodedVals) |
| ws.memMu <- mem |
| b.Run(strconv.Itoa(sz), func(b *testing.B) { |
| i := 0 |
| ws.fuzzFn = func(_ CorpusEntry) (time.Duration, error) { |
| if i == 0 { |
| i++ |
| return time.Second, errors.New("initial failure for deflake") |
| } |
| return time.Second, nil |
| } |
| for i := 0; i < b.N; i++ { |
| b.SetBytes(int64(sz)) |
| ws.minimize(ctx, minimizeArgs{}) |
| } |
| }) |
| } |
| } |