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 cmd := exec.Command(testenv.Executable(t))
91 cmd.Env = append(os.Environ(), "GOROOT=", "GO_RUNTIME_DEBUG_TEST_ENTRYPOINT=dumpgoroot")
92 out, err := cmd.Output()
93 if err != nil {
94 t.Fatal(err)
95 }
96 fileGoroot = string(bytes.TrimSpace(out))
97 } else {
98
99
100 fileGoroot = runtime.GOROOT()
101 }
102 filePrefix := ""
103 if fileGoroot != "" {
104 filePrefix = filepath.ToSlash(fileGoroot) + "/src/"
105 }
106
107 n := 0
108 frame := func(file, code string) {
109 t.Helper()
110
111 line := lines[n]
112 if !strings.Contains(line, code) {
113 t.Errorf("expected %q in %q", code, line)
114 }
115 n++
116
117 line = lines[n]
118
119 wantPrefix := "\t" + filePrefix + file
120 if !strings.HasPrefix(line, wantPrefix) {
121 t.Errorf("in line %q, expected prefix %q", line, wantPrefix)
122 }
123 n++
124 }
125 n++
126
127 frame("runtime/debug/stack.go", "runtime/debug.Stack")
128 frame("runtime/debug/stack_test.go", "runtime/debug_test.(*T).ptrmethod")
129 frame("runtime/debug/stack_test.go", "runtime/debug_test.T.method")
130 frame("runtime/debug/stack_test.go", "runtime/debug_test.TestStack")
131 frame("testing/testing.go", "")
132 }
133
134 func TestSetCrashOutput(t *testing.T) {
135 crashOutput := filepath.Join(t.TempDir(), "crash.out")
136
137 cmd := exec.Command(testenv.Executable(t))
138 cmd.Stderr = new(strings.Builder)
139 cmd.Env = append(os.Environ(), "GO_RUNTIME_DEBUG_TEST_ENTRYPOINT=setcrashoutput", "CRASHOUTPUT="+crashOutput)
140 err := cmd.Run()
141 stderr := fmt.Sprint(cmd.Stderr)
142 if err == nil {
143 t.Fatalf("child process succeeded unexpectedly (stderr: %s)", stderr)
144 }
145 t.Logf("child process finished with error %v and stderr <<%s>>", err, stderr)
146
147
148
149
150
151
152
153
154
155
156
157 data, err := os.ReadFile(crashOutput)
158 if err != nil {
159 t.Fatalf("child process failed to write crash report: %v", err)
160 }
161 crash := string(data)
162 t.Logf("crash = <<%s>>", crash)
163 t.Logf("stderr = <<%s>>", stderr)
164
165
166 for _, want := range []string{
167 "panic: oops",
168 "goroutine 1",
169 "debug_test.TestMain",
170 } {
171 if !strings.Contains(crash, want) {
172 t.Errorf("crash output does not contain %q", want)
173 }
174 if !strings.Contains(stderr, want) {
175 t.Errorf("stderr output does not contain %q", want)
176 }
177 }
178
179
180 printlnOnly := "hello"
181 if strings.Contains(crash, printlnOnly) {
182 t.Errorf("crash output contains %q, but should not", printlnOnly)
183 }
184 if !strings.Contains(stderr, printlnOnly) {
185 t.Errorf("stderr output does not contain %q, but should", printlnOnly)
186 }
187 }
188
View as plain text