Source file src/cmd/cgo/internal/testplugin/plugin_test.go

     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  
     5  package plugin_test
     6  
     7  import (
     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  
    25  var globalSkip = func(t *testing.T) {}
    26  
    27  var gcflags string = os.Getenv("GO_GCFLAGS")
    28  var goroot string
    29  
    30  func 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/
    37  var tmpDir string
    38  
    39  // prettyPrintf prints lines with tmpDir sanitized.
    40  func 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  
    48  func 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  
   135  func 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.
   147  func 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
   158  func 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  
   189  func 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  
   207  func 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  
   214  func 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  
   223  func TestRunHost(t *testing.T) {
   224  	globalSkip(t)
   225  	run(t, "./host.exe")
   226  }
   227  
   228  func 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  
   236  func 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  
   252  func 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  
   260  func 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  
   267  func 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  
   274  func TestIssue19529(t *testing.T) {
   275  	globalSkip(t)
   276  	goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./issue19529/plugin.go")
   277  }
   278  
   279  func 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  
   287  func 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  
   294  func 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  
   301  func 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
   315  func 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  
   322  func 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  
   330  func 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  
   337  func 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  
   344  func 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  
   352  func TestIssue52937(t *testing.T) {
   353  	globalSkip(t)
   354  	goCmd(t, "build", "-buildmode=plugin", "-o", "issue52937.so", "./issue52937/main.go")
   355  }
   356  
   357  func 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  
   364  func 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  
   384  func 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  
   393  func 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  
   400  func 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  
   418  func 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  

View as plain text