|  | // 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{}) | 
|  | } | 
|  | }) | 
|  | } | 
|  | } |