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  // 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  }
   200  

View as plain text