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 // +build darwin dragonfly freebsd linux netbsd openbsd solaris 6 7 package runtime_test 8 9 import ( 10 "bytes" 11 "internal/testenv" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "runtime" 17 "syscall" 18 "testing" 19 ) 20 21 func TestCrashDumpsAllThreads(t *testing.T) { 22 switch runtime.GOOS { 23 case "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris": 24 default: 25 t.Skipf("skipping; not supported on %v", runtime.GOOS) 26 } 27 28 // We don't use executeTest because we need to kill the 29 // program while it is running. 30 31 testenv.MustHaveGoBuild(t) 32 33 checkStaleRuntime(t) 34 35 dir, err := ioutil.TempDir("", "go-build") 36 if err != nil { 37 t.Fatalf("failed to create temp directory: %v", err) 38 } 39 defer os.RemoveAll(dir) 40 41 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), []byte(crashDumpsAllThreadsSource), 0666); err != nil { 42 t.Fatalf("failed to create Go file: %v", err) 43 } 44 45 cmd := exec.Command("go", "build", "-o", "a.exe") 46 cmd.Dir = dir 47 out, err := testEnv(cmd).CombinedOutput() 48 if err != nil { 49 t.Fatalf("building source: %v\n%s", err, out) 50 } 51 52 cmd = exec.Command(filepath.Join(dir, "a.exe")) 53 cmd = testEnv(cmd) 54 cmd.Env = append(cmd.Env, "GOTRACEBACK=crash") 55 var outbuf bytes.Buffer 56 cmd.Stdout = &outbuf 57 cmd.Stderr = &outbuf 58 59 rp, wp, err := os.Pipe() 60 if err != nil { 61 t.Fatal(err) 62 } 63 cmd.ExtraFiles = []*os.File{wp} 64 65 if err := cmd.Start(); err != nil { 66 t.Fatalf("starting program: %v", err) 67 } 68 69 if err := wp.Close(); err != nil { 70 t.Logf("closing write pipe: %v", err) 71 } 72 if _, err := rp.Read(make([]byte, 1)); err != nil { 73 t.Fatalf("reading from pipe: %v", err) 74 } 75 76 if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil { 77 t.Fatalf("signal: %v", err) 78 } 79 80 // No point in checking the error return from Wait--we expect 81 // it to fail. 82 cmd.Wait() 83 84 // We want to see a stack trace for each thread. 85 // Before https://golang.org/cl/2811 running threads would say 86 // "goroutine running on other thread; stack unavailable". 87 out = outbuf.Bytes() 88 n := bytes.Count(out, []byte("main.loop(")) 89 if n != 4 { 90 t.Errorf("found %d instances of main.loop; expected 4", n) 91 t.Logf("%s", out) 92 } 93 } 94 95 const crashDumpsAllThreadsSource = ` 96 package main 97 98 import ( 99 "fmt" 100 "os" 101 "runtime" 102 ) 103 104 func main() { 105 const count = 4 106 runtime.GOMAXPROCS(count + 1) 107 108 chans := make([]chan bool, count) 109 for i := range chans { 110 chans[i] = make(chan bool) 111 go loop(i, chans[i]) 112 } 113 114 // Wait for all the goroutines to start executing. 115 for _, c := range chans { 116 <-c 117 } 118 119 // Tell our parent that all the goroutines are executing. 120 if _, err := os.NewFile(3, "pipe").WriteString("x"); err != nil { 121 fmt.Fprintf(os.Stderr, "write to pipe failed: %v\n", err) 122 os.Exit(2) 123 } 124 125 select {} 126 } 127 128 func loop(i int, c chan bool) { 129 close(c) 130 for { 131 for j := 0; j < 0x7fffffff; j++ { 132 } 133 } 134 } 135 ` 136