github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/runtime/debug/stack_test.go (about) 1 // Copyright 2011 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 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) // e.g. EMFILE 34 } 35 println("hello") 36 panic("oops") 37 } 38 39 // default: run the tests. 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 /* 53 The traceback should look something like this, modulo line numbers and hex constants. 54 Don't worry much about the base levels, but check the ones in our own package. 55 56 goroutine 10 [running]: 57 runtime/debug.Stack(0x0, 0x0, 0x0) 58 /Users/r/go/src/runtime/debug/stack.go:28 +0x80 59 runtime/debug.(*T).ptrmethod(0xc82005ee70, 0x0, 0x0, 0x0) 60 /Users/r/go/src/runtime/debug/stack_test.go:15 +0x29 61 runtime/debug.T.method(0x0, 0x0, 0x0, 0x0) 62 /Users/r/go/src/runtime/debug/stack_test.go:18 +0x32 63 runtime/debug.TestStack(0xc8201ce000) 64 /Users/r/go/src/runtime/debug/stack_test.go:37 +0x38 65 testing.tRunner(0xc8201ce000, 0x664b58) 66 /Users/r/go/src/testing/testing.go:456 +0x98 67 created by testing.RunTests 68 /Users/r/go/src/testing/testing.go:561 +0x86d 69 */ 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 // If built with -trimpath, file locations should start with package paths. 78 // Otherwise, file locations should start with a GOROOT/src prefix 79 // (for whatever value of GOROOT is baked into the binary, not the one 80 // that may be set in the environment). 81 fileGoroot := "" 82 if envGoroot := os.Getenv("GOROOT"); envGoroot != "" { 83 // Since GOROOT is set explicitly in the environment, we can't be certain 84 // that it is the same GOROOT value baked into the binary, and we can't 85 // change the value in-process because runtime.GOROOT uses the value from 86 // initial (not current) environment. Spawn a subprocess to determine the 87 // real baked-in GOROOT. 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 // Since GOROOT is not set in the environment, its value (if any) must come 103 // from the path embedded in the binary. 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 // Read the file the child process should have written. 158 // It should contain a crash report such as this: 159 // 160 // panic: oops 161 // 162 // goroutine 1 [running]: 163 // runtime/debug_test.TestMain(0x1400007e0a0) 164 // GOROOT/src/runtime/debug/stack_test.go:33 +0x18c 165 // main.main() 166 // _testmain.go:71 +0x170 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 // Check that the crash file and the stderr both contain the panic and stack trace. 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 // Check that stderr, but not crash, contains the output of println(). 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 }