github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/testing/internal/testdeps/deps.go (about) 1 // Copyright 2016 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 testdeps provides access to dependencies needed by test execution. 6 // 7 // This package is imported by the generated main package, which passes 8 // TestDeps into testing.Main. This allows tests to use packages at run time 9 // without making those packages direct dependencies of package testing. 10 // Direct dependencies of package testing are harder to write tests for. 11 package testdeps 12 13 import ( 14 "bufio" 15 "context" 16 "internal/fuzz" 17 "internal/testlog" 18 "io" 19 "os" 20 "os/signal" 21 "reflect" 22 "regexp" 23 "runtime/pprof" 24 "strings" 25 "sync" 26 "time" 27 ) 28 29 // TestDeps is an implementation of the testing.testDeps interface, 30 // suitable for passing to testing.MainStart. 31 type TestDeps struct{} 32 33 var matchPat string 34 var matchRe *regexp.Regexp 35 36 func (TestDeps) MatchString(pat, str string) (result bool, err error) { 37 if matchRe == nil || matchPat != pat { 38 matchPat = pat 39 matchRe, err = regexp.Compile(matchPat) 40 if err != nil { 41 return 42 } 43 } 44 return matchRe.MatchString(str), nil 45 } 46 47 func (TestDeps) StartCPUProfile(w io.Writer) error { 48 return pprof.StartCPUProfile(w) 49 } 50 51 func (TestDeps) StopCPUProfile() { 52 pprof.StopCPUProfile() 53 } 54 55 func (TestDeps) WriteProfileTo(name string, w io.Writer, debug int) error { 56 return pprof.Lookup(name).WriteTo(w, debug) 57 } 58 59 // ImportPath is the import path of the testing binary, set by the generated main function. 60 var ImportPath string 61 62 func (TestDeps) ImportPath() string { 63 return ImportPath 64 } 65 66 // testLog implements testlog.Interface, logging actions by package os. 67 type testLog struct { 68 mu sync.Mutex 69 w *bufio.Writer 70 set bool 71 } 72 73 func (l *testLog) Getenv(key string) { 74 l.add("getenv", key) 75 } 76 77 func (l *testLog) Open(name string) { 78 l.add("open", name) 79 } 80 81 func (l *testLog) Stat(name string) { 82 l.add("stat", name) 83 } 84 85 func (l *testLog) Chdir(name string) { 86 l.add("chdir", name) 87 } 88 89 // add adds the (op, name) pair to the test log. 90 func (l *testLog) add(op, name string) { 91 if strings.Contains(name, "\n") || name == "" { 92 return 93 } 94 95 l.mu.Lock() 96 defer l.mu.Unlock() 97 if l.w == nil { 98 return 99 } 100 l.w.WriteString(op) 101 l.w.WriteByte(' ') 102 l.w.WriteString(name) 103 l.w.WriteByte('\n') 104 } 105 106 var log testLog 107 108 func (TestDeps) StartTestLog(w io.Writer) { 109 log.mu.Lock() 110 log.w = bufio.NewWriter(w) 111 if !log.set { 112 // Tests that define TestMain and then run m.Run multiple times 113 // will call StartTestLog/StopTestLog multiple times. 114 // Checking log.set avoids calling testlog.SetLogger multiple times 115 // (which will panic) and also avoids writing the header multiple times. 116 log.set = true 117 testlog.SetLogger(&log) 118 log.w.WriteString("# test log\n") // known to cmd/go/internal/test/test.go 119 } 120 log.mu.Unlock() 121 } 122 123 func (TestDeps) StopTestLog() error { 124 log.mu.Lock() 125 defer log.mu.Unlock() 126 err := log.w.Flush() 127 log.w = nil 128 return err 129 } 130 131 // SetPanicOnExit0 tells the os package whether to panic on os.Exit(0). 132 func (TestDeps) SetPanicOnExit0(v bool) { 133 testlog.SetPanicOnExit0(v) 134 } 135 136 func (TestDeps) CoordinateFuzzing( 137 timeout time.Duration, 138 limit int64, 139 minimizeTimeout time.Duration, 140 minimizeLimit int64, 141 parallel int, 142 seed []fuzz.CorpusEntry, 143 types []reflect.Type, 144 corpusDir, 145 cacheDir string) (err error) { 146 // Fuzzing may be interrupted with a timeout or if the user presses ^C. 147 // In either case, we'll stop worker processes gracefully and save 148 // crashers and interesting values. 149 ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) 150 defer cancel() 151 err = fuzz.CoordinateFuzzing(ctx, fuzz.CoordinateFuzzingOpts{ 152 Log: os.Stderr, 153 Timeout: timeout, 154 Limit: limit, 155 MinimizeTimeout: minimizeTimeout, 156 MinimizeLimit: minimizeLimit, 157 Parallel: parallel, 158 Seed: seed, 159 Types: types, 160 CorpusDir: corpusDir, 161 CacheDir: cacheDir, 162 }) 163 if err == ctx.Err() { 164 return nil 165 } 166 return err 167 } 168 169 func (TestDeps) RunFuzzWorker(fn func(fuzz.CorpusEntry) error) error { 170 // Worker processes may or may not receive a signal when the user presses ^C 171 // On POSIX operating systems, a signal sent to a process group is delivered 172 // to all processes in that group. This is not the case on Windows. 173 // If the worker is interrupted, return quickly and without error. 174 // If only the coordinator process is interrupted, it tells each worker 175 // process to stop by closing its "fuzz_in" pipe. 176 ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) 177 defer cancel() 178 err := fuzz.RunFuzzWorker(ctx, fn) 179 if err == ctx.Err() { 180 return nil 181 } 182 return err 183 } 184 185 func (TestDeps) ReadCorpus(dir string, types []reflect.Type) ([]fuzz.CorpusEntry, error) { 186 return fuzz.ReadCorpus(dir, types) 187 } 188 189 func (TestDeps) CheckCorpus(vals []any, types []reflect.Type) error { 190 return fuzz.CheckCorpus(vals, types) 191 } 192 193 func (TestDeps) ResetCoverage() { 194 fuzz.ResetCoverage() 195 } 196 197 func (TestDeps) SnapshotCoverage() { 198 fuzz.SnapshotCoverage() 199 }