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//go:build cgo 6 7package runtime_test 8 9import ( 10 "bytes" 11 "fmt" 12 "internal/testenv" 13 "os" 14 "os/exec" 15 "runtime" 16 "strconv" 17 "strings" 18 "testing" 19 "time" 20) 21 22func TestCgoCrashHandler(t *testing.T) { 23 t.Parallel() 24 testCrashHandler(t, true) 25} 26 27func TestCgoSignalDeadlock(t *testing.T) { 28 // Don't call t.Parallel, since too much work going on at the 29 // same time can cause the testprogcgo code to overrun its 30 // timeouts (issue #18598). 31 32 if testing.Short() && runtime.GOOS == "windows" { 33 t.Skip("Skipping in short mode") // takes up to 64 seconds 34 } 35 got := runTestProg(t, "testprogcgo", "CgoSignalDeadlock") 36 want := "OK\n" 37 if got != want { 38 t.Fatalf("expected %q, but got:\n%s", want, got) 39 } 40} 41 42func TestCgoTraceback(t *testing.T) { 43 t.Parallel() 44 got := runTestProg(t, "testprogcgo", "CgoTraceback") 45 want := "OK\n" 46 if got != want { 47 t.Fatalf("expected %q, but got:\n%s", want, got) 48 } 49} 50 51func TestCgoCallbackGC(t *testing.T) { 52 t.Parallel() 53 switch runtime.GOOS { 54 case "plan9", "windows": 55 t.Skipf("no pthreads on %s", runtime.GOOS) 56 } 57 if testing.Short() { 58 switch { 59 case runtime.GOOS == "dragonfly": 60 t.Skip("see golang.org/issue/11990") 61 case runtime.GOOS == "linux" && runtime.GOARCH == "arm": 62 t.Skip("too slow for arm builders") 63 case runtime.GOOS == "linux" && (runtime.GOARCH == "mips64" || runtime.GOARCH == "mips64le"): 64 t.Skip("too slow for mips64x builders") 65 } 66 } 67 if testenv.Builder() == "darwin-amd64-10_14" { 68 // TODO(#23011): When the 10.14 builders are gone, remove this skip. 69 t.Skip("skipping due to platform bug on macOS 10.14; see https://golang.org/issue/43926") 70 } 71 got := runTestProg(t, "testprogcgo", "CgoCallbackGC") 72 want := "OK\n" 73 if got != want { 74 t.Fatalf("expected %q, but got:\n%s", want, got) 75 } 76} 77 78func TestCgoExternalThreadPanic(t *testing.T) { 79 t.Parallel() 80 if runtime.GOOS == "plan9" { 81 t.Skipf("no pthreads on %s", runtime.GOOS) 82 } 83 got := runTestProg(t, "testprogcgo", "CgoExternalThreadPanic") 84 want := "panic: BOOM" 85 if !strings.Contains(got, want) { 86 t.Fatalf("want failure containing %q. output:\n%s\n", want, got) 87 } 88} 89 90func TestCgoExternalThreadSIGPROF(t *testing.T) { 91 t.Parallel() 92 // issue 9456. 93 switch runtime.GOOS { 94 case "plan9", "windows": 95 t.Skipf("no pthreads on %s", runtime.GOOS) 96 } 97 98 exe, err := buildTestProg(t, "testprogcgo", "-tags=threadprof") 99 if err != nil { 100 t.Fatal(err) 101 } 102 103 got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoExternalThreadSIGPROF")).CombinedOutput() 104 if err != nil { 105 t.Fatalf("exit status: %v\n%s", err, got) 106 } 107 108 if want := "OK\n"; string(got) != want { 109 t.Fatalf("expected %q, but got:\n%s", want, got) 110 } 111} 112 113func TestCgoExternalThreadSignal(t *testing.T) { 114 t.Parallel() 115 // issue 10139 116 switch runtime.GOOS { 117 case "plan9", "windows": 118 t.Skipf("no pthreads on %s", runtime.GOOS) 119 } 120 121 exe, err := buildTestProg(t, "testprogcgo", "-tags=threadprof") 122 if err != nil { 123 t.Fatal(err) 124 } 125 126 got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoExternalThreadSignal")).CombinedOutput() 127 if err != nil { 128 t.Fatalf("exit status: %v\n%s", err, got) 129 } 130 131 want := []byte("OK\n") 132 if !bytes.Equal(got, want) { 133 t.Fatalf("expected %q, but got:\n%s", want, got) 134 } 135} 136 137func TestCgoDLLImports(t *testing.T) { 138 // test issue 9356 139 if runtime.GOOS != "windows" { 140 t.Skip("skipping windows specific test") 141 } 142 got := runTestProg(t, "testprogcgo", "CgoDLLImportsMain") 143 want := "OK\n" 144 if got != want { 145 t.Fatalf("expected %q, but got %v", want, got) 146 } 147} 148 149func TestCgoExecSignalMask(t *testing.T) { 150 t.Parallel() 151 // Test issue 13164. 152 switch runtime.GOOS { 153 case "windows", "plan9": 154 t.Skipf("skipping signal mask test on %s", runtime.GOOS) 155 } 156 got := runTestProg(t, "testprogcgo", "CgoExecSignalMask", "GOTRACEBACK=system") 157 want := "OK\n" 158 if got != want { 159 t.Errorf("expected %q, got %v", want, got) 160 } 161} 162 163func TestEnsureDropM(t *testing.T) { 164 t.Parallel() 165 // Test for issue 13881. 166 switch runtime.GOOS { 167 case "windows", "plan9": 168 t.Skipf("skipping dropm test on %s", runtime.GOOS) 169 } 170 got := runTestProg(t, "testprogcgo", "EnsureDropM") 171 want := "OK\n" 172 if got != want { 173 t.Errorf("expected %q, got %v", want, got) 174 } 175} 176 177// Test for issue 14387. 178// Test that the program that doesn't need any cgo pointer checking 179// takes about the same amount of time with it as without it. 180func TestCgoCheckBytes(t *testing.T) { 181 t.Parallel() 182 // Make sure we don't count the build time as part of the run time. 183 testenv.MustHaveGoBuild(t) 184 exe, err := buildTestProg(t, "testprogcgo") 185 if err != nil { 186 t.Fatal(err) 187 } 188 189 // Try it 10 times to avoid flakiness. 190 const tries = 10 191 var tot1, tot2 time.Duration 192 for i := 0; i < tries; i++ { 193 cmd := testenv.CleanCmdEnv(exec.Command(exe, "CgoCheckBytes")) 194 cmd.Env = append(cmd.Env, "GODEBUG=cgocheck=0", fmt.Sprintf("GO_CGOCHECKBYTES_TRY=%d", i)) 195 196 start := time.Now() 197 cmd.Run() 198 d1 := time.Since(start) 199 200 cmd = testenv.CleanCmdEnv(exec.Command(exe, "CgoCheckBytes")) 201 cmd.Env = append(cmd.Env, fmt.Sprintf("GO_CGOCHECKBYTES_TRY=%d", i)) 202 203 start = time.Now() 204 cmd.Run() 205 d2 := time.Since(start) 206 207 if d1*20 > d2 { 208 // The slow version (d2) was less than 20 times 209 // slower than the fast version (d1), so OK. 210 return 211 } 212 213 tot1 += d1 214 tot2 += d2 215 } 216 217 t.Errorf("cgo check too slow: got %v, expected at most %v", tot2/tries, (tot1/tries)*20) 218} 219 220func TestCgoPanicDeadlock(t *testing.T) { 221 t.Parallel() 222 // test issue 14432 223 got := runTestProg(t, "testprogcgo", "CgoPanicDeadlock") 224 want := "panic: cgo error\n\n" 225 if !strings.HasPrefix(got, want) { 226 t.Fatalf("output does not start with %q:\n%s", want, got) 227 } 228} 229 230func TestCgoCCodeSIGPROF(t *testing.T) { 231 t.Parallel() 232 got := runTestProg(t, "testprogcgo", "CgoCCodeSIGPROF") 233 want := "OK\n" 234 if got != want { 235 t.Errorf("expected %q got %v", want, got) 236 } 237} 238 239func TestCgoCrashTraceback(t *testing.T) { 240 t.Parallel() 241 switch platform := runtime.GOOS + "/" + runtime.GOARCH; platform { 242 case "darwin/amd64": 243 case "linux/amd64": 244 case "linux/ppc64le": 245 default: 246 t.Skipf("not yet supported on %s", platform) 247 } 248 got := runTestProg(t, "testprogcgo", "CrashTraceback") 249 for i := 1; i <= 3; i++ { 250 if !strings.Contains(got, fmt.Sprintf("cgo symbolizer:%d", i)) { 251 t.Errorf("missing cgo symbolizer:%d", i) 252 } 253 } 254} 255 256func TestCgoCrashTracebackGo(t *testing.T) { 257 t.Parallel() 258 switch platform := runtime.GOOS + "/" + runtime.GOARCH; platform { 259 case "darwin/amd64": 260 case "linux/amd64": 261 case "linux/ppc64le": 262 default: 263 t.Skipf("not yet supported on %s", platform) 264 } 265 got := runTestProg(t, "testprogcgo", "CrashTracebackGo") 266 for i := 1; i <= 3; i++ { 267 want := fmt.Sprintf("main.h%d", i) 268 if !strings.Contains(got, want) { 269 t.Errorf("missing %s", want) 270 } 271 } 272} 273 274func TestCgoTracebackContext(t *testing.T) { 275 t.Parallel() 276 got := runTestProg(t, "testprogcgo", "TracebackContext") 277 want := "OK\n" 278 if got != want { 279 t.Errorf("expected %q got %v", want, got) 280 } 281} 282 283func TestCgoTracebackContextPreemption(t *testing.T) { 284 t.Parallel() 285 got := runTestProg(t, "testprogcgo", "TracebackContextPreemption") 286 want := "OK\n" 287 if got != want { 288 t.Errorf("expected %q got %v", want, got) 289 } 290} 291 292func testCgoPprof(t *testing.T, buildArg, runArg, top, bottom string) { 293 t.Parallel() 294 if runtime.GOOS != "linux" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "ppc64le") { 295 t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH) 296 } 297 testenv.MustHaveGoRun(t) 298 299 exe, err := buildTestProg(t, "testprogcgo", buildArg) 300 if err != nil { 301 t.Fatal(err) 302 } 303 304 cmd := testenv.CleanCmdEnv(exec.Command(exe, runArg)) 305 got, err := cmd.CombinedOutput() 306 if err != nil { 307 if testenv.Builder() == "linux-amd64-alpine" { 308 // See Issue 18243 and Issue 19938. 309 t.Skipf("Skipping failing test on Alpine (golang.org/issue/18243). Ignoring error: %v", err) 310 } 311 t.Fatalf("%s\n\n%v", got, err) 312 } 313 fn := strings.TrimSpace(string(got)) 314 defer os.Remove(fn) 315 316 for try := 0; try < 2; try++ { 317 cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-tagignore=ignore", "-traces")) 318 // Check that pprof works both with and without explicit executable on command line. 319 if try == 0 { 320 cmd.Args = append(cmd.Args, exe, fn) 321 } else { 322 cmd.Args = append(cmd.Args, fn) 323 } 324 325 found := false 326 for i, e := range cmd.Env { 327 if strings.HasPrefix(e, "PPROF_TMPDIR=") { 328 cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir() 329 found = true 330 break 331 } 332 } 333 if !found { 334 cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir()) 335 } 336 337 out, err := cmd.CombinedOutput() 338 t.Logf("%s:\n%s", cmd.Args, out) 339 if err != nil { 340 t.Error(err) 341 continue 342 } 343 344 trace := findTrace(string(out), top) 345 if len(trace) == 0 { 346 t.Errorf("%s traceback missing.", top) 347 continue 348 } 349 if trace[len(trace)-1] != bottom { 350 t.Errorf("invalid traceback origin: got=%v; want=[%s ... %s]", trace, top, bottom) 351 } 352 } 353} 354 355func TestCgoPprof(t *testing.T) { 356 testCgoPprof(t, "", "CgoPprof", "cpuHog", "runtime.main") 357} 358 359func TestCgoPprofPIE(t *testing.T) { 360 testCgoPprof(t, "-buildmode=pie", "CgoPprof", "cpuHog", "runtime.main") 361} 362 363func TestCgoPprofThread(t *testing.T) { 364 testCgoPprof(t, "", "CgoPprofThread", "cpuHogThread", "cpuHogThread2") 365} 366 367func TestCgoPprofThreadNoTraceback(t *testing.T) { 368 testCgoPprof(t, "", "CgoPprofThreadNoTraceback", "cpuHogThread", "runtime._ExternalCode") 369} 370 371func TestRaceProf(t *testing.T) { 372 if (runtime.GOOS != "linux" && runtime.GOOS != "freebsd") || runtime.GOARCH != "amd64" { 373 t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH) 374 } 375 376 testenv.MustHaveGoRun(t) 377 378 // This test requires building various packages with -race, so 379 // it's somewhat slow. 380 if testing.Short() { 381 t.Skip("skipping test in -short mode") 382 } 383 384 exe, err := buildTestProg(t, "testprogcgo", "-race") 385 if err != nil { 386 t.Fatal(err) 387 } 388 389 got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoRaceprof")).CombinedOutput() 390 if err != nil { 391 t.Fatal(err) 392 } 393 want := "OK\n" 394 if string(got) != want { 395 t.Errorf("expected %q got %s", want, got) 396 } 397} 398 399func TestRaceSignal(t *testing.T) { 400 t.Parallel() 401 if (runtime.GOOS != "linux" && runtime.GOOS != "freebsd") || runtime.GOARCH != "amd64" { 402 t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH) 403 } 404 405 testenv.MustHaveGoRun(t) 406 407 // This test requires building various packages with -race, so 408 // it's somewhat slow. 409 if testing.Short() { 410 t.Skip("skipping test in -short mode") 411 } 412 413 exe, err := buildTestProg(t, "testprogcgo", "-race") 414 if err != nil { 415 t.Fatal(err) 416 } 417 418 got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoRaceSignal")).CombinedOutput() 419 if err != nil { 420 t.Logf("%s\n", got) 421 t.Fatal(err) 422 } 423 want := "OK\n" 424 if string(got) != want { 425 t.Errorf("expected %q got %s", want, got) 426 } 427} 428 429func TestCgoNumGoroutine(t *testing.T) { 430 switch runtime.GOOS { 431 case "windows", "plan9": 432 t.Skipf("skipping numgoroutine test on %s", runtime.GOOS) 433 } 434 t.Parallel() 435 got := runTestProg(t, "testprogcgo", "NumGoroutine") 436 want := "OK\n" 437 if got != want { 438 t.Errorf("expected %q got %v", want, got) 439 } 440} 441 442func TestCatchPanic(t *testing.T) { 443 t.Parallel() 444 switch runtime.GOOS { 445 case "plan9", "windows": 446 t.Skipf("no signals on %s", runtime.GOOS) 447 case "darwin": 448 if runtime.GOARCH == "amd64" { 449 t.Skipf("crash() on darwin/amd64 doesn't raise SIGABRT") 450 } 451 } 452 453 testenv.MustHaveGoRun(t) 454 455 exe, err := buildTestProg(t, "testprogcgo") 456 if err != nil { 457 t.Fatal(err) 458 } 459 460 for _, early := range []bool{true, false} { 461 cmd := testenv.CleanCmdEnv(exec.Command(exe, "CgoCatchPanic")) 462 // Make sure a panic results in a crash. 463 cmd.Env = append(cmd.Env, "GOTRACEBACK=crash") 464 if early { 465 // Tell testprogcgo to install an early signal handler for SIGABRT 466 cmd.Env = append(cmd.Env, "CGOCATCHPANIC_EARLY_HANDLER=1") 467 } 468 if out, err := cmd.CombinedOutput(); err != nil { 469 t.Errorf("testprogcgo CgoCatchPanic failed: %v\n%s", err, out) 470 } 471 } 472} 473 474func TestCgoLockOSThreadExit(t *testing.T) { 475 switch runtime.GOOS { 476 case "plan9", "windows": 477 t.Skipf("no pthreads on %s", runtime.GOOS) 478 } 479 t.Parallel() 480 testLockOSThreadExit(t, "testprogcgo") 481} 482 483func TestWindowsStackMemoryCgo(t *testing.T) { 484 if runtime.GOOS != "windows" { 485 t.Skip("skipping windows specific test") 486 } 487 testenv.SkipFlaky(t, 22575) 488 o := runTestProg(t, "testprogcgo", "StackMemory") 489 stackUsage, err := strconv.Atoi(o) 490 if err != nil { 491 t.Fatalf("Failed to read stack usage: %v", err) 492 } 493 if expected, got := 100<<10, stackUsage; got > expected { 494 t.Fatalf("expected < %d bytes of memory per thread, got %d", expected, got) 495 } 496} 497 498func TestSigStackSwapping(t *testing.T) { 499 switch runtime.GOOS { 500 case "plan9", "windows": 501 t.Skipf("no sigaltstack on %s", runtime.GOOS) 502 } 503 t.Parallel() 504 got := runTestProg(t, "testprogcgo", "SigStack") 505 want := "OK\n" 506 if got != want { 507 t.Errorf("expected %q got %v", want, got) 508 } 509} 510 511func TestCgoTracebackSigpanic(t *testing.T) { 512 // Test unwinding over a sigpanic in C code without a C 513 // symbolizer. See issue #23576. 514 if runtime.GOOS == "windows" { 515 // On Windows if we get an exception in C code, we let 516 // the Windows exception handler unwind it, rather 517 // than injecting a sigpanic. 518 t.Skip("no sigpanic in C on windows") 519 } 520 t.Parallel() 521 got := runTestProg(t, "testprogcgo", "TracebackSigpanic") 522 t.Log(got) 523 want := "runtime.sigpanic" 524 if !strings.Contains(got, want) { 525 t.Errorf("did not see %q in output", want) 526 } 527 // No runtime errors like "runtime: unexpected return pc". 528 nowant := "runtime: " 529 if strings.Contains(got, nowant) { 530 t.Errorf("unexpectedly saw %q in output", nowant) 531 } 532} 533 534func TestCgoPanicCallback(t *testing.T) { 535 t.Parallel() 536 got := runTestProg(t, "testprogcgo", "PanicCallback") 537 t.Log(got) 538 want := "panic: runtime error: invalid memory address or nil pointer dereference" 539 if !strings.Contains(got, want) { 540 t.Errorf("did not see %q in output", want) 541 } 542 want = "panic_callback" 543 if !strings.Contains(got, want) { 544 t.Errorf("did not see %q in output", want) 545 } 546 want = "PanicCallback" 547 if !strings.Contains(got, want) { 548 t.Errorf("did not see %q in output", want) 549 } 550 // No runtime errors like "runtime: unexpected return pc". 551 nowant := "runtime: " 552 if strings.Contains(got, nowant) { 553 t.Errorf("did not see %q in output", want) 554 } 555} 556 557// Test that C code called via cgo can use large Windows thread stacks 558// and call back in to Go without crashing. See issue #20975. 559// 560// See also TestBigStackCallbackSyscall. 561func TestBigStackCallbackCgo(t *testing.T) { 562 if runtime.GOOS != "windows" { 563 t.Skip("skipping windows specific test") 564 } 565 t.Parallel() 566 got := runTestProg(t, "testprogcgo", "BigStack") 567 want := "OK\n" 568 if got != want { 569 t.Errorf("expected %q got %v", want, got) 570 } 571} 572 573func nextTrace(lines []string) ([]string, []string) { 574 var trace []string 575 for n, line := range lines { 576 if strings.HasPrefix(line, "---") { 577 return trace, lines[n+1:] 578 } 579 fields := strings.Fields(strings.TrimSpace(line)) 580 if len(fields) == 0 { 581 continue 582 } 583 // Last field contains the function name. 584 trace = append(trace, fields[len(fields)-1]) 585 } 586 return nil, nil 587} 588 589func findTrace(text, top string) []string { 590 lines := strings.Split(text, "\n") 591 _, lines = nextTrace(lines) // Skip the header. 592 for len(lines) > 0 { 593 var t []string 594 t, lines = nextTrace(lines) 595 if len(t) == 0 { 596 continue 597 } 598 if t[0] == top { 599 return t 600 } 601 } 602 return nil 603} 604 605func TestSegv(t *testing.T) { 606 switch runtime.GOOS { 607 case "plan9", "windows": 608 t.Skipf("no signals on %s", runtime.GOOS) 609 } 610 611 for _, test := range []string{"Segv", "SegvInCgo"} { 612 test := test 613 t.Run(test, func(t *testing.T) { 614 t.Parallel() 615 got := runTestProg(t, "testprogcgo", test) 616 t.Log(got) 617 want := "SIGSEGV" 618 if !strings.Contains(got, want) { 619 t.Errorf("did not see %q in output", want) 620 } 621 622 // No runtime errors like "runtime: unknown pc". 623 switch runtime.GOOS { 624 case "darwin", "illumos", "solaris": 625 // TODO(golang.org/issue/49182): Skip, runtime 626 // throws while attempting to generate 627 // traceback. 628 default: 629 nowant := "runtime: " 630 if strings.Contains(got, nowant) { 631 t.Errorf("unexpectedly saw %q in output", nowant) 632 } 633 } 634 }) 635 } 636} 637 638func TestAbortInCgo(t *testing.T) { 639 switch runtime.GOOS { 640 case "plan9", "windows": 641 // N.B. On Windows, C abort() causes the program to exit 642 // without going through the runtime at all. 643 t.Skipf("no signals on %s", runtime.GOOS) 644 } 645 646 t.Parallel() 647 got := runTestProg(t, "testprogcgo", "Abort") 648 t.Log(got) 649 want := "SIGABRT" 650 if !strings.Contains(got, want) { 651 t.Errorf("did not see %q in output", want) 652 } 653 // No runtime errors like "runtime: unknown pc". 654 nowant := "runtime: " 655 if strings.Contains(got, nowant) { 656 t.Errorf("did not see %q in output", want) 657 } 658} 659 660// TestEINTR tests that we handle EINTR correctly. 661// See issue #20400 and friends. 662func TestEINTR(t *testing.T) { 663 switch runtime.GOOS { 664 case "plan9", "windows": 665 t.Skipf("no EINTR on %s", runtime.GOOS) 666 case "linux": 667 if runtime.GOARCH == "386" { 668 // On linux-386 the Go signal handler sets 669 // a restorer function that is not preserved 670 // by the C sigaction call in the test, 671 // causing the signal handler to crash when 672 // returning the normal code. The test is not 673 // architecture-specific, so just skip on 386 674 // rather than doing a complicated workaround. 675 t.Skip("skipping on linux-386; C sigaction does not preserve Go restorer") 676 } 677 } 678 679 t.Parallel() 680 output := runTestProg(t, "testprogcgo", "EINTR") 681 want := "OK\n" 682 if output != want { 683 t.Fatalf("want %s, got %s\n", want, output) 684 } 685} 686 687// Issue #42207. 688func TestNeedmDeadlock(t *testing.T) { 689 switch runtime.GOOS { 690 case "plan9", "windows": 691 t.Skipf("no signals on %s", runtime.GOOS) 692 } 693 output := runTestProg(t, "testprogcgo", "NeedmDeadlock") 694 want := "OK\n" 695 if output != want { 696 t.Fatalf("want %s, got %s\n", want, output) 697 } 698} 699 700func TestCgoTracebackGoroutineProfile(t *testing.T) { 701 output := runTestProg(t, "testprogcgo", "GoroutineProfile") 702 want := "OK\n" 703 if output != want { 704 t.Fatalf("want %s, got %s\n", want, output) 705 } 706} 707