]> Cypherpunks.ru repositories - gostls13.git/blob - src/runtime/crash_test.go
runtime: make test independent of inlining
[gostls13.git] / src / runtime / crash_test.go
1 // Copyright 2012 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 runtime_test
6
7 import (
8         "bytes"
9         "flag"
10         "fmt"
11         "internal/testenv"
12         "io/ioutil"
13         "os"
14         "os/exec"
15         "path/filepath"
16         "regexp"
17         "runtime"
18         "strconv"
19         "strings"
20         "sync"
21         "testing"
22         "time"
23 )
24
25 var toRemove []string
26
27 func TestMain(m *testing.M) {
28         status := m.Run()
29         for _, file := range toRemove {
30                 os.RemoveAll(file)
31         }
32         os.Exit(status)
33 }
34
35 func testEnv(cmd *exec.Cmd) *exec.Cmd {
36         if cmd.Env != nil {
37                 panic("environment already set")
38         }
39         for _, env := range os.Environ() {
40                 // Exclude GODEBUG from the environment to prevent its output
41                 // from breaking tests that are trying to parse other command output.
42                 if strings.HasPrefix(env, "GODEBUG=") {
43                         continue
44                 }
45                 // Exclude GOTRACEBACK for the same reason.
46                 if strings.HasPrefix(env, "GOTRACEBACK=") {
47                         continue
48                 }
49                 cmd.Env = append(cmd.Env, env)
50         }
51         return cmd
52 }
53
54 var testprog struct {
55         sync.Mutex
56         dir    string
57         target map[string]buildexe
58 }
59
60 type buildexe struct {
61         exe string
62         err error
63 }
64
65 func runTestProg(t *testing.T, binary, name string) string {
66         testenv.MustHaveGoBuild(t)
67
68         exe, err := buildTestProg(t, binary)
69         if err != nil {
70                 t.Fatal(err)
71         }
72
73         cmd := testEnv(exec.Command(exe, name))
74         var b bytes.Buffer
75         cmd.Stdout = &b
76         cmd.Stderr = &b
77         if err := cmd.Start(); err != nil {
78                 t.Fatalf("starting %s %s: %v", binary, name, err)
79         }
80
81         // If the process doesn't complete within 1 minute,
82         // assume it is hanging and kill it to get a stack trace.
83         p := cmd.Process
84         done := make(chan bool)
85         go func() {
86                 scale := 1
87                 // This GOARCH/GOOS test is copied from cmd/dist/test.go.
88                 // TODO(iant): Have cmd/dist update the environment variable.
89                 if runtime.GOARCH == "arm" || runtime.GOOS == "windows" {
90                         scale = 2
91                 }
92                 if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
93                         if sc, err := strconv.Atoi(s); err == nil {
94                                 scale = sc
95                         }
96                 }
97
98                 select {
99                 case <-done:
100                 case <-time.After(time.Duration(scale) * time.Minute):
101                         p.Signal(sigquit)
102                 }
103         }()
104
105         if err := cmd.Wait(); err != nil {
106                 t.Logf("%s %s exit status: %v", binary, name, err)
107         }
108         close(done)
109
110         return b.String()
111 }
112
113 func buildTestProg(t *testing.T, binary string, flags ...string) (string, error) {
114         checkStaleRuntime(t)
115
116         testprog.Lock()
117         defer testprog.Unlock()
118         if testprog.dir == "" {
119                 dir, err := ioutil.TempDir("", "go-build")
120                 if err != nil {
121                         t.Fatalf("failed to create temp directory: %v", err)
122                 }
123                 testprog.dir = dir
124                 toRemove = append(toRemove, dir)
125         }
126
127         if testprog.target == nil {
128                 testprog.target = make(map[string]buildexe)
129         }
130         name := binary
131         if len(flags) > 0 {
132                 name += "_" + strings.Join(flags, "_")
133         }
134         target, ok := testprog.target[name]
135         if ok {
136                 return target.exe, target.err
137         }
138
139         exe := filepath.Join(testprog.dir, name+".exe")
140         cmd := exec.Command(testenv.GoToolPath(t), append([]string{"build", "-o", exe}, flags...)...)
141         cmd.Dir = "testdata/" + binary
142         out, err := testEnv(cmd).CombinedOutput()
143         if err != nil {
144                 target.err = fmt.Errorf("building %s %v: %v\n%s", binary, flags, err, out)
145                 testprog.target[name] = target
146                 return "", target.err
147         }
148         target.exe = exe
149         testprog.target[name] = target
150         return exe, nil
151 }
152
153 var (
154         staleRuntimeOnce sync.Once // guards init of staleRuntimeErr
155         staleRuntimeErr  error
156 )
157
158 func checkStaleRuntime(t *testing.T) {
159         staleRuntimeOnce.Do(func() {
160                 // 'go run' uses the installed copy of runtime.a, which may be out of date.
161                 out, err := testEnv(exec.Command(testenv.GoToolPath(t), "list", "-f", "{{.Stale}}", "runtime")).CombinedOutput()
162                 if err != nil {
163                         staleRuntimeErr = fmt.Errorf("failed to execute 'go list': %v\n%v", err, string(out))
164                         return
165                 }
166                 if string(out) != "false\n" {
167                         t.Logf("go list -f {{.Stale}} runtime:\n%s", out)
168                         out, err := testEnv(exec.Command(testenv.GoToolPath(t), "list", "-f", "{{.StaleReason}}", "runtime")).CombinedOutput()
169                         if err != nil {
170                                 t.Logf("go list -f {{.StaleReason}} failed: %v", err)
171                         }
172                         t.Logf("go list -f {{.StaleReason}} runtime:\n%s", out)
173                         staleRuntimeErr = fmt.Errorf("Stale runtime.a. Run 'go install runtime'.")
174                 }
175         })
176         if staleRuntimeErr != nil {
177                 t.Fatal(staleRuntimeErr)
178         }
179 }
180
181 func testCrashHandler(t *testing.T, cgo bool) {
182         type crashTest struct {
183                 Cgo bool
184         }
185         var output string
186         if cgo {
187                 output = runTestProg(t, "testprogcgo", "Crash")
188         } else {
189                 output = runTestProg(t, "testprog", "Crash")
190         }
191         want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
192         if output != want {
193                 t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
194         }
195 }
196
197 func TestCrashHandler(t *testing.T) {
198         testCrashHandler(t, false)
199 }
200
201 func testDeadlock(t *testing.T, name string) {
202         output := runTestProg(t, "testprog", name)
203         want := "fatal error: all goroutines are asleep - deadlock!\n"
204         if !strings.HasPrefix(output, want) {
205                 t.Fatalf("output does not start with %q:\n%s", want, output)
206         }
207 }
208
209 func TestSimpleDeadlock(t *testing.T) {
210         testDeadlock(t, "SimpleDeadlock")
211 }
212
213 func TestInitDeadlock(t *testing.T) {
214         testDeadlock(t, "InitDeadlock")
215 }
216
217 func TestLockedDeadlock(t *testing.T) {
218         testDeadlock(t, "LockedDeadlock")
219 }
220
221 func TestLockedDeadlock2(t *testing.T) {
222         testDeadlock(t, "LockedDeadlock2")
223 }
224
225 func TestGoexitDeadlock(t *testing.T) {
226         output := runTestProg(t, "testprog", "GoexitDeadlock")
227         want := "no goroutines (main called runtime.Goexit) - deadlock!"
228         if !strings.Contains(output, want) {
229                 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
230         }
231 }
232
233 func TestStackOverflow(t *testing.T) {
234         output := runTestProg(t, "testprog", "StackOverflow")
235         want := "runtime: goroutine stack exceeds 1474560-byte limit\nfatal error: stack overflow"
236         if !strings.HasPrefix(output, want) {
237                 t.Fatalf("output does not start with %q:\n%s", want, output)
238         }
239 }
240
241 func TestThreadExhaustion(t *testing.T) {
242         output := runTestProg(t, "testprog", "ThreadExhaustion")
243         want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
244         if !strings.HasPrefix(output, want) {
245                 t.Fatalf("output does not start with %q:\n%s", want, output)
246         }
247 }
248
249 func TestRecursivePanic(t *testing.T) {
250         output := runTestProg(t, "testprog", "RecursivePanic")
251         want := `wrap: bad
252 panic: again
253
254 `
255         if !strings.HasPrefix(output, want) {
256                 t.Fatalf("output does not start with %q:\n%s", want, output)
257         }
258
259 }
260
261 func TestGoexitCrash(t *testing.T) {
262         output := runTestProg(t, "testprog", "GoexitExit")
263         want := "no goroutines (main called runtime.Goexit) - deadlock!"
264         if !strings.Contains(output, want) {
265                 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
266         }
267 }
268
269 func TestGoexitDefer(t *testing.T) {
270         c := make(chan struct{})
271         go func() {
272                 defer func() {
273                         r := recover()
274                         if r != nil {
275                                 t.Errorf("non-nil recover during Goexit")
276                         }
277                         c <- struct{}{}
278                 }()
279                 runtime.Goexit()
280         }()
281         // Note: if the defer fails to run, we will get a deadlock here
282         <-c
283 }
284
285 func TestGoNil(t *testing.T) {
286         output := runTestProg(t, "testprog", "GoNil")
287         want := "go of nil func value"
288         if !strings.Contains(output, want) {
289                 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
290         }
291 }
292
293 func TestMainGoroutineID(t *testing.T) {
294         output := runTestProg(t, "testprog", "MainGoroutineID")
295         want := "panic: test\n\ngoroutine 1 [running]:\n"
296         if !strings.HasPrefix(output, want) {
297                 t.Fatalf("output does not start with %q:\n%s", want, output)
298         }
299 }
300
301 func TestNoHelperGoroutines(t *testing.T) {
302         output := runTestProg(t, "testprog", "NoHelperGoroutines")
303         matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1)
304         if len(matches) != 1 || matches[0][0] != "goroutine 1 [" {
305                 t.Fatalf("want to see only goroutine 1, see:\n%s", output)
306         }
307 }
308
309 func TestBreakpoint(t *testing.T) {
310         output := runTestProg(t, "testprog", "Breakpoint")
311         // If runtime.Breakpoint() is inlined, then the stack trace prints
312         // "runtime.Breakpoint(...)" instead of "runtime.Breakpoint()".
313         want := "runtime.Breakpoint("
314         if !strings.Contains(output, want) {
315                 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
316         }
317 }
318
319 func TestGoexitInPanic(t *testing.T) {
320         // see issue 8774: this code used to trigger an infinite recursion
321         output := runTestProg(t, "testprog", "GoexitInPanic")
322         want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
323         if !strings.HasPrefix(output, want) {
324                 t.Fatalf("output does not start with %q:\n%s", want, output)
325         }
326 }
327
328 // Issue 14965: Runtime panics should be of type runtime.Error
329 func TestRuntimePanicWithRuntimeError(t *testing.T) {
330         testCases := [...]func(){
331                 0: func() {
332                         var m map[uint64]bool
333                         m[1234] = true
334                 },
335                 1: func() {
336                         ch := make(chan struct{})
337                         close(ch)
338                         close(ch)
339                 },
340                 2: func() {
341                         var ch = make(chan struct{})
342                         close(ch)
343                         ch <- struct{}{}
344                 },
345                 3: func() {
346                         var s = make([]int, 2)
347                         _ = s[2]
348                 },
349                 4: func() {
350                         n := -1
351                         _ = make(chan bool, n)
352                 },
353                 5: func() {
354                         close((chan bool)(nil))
355                 },
356         }
357
358         for i, fn := range testCases {
359                 got := panicValue(fn)
360                 if _, ok := got.(runtime.Error); !ok {
361                         t.Errorf("test #%d: recovered value %v(type %T) does not implement runtime.Error", i, got, got)
362                 }
363         }
364 }
365
366 func panicValue(fn func()) (recovered interface{}) {
367         defer func() {
368                 recovered = recover()
369         }()
370         fn()
371         return
372 }
373
374 func TestPanicAfterGoexit(t *testing.T) {
375         // an uncaught panic should still work after goexit
376         output := runTestProg(t, "testprog", "PanicAfterGoexit")
377         want := "panic: hello"
378         if !strings.HasPrefix(output, want) {
379                 t.Fatalf("output does not start with %q:\n%s", want, output)
380         }
381 }
382
383 func TestRecoveredPanicAfterGoexit(t *testing.T) {
384         output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit")
385         want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
386         if !strings.HasPrefix(output, want) {
387                 t.Fatalf("output does not start with %q:\n%s", want, output)
388         }
389 }
390
391 func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
392         // 1. defer a function that recovers
393         // 2. defer a function that panics
394         // 3. call goexit
395         // Goexit should run the #2 defer. Its panic
396         // should be caught by the #1 defer, and execution
397         // should resume in the caller. Like the Goexit
398         // never happened!
399         defer func() {
400                 r := recover()
401                 if r == nil {
402                         panic("bad recover")
403                 }
404         }()
405         defer func() {
406                 panic("hello")
407         }()
408         runtime.Goexit()
409 }
410
411 func TestNetpollDeadlock(t *testing.T) {
412         t.Parallel()
413         output := runTestProg(t, "testprognet", "NetpollDeadlock")
414         want := "done\n"
415         if !strings.HasSuffix(output, want) {
416                 t.Fatalf("output does not start with %q:\n%s", want, output)
417         }
418 }
419
420 func TestPanicTraceback(t *testing.T) {
421         t.Parallel()
422         output := runTestProg(t, "testprog", "PanicTraceback")
423         want := "panic: hello"
424         if !strings.HasPrefix(output, want) {
425                 t.Fatalf("output does not start with %q:\n%s", want, output)
426         }
427
428         // Check functions in the traceback.
429         fns := []string{"main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"}
430         for _, fn := range fns {
431                 re := regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `\(.*\n`)
432                 idx := re.FindStringIndex(output)
433                 if idx == nil {
434                         t.Fatalf("expected %q function in traceback:\n%s", fn, output)
435                 }
436                 output = output[idx[1]:]
437         }
438 }
439
440 func testPanicDeadlock(t *testing.T, name string, want string) {
441         // test issue 14432
442         output := runTestProg(t, "testprog", name)
443         if !strings.HasPrefix(output, want) {
444                 t.Fatalf("output does not start with %q:\n%s", want, output)
445         }
446 }
447
448 func TestPanicDeadlockGosched(t *testing.T) {
449         testPanicDeadlock(t, "GoschedInPanic", "panic: errorThatGosched\n\n")
450 }
451
452 func TestPanicDeadlockSyscall(t *testing.T) {
453         testPanicDeadlock(t, "SyscallInPanic", "1\n2\npanic: 3\n\n")
454 }
455
456 func TestPanicLoop(t *testing.T) {
457         output := runTestProg(t, "testprog", "PanicLoop")
458         if want := "panic while printing panic value"; !strings.Contains(output, want) {
459                 t.Errorf("output does not contain %q:\n%s", want, output)
460         }
461 }
462
463 func TestMemPprof(t *testing.T) {
464         testenv.MustHaveGoRun(t)
465
466         exe, err := buildTestProg(t, "testprog")
467         if err != nil {
468                 t.Fatal(err)
469         }
470
471         got, err := testEnv(exec.Command(exe, "MemProf")).CombinedOutput()
472         if err != nil {
473                 t.Fatal(err)
474         }
475         fn := strings.TrimSpace(string(got))
476         defer os.Remove(fn)
477
478         for try := 0; try < 2; try++ {
479                 cmd := testEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-alloc_space", "-top"))
480                 // Check that pprof works both with and without explicit executable on command line.
481                 if try == 0 {
482                         cmd.Args = append(cmd.Args, exe, fn)
483                 } else {
484                         cmd.Args = append(cmd.Args, fn)
485                 }
486                 found := false
487                 for i, e := range cmd.Env {
488                         if strings.HasPrefix(e, "PPROF_TMPDIR=") {
489                                 cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir()
490                                 found = true
491                                 break
492                         }
493                 }
494                 if !found {
495                         cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir())
496                 }
497
498                 top, err := cmd.CombinedOutput()
499                 t.Logf("%s:\n%s", cmd.Args, top)
500                 if err != nil {
501                         t.Error(err)
502                 } else if !bytes.Contains(top, []byte("MemProf")) {
503                         t.Error("missing MemProf in pprof output")
504                 }
505         }
506 }
507
508 var concurrentMapTest = flag.Bool("run_concurrent_map_tests", false, "also run flaky concurrent map tests")
509
510 func TestConcurrentMapWrites(t *testing.T) {
511         if !*concurrentMapTest {
512                 t.Skip("skipping without -run_concurrent_map_tests")
513         }
514         testenv.MustHaveGoRun(t)
515         output := runTestProg(t, "testprog", "concurrentMapWrites")
516         want := "fatal error: concurrent map writes"
517         if !strings.HasPrefix(output, want) {
518                 t.Fatalf("output does not start with %q:\n%s", want, output)
519         }
520 }
521 func TestConcurrentMapReadWrite(t *testing.T) {
522         if !*concurrentMapTest {
523                 t.Skip("skipping without -run_concurrent_map_tests")
524         }
525         testenv.MustHaveGoRun(t)
526         output := runTestProg(t, "testprog", "concurrentMapReadWrite")
527         want := "fatal error: concurrent map read and map write"
528         if !strings.HasPrefix(output, want) {
529                 t.Fatalf("output does not start with %q:\n%s", want, output)
530         }
531 }
532 func TestConcurrentMapIterateWrite(t *testing.T) {
533         if !*concurrentMapTest {
534                 t.Skip("skipping without -run_concurrent_map_tests")
535         }
536         testenv.MustHaveGoRun(t)
537         output := runTestProg(t, "testprog", "concurrentMapIterateWrite")
538         want := "fatal error: concurrent map iteration and map write"
539         if !strings.HasPrefix(output, want) {
540                 t.Fatalf("output does not start with %q:\n%s", want, output)
541         }
542 }
543
544 type point struct {
545         x, y *int
546 }
547
548 func (p *point) negate() {
549         *p.x = *p.x * -1
550         *p.y = *p.y * -1
551 }
552
553 // Test for issue #10152.
554 func TestPanicInlined(t *testing.T) {
555         defer func() {
556                 r := recover()
557                 if r == nil {
558                         t.Fatalf("recover failed")
559                 }
560                 buf := make([]byte, 2048)
561                 n := runtime.Stack(buf, false)
562                 buf = buf[:n]
563                 if !bytes.Contains(buf, []byte("(*point).negate(")) {
564                         t.Fatalf("expecting stack trace to contain call to (*point).negate()")
565                 }
566         }()
567
568         pt := new(point)
569         pt.negate()
570 }