Source file src/runtime/crash_test.go

     1  // Copyright 2012 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 runtime_test
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"errors"
    11  	"flag"
    12  	"fmt"
    13  	"internal/asan"
    14  	"internal/msan"
    15  	"internal/profile"
    16  	"internal/race"
    17  	"internal/testenv"
    18  	traceparse "internal/trace"
    19  	"io"
    20  	"log"
    21  	"os"
    22  	"os/exec"
    23  	"path/filepath"
    24  	"regexp"
    25  	"runtime"
    26  	"runtime/trace"
    27  	"strings"
    28  	"sync"
    29  	"testing"
    30  	"time"
    31  )
    32  
    33  var toRemove []string
    34  
    35  const entrypointVar = "RUNTIME_TEST_ENTRYPOINT"
    36  
    37  func TestMain(m *testing.M) {
    38  	switch entrypoint := os.Getenv(entrypointVar); entrypoint {
    39  	case "panic":
    40  		crashViaPanic()
    41  		panic("unreachable")
    42  	case "trap":
    43  		crashViaTrap()
    44  		panic("unreachable")
    45  	default:
    46  		log.Fatalf("invalid %s: %q", entrypointVar, entrypoint)
    47  	case "":
    48  		// fall through to normal behavior
    49  	}
    50  
    51  	_, coreErrBefore := os.Stat("core")
    52  
    53  	status := m.Run()
    54  	for _, file := range toRemove {
    55  		os.RemoveAll(file)
    56  	}
    57  
    58  	_, coreErrAfter := os.Stat("core")
    59  	if coreErrBefore != nil && coreErrAfter == nil {
    60  		fmt.Fprintln(os.Stderr, "runtime.test: some test left a core file behind")
    61  		if status == 0 {
    62  			status = 1
    63  		}
    64  	}
    65  
    66  	os.Exit(status)
    67  }
    68  
    69  var testprog struct {
    70  	sync.Mutex
    71  	dir    string
    72  	target map[string]*buildexe
    73  }
    74  
    75  type buildexe struct {
    76  	once sync.Once
    77  	exe  string
    78  	err  error
    79  }
    80  
    81  func runTestProg(t *testing.T, binary, name string, env ...string) string {
    82  	if *flagQuick {
    83  		t.Skip("-quick")
    84  	}
    85  
    86  	testenv.MustHaveGoBuild(t)
    87  	t.Helper()
    88  
    89  	exe, err := buildTestProg(t, binary)
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  
    94  	return runBuiltTestProg(t, exe, name, env...)
    95  }
    96  
    97  func runBuiltTestProg(t *testing.T, exe, name string, env ...string) string {
    98  	t.Helper()
    99  
   100  	if *flagQuick {
   101  		t.Skip("-quick")
   102  	}
   103  
   104  	start := time.Now()
   105  
   106  	cmd := testenv.CleanCmdEnv(testenv.Command(t, exe, name))
   107  	cmd.Env = append(cmd.Env, env...)
   108  	if testing.Short() {
   109  		cmd.Env = append(cmd.Env, "RUNTIME_TEST_SHORT=1")
   110  	}
   111  	out, err := cmd.CombinedOutput()
   112  	if err == nil {
   113  		t.Logf("%v (%v): ok", cmd, time.Since(start))
   114  	} else {
   115  		if _, ok := err.(*exec.ExitError); ok {
   116  			t.Logf("%v: %v", cmd, err)
   117  		} else if errors.Is(err, exec.ErrWaitDelay) {
   118  			t.Fatalf("%v: %v", cmd, err)
   119  		} else {
   120  			t.Fatalf("%v failed to start: %v", cmd, err)
   121  		}
   122  	}
   123  	return string(out)
   124  }
   125  
   126  var serializeBuild = make(chan bool, 2)
   127  
   128  func buildTestProg(t *testing.T, binary string, flags ...string) (string, error) {
   129  	if *flagQuick {
   130  		t.Skip("-quick")
   131  	}
   132  	testenv.MustHaveGoBuild(t)
   133  
   134  	testprog.Lock()
   135  	if testprog.dir == "" {
   136  		dir, err := os.MkdirTemp("", "go-build")
   137  		if err != nil {
   138  			t.Fatalf("failed to create temp directory: %v", err)
   139  		}
   140  		testprog.dir = dir
   141  		toRemove = append(toRemove, dir)
   142  	}
   143  
   144  	if testprog.target == nil {
   145  		testprog.target = make(map[string]*buildexe)
   146  	}
   147  	name := binary
   148  	if len(flags) > 0 {
   149  		name += "_" + strings.Join(flags, "_")
   150  	}
   151  	target, ok := testprog.target[name]
   152  	if !ok {
   153  		target = &buildexe{}
   154  		testprog.target[name] = target
   155  	}
   156  
   157  	dir := testprog.dir
   158  
   159  	// Unlock testprog while actually building, so that other
   160  	// tests can look up executables that were already built.
   161  	testprog.Unlock()
   162  
   163  	target.once.Do(func() {
   164  		// Only do two "go build"'s at a time,
   165  		// to keep load from getting too high.
   166  		serializeBuild <- true
   167  		defer func() { <-serializeBuild }()
   168  
   169  		// Don't get confused if testenv.GoToolPath calls t.Skip.
   170  		target.err = errors.New("building test called t.Skip")
   171  
   172  		if asan.Enabled {
   173  			flags = append(flags, "-asan")
   174  		}
   175  		if msan.Enabled {
   176  			flags = append(flags, "-msan")
   177  		}
   178  		if race.Enabled {
   179  			flags = append(flags, "-race")
   180  		}
   181  
   182  		exe := filepath.Join(dir, name+".exe")
   183  
   184  		start := time.Now()
   185  		cmd := exec.Command(testenv.GoToolPath(t), append([]string{"build", "-o", exe}, flags...)...)
   186  		t.Logf("running %v", cmd)
   187  		cmd.Dir = "testdata/" + binary
   188  		cmd = testenv.CleanCmdEnv(cmd)
   189  
   190  		// If tests need any experimental flags, add them here.
   191  		//
   192  		// TODO(vsaioc): Remove `goroutineleakprofile` once the feature is no longer experimental.
   193  		edited := false
   194  		for i := range cmd.Env {
   195  			e := cmd.Env[i]
   196  			if _, vars, ok := strings.Cut(e, "GOEXPERIMENT="); ok {
   197  				cmd.Env[i] = "GOEXPERIMENT=" + vars + ",goroutineleakprofile"
   198  				edited, _ = true, vars
   199  			}
   200  		}
   201  		if !edited {
   202  			cmd.Env = append(cmd.Env, "GOEXPERIMENT=goroutineleakprofile")
   203  		}
   204  
   205  		out, err := cmd.CombinedOutput()
   206  		if err != nil {
   207  			target.err = fmt.Errorf("building %s %v: %v\n%s", binary, flags, err, out)
   208  		} else {
   209  			t.Logf("built %v in %v", name, time.Since(start))
   210  			target.exe = exe
   211  			target.err = nil
   212  		}
   213  	})
   214  
   215  	return target.exe, target.err
   216  }
   217  
   218  func TestVDSO(t *testing.T) {
   219  	t.Parallel()
   220  	output := runTestProg(t, "testprog", "SignalInVDSO")
   221  	want := "success\n"
   222  	if output != want {
   223  		t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
   224  	}
   225  }
   226  
   227  func testCrashHandler(t *testing.T, cgo bool) {
   228  	type crashTest struct {
   229  		Cgo bool
   230  	}
   231  	var output string
   232  	if cgo {
   233  		if runtime.GOOS == "freebsd" && race.Enabled {
   234  			t.Skipf("race + cgo freebsd not supported. See https://go.dev/issue/73788.")
   235  		}
   236  		output = runTestProg(t, "testprogcgo", "Crash")
   237  	} else {
   238  		output = runTestProg(t, "testprog", "Crash")
   239  	}
   240  	want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
   241  	if output != want {
   242  		t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
   243  	}
   244  }
   245  
   246  func TestCrashHandler(t *testing.T) {
   247  	testCrashHandler(t, false)
   248  }
   249  
   250  var deadlockBuildTypes = testenv.SpecialBuildTypes{
   251  	// External linking brings in cgo, causing deadlock detection not working.
   252  	Cgo:  false,
   253  	Asan: asan.Enabled,
   254  	Msan: msan.Enabled,
   255  	Race: race.Enabled,
   256  }
   257  
   258  func testDeadlock(t *testing.T, name string) {
   259  	// External linking brings in cgo, causing deadlock detection not working.
   260  	testenv.MustInternalLink(t, deadlockBuildTypes)
   261  
   262  	output := runTestProg(t, "testprog", name)
   263  	want := "fatal error: all goroutines are asleep - deadlock!\n"
   264  	if !strings.HasPrefix(output, want) {
   265  		t.Fatalf("output does not start with %q:\n%s", want, output)
   266  	}
   267  }
   268  
   269  func TestSimpleDeadlock(t *testing.T) {
   270  	testDeadlock(t, "SimpleDeadlock")
   271  }
   272  
   273  func TestInitDeadlock(t *testing.T) {
   274  	testDeadlock(t, "InitDeadlock")
   275  }
   276  
   277  func TestLockedDeadlock(t *testing.T) {
   278  	testDeadlock(t, "LockedDeadlock")
   279  }
   280  
   281  func TestLockedDeadlock2(t *testing.T) {
   282  	testDeadlock(t, "LockedDeadlock2")
   283  }
   284  
   285  func TestGoexitDeadlock(t *testing.T) {
   286  	// External linking brings in cgo, causing deadlock detection not working.
   287  	testenv.MustInternalLink(t, deadlockBuildTypes)
   288  
   289  	output := runTestProg(t, "testprog", "GoexitDeadlock")
   290  	want := "no goroutines (main called runtime.Goexit) - deadlock!"
   291  	if !strings.Contains(output, want) {
   292  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   293  	}
   294  }
   295  
   296  func TestStackOverflow(t *testing.T) {
   297  	output := runTestProg(t, "testprog", "StackOverflow")
   298  	want := []string{
   299  		"runtime: goroutine stack exceeds 1474560-byte limit\n",
   300  		"fatal error: stack overflow",
   301  		// information about the current SP and stack bounds
   302  		"runtime: sp=",
   303  		"stack=[",
   304  	}
   305  	if !strings.HasPrefix(output, want[0]) {
   306  		t.Errorf("output does not start with %q", want[0])
   307  	}
   308  	for _, s := range want[1:] {
   309  		if !strings.Contains(output, s) {
   310  			t.Errorf("output does not contain %q", s)
   311  		}
   312  	}
   313  	if t.Failed() {
   314  		t.Logf("output:\n%s", output)
   315  	}
   316  }
   317  
   318  func TestThreadExhaustion(t *testing.T) {
   319  	output := runTestProg(t, "testprog", "ThreadExhaustion")
   320  	want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
   321  	if !strings.HasPrefix(output, want) {
   322  		t.Fatalf("output does not start with %q:\n%s", want, output)
   323  	}
   324  }
   325  
   326  func TestRecursivePanic(t *testing.T) {
   327  	output := runTestProg(t, "testprog", "RecursivePanic")
   328  	want := `wrap: bad
   329  panic: again
   330  
   331  `
   332  	if !strings.HasPrefix(output, want) {
   333  		t.Fatalf("output does not start with %q:\n%s", want, output)
   334  	}
   335  
   336  }
   337  
   338  func TestRecursivePanic2(t *testing.T) {
   339  	output := runTestProg(t, "testprog", "RecursivePanic2")
   340  	want := `first panic
   341  second panic
   342  panic: third panic
   343  
   344  `
   345  	if !strings.HasPrefix(output, want) {
   346  		t.Fatalf("output does not start with %q:\n%s", want, output)
   347  	}
   348  
   349  }
   350  
   351  func TestRecursivePanic3(t *testing.T) {
   352  	output := runTestProg(t, "testprog", "RecursivePanic3")
   353  	want := `panic: first panic
   354  
   355  `
   356  	if !strings.HasPrefix(output, want) {
   357  		t.Fatalf("output does not start with %q:\n%s", want, output)
   358  	}
   359  
   360  }
   361  
   362  func TestRecursivePanic4(t *testing.T) {
   363  	output := runTestProg(t, "testprog", "RecursivePanic4")
   364  	want := `panic: first panic [recovered]
   365  	panic: second panic
   366  `
   367  	if !strings.HasPrefix(output, want) {
   368  		t.Fatalf("output does not start with %q:\n%s", want, output)
   369  	}
   370  
   371  }
   372  
   373  func TestRecursivePanic5(t *testing.T) {
   374  	output := runTestProg(t, "testprog", "RecursivePanic5")
   375  	want := `first panic
   376  second panic
   377  panic: third panic
   378  `
   379  	if !strings.HasPrefix(output, want) {
   380  		t.Fatalf("output does not start with %q:\n%s", want, output)
   381  	}
   382  
   383  }
   384  
   385  func TestRepanickedPanic(t *testing.T) {
   386  	output := runTestProg(t, "testprog", "RepanickedPanic")
   387  	want := `panic: message [recovered, repanicked]
   388  `
   389  	if !strings.HasPrefix(output, want) {
   390  		t.Fatalf("output does not start with %q:\n%s", want, output)
   391  	}
   392  }
   393  
   394  func TestRepanickedMiddlePanic(t *testing.T) {
   395  	output := runTestProg(t, "testprog", "RepanickedMiddlePanic")
   396  	want := `panic: inner [recovered]
   397  	panic: middle [recovered, repanicked]
   398  	panic: outer
   399  `
   400  	if !strings.HasPrefix(output, want) {
   401  		t.Fatalf("output does not start with %q:\n%s", want, output)
   402  	}
   403  }
   404  
   405  func TestRepanickedPanicSandwich(t *testing.T) {
   406  	output := runTestProg(t, "testprog", "RepanickedPanicSandwich")
   407  	want := `panic: outer [recovered]
   408  	panic: inner [recovered]
   409  	panic: outer
   410  `
   411  	if !strings.HasPrefix(output, want) {
   412  		t.Fatalf("output does not start with %q:\n%s", want, output)
   413  	}
   414  }
   415  
   416  func TestGoexitCrash(t *testing.T) {
   417  	// External linking brings in cgo, causing deadlock detection not working.
   418  	testenv.MustInternalLink(t, deadlockBuildTypes)
   419  
   420  	output := runTestProg(t, "testprog", "GoexitExit")
   421  	want := "no goroutines (main called runtime.Goexit) - deadlock!"
   422  	if !strings.Contains(output, want) {
   423  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   424  	}
   425  }
   426  
   427  func TestGoexitDefer(t *testing.T) {
   428  	c := make(chan struct{})
   429  	go func() {
   430  		defer func() {
   431  			r := recover()
   432  			if r != nil {
   433  				t.Errorf("non-nil recover during Goexit")
   434  			}
   435  			c <- struct{}{}
   436  		}()
   437  		runtime.Goexit()
   438  	}()
   439  	// Note: if the defer fails to run, we will get a deadlock here
   440  	<-c
   441  }
   442  
   443  func TestGoNil(t *testing.T) {
   444  	output := runTestProg(t, "testprog", "GoNil")
   445  	want := "go of nil func value"
   446  	if !strings.Contains(output, want) {
   447  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   448  	}
   449  }
   450  
   451  func TestMainGoroutineID(t *testing.T) {
   452  	output := runTestProg(t, "testprog", "MainGoroutineID")
   453  	want := "panic: test\n\ngoroutine 1 [running]:\n"
   454  	if !strings.HasPrefix(output, want) {
   455  		t.Fatalf("output does not start with %q:\n%s", want, output)
   456  	}
   457  }
   458  
   459  func TestNoHelperGoroutines(t *testing.T) {
   460  	output := runTestProg(t, "testprog", "NoHelperGoroutines")
   461  	matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1)
   462  	if len(matches) != 1 || matches[0][0] != "goroutine 1 [" {
   463  		t.Fatalf("want to see only goroutine 1, see:\n%s", output)
   464  	}
   465  }
   466  
   467  func TestBreakpoint(t *testing.T) {
   468  	output := runTestProg(t, "testprog", "Breakpoint")
   469  	// If runtime.Breakpoint() is inlined, then the stack trace prints
   470  	// "runtime.Breakpoint(...)" instead of "runtime.Breakpoint()".
   471  	want := "runtime.Breakpoint("
   472  	if !strings.Contains(output, want) {
   473  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   474  	}
   475  }
   476  
   477  func TestGoexitInPanic(t *testing.T) {
   478  	// External linking brings in cgo, causing deadlock detection not working.
   479  	testenv.MustInternalLink(t, deadlockBuildTypes)
   480  
   481  	// see issue 8774: this code used to trigger an infinite recursion
   482  	output := runTestProg(t, "testprog", "GoexitInPanic")
   483  	want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
   484  	if !strings.HasPrefix(output, want) {
   485  		t.Fatalf("output does not start with %q:\n%s", want, output)
   486  	}
   487  }
   488  
   489  // Issue 14965: Runtime panics should be of type runtime.Error
   490  func TestRuntimePanicWithRuntimeError(t *testing.T) {
   491  	testCases := [...]func(){
   492  		0: func() {
   493  			var m map[uint64]bool
   494  			m[1234] = true
   495  		},
   496  		1: func() {
   497  			ch := make(chan struct{})
   498  			close(ch)
   499  			close(ch)
   500  		},
   501  		2: func() {
   502  			var ch = make(chan struct{})
   503  			close(ch)
   504  			ch <- struct{}{}
   505  		},
   506  		3: func() {
   507  			var s = make([]int, 2)
   508  			_ = s[2]
   509  		},
   510  		4: func() {
   511  			n := -1
   512  			_ = make(chan bool, n)
   513  		},
   514  		5: func() {
   515  			close((chan bool)(nil))
   516  		},
   517  	}
   518  
   519  	for i, fn := range testCases {
   520  		got := panicValue(fn)
   521  		if _, ok := got.(runtime.Error); !ok {
   522  			t.Errorf("test #%d: recovered value %v(type %T) does not implement runtime.Error", i, got, got)
   523  		}
   524  	}
   525  }
   526  
   527  func panicValue(fn func()) (recovered any) {
   528  	defer func() {
   529  		recovered = recover()
   530  	}()
   531  	fn()
   532  	return
   533  }
   534  
   535  func TestPanicAfterGoexit(t *testing.T) {
   536  	// an uncaught panic should still work after goexit
   537  	output := runTestProg(t, "testprog", "PanicAfterGoexit")
   538  	want := "panic: hello"
   539  	if !strings.HasPrefix(output, want) {
   540  		t.Fatalf("output does not start with %q:\n%s", want, output)
   541  	}
   542  }
   543  
   544  func TestRecoveredPanicAfterGoexit(t *testing.T) {
   545  	// External linking brings in cgo, causing deadlock detection not working.
   546  	testenv.MustInternalLink(t, deadlockBuildTypes)
   547  
   548  	output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit")
   549  	want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
   550  	if !strings.HasPrefix(output, want) {
   551  		t.Fatalf("output does not start with %q:\n%s", want, output)
   552  	}
   553  }
   554  
   555  func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
   556  	// External linking brings in cgo, causing deadlock detection not working.
   557  	testenv.MustInternalLink(t, deadlockBuildTypes)
   558  
   559  	t.Parallel()
   560  	output := runTestProg(t, "testprog", "RecoverBeforePanicAfterGoexit")
   561  	want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
   562  	if !strings.HasPrefix(output, want) {
   563  		t.Fatalf("output does not start with %q:\n%s", want, output)
   564  	}
   565  }
   566  
   567  func TestRecoverBeforePanicAfterGoexit2(t *testing.T) {
   568  	// External linking brings in cgo, causing deadlock detection not working.
   569  	testenv.MustInternalLink(t, deadlockBuildTypes)
   570  
   571  	t.Parallel()
   572  	output := runTestProg(t, "testprog", "RecoverBeforePanicAfterGoexit2")
   573  	want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
   574  	if !strings.HasPrefix(output, want) {
   575  		t.Fatalf("output does not start with %q:\n%s", want, output)
   576  	}
   577  }
   578  
   579  func TestNetpollDeadlock(t *testing.T) {
   580  	t.Parallel()
   581  	output := runTestProg(t, "testprognet", "NetpollDeadlock")
   582  	want := "done\n"
   583  	if !strings.HasSuffix(output, want) {
   584  		t.Fatalf("output does not start with %q:\n%s", want, output)
   585  	}
   586  }
   587  
   588  func TestPanicTraceback(t *testing.T) {
   589  	t.Parallel()
   590  	output := runTestProg(t, "testprog", "PanicTraceback")
   591  	want := "panic: hello\n\tpanic: panic pt2\n\tpanic: panic pt1\n"
   592  	if !strings.HasPrefix(output, want) {
   593  		t.Fatalf("output does not start with %q:\n%s", want, output)
   594  	}
   595  
   596  	// Check functions in the traceback.
   597  	fns := []string{"main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"}
   598  	for _, fn := range fns {
   599  		re := regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `\(.*\n`)
   600  		idx := re.FindStringIndex(output)
   601  		if idx == nil {
   602  			t.Fatalf("expected %q function in traceback:\n%s", fn, output)
   603  		}
   604  		output = output[idx[1]:]
   605  	}
   606  }
   607  
   608  func testPanicDeadlock(t *testing.T, name string, want string) {
   609  	// test issue 14432
   610  	output := runTestProg(t, "testprog", name)
   611  	if !strings.HasPrefix(output, want) {
   612  		t.Fatalf("output does not start with %q:\n%s", want, output)
   613  	}
   614  }
   615  
   616  func TestPanicDeadlockGosched(t *testing.T) {
   617  	testPanicDeadlock(t, "GoschedInPanic", "panic: errorThatGosched\n\n")
   618  }
   619  
   620  func TestPanicDeadlockSyscall(t *testing.T) {
   621  	testPanicDeadlock(t, "SyscallInPanic", "1\n2\npanic: 3\n\n")
   622  }
   623  
   624  func TestPanicLoop(t *testing.T) {
   625  	output := runTestProg(t, "testprog", "PanicLoop")
   626  	if want := "panic while printing panic value"; !strings.Contains(output, want) {
   627  		t.Errorf("output does not contain %q:\n%s", want, output)
   628  	}
   629  }
   630  
   631  func TestMemPprof(t *testing.T) {
   632  	testenv.MustHaveGoRun(t)
   633  
   634  	exe, err := buildTestProg(t, "testprog")
   635  	if err != nil {
   636  		t.Fatal(err)
   637  	}
   638  
   639  	got, err := testenv.CleanCmdEnv(exec.Command(exe, "MemProf")).CombinedOutput()
   640  	if err != nil {
   641  		t.Fatalf("testprog failed: %s, output:\n%s", err, got)
   642  	}
   643  	fn := strings.TrimSpace(string(got))
   644  	defer os.Remove(fn)
   645  
   646  	for try := 0; try < 2; try++ {
   647  		cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-alloc_space", "-top"))
   648  		// Check that pprof works both with and without explicit executable on command line.
   649  		if try == 0 {
   650  			cmd.Args = append(cmd.Args, exe, fn)
   651  		} else {
   652  			cmd.Args = append(cmd.Args, fn)
   653  		}
   654  		found := false
   655  		for i, e := range cmd.Env {
   656  			if strings.HasPrefix(e, "PPROF_TMPDIR=") {
   657  				cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir()
   658  				found = true
   659  				break
   660  			}
   661  		}
   662  		if !found {
   663  			cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir())
   664  		}
   665  
   666  		top, err := cmd.CombinedOutput()
   667  		t.Logf("%s:\n%s", cmd.Args, top)
   668  		if err != nil {
   669  			t.Error(err)
   670  		} else if !bytes.Contains(top, []byte("MemProf")) {
   671  			t.Error("missing MemProf in pprof output")
   672  		}
   673  	}
   674  }
   675  
   676  var concurrentMapTest = flag.Bool("run_concurrent_map_tests", false, "also run flaky concurrent map tests")
   677  
   678  func TestConcurrentMapWrites(t *testing.T) {
   679  	if !*concurrentMapTest {
   680  		t.Skip("skipping without -run_concurrent_map_tests")
   681  	}
   682  	if race.Enabled {
   683  		t.Skip("skipping test: -race will catch the race, this test is for the built-in race detection")
   684  	}
   685  	testenv.MustHaveGoRun(t)
   686  	output := runTestProg(t, "testprog", "concurrentMapWrites")
   687  	want := "fatal error: concurrent map writes\n"
   688  	// Concurrent writes can corrupt the map in a way that we
   689  	// detect with a separate throw.
   690  	want2 := "fatal error: small map with no empty slot (concurrent map writes?)\n"
   691  	if !strings.HasPrefix(output, want) && !strings.HasPrefix(output, want2) {
   692  		t.Fatalf("output does not start with %q:\n%s", want, output)
   693  	}
   694  }
   695  func TestConcurrentMapReadWrite(t *testing.T) {
   696  	if !*concurrentMapTest {
   697  		t.Skip("skipping without -run_concurrent_map_tests")
   698  	}
   699  	if race.Enabled {
   700  		t.Skip("skipping test: -race will catch the race, this test is for the built-in race detection")
   701  	}
   702  	testenv.MustHaveGoRun(t)
   703  	output := runTestProg(t, "testprog", "concurrentMapReadWrite")
   704  	want := "fatal error: concurrent map read and map write\n"
   705  	// Concurrent writes can corrupt the map in a way that we
   706  	// detect with a separate throw.
   707  	want2 := "fatal error: small map with no empty slot (concurrent map writes?)\n"
   708  	if !strings.HasPrefix(output, want) && !strings.HasPrefix(output, want2) {
   709  		t.Fatalf("output does not start with %q:\n%s", want, output)
   710  	}
   711  }
   712  func TestConcurrentMapIterateWrite(t *testing.T) {
   713  	if !*concurrentMapTest {
   714  		t.Skip("skipping without -run_concurrent_map_tests")
   715  	}
   716  	if race.Enabled {
   717  		t.Skip("skipping test: -race will catch the race, this test is for the built-in race detection")
   718  	}
   719  	testenv.MustHaveGoRun(t)
   720  	output := runTestProg(t, "testprog", "concurrentMapIterateWrite")
   721  	want := "fatal error: concurrent map iteration and map write\n"
   722  	// Concurrent writes can corrupt the map in a way that we
   723  	// detect with a separate throw.
   724  	want2 := "fatal error: small map with no empty slot (concurrent map writes?)\n"
   725  	if !strings.HasPrefix(output, want) && !strings.HasPrefix(output, want2) {
   726  		t.Fatalf("output does not start with %q:\n%s", want, output)
   727  	}
   728  }
   729  
   730  func TestConcurrentMapWritesIssue69447(t *testing.T) {
   731  	testenv.MustHaveGoRun(t)
   732  	if race.Enabled {
   733  		t.Skip("skipping test: -race will catch the race, this test is for the built-in race detection")
   734  	}
   735  	exe, err := buildTestProg(t, "testprog")
   736  	if err != nil {
   737  		t.Fatal(err)
   738  	}
   739  	for i := 0; i < 200; i++ {
   740  		output := runBuiltTestProg(t, exe, "concurrentMapWrites")
   741  		if output == "" {
   742  			// If we didn't detect an error, that's ok.
   743  			// This case makes this test not flaky like
   744  			// the other ones above.
   745  			// (More correctly, this case makes this test flaky
   746  			// in the other direction, in that it might not
   747  			// detect a problem even if there is one.)
   748  			continue
   749  		}
   750  		want := "fatal error: concurrent map writes\n"
   751  		// Concurrent writes can corrupt the map in a way that we
   752  		// detect with a separate throw.
   753  		want2 := "fatal error: small map with no empty slot (concurrent map writes?)\n"
   754  		if !strings.HasPrefix(output, want) && !strings.HasPrefix(output, want2) {
   755  			t.Fatalf("output does not start with %q:\n%s", want, output)
   756  		}
   757  	}
   758  }
   759  
   760  type point struct {
   761  	x, y *int
   762  }
   763  
   764  func (p *point) negate() {
   765  	*p.x = *p.x * -1
   766  	*p.y = *p.y * -1
   767  }
   768  
   769  // Test for issue #10152.
   770  func TestPanicInlined(t *testing.T) {
   771  	defer func() {
   772  		r := recover()
   773  		if r == nil {
   774  			t.Fatalf("recover failed")
   775  		}
   776  		buf := make([]byte, 2048)
   777  		n := runtime.Stack(buf, false)
   778  		buf = buf[:n]
   779  		if !bytes.Contains(buf, []byte("(*point).negate(")) {
   780  			t.Fatalf("expecting stack trace to contain call to (*point).negate()")
   781  		}
   782  	}()
   783  
   784  	pt := new(point)
   785  	pt.negate()
   786  }
   787  
   788  // Test for issues #3934 and #20018.
   789  // We want to delay exiting until a panic print is complete.
   790  func TestPanicRace(t *testing.T) {
   791  	testenv.MustHaveGoRun(t)
   792  
   793  	exe, err := buildTestProg(t, "testprog")
   794  	if err != nil {
   795  		t.Fatal(err)
   796  	}
   797  
   798  	// The test is intentionally racy, and in my testing does not
   799  	// produce the expected output about 0.05% of the time.
   800  	// So run the program in a loop and only fail the test if we
   801  	// get the wrong output ten times in a row.
   802  	const tries = 10
   803  retry:
   804  	for i := 0; i < tries; i++ {
   805  		got, err := testenv.CleanCmdEnv(exec.Command(exe, "PanicRace")).CombinedOutput()
   806  		if err == nil {
   807  			t.Logf("try %d: program exited successfully, should have failed", i+1)
   808  			continue
   809  		}
   810  
   811  		if i > 0 {
   812  			t.Logf("try %d:\n", i+1)
   813  		}
   814  		t.Logf("%s\n", got)
   815  
   816  		wants := []string{
   817  			"panic: crash",
   818  			"PanicRace",
   819  			"created by ",
   820  		}
   821  		for _, want := range wants {
   822  			if !bytes.Contains(got, []byte(want)) {
   823  				t.Logf("did not find expected string %q", want)
   824  				continue retry
   825  			}
   826  		}
   827  
   828  		// Test generated expected output.
   829  		return
   830  	}
   831  	t.Errorf("test ran %d times without producing expected output", tries)
   832  }
   833  
   834  func TestBadTraceback(t *testing.T) {
   835  	if asan.Enabled || msan.Enabled || race.Enabled {
   836  		t.Skip("skipped test: checkptr mode catches the corruption")
   837  	}
   838  	output := runTestProg(t, "testprog", "BadTraceback")
   839  	for _, want := range []string{
   840  		"unexpected return pc",
   841  		"called from 0xbad",
   842  		"00000bad",    // Smashed LR in hex dump
   843  		"<main.badLR", // Symbolization in hex dump (badLR1 or badLR2)
   844  	} {
   845  		if !strings.Contains(output, want) {
   846  			t.Errorf("output does not contain %q:\n%s", want, output)
   847  		}
   848  	}
   849  }
   850  
   851  func TestTimePprof(t *testing.T) {
   852  	// This test is unreliable on any system in which nanotime
   853  	// calls into libc.
   854  	switch runtime.GOOS {
   855  	case "aix", "darwin", "illumos", "openbsd", "solaris":
   856  		t.Skipf("skipping on %s because nanotime calls libc", runtime.GOOS)
   857  	}
   858  	if race.Enabled || asan.Enabled || msan.Enabled {
   859  		t.Skip("skipping on sanitizers because the sanitizer runtime is external code")
   860  	}
   861  
   862  	// Pass GOTRACEBACK for issue #41120 to try to get more
   863  	// information on timeout.
   864  	fn := runTestProg(t, "testprog", "TimeProf", "GOTRACEBACK=crash")
   865  	fn = strings.TrimSpace(fn)
   866  	defer os.Remove(fn)
   867  
   868  	cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-top", "-nodecount=1", fn))
   869  	cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir())
   870  	top, err := cmd.CombinedOutput()
   871  	t.Logf("%s", top)
   872  	if err != nil {
   873  		t.Error(err)
   874  	} else if bytes.Contains(top, []byte("ExternalCode")) {
   875  		t.Error("profiler refers to ExternalCode")
   876  	}
   877  }
   878  
   879  // Test that runtime.abort does so.
   880  func TestAbort(t *testing.T) {
   881  	// Pass GOTRACEBACK to ensure we get runtime frames.
   882  	output := runTestProg(t, "testprog", "Abort", "GOTRACEBACK=system")
   883  	if want := "runtime.abort"; !strings.Contains(output, want) {
   884  		t.Errorf("output does not contain %q:\n%s", want, output)
   885  	}
   886  	if strings.Contains(output, "BAD") {
   887  		t.Errorf("output contains BAD:\n%s", output)
   888  	}
   889  	// Check that it's a signal traceback.
   890  	want := "PC="
   891  	// For systems that use a breakpoint, check specifically for that.
   892  	switch runtime.GOARCH {
   893  	case "386", "amd64":
   894  		switch runtime.GOOS {
   895  		case "plan9":
   896  			want = "sys: breakpoint"
   897  		case "windows":
   898  			want = "Exception 0x80000003"
   899  		default:
   900  			want = "SIGTRAP"
   901  		}
   902  	}
   903  	if !strings.Contains(output, want) {
   904  		t.Errorf("output does not contain %q:\n%s", want, output)
   905  	}
   906  }
   907  
   908  // For TestRuntimePanic: test a panic in the runtime package without
   909  // involving the testing harness.
   910  func init() {
   911  	if os.Getenv("GO_TEST_RUNTIME_PANIC") == "1" {
   912  		defer func() {
   913  			if r := recover(); r != nil {
   914  				// We expect to crash, so exit 0
   915  				// to indicate failure.
   916  				os.Exit(0)
   917  			}
   918  		}()
   919  		runtime.PanicForTesting(nil, 1)
   920  		// We expect to crash, so exit 0 to indicate failure.
   921  		os.Exit(0)
   922  	}
   923  	if os.Getenv("GO_TEST_RUNTIME_NPE_READMEMSTATS") == "1" {
   924  		runtime.ReadMemStats(nil)
   925  		os.Exit(0)
   926  	}
   927  	if os.Getenv("GO_TEST_RUNTIME_NPE_FUNCMETHOD") == "1" {
   928  		var f *runtime.Func
   929  		_ = f.Entry()
   930  		os.Exit(0)
   931  	}
   932  
   933  }
   934  
   935  func TestRuntimePanic(t *testing.T) {
   936  	cmd := testenv.CleanCmdEnv(exec.Command(testenv.Executable(t), "-test.run=^TestRuntimePanic$"))
   937  	cmd.Env = append(cmd.Env, "GO_TEST_RUNTIME_PANIC=1")
   938  	out, err := cmd.CombinedOutput()
   939  	t.Logf("%s", out)
   940  	if err == nil {
   941  		t.Error("child process did not fail")
   942  	} else if want := "runtime.unexportedPanicForTesting"; !bytes.Contains(out, []byte(want)) {
   943  		t.Errorf("output did not contain expected string %q", want)
   944  	}
   945  }
   946  
   947  func TestTracebackRuntimeFunction(t *testing.T) {
   948  	cmd := testenv.CleanCmdEnv(exec.Command(testenv.Executable(t), "-test.run=^TestTracebackRuntimeFunction$"))
   949  	cmd.Env = append(cmd.Env, "GO_TEST_RUNTIME_NPE_READMEMSTATS=1")
   950  	out, err := cmd.CombinedOutput()
   951  	t.Logf("%s", out)
   952  	if err == nil {
   953  		t.Error("child process did not fail")
   954  	} else if want := "runtime.ReadMemStats"; !bytes.Contains(out, []byte(want)) {
   955  		t.Errorf("output did not contain expected string %q", want)
   956  	}
   957  }
   958  
   959  func TestTracebackRuntimeMethod(t *testing.T) {
   960  	cmd := testenv.CleanCmdEnv(exec.Command(testenv.Executable(t), "-test.run=^TestTracebackRuntimeMethod$"))
   961  	cmd.Env = append(cmd.Env, "GO_TEST_RUNTIME_NPE_FUNCMETHOD=1")
   962  	out, err := cmd.CombinedOutput()
   963  	t.Logf("%s", out)
   964  	if err == nil {
   965  		t.Error("child process did not fail")
   966  	} else if want := "runtime.(*Func).Entry"; !bytes.Contains(out, []byte(want)) {
   967  		t.Errorf("output did not contain expected string %q", want)
   968  	}
   969  }
   970  
   971  // Test that g0 stack overflows are handled gracefully.
   972  func TestG0StackOverflow(t *testing.T) {
   973  	if runtime.GOOS == "ios" {
   974  		testenv.SkipFlaky(t, 62671)
   975  	}
   976  
   977  	if os.Getenv("TEST_G0_STACK_OVERFLOW") != "1" {
   978  		cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.Executable(t), "-test.run=^TestG0StackOverflow$", "-test.v"))
   979  		cmd.Env = append(cmd.Env, "TEST_G0_STACK_OVERFLOW=1")
   980  		out, err := cmd.CombinedOutput()
   981  		t.Logf("output:\n%s", out)
   982  		// Don't check err since it's expected to crash.
   983  		if n := strings.Count(string(out), "morestack on g0\n"); n != 1 {
   984  			t.Fatalf("%s\n(exit status %v)", out, err)
   985  		}
   986  		if runtime.CrashStackImplemented {
   987  			// check for a stack trace
   988  			want := "runtime.stackOverflow"
   989  			if n := strings.Count(string(out), want); n < 5 {
   990  				t.Errorf("output does not contain %q at least 5 times:\n%s", want, out)
   991  			}
   992  			return // it's not a signal-style traceback
   993  		}
   994  		// Check that it's a signal-style traceback.
   995  		if runtime.GOOS != "windows" {
   996  			if want := "PC="; !strings.Contains(string(out), want) {
   997  				t.Errorf("output does not contain %q:\n%s", want, out)
   998  			}
   999  		}
  1000  		return
  1001  	}
  1002  
  1003  	runtime.G0StackOverflow()
  1004  }
  1005  
  1006  // For TestCrashWhileTracing: test a panic without involving the testing
  1007  // harness, as we rely on stdout only containing trace output.
  1008  func init() {
  1009  	if os.Getenv("TEST_CRASH_WHILE_TRACING") == "1" {
  1010  		trace.Start(os.Stdout)
  1011  		trace.Log(context.Background(), "xyzzy-cat", "xyzzy-msg")
  1012  		panic("yzzyx")
  1013  	}
  1014  }
  1015  
  1016  func TestCrashWhileTracing(t *testing.T) {
  1017  	testenv.MustHaveExec(t)
  1018  
  1019  	cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.Executable(t)))
  1020  	cmd.Env = append(cmd.Env, "TEST_CRASH_WHILE_TRACING=1")
  1021  	stdOut, err := cmd.StdoutPipe()
  1022  	var errOut bytes.Buffer
  1023  	cmd.Stderr = &errOut
  1024  
  1025  	if err := cmd.Start(); err != nil {
  1026  		t.Fatalf("could not start subprocess: %v", err)
  1027  	}
  1028  	r, err := traceparse.NewReader(stdOut)
  1029  	if err != nil {
  1030  		t.Fatalf("could not create trace.NewReader: %v", err)
  1031  	}
  1032  	var seen bool
  1033  	nSync := 0
  1034  	i := 1
  1035  loop:
  1036  	for ; ; i++ {
  1037  		ev, err := r.ReadEvent()
  1038  		if err != nil {
  1039  			// We may have a broken tail to the trace -- that's OK.
  1040  			// We'll make sure we saw at least one complete generation.
  1041  			if err != io.EOF {
  1042  				t.Logf("error at event %d: %v", i, err)
  1043  			}
  1044  			break loop
  1045  		}
  1046  		switch ev.Kind() {
  1047  		case traceparse.EventSync:
  1048  			nSync = ev.Sync().N
  1049  		case traceparse.EventLog:
  1050  			v := ev.Log()
  1051  			if v.Category == "xyzzy-cat" && v.Message == "xyzzy-msg" {
  1052  				// Should we already stop reading here? More events may come, but
  1053  				// we're not guaranteeing a fully unbroken trace until the last
  1054  				// byte...
  1055  				seen = true
  1056  			}
  1057  		}
  1058  	}
  1059  	if err := cmd.Wait(); err == nil {
  1060  		t.Error("the process should have panicked")
  1061  	}
  1062  	if nSync <= 1 {
  1063  		t.Errorf("expected at least one full generation to have been emitted before the trace was considered broken")
  1064  	}
  1065  	if !seen {
  1066  		t.Errorf("expected one matching log event matching, but none of the %d received trace events match", i)
  1067  	}
  1068  	t.Logf("stderr output:\n%s", errOut.String())
  1069  	needle := "yzzyx\n"
  1070  	if n := strings.Count(errOut.String(), needle); n != 1 {
  1071  		t.Fatalf("did not find expected panic message %q\n(exit status %v)", needle, err)
  1072  	}
  1073  }
  1074  
  1075  // Test that panic message is not clobbered.
  1076  // See issue 30150.
  1077  func TestDoublePanic(t *testing.T) {
  1078  	output := runTestProg(t, "testprog", "DoublePanic", "GODEBUG=clobberfree=1")
  1079  	wants := []string{"panic: XXX", "panic: YYY"}
  1080  	for _, want := range wants {
  1081  		if !strings.Contains(output, want) {
  1082  			t.Errorf("output:\n%s\n\nwant output containing: %s", output, want)
  1083  		}
  1084  	}
  1085  }
  1086  
  1087  // Test that panic while panicking discards error message
  1088  // See issue 52257
  1089  func TestPanicWhilePanicking(t *testing.T) {
  1090  	tests := []struct {
  1091  		Want string
  1092  		Func string
  1093  	}{
  1094  		{
  1095  			"panic while printing panic value: important multi-line\n\terror message",
  1096  			"ErrorPanic",
  1097  		},
  1098  		{
  1099  			"panic while printing panic value: important multi-line\n\tstringer message",
  1100  			"StringerPanic",
  1101  		},
  1102  		{
  1103  			"panic while printing panic value: type",
  1104  			"DoubleErrorPanic",
  1105  		},
  1106  		{
  1107  			"panic while printing panic value: type",
  1108  			"DoubleStringerPanic",
  1109  		},
  1110  		{
  1111  			"panic while printing panic value: type",
  1112  			"CircularPanic",
  1113  		},
  1114  		{
  1115  			"important multi-line\n\tstring message",
  1116  			"StringPanic",
  1117  		},
  1118  		{
  1119  			"nil",
  1120  			"NilPanic",
  1121  		},
  1122  	}
  1123  	for _, x := range tests {
  1124  		output := runTestProg(t, "testprog", x.Func)
  1125  		if !strings.Contains(output, x.Want) {
  1126  			t.Errorf("output does not contain %q:\n%s", x.Want, output)
  1127  		}
  1128  	}
  1129  }
  1130  
  1131  func TestPanicOnUnsafeSlice(t *testing.T) {
  1132  	output := runTestProg(t, "testprog", "panicOnNilAndEleSizeIsZero")
  1133  	// Note: This is normally a panic, but is a throw when checkptr is
  1134  	// enabled.
  1135  	want := "unsafe.Slice: ptr is nil and len is not zero"
  1136  	if !strings.Contains(output, want) {
  1137  		t.Errorf("output does not contain %q:\n%s", want, output)
  1138  	}
  1139  }
  1140  
  1141  func TestNetpollWaiters(t *testing.T) {
  1142  	t.Parallel()
  1143  	output := runTestProg(t, "testprognet", "NetpollWaiters")
  1144  	want := "OK\n"
  1145  	if output != want {
  1146  		t.Fatalf("output is not %q\n%s", want, output)
  1147  	}
  1148  }
  1149  
  1150  func TestFinalizerOrCleanupDeadlock(t *testing.T) {
  1151  	t.Parallel()
  1152  
  1153  	for _, useCleanup := range []bool{false, true} {
  1154  		progName := "Finalizer"
  1155  		want := "runtime.runFinalizers"
  1156  		if useCleanup {
  1157  			progName = "Cleanup"
  1158  			want = "runtime.runCleanups"
  1159  		}
  1160  
  1161  		// The runtime.runFinalizers/runtime.runCleanups frame should appear in panics, even if
  1162  		// runtime frames are normally hidden (GOTRACEBACK=all).
  1163  		t.Run("Panic", func(t *testing.T) {
  1164  			t.Parallel()
  1165  			output := runTestProg(t, "testprog", progName+"Deadlock", "GOTRACEBACK=all", "GO_TEST_FINALIZER_DEADLOCK=panic")
  1166  			want := want + "()"
  1167  			if !strings.Contains(output, want) {
  1168  				t.Errorf("output does not contain %q:\n%s", want, output)
  1169  			}
  1170  		})
  1171  
  1172  		// The runtime.runFinalizers/runtime.Cleanups frame should appear in runtime.Stack,
  1173  		// even though runtime frames are normally hidden.
  1174  		t.Run("Stack", func(t *testing.T) {
  1175  			t.Parallel()
  1176  			output := runTestProg(t, "testprog", progName+"Deadlock", "GO_TEST_FINALIZER_DEADLOCK=stack")
  1177  			want := want + "()"
  1178  			if !strings.Contains(output, want) {
  1179  				t.Errorf("output does not contain %q:\n%s", want, output)
  1180  			}
  1181  		})
  1182  
  1183  		// The runtime.runFinalizers/runtime.Cleanups frame should appear in goroutine
  1184  		// profiles.
  1185  		t.Run("PprofProto", func(t *testing.T) {
  1186  			t.Parallel()
  1187  			output := runTestProg(t, "testprog", progName+"Deadlock", "GO_TEST_FINALIZER_DEADLOCK=pprof_proto")
  1188  
  1189  			p, err := profile.Parse(strings.NewReader(output))
  1190  			if err != nil {
  1191  				// Logging the binary proto data is not very nice, but it might
  1192  				// be a text error message instead.
  1193  				t.Logf("Output: %s", output)
  1194  				t.Fatalf("Error parsing proto output: %v", err)
  1195  			}
  1196  			for _, s := range p.Sample {
  1197  				for _, loc := range s.Location {
  1198  					for _, line := range loc.Line {
  1199  						if line.Function.Name == want {
  1200  							// Done!
  1201  							return
  1202  						}
  1203  					}
  1204  				}
  1205  			}
  1206  			t.Errorf("Profile does not contain %q:\n%s", want, p)
  1207  		})
  1208  
  1209  		// The runtime.runFinalizers/runtime.runCleanups frame should appear in goroutine
  1210  		// profiles (debug=1).
  1211  		t.Run("PprofDebug1", func(t *testing.T) {
  1212  			t.Parallel()
  1213  			output := runTestProg(t, "testprog", progName+"Deadlock", "GO_TEST_FINALIZER_DEADLOCK=pprof_debug1")
  1214  			want := want + "+"
  1215  			if !strings.Contains(output, want) {
  1216  				t.Errorf("output does not contain %q:\n%s", want, output)
  1217  			}
  1218  		})
  1219  
  1220  		// The runtime.runFinalizers/runtime.runCleanups frame should appear in goroutine
  1221  		// profiles (debug=2).
  1222  		t.Run("PprofDebug2", func(t *testing.T) {
  1223  			t.Parallel()
  1224  			output := runTestProg(t, "testprog", progName+"Deadlock", "GO_TEST_FINALIZER_DEADLOCK=pprof_debug2")
  1225  			want := want + "()"
  1226  			if !strings.Contains(output, want) {
  1227  				t.Errorf("output does not contain %q:\n%s", want, output)
  1228  			}
  1229  		})
  1230  	}
  1231  }
  1232  
  1233  func TestSynctestCondSignalFromNoBubble(t *testing.T) {
  1234  	for _, test := range []string{
  1235  		"SynctestCond/signal/no_bubble",
  1236  		"SynctestCond/broadcast/no_bubble",
  1237  		"SynctestCond/signal/other_bubble",
  1238  		"SynctestCond/broadcast/other_bubble",
  1239  	} {
  1240  		t.Run(test, func(t *testing.T) {
  1241  			output := runTestProg(t, "testprog", test)
  1242  			want := "fatal error: semaphore wake of synctest goroutine from outside bubble"
  1243  			if !strings.Contains(output, want) {
  1244  				t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
  1245  			}
  1246  		})
  1247  	}
  1248  }
  1249  

View as plain text