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  // testLog implements testlog.Interface, logging actions by package os.
    70  type testLog struct {
    71  	mu  sync.Mutex
    72  	w   *bufio.Writer
    73  	set bool
    74  }
    75  
    76  func (l *testLog) Getenv(key string) {
    77  	l.add("getenv", key)
    78  }
    79  
    80  func (l *testLog) Open(name string) {
    81  	l.add("open", name)
    82  }
    83  
    84  func (l *testLog) Stat(name string) {
    85  	l.add("stat", name)
    86  }
    87  
    88  func (l *testLog) Chdir(name string) {
    89  	l.add("chdir", name)
    90  }
    91  
    92  // add adds the (op, name) pair to the test log.
    93  func (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  
   109  var log testLog
   110  
   111  func (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  
   126  func (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).
   135  func (TestDeps) SetPanicOnExit0(v bool) {
   136  	testlog.SetPanicOnExit0(v)
   137  }
   138  
   139  func (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  
   172  func (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  
   188  func (TestDeps) ReadCorpus(dir string, types []reflect.Type) ([]fuzz.CorpusEntry, error) {
   189  	return fuzz.ReadCorpus(dir, types)
   190  }
   191  
   192  func (TestDeps) CheckCorpus(vals []any, types []reflect.Type) error {
   193  	return fuzz.CheckCorpus(vals, types)
   194  }
   195  
   196  func (TestDeps) ResetCoverage() {
   197  	fuzz.ResetCoverage()
   198  }
   199  
   200  func (TestDeps) SnapshotCoverage() {
   201  	fuzz.SnapshotCoverage()
   202  }
   203  
   204  var CoverMode string
   205  var Covered string
   206  var 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).
   212  var (
   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  
   218  func (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  
   225  func 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  

View as plain text