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. 11package testdeps 12 13import ( 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// Cover indicates whether coverage is enabled. 30var Cover bool 31 32// TestDeps is an implementation of the testing.testDeps interface, 33// suitable for passing to [testing.MainStart]. 34type TestDeps struct{} 35 36var matchPat string 37var matchRe *regexp.Regexp 38 39func (TestDeps) MatchString(pat, str string) (result bool, err error) { 40 if matchRe == nil || matchPat != pat { 41 matchPat = pat 42 matchRe, err = regexp.Compile(matchPat) 43 if err != nil { 44 return 45 } 46 } 47 return matchRe.MatchString(str), nil 48} 49 50func (TestDeps) StartCPUProfile(w io.Writer) error { 51 return pprof.StartCPUProfile(w) 52} 53 54func (TestDeps) StopCPUProfile() { 55 pprof.StopCPUProfile() 56} 57 58func (TestDeps) WriteProfileTo(name string, w io.Writer, debug int) error { 59 return pprof.Lookup(name).WriteTo(w, debug) 60} 61 62// ImportPath is the import path of the testing binary, set by the generated main function. 63var ImportPath string 64 65func (TestDeps) ImportPath() string { 66 return ImportPath 67} 68 69// testLog implements testlog.Interface, logging actions by package os. 70type testLog struct { 71 mu sync.Mutex 72 w *bufio.Writer 73 set bool 74} 75 76func (l *testLog) Getenv(key string) { 77 l.add("getenv", key) 78} 79 80func (l *testLog) Open(name string) { 81 l.add("open", name) 82} 83 84func (l *testLog) Stat(name string) { 85 l.add("stat", name) 86} 87 88func (l *testLog) Chdir(name string) { 89 l.add("chdir", name) 90} 91 92// add adds the (op, name) pair to the test log. 93func (l *testLog) add(op, name string) { 94 if strings.Contains(name, "\n") || name == "" { 95 return 96 } 97 98 l.mu.Lock() 99 defer l.mu.Unlock() 100 if l.w == nil { 101 return 102 } 103 l.w.WriteString(op) 104 l.w.WriteByte(' ') 105 l.w.WriteString(name) 106 l.w.WriteByte('\n') 107} 108 109var log testLog 110 111func (TestDeps) StartTestLog(w io.Writer) { 112 log.mu.Lock() 113 log.w = bufio.NewWriter(w) 114 if !log.set { 115 // Tests that define TestMain and then run m.Run multiple times 116 // will call StartTestLog/StopTestLog multiple times. 117 // Checking log.set avoids calling testlog.SetLogger multiple times 118 // (which will panic) and also avoids writing the header multiple times. 119 log.set = true 120 testlog.SetLogger(&log) 121 log.w.WriteString("# test log\n") // known to cmd/go/internal/test/test.go 122 } 123 log.mu.Unlock() 124} 125 126func (TestDeps) StopTestLog() error { 127 log.mu.Lock() 128 defer log.mu.Unlock() 129 err := log.w.Flush() 130 log.w = nil 131 return err 132} 133 134// SetPanicOnExit0 tells the os package whether to panic on os.Exit(0). 135func (TestDeps) SetPanicOnExit0(v bool) { 136 testlog.SetPanicOnExit0(v) 137} 138 139func (TestDeps) CoordinateFuzzing( 140 timeout time.Duration, 141 limit int64, 142 minimizeTimeout time.Duration, 143 minimizeLimit int64, 144 parallel int, 145 seed []fuzz.CorpusEntry, 146 types []reflect.Type, 147 corpusDir, 148 cacheDir string) (err error) { 149 // Fuzzing may be interrupted with a timeout or if the user presses ^C. 150 // In either case, we'll stop worker processes gracefully and save 151 // crashers and interesting values. 152 ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) 153 defer cancel() 154 err = fuzz.CoordinateFuzzing(ctx, fuzz.CoordinateFuzzingOpts{ 155 Log: os.Stderr, 156 Timeout: timeout, 157 Limit: limit, 158 MinimizeTimeout: minimizeTimeout, 159 MinimizeLimit: minimizeLimit, 160 Parallel: parallel, 161 Seed: seed, 162 Types: types, 163 CorpusDir: corpusDir, 164 CacheDir: cacheDir, 165 }) 166 if err == ctx.Err() { 167 return nil 168 } 169 return err 170} 171 172func (TestDeps) RunFuzzWorker(fn func(fuzz.CorpusEntry) error) error { 173 // Worker processes may or may not receive a signal when the user presses ^C 174 // On POSIX operating systems, a signal sent to a process group is delivered 175 // to all processes in that group. This is not the case on Windows. 176 // If the worker is interrupted, return quickly and without error. 177 // If only the coordinator process is interrupted, it tells each worker 178 // process to stop by closing its "fuzz_in" pipe. 179 ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) 180 defer cancel() 181 err := fuzz.RunFuzzWorker(ctx, fn) 182 if err == ctx.Err() { 183 return nil 184 } 185 return err 186} 187 188func (TestDeps) ReadCorpus(dir string, types []reflect.Type) ([]fuzz.CorpusEntry, error) { 189 return fuzz.ReadCorpus(dir, types) 190} 191 192func (TestDeps) CheckCorpus(vals []any, types []reflect.Type) error { 193 return fuzz.CheckCorpus(vals, types) 194} 195 196func (TestDeps) ResetCoverage() { 197 fuzz.ResetCoverage() 198} 199 200func (TestDeps) SnapshotCoverage() { 201 fuzz.SnapshotCoverage() 202} 203 204var CoverMode string 205var Covered string 206var CoverSelectedPackages []string 207 208// These variables below are set at runtime (via code in testmain) to point 209// to the equivalent functions in package internal/coverage/cfile; doing 210// things this way allows us to have tests import internal/coverage/cfile 211// only when -cover is in effect (as opposed to importing for all tests). 212var ( 213 CoverSnapshotFunc func() float64 214 CoverProcessTestDirFunc func(dir string, cfile string, cm string, cpkg string, w io.Writer, selpkgs []string) error 215 CoverMarkProfileEmittedFunc func(val bool) 216) 217 218func (TestDeps) InitRuntimeCoverage() (mode string, tearDown func(string, string) (string, error), snapcov func() float64) { 219 if CoverMode == "" { 220 return 221 } 222 return CoverMode, coverTearDown, CoverSnapshotFunc 223} 224 225func coverTearDown(coverprofile string, gocoverdir string) (string, error) { 226 var err error 227 if gocoverdir == "" { 228 gocoverdir, err = os.MkdirTemp("", "gocoverdir") 229 if err != nil { 230 return "error setting GOCOVERDIR: bad os.MkdirTemp return", err 231 } 232 defer os.RemoveAll(gocoverdir) 233 } 234 CoverMarkProfileEmittedFunc(true) 235 cmode := CoverMode 236 if err := CoverProcessTestDirFunc(gocoverdir, coverprofile, cmode, Covered, os.Stdout, CoverSelectedPackages); err != nil { 237 return "error generating coverage report", err 238 } 239 return "", nil 240} 241