1
2
3
4
5 package debug_test
6
7 import (
8 "bytes"
9 "fmt"
10 "internal/testenv"
11 "log"
12 "os"
13 "os/exec"
14 "path/filepath"
15 "runtime"
16 . "runtime/debug"
17 "strings"
18 "testing"
19 )
20
21 func TestMain(m *testing.M) {
22 switch os.Getenv("GO_RUNTIME_DEBUG_TEST_ENTRYPOINT") {
23 case "dumpgoroot":
24 fmt.Println(runtime.GOROOT())
25 os.Exit(0)
26
27 case "setcrashoutput":
28 f, err := os.Create(os.Getenv("CRASHOUTPUT"))
29 if err != nil {
30 log.Fatal(err)
31 }
32 if err := SetCrashOutput(f); err != nil {
33 log.Fatal(err)
34 }
35 println("hello")
36 panic("oops")
37 }
38
39
40 os.Exit(m.Run())
41 }
42
43 type T int
44
45 func (t *T) ptrmethod() []byte {
46 return Stack()
47 }
48 func (t T) method() []byte {
49 return t.ptrmethod()
50 }
51
52
70 func TestStack(t *testing.T) {
71 b := T(0).method()
72 lines := strings.Split(string(b), "\n")
73 if len(lines) < 6 {
74 t.Fatal("too few lines")
75 }
76
77
78
79
80
81 fileGoroot := ""
82 if envGoroot := os.Getenv("GOROOT"); envGoroot != "" {
83
84
85
86
87
88 t.Logf("found GOROOT %q from environment; checking embedded GOROOT value", envGoroot)
89 testenv.MustHaveExec(t)
90 exe, err := os.Executable()
91 if err != nil {
92 t.Fatal(err)
93 }
94 cmd := exec.Command(exe)
95 cmd.Env = append(os.Environ(), "GOROOT=", "GO_RUNTIME_DEBUG_TEST_ENTRYPOINT=dumpgoroot")
96 out, err := cmd.Output()
97 if err != nil {
98 t.Fatal(err)
99 }
100 fileGoroot = string(bytes.TrimSpace(out))
101 } else {
102
103
104 fileGoroot = runtime.GOROOT()
105 }
106 filePrefix := ""
107 if fileGoroot != "" {
108 filePrefix = filepath.ToSlash(fileGoroot) + "/src/"
109 }
110
111 n := 0
112 frame := func(file, code string) {
113 t.Helper()
114
115 line := lines[n]
116 if !strings.Contains(line, code) {
117 t.Errorf("expected %q in %q", code, line)
118 }
119 n++
120
121 line = lines[n]
122
123 wantPrefix := "\t" + filePrefix + file
124 if !strings.HasPrefix(line, wantPrefix) {
125 t.Errorf("in line %q, expected prefix %q", line, wantPrefix)
126 }
127 n++
128 }
129 n++
130
131 frame("runtime/debug/stack.go", "runtime/debug.Stack")
132 frame("runtime/debug/stack_test.go", "runtime/debug_test.(*T).ptrmethod")
133 frame("runtime/debug/stack_test.go", "runtime/debug_test.T.method")
134 frame("runtime/debug/stack_test.go", "runtime/debug_test.TestStack")
135 frame("testing/testing.go", "")
136 }
137
138 func TestSetCrashOutput(t *testing.T) {
139 testenv.MustHaveExec(t)
140 exe, err := os.Executable()
141 if err != nil {
142 t.Fatal(err)
143 }
144
145 crashOutput := filepath.Join(t.TempDir(), "crash.out")
146
147 cmd := exec.Command(exe)
148 cmd.Stderr = new(strings.Builder)
149 cmd.Env = append(os.Environ(), "GO_RUNTIME_DEBUG_TEST_ENTRYPOINT=setcrashoutput", "CRASHOUTPUT="+crashOutput)
150 err = cmd.Run()
151 stderr := fmt.Sprint(cmd.Stderr)
152 if err == nil {
153 t.Fatalf("child process succeeded unexpectedly (stderr: %s)", stderr)
154 }
155 t.Logf("child process finished with error %v and stderr <<%s>>", err, stderr)
156
157
158
159
160
161
162
163
164
165
166
167 data, err := os.ReadFile(crashOutput)
168 if err != nil {
169 t.Fatalf("child process failed to write crash report: %v", err)
170 }
171 crash := string(data)
172 t.Logf("crash = <<%s>>", crash)
173 t.Logf("stderr = <<%s>>", stderr)
174
175
176 for _, want := range []string{
177 "panic: oops",
178 "goroutine 1",
179 "debug_test.TestMain",
180 } {
181 if !strings.Contains(crash, want) {
182 t.Errorf("crash output does not contain %q", want)
183 }
184 if !strings.Contains(stderr, want) {
185 t.Errorf("stderr output does not contain %q", want)
186 }
187 }
188
189
190 printlnOnly := "hello"
191 if strings.Contains(crash, printlnOnly) {
192 t.Errorf("crash output contains %q, but should not", printlnOnly)
193 }
194 if !strings.Contains(stderr, printlnOnly) {
195 t.Errorf("stderr output does not contain %q, but should", printlnOnly)
196 }
197 }
198
View as plain text