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