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// +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	got := runTestProg(t, "testprogcgo", "CgoCallbackGC")
68	want := "OK\n"
69	if got != want {
70		t.Fatalf("expected %q, but got:\n%s", want, got)
71	}
72}
73
74func TestCgoExternalThreadPanic(t *testing.T) {
75	t.Parallel()
76	if runtime.GOOS == "plan9" {
77		t.Skipf("no pthreads on %s", runtime.GOOS)
78	}
79	got := runTestProg(t, "testprogcgo", "CgoExternalThreadPanic")
80	want := "panic: BOOM"
81	if !strings.Contains(got, want) {
82		t.Fatalf("want failure containing %q. output:\n%s\n", want, got)
83	}
84}
85
86func TestCgoExternalThreadSIGPROF(t *testing.T) {
87	t.Parallel()
88	// issue 9456.
89	switch runtime.GOOS {
90	case "plan9", "windows":
91		t.Skipf("no pthreads on %s", runtime.GOOS)
92	}
93	if runtime.GOARCH == "ppc64" && runtime.GOOS == "linux" {
94		// TODO(austin) External linking not implemented on
95		// linux/ppc64 (issue #8912)
96		t.Skipf("no external linking on ppc64")
97	}
98
99	exe, err := buildTestProg(t, "testprogcgo", "-tags=threadprof")
100	if err != nil {
101		t.Fatal(err)
102	}
103
104	got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoExternalThreadSIGPROF")).CombinedOutput()
105	if err != nil {
106		t.Fatalf("exit status: %v\n%s", err, got)
107	}
108
109	if want := "OK\n"; string(got) != want {
110		t.Fatalf("expected %q, but got:\n%s", want, got)
111	}
112}
113
114func TestCgoExternalThreadSignal(t *testing.T) {
115	t.Parallel()
116	// issue 10139
117	switch runtime.GOOS {
118	case "plan9", "windows":
119		t.Skipf("no pthreads on %s", runtime.GOOS)
120	}
121
122	exe, err := buildTestProg(t, "testprogcgo", "-tags=threadprof")
123	if err != nil {
124		t.Fatal(err)
125	}
126
127	got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoExternalThreadSIGPROF")).CombinedOutput()
128	if err != nil {
129		t.Fatalf("exit status: %v\n%s", err, got)
130	}
131
132	want := []byte("OK\n")
133	if !bytes.Equal(got, want) {
134		t.Fatalf("expected %q, but got:\n%s", want, got)
135	}
136}
137
138func TestCgoDLLImports(t *testing.T) {
139	// test issue 9356
140	if runtime.GOOS != "windows" {
141		t.Skip("skipping windows specific test")
142	}
143	got := runTestProg(t, "testprogcgo", "CgoDLLImportsMain")
144	want := "OK\n"
145	if got != want {
146		t.Fatalf("expected %q, but got %v", want, got)
147	}
148}
149
150func TestCgoExecSignalMask(t *testing.T) {
151	t.Parallel()
152	// Test issue 13164.
153	switch runtime.GOOS {
154	case "windows", "plan9":
155		t.Skipf("skipping signal mask test on %s", runtime.GOOS)
156	}
157	got := runTestProg(t, "testprogcgo", "CgoExecSignalMask")
158	want := "OK\n"
159	if got != want {
160		t.Errorf("expected %q, got %v", want, got)
161	}
162}
163
164func TestEnsureDropM(t *testing.T) {
165	t.Parallel()
166	// Test for issue 13881.
167	switch runtime.GOOS {
168	case "windows", "plan9":
169		t.Skipf("skipping dropm test on %s", runtime.GOOS)
170	}
171	got := runTestProg(t, "testprogcgo", "EnsureDropM")
172	want := "OK\n"
173	if got != want {
174		t.Errorf("expected %q, got %v", want, got)
175	}
176}
177
178// Test for issue 14387.
179// Test that the program that doesn't need any cgo pointer checking
180// takes about the same amount of time with it as without it.
181func TestCgoCheckBytes(t *testing.T) {
182	t.Parallel()
183	// Make sure we don't count the build time as part of the run time.
184	testenv.MustHaveGoBuild(t)
185	exe, err := buildTestProg(t, "testprogcgo")
186	if err != nil {
187		t.Fatal(err)
188	}
189
190	// Try it 10 times to avoid flakiness.
191	const tries = 10
192	var tot1, tot2 time.Duration
193	for i := 0; i < tries; i++ {
194		cmd := testenv.CleanCmdEnv(exec.Command(exe, "CgoCheckBytes"))
195		cmd.Env = append(cmd.Env, "GODEBUG=cgocheck=0", fmt.Sprintf("GO_CGOCHECKBYTES_TRY=%d", i))
196
197		start := time.Now()
198		cmd.Run()
199		d1 := time.Since(start)
200
201		cmd = testenv.CleanCmdEnv(exec.Command(exe, "CgoCheckBytes"))
202		cmd.Env = append(cmd.Env, fmt.Sprintf("GO_CGOCHECKBYTES_TRY=%d", i))
203
204		start = time.Now()
205		cmd.Run()
206		d2 := time.Since(start)
207
208		if d1*20 > d2 {
209			// The slow version (d2) was less than 20 times
210			// slower than the fast version (d1), so OK.
211			return
212		}
213
214		tot1 += d1
215		tot2 += d2
216	}
217
218	t.Errorf("cgo check too slow: got %v, expected at most %v", tot2/tries, (tot1/tries)*20)
219}
220
221func TestCgoPanicDeadlock(t *testing.T) {
222	t.Parallel()
223	// test issue 14432
224	got := runTestProg(t, "testprogcgo", "CgoPanicDeadlock")
225	want := "panic: cgo error\n\n"
226	if !strings.HasPrefix(got, want) {
227		t.Fatalf("output does not start with %q:\n%s", want, got)
228	}
229}
230
231func TestCgoCCodeSIGPROF(t *testing.T) {
232	t.Parallel()
233	got := runTestProg(t, "testprogcgo", "CgoCCodeSIGPROF")
234	want := "OK\n"
235	if got != want {
236		t.Errorf("expected %q got %v", want, got)
237	}
238}
239
240func TestCgoCrashTraceback(t *testing.T) {
241	t.Parallel()
242	switch platform := runtime.GOOS + "/" + runtime.GOARCH; platform {
243	case "darwin/amd64":
244	case "linux/amd64":
245	case "linux/ppc64le":
246	default:
247		t.Skipf("not yet supported on %s", platform)
248	}
249	if runtime.Compiler == "gccgo" {
250		t.Skip("gccgo does not have SetCgoTraceback")
251	}
252	got := runTestProg(t, "testprogcgo", "CrashTraceback")
253	for i := 1; i <= 3; i++ {
254		if !strings.Contains(got, fmt.Sprintf("cgo symbolizer:%d", i)) {
255			t.Errorf("missing cgo symbolizer:%d", i)
256		}
257	}
258}
259
260func TestCgoTracebackContext(t *testing.T) {
261	t.Parallel()
262	if runtime.Compiler == "gccgo" {
263		t.Skip("gccgo does not have SetCgoTraceback")
264	}
265	got := runTestProg(t, "testprogcgo", "TracebackContext")
266	want := "OK\n"
267	if got != want {
268		t.Errorf("expected %q got %v", want, got)
269	}
270}
271
272func testCgoPprof(t *testing.T, buildArg, runArg, top, bottom string) {
273	t.Parallel()
274	if runtime.GOOS != "linux" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "ppc64le") {
275		t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH)
276	}
277	if runtime.Compiler == "gccgo" {
278		t.Skip("gccgo does not have SetCgoTraceback")
279	}
280	testenv.MustHaveGoRun(t)
281
282	exe, err := buildTestProg(t, "testprogcgo", buildArg)
283	if err != nil {
284		t.Fatal(err)
285	}
286
287	got, err := testenv.CleanCmdEnv(exec.Command(exe, runArg)).CombinedOutput()
288	if err != nil {
289		if testenv.Builder() == "linux-amd64-alpine" {
290			// See Issue 18243 and Issue 19938.
291			t.Skipf("Skipping failing test on Alpine (golang.org/issue/18243). Ignoring error: %v", err)
292		}
293		t.Fatalf("%s\n\n%v", got, err)
294	}
295	fn := strings.TrimSpace(string(got))
296	defer os.Remove(fn)
297
298	for try := 0; try < 2; try++ {
299		cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-traces"))
300		// Check that pprof works both with and without explicit executable on command line.
301		if try == 0 {
302			cmd.Args = append(cmd.Args, exe, fn)
303		} else {
304			cmd.Args = append(cmd.Args, fn)
305		}
306
307		found := false
308		for i, e := range cmd.Env {
309			if strings.HasPrefix(e, "PPROF_TMPDIR=") {
310				cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir()
311				found = true
312				break
313			}
314		}
315		if !found {
316			cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir())
317		}
318
319		out, err := cmd.CombinedOutput()
320		t.Logf("%s:\n%s", cmd.Args, out)
321		if err != nil {
322			t.Error(err)
323			continue
324		}
325
326		trace := findTrace(string(out), top)
327		if len(trace) == 0 {
328			t.Errorf("%s traceback missing.", top)
329			continue
330		}
331		if trace[len(trace)-1] != bottom {
332			t.Errorf("invalid traceback origin: got=%v; want=[%s ... %s]", trace, top, bottom)
333		}
334	}
335}
336
337func TestCgoPprof(t *testing.T) {
338	testCgoPprof(t, "", "CgoPprof", "cpuHog", "runtime.main")
339}
340
341func TestCgoPprofPIE(t *testing.T) {
342	testCgoPprof(t, "-buildmode=pie", "CgoPprof", "cpuHog", "runtime.main")
343}
344
345func TestCgoPprofThread(t *testing.T) {
346	testCgoPprof(t, "", "CgoPprofThread", "cpuHogThread", "cpuHogThread2")
347}
348
349func TestCgoPprofThreadNoTraceback(t *testing.T) {
350	testCgoPprof(t, "", "CgoPprofThreadNoTraceback", "cpuHogThread", "runtime._ExternalCode")
351}
352
353func TestRaceProf(t *testing.T) {
354	if (runtime.GOOS != "linux" && runtime.GOOS != "freebsd") || runtime.GOARCH != "amd64" {
355		t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH)
356	}
357	if runtime.Compiler == "gccgo" {
358		t.Skip("gccgo does not have SetCgoTraceback")
359	}
360
361	testenv.MustHaveGoRun(t)
362
363	// This test requires building various packages with -race, so
364	// it's somewhat slow.
365	if testing.Short() {
366		t.Skip("skipping test in -short mode")
367	}
368
369	exe, err := buildTestProg(t, "testprogcgo", "-race")
370	if err != nil {
371		t.Fatal(err)
372	}
373
374	got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoRaceprof")).CombinedOutput()
375	if err != nil {
376		t.Fatal(err)
377	}
378	want := "OK\n"
379	if string(got) != want {
380		t.Errorf("expected %q got %s", want, got)
381	}
382}
383
384func TestRaceSignal(t *testing.T) {
385	t.Parallel()
386	if (runtime.GOOS != "linux" && runtime.GOOS != "freebsd") || runtime.GOARCH != "amd64" {
387		t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH)
388	}
389
390	testenv.MustHaveGoRun(t)
391
392	// This test requires building various packages with -race, so
393	// it's somewhat slow.
394	if testing.Short() {
395		t.Skip("skipping test in -short mode")
396	}
397
398	exe, err := buildTestProg(t, "testprogcgo", "-race")
399	if err != nil {
400		t.Fatal(err)
401	}
402
403	got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoRaceSignal")).CombinedOutput()
404	if err != nil {
405		t.Logf("%s\n", got)
406		t.Fatal(err)
407	}
408	want := "OK\n"
409	if string(got) != want {
410		t.Errorf("expected %q got %s", want, got)
411	}
412}
413
414func TestCgoNumGoroutine(t *testing.T) {
415	switch runtime.GOOS {
416	case "windows", "plan9":
417		t.Skipf("skipping numgoroutine test on %s", runtime.GOOS)
418	}
419	t.Parallel()
420	got := runTestProg(t, "testprogcgo", "NumGoroutine")
421	want := "OK\n"
422	if got != want {
423		t.Errorf("expected %q got %v", want, got)
424	}
425}
426
427func TestCatchPanic(t *testing.T) {
428	t.Parallel()
429	switch runtime.GOOS {
430	case "plan9", "windows":
431		t.Skipf("no signals on %s", runtime.GOOS)
432	case "darwin":
433		if runtime.GOARCH == "amd64" {
434			t.Skipf("crash() on darwin/amd64 doesn't raise SIGABRT")
435		}
436	}
437
438	testenv.MustHaveGoRun(t)
439
440	exe, err := buildTestProg(t, "testprogcgo")
441	if err != nil {
442		t.Fatal(err)
443	}
444
445	for _, early := range []bool{true, false} {
446		cmd := testenv.CleanCmdEnv(exec.Command(exe, "CgoCatchPanic"))
447		// Make sure a panic results in a crash.
448		cmd.Env = append(cmd.Env, "GOTRACEBACK=crash")
449		if early {
450			// Tell testprogcgo to install an early signal handler for SIGABRT
451			cmd.Env = append(cmd.Env, "CGOCATCHPANIC_EARLY_HANDLER=1")
452		}
453		if out, err := cmd.CombinedOutput(); err != nil {
454			t.Errorf("testprogcgo CgoCatchPanic failed: %v\n%s", err, out)
455		}
456	}
457}
458
459func TestCgoLockOSThreadExit(t *testing.T) {
460	switch runtime.GOOS {
461	case "plan9", "windows":
462		t.Skipf("no pthreads on %s", runtime.GOOS)
463	}
464	t.Parallel()
465	testLockOSThreadExit(t, "testprogcgo")
466}
467
468func TestWindowsStackMemoryCgo(t *testing.T) {
469	if runtime.GOOS != "windows" {
470		t.Skip("skipping windows specific test")
471	}
472	testenv.SkipFlaky(t, 22575)
473	o := runTestProg(t, "testprogcgo", "StackMemory")
474	stackUsage, err := strconv.Atoi(o)
475	if err != nil {
476		t.Fatalf("Failed to read stack usage: %v", err)
477	}
478	if expected, got := 100<<10, stackUsage; got > expected {
479		t.Fatalf("expected < %d bytes of memory per thread, got %d", expected, got)
480	}
481}
482
483func TestSigStackSwapping(t *testing.T) {
484	switch runtime.GOOS {
485	case "plan9", "windows":
486		t.Skipf("no sigaltstack on %s", runtime.GOOS)
487	}
488	t.Parallel()
489	got := runTestProg(t, "testprogcgo", "SigStack")
490	want := "OK\n"
491	if got != want {
492		t.Errorf("expected %q got %v", want, got)
493	}
494}
495
496func TestCgoTracebackSigpanic(t *testing.T) {
497	// Test unwinding over a sigpanic in C code without a C
498	// symbolizer. See issue #23576.
499	if runtime.GOOS == "windows" {
500		// On Windows if we get an exception in C code, we let
501		// the Windows exception handler unwind it, rather
502		// than injecting a sigpanic.
503		t.Skip("no sigpanic in C on windows")
504	}
505	t.Parallel()
506	got := runTestProg(t, "testprogcgo", "TracebackSigpanic")
507	want := "runtime.sigpanic"
508	if !strings.Contains(got, want) {
509		t.Fatalf("want failure containing %q. output:\n%s\n", want, got)
510	}
511	nowant := "unexpected return pc"
512	if strings.Contains(got, nowant) {
513		t.Fatalf("failure incorrectly contains %q. output:\n%s\n", nowant, got)
514	}
515}
516
517// Test that C code called via cgo can use large Windows thread stacks
518// and call back in to Go without crashing. See issue #20975.
519//
520// See also TestBigStackCallbackSyscall.
521func TestBigStackCallbackCgo(t *testing.T) {
522	if runtime.GOOS != "windows" {
523		t.Skip("skipping windows specific test")
524	}
525	t.Parallel()
526	got := runTestProg(t, "testprogcgo", "BigStack")
527	want := "OK\n"
528	if got != want {
529		t.Errorf("expected %q got %v", want, got)
530	}
531}
532
533func nextTrace(lines []string) ([]string, []string) {
534	var trace []string
535	for n, line := range lines {
536		if strings.HasPrefix(line, "---") {
537			return trace, lines[n+1:]
538		}
539		fields := strings.Fields(strings.TrimSpace(line))
540		if len(fields) == 0 {
541			continue
542		}
543		// Last field contains the function name.
544		trace = append(trace, fields[len(fields)-1])
545	}
546	return nil, nil
547}
548
549func findTrace(text, top string) []string {
550	lines := strings.Split(text, "\n")
551	_, lines = nextTrace(lines) // Skip the header.
552	for len(lines) > 0 {
553		var t []string
554		t, lines = nextTrace(lines)
555		if len(t) == 0 {
556			continue
557		}
558		if t[0] == top {
559			return t
560		}
561	}
562	return nil
563}
564