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