Source file src/testing/internal/testdeps/deps.go

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

View as plain text