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