]> Cypherpunks.ru repositories - gostls13.git/blob - src/runtime/crash_test.go
runtime, cmd/go: roll back stale message, test detail
[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         want := "runtime.Breakpoint()"
312         if !strings.Contains(output, want) {
313                 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
314         }
315 }
316
317 func TestGoexitInPanic(t *testing.T) {
318         // see issue 8774: this code used to trigger an infinite recursion
319         output := runTestProg(t, "testprog", "GoexitInPanic")
320         want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
321         if !strings.HasPrefix(output, want) {
322                 t.Fatalf("output does not start with %q:\n%s", want, output)
323         }
324 }
325
326 // Issue 14965: Runtime panics should be of type runtime.Error
327 func TestRuntimePanicWithRuntimeError(t *testing.T) {
328         testCases := [...]func(){
329                 0: func() {
330                         var m map[uint64]bool
331                         m[1234] = true
332                 },
333                 1: func() {
334                         ch := make(chan struct{})
335                         close(ch)
336                         close(ch)
337                 },
338                 2: func() {
339                         var ch = make(chan struct{})
340                         close(ch)
341                         ch <- struct{}{}
342                 },
343                 3: func() {
344                         var s = make([]int, 2)
345                         _ = s[2]
346                 },
347                 4: func() {
348                         n := -1
349                         _ = make(chan bool, n)
350                 },
351                 5: func() {
352                         close((chan bool)(nil))
353                 },
354         }
355
356         for i, fn := range testCases {
357                 got := panicValue(fn)
358                 if _, ok := got.(runtime.Error); !ok {
359                         t.Errorf("test #%d: recovered value %v(type %T) does not implement runtime.Error", i, got, got)
360                 }
361         }
362 }
363
364 func panicValue(fn func()) (recovered interface{}) {
365         defer func() {
366                 recovered = recover()
367         }()
368         fn()
369         return
370 }
371
372 func TestPanicAfterGoexit(t *testing.T) {
373         // an uncaught panic should still work after goexit
374         output := runTestProg(t, "testprog", "PanicAfterGoexit")
375         want := "panic: hello"
376         if !strings.HasPrefix(output, want) {
377                 t.Fatalf("output does not start with %q:\n%s", want, output)
378         }
379 }
380
381 func TestRecoveredPanicAfterGoexit(t *testing.T) {
382         output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit")
383         want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
384         if !strings.HasPrefix(output, want) {
385                 t.Fatalf("output does not start with %q:\n%s", want, output)
386         }
387 }
388
389 func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
390         // 1. defer a function that recovers
391         // 2. defer a function that panics
392         // 3. call goexit
393         // Goexit should run the #2 defer. Its panic
394         // should be caught by the #1 defer, and execution
395         // should resume in the caller. Like the Goexit
396         // never happened!
397         defer func() {
398                 r := recover()
399                 if r == nil {
400                         panic("bad recover")
401                 }
402         }()
403         defer func() {
404                 panic("hello")
405         }()
406         runtime.Goexit()
407 }
408
409 func TestNetpollDeadlock(t *testing.T) {
410         t.Parallel()
411         output := runTestProg(t, "testprognet", "NetpollDeadlock")
412         want := "done\n"
413         if !strings.HasSuffix(output, want) {
414                 t.Fatalf("output does not start with %q:\n%s", want, output)
415         }
416 }
417
418 func TestPanicTraceback(t *testing.T) {
419         t.Parallel()
420         output := runTestProg(t, "testprog", "PanicTraceback")
421         want := "panic: hello"
422         if !strings.HasPrefix(output, want) {
423                 t.Fatalf("output does not start with %q:\n%s", want, output)
424         }
425
426         // Check functions in the traceback.
427         fns := []string{"main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"}
428         for _, fn := range fns {
429                 re := regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `\(.*\n`)
430                 idx := re.FindStringIndex(output)
431                 if idx == nil {
432                         t.Fatalf("expected %q function in traceback:\n%s", fn, output)
433                 }
434                 output = output[idx[1]:]
435         }
436 }
437
438 func testPanicDeadlock(t *testing.T, name string, want string) {
439         // test issue 14432
440         output := runTestProg(t, "testprog", name)
441         if !strings.HasPrefix(output, want) {
442                 t.Fatalf("output does not start with %q:\n%s", want, output)
443         }
444 }
445
446 func TestPanicDeadlockGosched(t *testing.T) {
447         testPanicDeadlock(t, "GoschedInPanic", "panic: errorThatGosched\n\n")
448 }
449
450 func TestPanicDeadlockSyscall(t *testing.T) {
451         testPanicDeadlock(t, "SyscallInPanic", "1\n2\npanic: 3\n\n")
452 }
453
454 func TestPanicLoop(t *testing.T) {
455         output := runTestProg(t, "testprog", "PanicLoop")
456         if want := "panic while printing panic value"; !strings.Contains(output, want) {
457                 t.Errorf("output does not contain %q:\n%s", want, output)
458         }
459 }
460
461 func TestMemPprof(t *testing.T) {
462         testenv.MustHaveGoRun(t)
463
464         exe, err := buildTestProg(t, "testprog")
465         if err != nil {
466                 t.Fatal(err)
467         }
468
469         got, err := testEnv(exec.Command(exe, "MemProf")).CombinedOutput()
470         if err != nil {
471                 t.Fatal(err)
472         }
473         fn := strings.TrimSpace(string(got))
474         defer os.Remove(fn)
475
476         for try := 0; try < 2; try++ {
477                 cmd := testEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-alloc_space", "-top"))
478                 // Check that pprof works both with and without explicit executable on command line.
479                 if try == 0 {
480                         cmd.Args = append(cmd.Args, exe, fn)
481                 } else {
482                         cmd.Args = append(cmd.Args, fn)
483                 }
484                 found := false
485                 for i, e := range cmd.Env {
486                         if strings.HasPrefix(e, "PPROF_TMPDIR=") {
487                                 cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir()
488                                 found = true
489                                 break
490                         }
491                 }
492                 if !found {
493                         cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir())
494                 }
495
496                 top, err := cmd.CombinedOutput()
497                 t.Logf("%s:\n%s", cmd.Args, top)
498                 if err != nil {
499                         t.Error(err)
500                 } else if !bytes.Contains(top, []byte("MemProf")) {
501                         t.Error("missing MemProf in pprof output")
502                 }
503         }
504 }
505
506 var concurrentMapTest = flag.Bool("run_concurrent_map_tests", false, "also run flaky concurrent map tests")
507
508 func TestConcurrentMapWrites(t *testing.T) {
509         if !*concurrentMapTest {
510                 t.Skip("skipping without -run_concurrent_map_tests")
511         }
512         testenv.MustHaveGoRun(t)
513         output := runTestProg(t, "testprog", "concurrentMapWrites")
514         want := "fatal error: concurrent map writes"
515         if !strings.HasPrefix(output, want) {
516                 t.Fatalf("output does not start with %q:\n%s", want, output)
517         }
518 }
519 func TestConcurrentMapReadWrite(t *testing.T) {
520         if !*concurrentMapTest {
521                 t.Skip("skipping without -run_concurrent_map_tests")
522         }
523         testenv.MustHaveGoRun(t)
524         output := runTestProg(t, "testprog", "concurrentMapReadWrite")
525         want := "fatal error: concurrent map read and map write"
526         if !strings.HasPrefix(output, want) {
527                 t.Fatalf("output does not start with %q:\n%s", want, output)
528         }
529 }
530 func TestConcurrentMapIterateWrite(t *testing.T) {
531         if !*concurrentMapTest {
532                 t.Skip("skipping without -run_concurrent_map_tests")
533         }
534         testenv.MustHaveGoRun(t)
535         output := runTestProg(t, "testprog", "concurrentMapIterateWrite")
536         want := "fatal error: concurrent map iteration and map write"
537         if !strings.HasPrefix(output, want) {
538                 t.Fatalf("output does not start with %q:\n%s", want, output)
539         }
540 }