Home | History | Annotate | Download | only in runtime
      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 	"flag"
     10 	"fmt"
     11 	"internal/testenv"
     12 	"io/ioutil"
     13 	"os"
     14 	"os/exec"
     15 	"path/filepath"
     16 	"regexp"
     17 	"runtime"
     18 	"strconv"
     19 	"strings"
     20 	"sync"
     21 	"testing"
     22 	"time"
     23 )
     24 
     25 var toRemove []string
     26 
     27 func TestMain(m *testing.M) {
     28 	status := m.Run()
     29 	for _, file := range toRemove {
     30 		os.RemoveAll(file)
     31 	}
     32 	os.Exit(status)
     33 }
     34 
     35 var testprog struct {
     36 	sync.Mutex
     37 	dir    string
     38 	target map[string]buildexe
     39 }
     40 
     41 type buildexe struct {
     42 	exe string
     43 	err error
     44 }
     45 
     46 func runTestProg(t *testing.T, binary, name string, env ...string) string {
     47 	if *flagQuick {
     48 		t.Skip("-quick")
     49 	}
     50 
     51 	testenv.MustHaveGoBuild(t)
     52 
     53 	exe, err := buildTestProg(t, binary)
     54 	if err != nil {
     55 		t.Fatal(err)
     56 	}
     57 
     58 	cmd := testenv.CleanCmdEnv(exec.Command(exe, name))
     59 	cmd.Env = append(cmd.Env, env...)
     60 	if testing.Short() {
     61 		cmd.Env = append(cmd.Env, "RUNTIME_TEST_SHORT=1")
     62 	}
     63 	var b bytes.Buffer
     64 	cmd.Stdout = &b
     65 	cmd.Stderr = &b
     66 	if err := cmd.Start(); err != nil {
     67 		t.Fatalf("starting %s %s: %v", binary, name, err)
     68 	}
     69 
     70 	// If the process doesn't complete within 1 minute,
     71 	// assume it is hanging and kill it to get a stack trace.
     72 	p := cmd.Process
     73 	done := make(chan bool)
     74 	go func() {
     75 		scale := 1
     76 		// This GOARCH/GOOS test is copied from cmd/dist/test.go.
     77 		// TODO(iant): Have cmd/dist update the environment variable.
     78 		if runtime.GOARCH == "arm" || runtime.GOOS == "windows" {
     79 			scale = 2
     80 		}
     81 		if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
     82 			if sc, err := strconv.Atoi(s); err == nil {
     83 				scale = sc
     84 			}
     85 		}
     86 
     87 		select {
     88 		case <-done:
     89 		case <-time.After(time.Duration(scale) * time.Minute):
     90 			p.Signal(sigquit)
     91 		}
     92 	}()
     93 
     94 	if err := cmd.Wait(); err != nil {
     95 		t.Logf("%s %s exit status: %v", binary, name, err)
     96 	}
     97 	close(done)
     98 
     99 	return b.String()
    100 }
    101 
    102 func buildTestProg(t *testing.T, binary string, flags ...string) (string, error) {
    103 	if *flagQuick {
    104 		t.Skip("-quick")
    105 	}
    106 
    107 	checkStaleRuntime(t)
    108 
    109 	testprog.Lock()
    110 	defer testprog.Unlock()
    111 	if testprog.dir == "" {
    112 		dir, err := ioutil.TempDir("", "go-build")
    113 		if err != nil {
    114 			t.Fatalf("failed to create temp directory: %v", err)
    115 		}
    116 		testprog.dir = dir
    117 		toRemove = append(toRemove, dir)
    118 	}
    119 
    120 	if testprog.target == nil {
    121 		testprog.target = make(map[string]buildexe)
    122 	}
    123 	name := binary
    124 	if len(flags) > 0 {
    125 		name += "_" + strings.Join(flags, "_")
    126 	}
    127 	target, ok := testprog.target[name]
    128 	if ok {
    129 		return target.exe, target.err
    130 	}
    131 
    132 	exe := filepath.Join(testprog.dir, name+".exe")
    133 	cmd := exec.Command(testenv.GoToolPath(t), append([]string{"build", "-o", exe}, flags...)...)
    134 	cmd.Dir = "testdata/" + binary
    135 	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
    136 	if err != nil {
    137 		target.err = fmt.Errorf("building %s %v: %v\n%s", binary, flags, err, out)
    138 		testprog.target[name] = target
    139 		return "", target.err
    140 	}
    141 	target.exe = exe
    142 	testprog.target[name] = target
    143 	return exe, nil
    144 }
    145 
    146 var (
    147 	staleRuntimeOnce sync.Once // guards init of staleRuntimeErr
    148 	staleRuntimeErr  error
    149 )
    150 
    151 func checkStaleRuntime(t *testing.T) {
    152 	staleRuntimeOnce.Do(func() {
    153 		// 'go run' uses the installed copy of runtime.a, which may be out of date.
    154 		out, err := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "list", "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "-f", "{{.Stale}}", "runtime")).CombinedOutput()
    155 		if err != nil {
    156 			staleRuntimeErr = fmt.Errorf("failed to execute 'go list': %v\n%v", err, string(out))
    157 			return
    158 		}
    159 		if string(out) != "false\n" {
    160 			t.Logf("go list -f {{.Stale}} runtime:\n%s", out)
    161 			out, err := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "list", "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "-f", "{{.StaleReason}}", "runtime")).CombinedOutput()
    162 			if err != nil {
    163 				t.Logf("go list -f {{.StaleReason}} failed: %v", err)
    164 			}
    165 			t.Logf("go list -f {{.StaleReason}} runtime:\n%s", out)
    166 			staleRuntimeErr = fmt.Errorf("Stale runtime.a. Run 'go install runtime'.")
    167 		}
    168 	})
    169 	if staleRuntimeErr != nil {
    170 		t.Fatal(staleRuntimeErr)
    171 	}
    172 }
    173 
    174 func testCrashHandler(t *testing.T, cgo bool) {
    175 	type crashTest struct {
    176 		Cgo bool
    177 	}
    178 	var output string
    179 	if cgo {
    180 		output = runTestProg(t, "testprogcgo", "Crash")
    181 	} else {
    182 		output = runTestProg(t, "testprog", "Crash")
    183 	}
    184 	want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
    185 	if output != want {
    186 		t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
    187 	}
    188 }
    189 
    190 func TestCrashHandler(t *testing.T) {
    191 	testCrashHandler(t, false)
    192 }
    193 
    194 func testDeadlock(t *testing.T, name string) {
    195 	output := runTestProg(t, "testprog", name)
    196 	want := "fatal error: all goroutines are asleep - deadlock!\n"
    197 	if !strings.HasPrefix(output, want) {
    198 		t.Fatalf("output does not start with %q:\n%s", want, output)
    199 	}
    200 }
    201 
    202 func TestSimpleDeadlock(t *testing.T) {
    203 	testDeadlock(t, "SimpleDeadlock")
    204 }
    205 
    206 func TestInitDeadlock(t *testing.T) {
    207 	testDeadlock(t, "InitDeadlock")
    208 }
    209 
    210 func TestLockedDeadlock(t *testing.T) {
    211 	testDeadlock(t, "LockedDeadlock")
    212 }
    213 
    214 func TestLockedDeadlock2(t *testing.T) {
    215 	testDeadlock(t, "LockedDeadlock2")
    216 }
    217 
    218 func TestGoexitDeadlock(t *testing.T) {
    219 	output := runTestProg(t, "testprog", "GoexitDeadlock")
    220 	want := "no goroutines (main called runtime.Goexit) - deadlock!"
    221 	if !strings.Contains(output, want) {
    222 		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
    223 	}
    224 }
    225 
    226 func TestStackOverflow(t *testing.T) {
    227 	output := runTestProg(t, "testprog", "StackOverflow")
    228 	want := "runtime: goroutine stack exceeds 1474560-byte limit\nfatal error: stack overflow"
    229 	if !strings.HasPrefix(output, want) {
    230 		t.Fatalf("output does not start with %q:\n%s", want, output)
    231 	}
    232 }
    233 
    234 func TestThreadExhaustion(t *testing.T) {
    235 	output := runTestProg(t, "testprog", "ThreadExhaustion")
    236 	want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
    237 	if !strings.HasPrefix(output, want) {
    238 		t.Fatalf("output does not start with %q:\n%s", want, output)
    239 	}
    240 }
    241 
    242 func TestRecursivePanic(t *testing.T) {
    243 	output := runTestProg(t, "testprog", "RecursivePanic")
    244 	want := `wrap: bad
    245 panic: again
    246 
    247 `
    248 	if !strings.HasPrefix(output, want) {
    249 		t.Fatalf("output does not start with %q:\n%s", want, output)
    250 	}
    251 
    252 }
    253 
    254 func TestGoexitCrash(t *testing.T) {
    255 	output := runTestProg(t, "testprog", "GoexitExit")
    256 	want := "no goroutines (main called runtime.Goexit) - deadlock!"
    257 	if !strings.Contains(output, want) {
    258 		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
    259 	}
    260 }
    261 
    262 func TestGoexitDefer(t *testing.T) {
    263 	c := make(chan struct{})
    264 	go func() {
    265 		defer func() {
    266 			r := recover()
    267 			if r != nil {
    268 				t.Errorf("non-nil recover during Goexit")
    269 			}
    270 			c <- struct{}{}
    271 		}()
    272 		runtime.Goexit()
    273 	}()
    274 	// Note: if the defer fails to run, we will get a deadlock here
    275 	<-c
    276 }
    277 
    278 func TestGoNil(t *testing.T) {
    279 	output := runTestProg(t, "testprog", "GoNil")
    280 	want := "go of nil func value"
    281 	if !strings.Contains(output, want) {
    282 		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
    283 	}
    284 }
    285 
    286 func TestMainGoroutineID(t *testing.T) {
    287 	output := runTestProg(t, "testprog", "MainGoroutineID")
    288 	want := "panic: test\n\ngoroutine 1 [running]:\n"
    289 	if !strings.HasPrefix(output, want) {
    290 		t.Fatalf("output does not start with %q:\n%s", want, output)
    291 	}
    292 }
    293 
    294 func TestNoHelperGoroutines(t *testing.T) {
    295 	output := runTestProg(t, "testprog", "NoHelperGoroutines")
    296 	matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1)
    297 	if len(matches) != 1 || matches[0][0] != "goroutine 1 [" {
    298 		t.Fatalf("want to see only goroutine 1, see:\n%s", output)
    299 	}
    300 }
    301 
    302 func TestBreakpoint(t *testing.T) {
    303 	output := runTestProg(t, "testprog", "Breakpoint")
    304 	// If runtime.Breakpoint() is inlined, then the stack trace prints
    305 	// "runtime.Breakpoint(...)" instead of "runtime.Breakpoint()".
    306 	want := "runtime.Breakpoint("
    307 	if !strings.Contains(output, want) {
    308 		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
    309 	}
    310 }
    311 
    312 func TestGoexitInPanic(t *testing.T) {
    313 	// see issue 8774: this code used to trigger an infinite recursion
    314 	output := runTestProg(t, "testprog", "GoexitInPanic")
    315 	want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
    316 	if !strings.HasPrefix(output, want) {
    317 		t.Fatalf("output does not start with %q:\n%s", want, output)
    318 	}
    319 }
    320 
    321 // Issue 14965: Runtime panics should be of type runtime.Error
    322 func TestRuntimePanicWithRuntimeError(t *testing.T) {
    323 	testCases := [...]func(){
    324 		0: func() {
    325 			var m map[uint64]bool
    326 			m[1234] = true
    327 		},
    328 		1: func() {
    329 			ch := make(chan struct{})
    330 			close(ch)
    331 			close(ch)
    332 		},
    333 		2: func() {
    334 			var ch = make(chan struct{})
    335 			close(ch)
    336 			ch <- struct{}{}
    337 		},
    338 		3: func() {
    339 			var s = make([]int, 2)
    340 			_ = s[2]
    341 		},
    342 		4: func() {
    343 			n := -1
    344 			_ = make(chan bool, n)
    345 		},
    346 		5: func() {
    347 			close((chan bool)(nil))
    348 		},
    349 	}
    350 
    351 	for i, fn := range testCases {
    352 		got := panicValue(fn)
    353 		if _, ok := got.(runtime.Error); !ok {
    354 			t.Errorf("test #%d: recovered value %v(type %T) does not implement runtime.Error", i, got, got)
    355 		}
    356 	}
    357 }
    358 
    359 func panicValue(fn func()) (recovered interface{}) {
    360 	defer func() {
    361 		recovered = recover()
    362 	}()
    363 	fn()
    364 	return
    365 }
    366 
    367 func TestPanicAfterGoexit(t *testing.T) {
    368 	// an uncaught panic should still work after goexit
    369 	output := runTestProg(t, "testprog", "PanicAfterGoexit")
    370 	want := "panic: hello"
    371 	if !strings.HasPrefix(output, want) {
    372 		t.Fatalf("output does not start with %q:\n%s", want, output)
    373 	}
    374 }
    375 
    376 func TestRecoveredPanicAfterGoexit(t *testing.T) {
    377 	output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit")
    378 	want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
    379 	if !strings.HasPrefix(output, want) {
    380 		t.Fatalf("output does not start with %q:\n%s", want, output)
    381 	}
    382 }
    383 
    384 func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
    385 	// 1. defer a function that recovers
    386 	// 2. defer a function that panics
    387 	// 3. call goexit
    388 	// Goexit should run the #2 defer. Its panic
    389 	// should be caught by the #1 defer, and execution
    390 	// should resume in the caller. Like the Goexit
    391 	// never happened!
    392 	defer func() {
    393 		r := recover()
    394 		if r == nil {
    395 			panic("bad recover")
    396 		}
    397 	}()
    398 	defer func() {
    399 		panic("hello")
    400 	}()
    401 	runtime.Goexit()
    402 }
    403 
    404 func TestNetpollDeadlock(t *testing.T) {
    405 	t.Parallel()
    406 	output := runTestProg(t, "testprognet", "NetpollDeadlock")
    407 	want := "done\n"
    408 	if !strings.HasSuffix(output, want) {
    409 		t.Fatalf("output does not start with %q:\n%s", want, output)
    410 	}
    411 }
    412 
    413 func TestPanicTraceback(t *testing.T) {
    414 	t.Parallel()
    415 	output := runTestProg(t, "testprog", "PanicTraceback")
    416 	want := "panic: hello"
    417 	if !strings.HasPrefix(output, want) {
    418 		t.Fatalf("output does not start with %q:\n%s", want, output)
    419 	}
    420 
    421 	// Check functions in the traceback.
    422 	fns := []string{"main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"}
    423 	for _, fn := range fns {
    424 		re := regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `\(.*\n`)
    425 		idx := re.FindStringIndex(output)
    426 		if idx == nil {
    427 			t.Fatalf("expected %q function in traceback:\n%s", fn, output)
    428 		}
    429 		output = output[idx[1]:]
    430 	}
    431 }
    432 
    433 func testPanicDeadlock(t *testing.T, name string, want string) {
    434 	// test issue 14432
    435 	output := runTestProg(t, "testprog", name)
    436 	if !strings.HasPrefix(output, want) {
    437 		t.Fatalf("output does not start with %q:\n%s", want, output)
    438 	}
    439 }
    440 
    441 func TestPanicDeadlockGosched(t *testing.T) {
    442 	testPanicDeadlock(t, "GoschedInPanic", "panic: errorThatGosched\n\n")
    443 }
    444 
    445 func TestPanicDeadlockSyscall(t *testing.T) {
    446 	testPanicDeadlock(t, "SyscallInPanic", "1\n2\npanic: 3\n\n")
    447 }
    448 
    449 func TestPanicLoop(t *testing.T) {
    450 	output := runTestProg(t, "testprog", "PanicLoop")
    451 	if want := "panic while printing panic value"; !strings.Contains(output, want) {
    452 		t.Errorf("output does not contain %q:\n%s", want, output)
    453 	}
    454 }
    455 
    456 func TestMemPprof(t *testing.T) {
    457 	testenv.MustHaveGoRun(t)
    458 
    459 	exe, err := buildTestProg(t, "testprog")
    460 	if err != nil {
    461 		t.Fatal(err)
    462 	}
    463 
    464 	got, err := testenv.CleanCmdEnv(exec.Command(exe, "MemProf")).CombinedOutput()
    465 	if err != nil {
    466 		t.Fatal(err)
    467 	}
    468 	fn := strings.TrimSpace(string(got))
    469 	defer os.Remove(fn)
    470 
    471 	for try := 0; try < 2; try++ {
    472 		cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-alloc_space", "-top"))
    473 		// Check that pprof works both with and without explicit executable on command line.
    474 		if try == 0 {
    475 			cmd.Args = append(cmd.Args, exe, fn)
    476 		} else {
    477 			cmd.Args = append(cmd.Args, fn)
    478 		}
    479 		found := false
    480 		for i, e := range cmd.Env {
    481 			if strings.HasPrefix(e, "PPROF_TMPDIR=") {
    482 				cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir()
    483 				found = true
    484 				break
    485 			}
    486 		}
    487 		if !found {
    488 			cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir())
    489 		}
    490 
    491 		top, err := cmd.CombinedOutput()
    492 		t.Logf("%s:\n%s", cmd.Args, top)
    493 		if err != nil {
    494 			t.Error(err)
    495 		} else if !bytes.Contains(top, []byte("MemProf")) {
    496 			t.Error("missing MemProf in pprof output")
    497 		}
    498 	}
    499 }
    500 
    501 var concurrentMapTest = flag.Bool("run_concurrent_map_tests", false, "also run flaky concurrent map tests")
    502 
    503 func TestConcurrentMapWrites(t *testing.T) {
    504 	if !*concurrentMapTest {
    505 		t.Skip("skipping without -run_concurrent_map_tests")
    506 	}
    507 	testenv.MustHaveGoRun(t)
    508 	output := runTestProg(t, "testprog", "concurrentMapWrites")
    509 	want := "fatal error: concurrent map writes"
    510 	if !strings.HasPrefix(output, want) {
    511 		t.Fatalf("output does not start with %q:\n%s", want, output)
    512 	}
    513 }
    514 func TestConcurrentMapReadWrite(t *testing.T) {
    515 	if !*concurrentMapTest {
    516 		t.Skip("skipping without -run_concurrent_map_tests")
    517 	}
    518 	testenv.MustHaveGoRun(t)
    519 	output := runTestProg(t, "testprog", "concurrentMapReadWrite")
    520 	want := "fatal error: concurrent map read and map write"
    521 	if !strings.HasPrefix(output, want) {
    522 		t.Fatalf("output does not start with %q:\n%s", want, output)
    523 	}
    524 }
    525 func TestConcurrentMapIterateWrite(t *testing.T) {
    526 	if !*concurrentMapTest {
    527 		t.Skip("skipping without -run_concurrent_map_tests")
    528 	}
    529 	testenv.MustHaveGoRun(t)
    530 	output := runTestProg(t, "testprog", "concurrentMapIterateWrite")
    531 	want := "fatal error: concurrent map iteration and map write"
    532 	if !strings.HasPrefix(output, want) {
    533 		t.Fatalf("output does not start with %q:\n%s", want, output)
    534 	}
    535 }
    536 
    537 type point struct {
    538 	x, y *int
    539 }
    540 
    541 func (p *point) negate() {
    542 	*p.x = *p.x * -1
    543 	*p.y = *p.y * -1
    544 }
    545 
    546 // Test for issue #10152.
    547 func TestPanicInlined(t *testing.T) {
    548 	defer func() {
    549 		r := recover()
    550 		if r == nil {
    551 			t.Fatalf("recover failed")
    552 		}
    553 		buf := make([]byte, 2048)
    554 		n := runtime.Stack(buf, false)
    555 		buf = buf[:n]
    556 		if !bytes.Contains(buf, []byte("(*point).negate(")) {
    557 			t.Fatalf("expecting stack trace to contain call to (*point).negate()")
    558 		}
    559 	}()
    560 
    561 	pt := new(point)
    562 	pt.negate()
    563 }
    564 
    565 // Test for issues #3934 and #20018.
    566 // We want to delay exiting until a panic print is complete.
    567 func TestPanicRace(t *testing.T) {
    568 	testenv.MustHaveGoRun(t)
    569 
    570 	exe, err := buildTestProg(t, "testprog")
    571 	if err != nil {
    572 		t.Fatal(err)
    573 	}
    574 
    575 	// The test is intentionally racy, and in my testing does not
    576 	// produce the expected output about 0.05% of the time.
    577 	// So run the program in a loop and only fail the test if we
    578 	// get the wrong output ten times in a row.
    579 	const tries = 10
    580 retry:
    581 	for i := 0; i < tries; i++ {
    582 		got, err := testenv.CleanCmdEnv(exec.Command(exe, "PanicRace")).CombinedOutput()
    583 		if err == nil {
    584 			t.Logf("try %d: program exited successfully, should have failed", i+1)
    585 			continue
    586 		}
    587 
    588 		if i > 0 {
    589 			t.Logf("try %d:\n", i+1)
    590 		}
    591 		t.Logf("%s\n", got)
    592 
    593 		wants := []string{
    594 			"panic: crash",
    595 			"PanicRace",
    596 			"created by ",
    597 		}
    598 		for _, want := range wants {
    599 			if !bytes.Contains(got, []byte(want)) {
    600 				t.Logf("did not find expected string %q", want)
    601 				continue retry
    602 			}
    603 		}
    604 
    605 		// Test generated expected output.
    606 		return
    607 	}
    608 	t.Errorf("test ran %d times without producing expected output", tries)
    609 }
    610 
    611 func TestBadTraceback(t *testing.T) {
    612 	output := runTestProg(t, "testprog", "BadTraceback")
    613 	for _, want := range []string{
    614 		"runtime: unexpected return pc",
    615 		"called from 0xbad",
    616 		"00000bad",    // Smashed LR in hex dump
    617 		"<main.badLR", // Symbolization in hex dump (badLR1 or badLR2)
    618 	} {
    619 		if !strings.Contains(output, want) {
    620 			t.Errorf("output does not contain %q:\n%s", want, output)
    621 		}
    622 	}
    623 }
    624