Source file src/runtime/debug/stack_test.go

     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  }
   198  

View as plain text