• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 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
5package plugin_test
6
7import (
8	"bytes"
9	"cmd/cgo/internal/cgotest"
10	"context"
11	"flag"
12	"fmt"
13	"internal/platform"
14	"internal/testenv"
15	"log"
16	"os"
17	"os/exec"
18	"path/filepath"
19	"runtime"
20	"strings"
21	"testing"
22	"time"
23)
24
25var globalSkip = func(t *testing.T) {}
26
27var gcflags string = os.Getenv("GO_GCFLAGS")
28var goroot string
29
30func TestMain(m *testing.M) {
31	flag.Parse()
32	log.SetFlags(log.Lshortfile)
33	os.Exit(testMain(m))
34}
35
36// tmpDir is used to cleanup logged commands -- s/tmpDir/$TMPDIR/
37var tmpDir string
38
39// prettyPrintf prints lines with tmpDir sanitized.
40func prettyPrintf(format string, args ...interface{}) {
41	s := fmt.Sprintf(format, args...)
42	if tmpDir != "" {
43		s = strings.ReplaceAll(s, tmpDir, "$TMPDIR")
44	}
45	fmt.Print(s)
46}
47
48func testMain(m *testing.M) int {
49	if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" {
50		globalSkip = func(t *testing.T) { t.Skip("short mode and $GO_BUILDER_NAME not set") }
51		return m.Run()
52	}
53	if !platform.BuildModeSupported(runtime.Compiler, "plugin", runtime.GOOS, runtime.GOARCH) {
54		globalSkip = func(t *testing.T) { t.Skip("plugin build mode not supported") }
55		return m.Run()
56	}
57	if !testenv.HasCGO() {
58		globalSkip = func(t *testing.T) { t.Skip("cgo not supported") }
59		return m.Run()
60	}
61
62	cwd, err := os.Getwd()
63	if err != nil {
64		log.Fatal(err)
65	}
66	goroot = filepath.Join(cwd, "../../../../..")
67
68	// Copy testdata into GOPATH/src/testplugin, along with a go.mod file
69	// declaring the same path.
70
71	GOPATH, err := os.MkdirTemp("", "plugin_test")
72	if err != nil {
73		log.Panic(err)
74	}
75	defer os.RemoveAll(GOPATH)
76	tmpDir = GOPATH
77	fmt.Printf("TMPDIR=%s\n", tmpDir)
78
79	modRoot := filepath.Join(GOPATH, "src", "testplugin")
80	altRoot := filepath.Join(GOPATH, "alt", "src", "testplugin")
81	for srcRoot, dstRoot := range map[string]string{
82		"testdata":                           modRoot,
83		filepath.Join("altpath", "testdata"): altRoot,
84	} {
85		if err := cgotest.OverlayDir(dstRoot, srcRoot); err != nil {
86			log.Panic(err)
87		}
88		prettyPrintf("mkdir -p %s\n", dstRoot)
89		prettyPrintf("rsync -a %s/ %s\n", srcRoot, dstRoot)
90
91		if err := os.WriteFile(filepath.Join(dstRoot, "go.mod"), []byte("module testplugin\n"), 0666); err != nil {
92			log.Panic(err)
93		}
94		prettyPrintf("echo 'module testplugin' > %s/go.mod\n", dstRoot)
95	}
96
97	os.Setenv("GOPATH", filepath.Join(GOPATH, "alt"))
98	if err := os.Chdir(altRoot); err != nil {
99		log.Panic(err)
100	} else {
101		prettyPrintf("cd %s\n", altRoot)
102	}
103	os.Setenv("PWD", altRoot)
104	goCmd(nil, "build", "-buildmode=plugin", "-o", filepath.Join(modRoot, "plugin-mismatch.so"), "./plugin-mismatch")
105
106	os.Setenv("GOPATH", GOPATH)
107	if err := os.Chdir(modRoot); err != nil {
108		log.Panic(err)
109	} else {
110		prettyPrintf("cd %s\n", modRoot)
111	}
112	os.Setenv("PWD", modRoot)
113
114	os.Setenv("LD_LIBRARY_PATH", modRoot)
115
116	goCmd(nil, "build", "-buildmode=plugin", "./plugin1")
117	goCmd(nil, "build", "-buildmode=plugin", "./plugin2")
118	so, err := os.ReadFile("plugin2.so")
119	if err != nil {
120		log.Panic(err)
121	}
122	if err := os.WriteFile("plugin2-dup.so", so, 0444); err != nil {
123		log.Panic(err)
124	}
125	prettyPrintf("cp plugin2.so plugin2-dup.so\n")
126
127	goCmd(nil, "build", "-buildmode=plugin", "-o=sub/plugin1.so", "./sub/plugin1")
128	goCmd(nil, "build", "-buildmode=plugin", "-o=unnamed1.so", "./unnamed1/main.go")
129	goCmd(nil, "build", "-buildmode=plugin", "-o=unnamed2.so", "./unnamed2/main.go")
130	goCmd(nil, "build", "-o", "host.exe", "./host")
131
132	return m.Run()
133}
134
135func goCmd(t *testing.T, op string, args ...string) string {
136	if t != nil {
137		t.Helper()
138	}
139	var flags []string
140	if op != "tool" {
141		flags = []string{"-gcflags", gcflags}
142	}
143	return run(t, filepath.Join(goroot, "bin", "go"), append(append([]string{op}, flags...), args...)...)
144}
145
146// escape converts a string to something suitable for a shell command line.
147func escape(s string) string {
148	s = strings.Replace(s, "\\", "\\\\", -1)
149	s = strings.Replace(s, "'", "\\'", -1)
150	// Conservative guess at characters that will force quoting
151	if s == "" || strings.ContainsAny(s, "\\ ;#*&$~?!|[]()<>{}`") {
152		s = "'" + s + "'"
153	}
154	return s
155}
156
157// asCommandLine renders cmd as something that could be copy-and-pasted into a command line
158func asCommandLine(cwd string, cmd *exec.Cmd) string {
159	s := "("
160	if cmd.Dir != "" && cmd.Dir != cwd {
161		s += "cd" + escape(cmd.Dir) + ";"
162	}
163	for _, e := range cmd.Env {
164		if !strings.HasPrefix(e, "PATH=") &&
165			!strings.HasPrefix(e, "HOME=") &&
166			!strings.HasPrefix(e, "USER=") &&
167			!strings.HasPrefix(e, "SHELL=") {
168			s += " "
169			s += escape(e)
170		}
171	}
172	// These EVs are relevant to this test.
173	for _, e := range os.Environ() {
174		if strings.HasPrefix(e, "PWD=") ||
175			strings.HasPrefix(e, "GOPATH=") ||
176			strings.HasPrefix(e, "LD_LIBRARY_PATH=") {
177			s += " "
178			s += escape(e)
179		}
180	}
181	for _, a := range cmd.Args {
182		s += " "
183		s += escape(a)
184	}
185	s += " )"
186	return s
187}
188
189func run(t *testing.T, bin string, args ...string) string {
190	cmd := exec.Command(bin, args...)
191	cmdLine := asCommandLine(".", cmd)
192	prettyPrintf("%s\n", cmdLine)
193	cmd.Stderr = new(strings.Builder)
194	out, err := cmd.Output()
195	if err != nil {
196		if t == nil {
197			log.Panicf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr)
198		} else {
199			t.Helper()
200			t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr)
201		}
202	}
203
204	return string(bytes.TrimSpace(out))
205}
206
207func TestDWARFSections(t *testing.T) {
208	// test that DWARF sections are emitted for plugins and programs importing "plugin"
209	globalSkip(t)
210	goCmd(t, "run", "./checkdwarf/main.go", "plugin2.so", "plugin2.UnexportedNameReuse")
211	goCmd(t, "run", "./checkdwarf/main.go", "./host.exe", "main.main")
212}
213
214func TestBuildID(t *testing.T) {
215	// check that plugin has build ID.
216	globalSkip(t)
217	b := goCmd(t, "tool", "buildid", "plugin1.so")
218	if len(b) == 0 {
219		t.Errorf("build id not found")
220	}
221}
222
223func TestRunHost(t *testing.T) {
224	globalSkip(t)
225	run(t, "./host.exe")
226}
227
228func TestUniqueTypesAndItabs(t *testing.T) {
229	globalSkip(t)
230	goCmd(t, "build", "-buildmode=plugin", "./iface_a")
231	goCmd(t, "build", "-buildmode=plugin", "./iface_b")
232	goCmd(t, "build", "-o", "iface.exe", "./iface")
233	run(t, "./iface.exe")
234}
235
236func TestIssue18676(t *testing.T) {
237	// make sure we don't add the same itab twice.
238	// The buggy code hangs forever, so use a timeout to check for that.
239	globalSkip(t)
240	goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./issue18676/plugin.go")
241	goCmd(t, "build", "-o", "issue18676.exe", "./issue18676/main.go")
242
243	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
244	defer cancel()
245	cmd := exec.CommandContext(ctx, "./issue18676.exe")
246	out, err := cmd.CombinedOutput()
247	if err != nil {
248		t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out)
249	}
250}
251
252func TestIssue19534(t *testing.T) {
253	// Test that we can load a plugin built in a path with non-alpha characters.
254	globalSkip(t)
255	goCmd(t, "build", "-buildmode=plugin", "-gcflags=-p=issue.19534", "-ldflags=-pluginpath=issue.19534", "-o", "plugin.so", "./issue19534/plugin.go")
256	goCmd(t, "build", "-o", "issue19534.exe", "./issue19534/main.go")
257	run(t, "./issue19534.exe")
258}
259
260func TestIssue18584(t *testing.T) {
261	globalSkip(t)
262	goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./issue18584/plugin.go")
263	goCmd(t, "build", "-o", "issue18584.exe", "./issue18584/main.go")
264	run(t, "./issue18584.exe")
265}
266
267func TestIssue19418(t *testing.T) {
268	globalSkip(t)
269	goCmd(t, "build", "-buildmode=plugin", "-ldflags=-X main.Val=linkstr", "-o", "plugin.so", "./issue19418/plugin.go")
270	goCmd(t, "build", "-o", "issue19418.exe", "./issue19418/main.go")
271	run(t, "./issue19418.exe")
272}
273
274func TestIssue19529(t *testing.T) {
275	globalSkip(t)
276	goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./issue19529/plugin.go")
277}
278
279func TestIssue22175(t *testing.T) {
280	globalSkip(t)
281	goCmd(t, "build", "-buildmode=plugin", "-o", "issue22175_plugin1.so", "./issue22175/plugin1.go")
282	goCmd(t, "build", "-buildmode=plugin", "-o", "issue22175_plugin2.so", "./issue22175/plugin2.go")
283	goCmd(t, "build", "-o", "issue22175.exe", "./issue22175/main.go")
284	run(t, "./issue22175.exe")
285}
286
287func TestIssue22295(t *testing.T) {
288	globalSkip(t)
289	goCmd(t, "build", "-buildmode=plugin", "-o", "issue.22295.so", "./issue22295.pkg")
290	goCmd(t, "build", "-o", "issue22295.exe", "./issue22295.pkg/main.go")
291	run(t, "./issue22295.exe")
292}
293
294func TestIssue24351(t *testing.T) {
295	globalSkip(t)
296	goCmd(t, "build", "-buildmode=plugin", "-o", "issue24351.so", "./issue24351/plugin.go")
297	goCmd(t, "build", "-o", "issue24351.exe", "./issue24351/main.go")
298	run(t, "./issue24351.exe")
299}
300
301func TestIssue25756(t *testing.T) {
302	globalSkip(t)
303	goCmd(t, "build", "-buildmode=plugin", "-o", "life.so", "./issue25756/plugin")
304	goCmd(t, "build", "-o", "issue25756.exe", "./issue25756/main.go")
305	// Fails intermittently, but 20 runs should cause the failure
306	for n := 20; n > 0; n-- {
307		t.Run(fmt.Sprint(n), func(t *testing.T) {
308			t.Parallel()
309			run(t, "./issue25756.exe")
310		})
311	}
312}
313
314// Test with main using -buildmode=pie with plugin for issue #43228
315func TestIssue25756pie(t *testing.T) {
316	globalSkip(t)
317	goCmd(t, "build", "-buildmode=plugin", "-o", "life.so", "./issue25756/plugin")
318	goCmd(t, "build", "-buildmode=pie", "-o", "issue25756pie.exe", "./issue25756/main.go")
319	run(t, "./issue25756pie.exe")
320}
321
322func TestMethod(t *testing.T) {
323	// Exported symbol's method must be live.
324	globalSkip(t)
325	goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./method/plugin.go")
326	goCmd(t, "build", "-o", "method.exe", "./method/main.go")
327	run(t, "./method.exe")
328}
329
330func TestMethod2(t *testing.T) {
331	globalSkip(t)
332	goCmd(t, "build", "-buildmode=plugin", "-o", "method2.so", "./method2/plugin.go")
333	goCmd(t, "build", "-o", "method2.exe", "./method2/main.go")
334	run(t, "./method2.exe")
335}
336
337func TestMethod3(t *testing.T) {
338	globalSkip(t)
339	goCmd(t, "build", "-buildmode=plugin", "-o", "method3.so", "./method3/plugin.go")
340	goCmd(t, "build", "-o", "method3.exe", "./method3/main.go")
341	run(t, "./method3.exe")
342}
343
344func TestIssue44956(t *testing.T) {
345	globalSkip(t)
346	goCmd(t, "build", "-buildmode=plugin", "-o", "issue44956p1.so", "./issue44956/plugin1.go")
347	goCmd(t, "build", "-buildmode=plugin", "-o", "issue44956p2.so", "./issue44956/plugin2.go")
348	goCmd(t, "build", "-o", "issue44956.exe", "./issue44956/main.go")
349	run(t, "./issue44956.exe")
350}
351
352func TestIssue52937(t *testing.T) {
353	globalSkip(t)
354	goCmd(t, "build", "-buildmode=plugin", "-o", "issue52937.so", "./issue52937/main.go")
355}
356
357func TestIssue53989(t *testing.T) {
358	globalSkip(t)
359	goCmd(t, "build", "-buildmode=plugin", "-o", "issue53989.so", "./issue53989/plugin.go")
360	goCmd(t, "build", "-o", "issue53989.exe", "./issue53989/main.go")
361	run(t, "./issue53989.exe")
362}
363
364func TestForkExec(t *testing.T) {
365	// Issue 38824: importing the plugin package causes it hang in forkExec on darwin.
366	globalSkip(t)
367
368	t.Parallel()
369	goCmd(t, "build", "-o", "forkexec.exe", "./forkexec/main.go")
370
371	for i := 0; i < 100; i++ {
372		cmd := testenv.Command(t, "./forkexec.exe", "1")
373		err := cmd.Run()
374		if err != nil {
375			if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
376				t.Logf("stderr:\n%s", ee.Stderr)
377			}
378			t.Errorf("running command failed: %v", err)
379			break
380		}
381	}
382}
383
384func TestSymbolNameMangle(t *testing.T) {
385	// Issue 58800: generic function name may contain weird characters
386	// that confuse the external linker.
387	// Issue 62098: the name mangling code doesn't handle some string
388	// symbols correctly.
389	globalSkip(t)
390	goCmd(t, "build", "-buildmode=plugin", "-o", "mangle.so", "./mangle/plugin.go")
391}
392
393func TestIssue62430(t *testing.T) {
394	globalSkip(t)
395	goCmd(t, "build", "-buildmode=plugin", "-o", "issue62430.so", "./issue62430/plugin.go")
396	goCmd(t, "build", "-o", "issue62430.exe", "./issue62430/main.go")
397	run(t, "./issue62430.exe")
398}
399
400func TestTextSectionSplit(t *testing.T) {
401	globalSkip(t)
402	if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" {
403		t.Skipf("text section splitting is not done in %s/%s", runtime.GOOS, runtime.GOARCH)
404	}
405
406	// Use -ldflags=-debugtextsize=262144 to let the linker split text section
407	// at a smaller size threshold, so it actually splits for the test binary.
408	goCmd(nil, "build", "-ldflags=-debugtextsize=262144", "-o", "host-split.exe", "./host")
409	run(t, "./host-split.exe")
410
411	// Check that we did split text sections.
412	syms := goCmd(nil, "tool", "nm", "host-split.exe")
413	if !strings.Contains(syms, "runtime.text.1") {
414		t.Errorf("runtime.text.1 not found, text section not split?")
415	}
416}
417
418func TestIssue67976(t *testing.T) {
419	// Issue 67976: build failure with loading a dynimport variable (the runtime/pprof
420	// package does this on darwin) in a plugin on darwin/amd64.
421	// The test program uses runtime/pprof in a plugin.
422	globalSkip(t)
423	goCmd(t, "build", "-buildmode=plugin", "-o", "issue67976.so", "./issue67976/plugin.go")
424}
425