github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/fuzz/worker_test.go (about) 1 // Copyright 2021 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package fuzz 6 7 import ( 8 "context" 9 "errors" 10 "flag" 11 "fmt" 12 "io" 13 "os" 14 "os/signal" 15 "reflect" 16 "strconv" 17 "testing" 18 "time" 19 20 "github.com/go-asm/go/race" 21 ) 22 23 var benchmarkWorkerFlag = flag.Bool("benchmarkworker", false, "") 24 25 func TestMain(m *testing.M) { 26 flag.Parse() 27 if *benchmarkWorkerFlag { 28 runBenchmarkWorker() 29 return 30 } 31 os.Exit(m.Run()) 32 } 33 34 func BenchmarkWorkerFuzzOverhead(b *testing.B) { 35 if race.Enabled { 36 b.Skip("TODO(48504): fix and re-enable") 37 } 38 origEnv := os.Getenv("GODEBUG") 39 defer func() { os.Setenv("GODEBUG", origEnv) }() 40 os.Setenv("GODEBUG", fmt.Sprintf("%s,fuzzseed=123", origEnv)) 41 42 ws := &workerServer{ 43 fuzzFn: func(_ CorpusEntry) (time.Duration, error) { return time.Second, nil }, 44 workerComm: workerComm{memMu: make(chan *sharedMem, 1)}, 45 } 46 47 mem, err := sharedMemTempFile(workerSharedMemSize) 48 if err != nil { 49 b.Fatalf("failed to create temporary shared memory file: %s", err) 50 } 51 defer func() { 52 if err := mem.Close(); err != nil { 53 b.Error(err) 54 } 55 }() 56 57 initialVal := []any{make([]byte, 32)} 58 encodedVals := marshalCorpusFile(initialVal...) 59 mem.setValue(encodedVals) 60 61 ws.memMu <- mem 62 63 b.ResetTimer() 64 for i := 0; i < b.N; i++ { 65 ws.m = newMutator() 66 mem.setValue(encodedVals) 67 mem.header().count = 0 68 69 ws.fuzz(context.Background(), fuzzArgs{Limit: 1}) 70 } 71 } 72 73 // BenchmarkWorkerPing acts as the coordinator and measures the time it takes 74 // a worker to respond to N pings. This is a rough measure of our RPC latency. 75 func BenchmarkWorkerPing(b *testing.B) { 76 if race.Enabled { 77 b.Skip("TODO(48504): fix and re-enable") 78 } 79 b.SetParallelism(1) 80 w := newWorkerForTest(b) 81 for i := 0; i < b.N; i++ { 82 if err := w.client.ping(context.Background()); err != nil { 83 b.Fatal(err) 84 } 85 } 86 } 87 88 // BenchmarkWorkerFuzz acts as the coordinator and measures the time it takes 89 // a worker to mutate a given input and call a trivial fuzz function N times. 90 func BenchmarkWorkerFuzz(b *testing.B) { 91 if race.Enabled { 92 b.Skip("TODO(48504): fix and re-enable") 93 } 94 b.SetParallelism(1) 95 w := newWorkerForTest(b) 96 entry := CorpusEntry{Values: []any{[]byte(nil)}} 97 entry.Data = marshalCorpusFile(entry.Values...) 98 for i := int64(0); i < int64(b.N); { 99 args := fuzzArgs{ 100 Limit: int64(b.N) - i, 101 Timeout: workerFuzzDuration, 102 } 103 _, resp, _, err := w.client.fuzz(context.Background(), entry, args) 104 if err != nil { 105 b.Fatal(err) 106 } 107 if resp.Err != "" { 108 b.Fatal(resp.Err) 109 } 110 if resp.Count == 0 { 111 b.Fatal("worker did not make progress") 112 } 113 i += resp.Count 114 } 115 } 116 117 // newWorkerForTest creates and starts a worker process for testing or 118 // benchmarking. The worker process calls RunFuzzWorker, which responds to 119 // RPC messages until it's stopped. The process is stopped and cleaned up 120 // automatically when the test is done. 121 func newWorkerForTest(tb testing.TB) *worker { 122 tb.Helper() 123 c, err := newCoordinator(CoordinateFuzzingOpts{ 124 Types: []reflect.Type{reflect.TypeOf([]byte(nil))}, 125 Log: io.Discard, 126 }) 127 if err != nil { 128 tb.Fatal(err) 129 } 130 dir := "" // same as self 131 binPath := os.Args[0] // same as self 132 args := append(os.Args[1:], "-benchmarkworker") 133 env := os.Environ() // same as self 134 w, err := newWorker(c, dir, binPath, args, env) 135 if err != nil { 136 tb.Fatal(err) 137 } 138 tb.Cleanup(func() { 139 if err := w.cleanup(); err != nil { 140 tb.Error(err) 141 } 142 }) 143 if err := w.startAndPing(context.Background()); err != nil { 144 tb.Fatal(err) 145 } 146 tb.Cleanup(func() { 147 if err := w.stop(); err != nil { 148 tb.Error(err) 149 } 150 }) 151 return w 152 } 153 154 func runBenchmarkWorker() { 155 ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) 156 defer cancel() 157 fn := func(CorpusEntry) error { return nil } 158 if err := RunFuzzWorker(ctx, fn); err != nil && err != ctx.Err() { 159 panic(err) 160 } 161 } 162 163 func BenchmarkWorkerMinimize(b *testing.B) { 164 if race.Enabled { 165 b.Skip("TODO(48504): fix and re-enable") 166 } 167 168 ws := &workerServer{ 169 workerComm: workerComm{memMu: make(chan *sharedMem, 1)}, 170 } 171 172 mem, err := sharedMemTempFile(workerSharedMemSize) 173 if err != nil { 174 b.Fatalf("failed to create temporary shared memory file: %s", err) 175 } 176 defer func() { 177 if err := mem.Close(); err != nil { 178 b.Error(err) 179 } 180 }() 181 ws.memMu <- mem 182 183 bytes := make([]byte, 1024) 184 ctx := context.Background() 185 for sz := 1; sz <= len(bytes); sz <<= 1 { 186 sz := sz 187 input := []any{bytes[:sz]} 188 encodedVals := marshalCorpusFile(input...) 189 mem = <-ws.memMu 190 mem.setValue(encodedVals) 191 ws.memMu <- mem 192 b.Run(strconv.Itoa(sz), func(b *testing.B) { 193 i := 0 194 ws.fuzzFn = func(_ CorpusEntry) (time.Duration, error) { 195 if i == 0 { 196 i++ 197 return time.Second, errors.New("initial failure for deflake") 198 } 199 return time.Second, nil 200 } 201 for i := 0; i < b.N; i++ { 202 b.SetBytes(int64(sz)) 203 ws.minimize(ctx, minimizeArgs{}) 204 } 205 }) 206 } 207 }