]> Cypherpunks.ru repositories - gostls13.git/blob - src/testing/internal/testdeps/deps.go
[dev.fuzz] testing/internal/testdeps: use signal.NotifyContext
[gostls13.git] / 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         "regexp"
22         "runtime/pprof"
23         "strings"
24         "sync"
25         "time"
26 )
27
28 // TestDeps is an implementation of the testing.testDeps interface,
29 // suitable for passing to testing.MainStart.
30 type TestDeps struct{}
31
32 var matchPat string
33 var matchRe *regexp.Regexp
34
35 func (TestDeps) MatchString(pat, str string) (result bool, err error) {
36         if matchRe == nil || matchPat != pat {
37                 matchPat = pat
38                 matchRe, err = regexp.Compile(matchPat)
39                 if err != nil {
40                         return
41                 }
42         }
43         return matchRe.MatchString(str), nil
44 }
45
46 func (TestDeps) StartCPUProfile(w io.Writer) error {
47         return pprof.StartCPUProfile(w)
48 }
49
50 func (TestDeps) StopCPUProfile() {
51         pprof.StopCPUProfile()
52 }
53
54 func (TestDeps) WriteProfileTo(name string, w io.Writer, debug int) error {
55         return pprof.Lookup(name).WriteTo(w, debug)
56 }
57
58 // ImportPath is the import path of the testing binary, set by the generated main function.
59 var ImportPath string
60
61 func (TestDeps) ImportPath() string {
62         return ImportPath
63 }
64
65 // testLog implements testlog.Interface, logging actions by package os.
66 type testLog struct {
67         mu  sync.Mutex
68         w   *bufio.Writer
69         set bool
70 }
71
72 func (l *testLog) Getenv(key string) {
73         l.add("getenv", key)
74 }
75
76 func (l *testLog) Open(name string) {
77         l.add("open", name)
78 }
79
80 func (l *testLog) Stat(name string) {
81         l.add("stat", name)
82 }
83
84 func (l *testLog) Chdir(name string) {
85         l.add("chdir", name)
86 }
87
88 // add adds the (op, name) pair to the test log.
89 func (l *testLog) add(op, name string) {
90         if strings.Contains(name, "\n") || name == "" {
91                 return
92         }
93
94         l.mu.Lock()
95         defer l.mu.Unlock()
96         if l.w == nil {
97                 return
98         }
99         l.w.WriteString(op)
100         l.w.WriteByte(' ')
101         l.w.WriteString(name)
102         l.w.WriteByte('\n')
103 }
104
105 var log testLog
106
107 func (TestDeps) StartTestLog(w io.Writer) {
108         log.mu.Lock()
109         log.w = bufio.NewWriter(w)
110         if !log.set {
111                 // Tests that define TestMain and then run m.Run multiple times
112                 // will call StartTestLog/StopTestLog multiple times.
113                 // Checking log.set avoids calling testlog.SetLogger multiple times
114                 // (which will panic) and also avoids writing the header multiple times.
115                 log.set = true
116                 testlog.SetLogger(&log)
117                 log.w.WriteString("# test log\n") // known to cmd/go/internal/test/test.go
118         }
119         log.mu.Unlock()
120 }
121
122 func (TestDeps) StopTestLog() error {
123         log.mu.Lock()
124         defer log.mu.Unlock()
125         err := log.w.Flush()
126         log.w = nil
127         return err
128 }
129
130 // SetPanicOnExit0 tells the os package whether to panic on os.Exit(0).
131 func (TestDeps) SetPanicOnExit0(v bool) {
132         testlog.SetPanicOnExit0(v)
133 }
134
135 func (TestDeps) CoordinateFuzzing(timeout time.Duration, parallel int, seed []fuzz.CorpusEntry, corpusDir, cacheDir string) error {
136         // Fuzzing may be interrupted with a timeout or if the user presses ^C.
137         // In either case, we'll stop worker processes gracefully and save
138         // crashers and interesting values.
139         ctx, cancel := context.WithCancel(context.Background())
140         if timeout > 0 {
141                 ctx, cancel = context.WithTimeout(ctx, timeout)
142         }
143         ctx, stop := signal.NotifyContext(ctx, os.Interrupt)
144         defer stop()
145         defer cancel()
146         err := fuzz.CoordinateFuzzing(ctx, parallel, seed, corpusDir, cacheDir)
147         if err == ctx.Err() {
148                 return nil
149         }
150         return err
151 }
152
153 func (TestDeps) RunFuzzWorker(fn func([]byte) error) error {
154         // Worker processes may or may not receive a signal when the user presses ^C
155         // On POSIX operating systems, a signal sent to a process group is delivered
156         // to all processes in that group. This is not the case on Windows.
157         // If the worker is interrupted, return quickly and without error.
158         // If only the coordinator process is interrupted, it tells each worker
159         // process to stop by closing its "fuzz_in" pipe.
160         ctx, cancel := context.WithCancel(context.Background())
161         ctx, stop := signal.NotifyContext(ctx, os.Interrupt)
162         defer stop()
163         defer cancel()
164         err := fuzz.RunFuzzWorker(ctx, fn)
165         if err == ctx.Err() {
166                 return nil
167         }
168         return nil
169 }
170
171 func (TestDeps) ReadCorpus(dir string) ([]fuzz.CorpusEntry, error) {
172         return fuzz.ReadCorpus(dir)
173 }