github.com/360nenz/gu@v0.0.0-20230623171552-8f1e25eda00d/gulang/misc/cgo/testplugin/plugin_test.go (about)

     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  	"context"
    10  	"flag"
    11  	"fmt"
    12  	"log"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  )
    20  
    21  var gcflags string = os.Getenv("GO_GCFLAGS")
    22  var goroot string
    23  
    24  func TestMain(m *testing.M) {
    25  	flag.Parse()
    26  	if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" {
    27  		fmt.Printf("SKIP - short mode and $GO_BUILDER_NAME not set\n")
    28  		os.Exit(0)
    29  	}
    30  	log.SetFlags(log.Lshortfile)
    31  	os.Exit(testMain(m))
    32  }
    33  
    34  // tmpDir is used to cleanup logged commands -- s/tmpDir/$TMPDIR/
    35  var tmpDir string
    36  
    37  // prettyPrintf prints lines with tmpDir sanitized.
    38  func prettyPrintf(format string, args ...interface{}) {
    39  	s := fmt.Sprintf(format, args...)
    40  	if tmpDir != "" {
    41  		s = strings.ReplaceAll(s, tmpDir, "$TMPDIR")
    42  	}
    43  	fmt.Print(s)
    44  }
    45  
    46  func testMain(m *testing.M) int {
    47  	cwd, err := os.Getwd()
    48  	if err != nil {
    49  		log.Fatal(err)
    50  	}
    51  	goroot = filepath.Join(cwd, "../../..")
    52  
    53  	// Copy testdata into GOPATH/src/testplugin, along with a go.mod file
    54  	// declaring the same path.
    55  
    56  	GOPATH, err := os.MkdirTemp("", "plugin_test")
    57  	if err != nil {
    58  		log.Panic(err)
    59  	}
    60  	defer os.RemoveAll(GOPATH)
    61  	tmpDir = GOPATH
    62  
    63  	modRoot := filepath.Join(GOPATH, "src", "testplugin")
    64  	altRoot := filepath.Join(GOPATH, "alt", "src", "testplugin")
    65  	for srcRoot, dstRoot := range map[string]string{
    66  		"testdata":                           modRoot,
    67  		filepath.Join("altpath", "testdata"): altRoot,
    68  	} {
    69  		if err := overlayDir(dstRoot, srcRoot); err != nil {
    70  			log.Panic(err)
    71  		}
    72  		prettyPrintf("mkdir -p %s\n", dstRoot)
    73  		prettyPrintf("rsync -a %s/ %s\n", srcRoot, dstRoot)
    74  
    75  		if err := os.WriteFile(filepath.Join(dstRoot, "go.mod"), []byte("module testplugin\n"), 0666); err != nil {
    76  			log.Panic(err)
    77  		}
    78  		prettyPrintf("echo 'module testplugin' > %s/go.mod\n", dstRoot)
    79  	}
    80  
    81  	os.Setenv("GOPATH", filepath.Join(GOPATH, "alt"))
    82  	if err := os.Chdir(altRoot); err != nil {
    83  		log.Panic(err)
    84  	} else {
    85  		prettyPrintf("cd %s\n", altRoot)
    86  	}
    87  	os.Setenv("PWD", altRoot)
    88  	goCmd(nil, "build", "-buildmode=plugin", "-o", filepath.Join(modRoot, "plugin-mismatch.so"), "./plugin-mismatch")
    89  
    90  	os.Setenv("GOPATH", GOPATH)
    91  	if err := os.Chdir(modRoot); err != nil {
    92  		log.Panic(err)
    93  	} else {
    94  		prettyPrintf("cd %s\n", modRoot)
    95  	}
    96  	os.Setenv("PWD", modRoot)
    97  
    98  	os.Setenv("LD_LIBRARY_PATH", modRoot)
    99  
   100  	goCmd(nil, "build", "-buildmode=plugin", "./plugin1")
   101  	goCmd(nil, "build", "-buildmode=plugin", "./plugin2")
   102  	so, err := os.ReadFile("plugin2.so")
   103  	if err != nil {
   104  		log.Panic(err)
   105  	}
   106  	if err := os.WriteFile("plugin2-dup.so", so, 0444); err != nil {
   107  		log.Panic(err)
   108  	}
   109  	prettyPrintf("cp plugin2.so plugin2-dup.so\n")
   110  
   111  	goCmd(nil, "build", "-buildmode=plugin", "-o=sub/plugin1.so", "./sub/plugin1")
   112  	goCmd(nil, "build", "-buildmode=plugin", "-o=unnamed1.so", "./unnamed1/main.go")
   113  	goCmd(nil, "build", "-buildmode=plugin", "-o=unnamed2.so", "./unnamed2/main.go")
   114  	goCmd(nil, "build", "-o", "host.exe", "./host")
   115  
   116  	return m.Run()
   117  }
   118  
   119  func goCmd(t *testing.T, op string, args ...string) {
   120  	if t != nil {
   121  		t.Helper()
   122  	}
   123  	run(t, filepath.Join(goroot, "bin", "go"), append([]string{op, "-gcflags", gcflags}, args...)...)
   124  }
   125  
   126  // escape converts a string to something suitable for a shell command line.
   127  func escape(s string) string {
   128  	s = strings.Replace(s, "\\", "\\\\", -1)
   129  	s = strings.Replace(s, "'", "\\'", -1)
   130  	// Conservative guess at characters that will force quoting
   131  	if s == "" || strings.ContainsAny(s, "\\ ;#*&$~?!|[]()<>{}`") {
   132  		s = "'" + s + "'"
   133  	}
   134  	return s
   135  }
   136  
   137  // asCommandLine renders cmd as something that could be copy-and-pasted into a command line
   138  func asCommandLine(cwd string, cmd *exec.Cmd) string {
   139  	s := "("
   140  	if cmd.Dir != "" && cmd.Dir != cwd {
   141  		s += "cd" + escape(cmd.Dir) + ";"
   142  	}
   143  	for _, e := range cmd.Env {
   144  		if !strings.HasPrefix(e, "PATH=") &&
   145  			!strings.HasPrefix(e, "HOME=") &&
   146  			!strings.HasPrefix(e, "USER=") &&
   147  			!strings.HasPrefix(e, "SHELL=") {
   148  			s += " "
   149  			s += escape(e)
   150  		}
   151  	}
   152  	// These EVs are relevant to this test.
   153  	for _, e := range os.Environ() {
   154  		if strings.HasPrefix(e, "PWD=") ||
   155  			strings.HasPrefix(e, "GOPATH=") ||
   156  			strings.HasPrefix(e, "LD_LIBRARY_PATH=") {
   157  			s += " "
   158  			s += escape(e)
   159  		}
   160  	}
   161  	for _, a := range cmd.Args {
   162  		s += " "
   163  		s += escape(a)
   164  	}
   165  	s += " )"
   166  	return s
   167  }
   168  
   169  func run(t *testing.T, bin string, args ...string) string {
   170  	cmd := exec.Command(bin, args...)
   171  	cmdLine := asCommandLine(".", cmd)
   172  	prettyPrintf("%s\n", cmdLine)
   173  	cmd.Stderr = new(strings.Builder)
   174  	out, err := cmd.Output()
   175  	if err != nil {
   176  		if t == nil {
   177  			log.Panicf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr)
   178  		} else {
   179  			t.Helper()
   180  			t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr)
   181  		}
   182  	}
   183  
   184  	return string(bytes.TrimSpace(out))
   185  }
   186  
   187  func TestDWARFSections(t *testing.T) {
   188  	// test that DWARF sections are emitted for plugins and programs importing "plugin"
   189  	goCmd(t, "run", "./checkdwarf/main.go", "plugin2.so", "plugin2.UnexportedNameReuse")
   190  	goCmd(t, "run", "./checkdwarf/main.go", "./host.exe", "main.main")
   191  }
   192  
   193  func TestRunHost(t *testing.T) {
   194  	run(t, "./host.exe")
   195  }
   196  
   197  func TestUniqueTypesAndItabs(t *testing.T) {
   198  	goCmd(t, "build", "-buildmode=plugin", "./iface_a")
   199  	goCmd(t, "build", "-buildmode=plugin", "./iface_b")
   200  	goCmd(t, "build", "-o", "iface.exe", "./iface")
   201  	run(t, "./iface.exe")
   202  }
   203  
   204  func TestIssue18676(t *testing.T) {
   205  	// make sure we don't add the same itab twice.
   206  	// The buggy code hangs forever, so use a timeout to check for that.
   207  	goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./issue18676/plugin.go")
   208  	goCmd(t, "build", "-o", "issue18676.exe", "./issue18676/main.go")
   209  
   210  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   211  	defer cancel()
   212  	cmd := exec.CommandContext(ctx, "./issue18676.exe")
   213  	out, err := cmd.CombinedOutput()
   214  	if err != nil {
   215  		t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out)
   216  	}
   217  }
   218  
   219  func TestIssue19534(t *testing.T) {
   220  	// Test that we can load a plugin built in a path with non-alpha characters.
   221  	goCmd(t, "build", "-buildmode=plugin", "-gcflags=-p=issue.19534", "-ldflags=-pluginpath=issue.19534", "-o", "plugin.so", "./issue19534/plugin.go")
   222  	goCmd(t, "build", "-o", "issue19534.exe", "./issue19534/main.go")
   223  	run(t, "./issue19534.exe")
   224  }
   225  
   226  func TestIssue18584(t *testing.T) {
   227  	goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./issue18584/plugin.go")
   228  	goCmd(t, "build", "-o", "issue18584.exe", "./issue18584/main.go")
   229  	run(t, "./issue18584.exe")
   230  }
   231  
   232  func TestIssue19418(t *testing.T) {
   233  	goCmd(t, "build", "-buildmode=plugin", "-ldflags=-X main.Val=linkstr", "-o", "plugin.so", "./issue19418/plugin.go")
   234  	goCmd(t, "build", "-o", "issue19418.exe", "./issue19418/main.go")
   235  	run(t, "./issue19418.exe")
   236  }
   237  
   238  func TestIssue19529(t *testing.T) {
   239  	goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./issue19529/plugin.go")
   240  }
   241  
   242  func TestIssue22175(t *testing.T) {
   243  	goCmd(t, "build", "-buildmode=plugin", "-o", "issue22175_plugin1.so", "./issue22175/plugin1.go")
   244  	goCmd(t, "build", "-buildmode=plugin", "-o", "issue22175_plugin2.so", "./issue22175/plugin2.go")
   245  	goCmd(t, "build", "-o", "issue22175.exe", "./issue22175/main.go")
   246  	run(t, "./issue22175.exe")
   247  }
   248  
   249  func TestIssue22295(t *testing.T) {
   250  	goCmd(t, "build", "-buildmode=plugin", "-o", "issue.22295.so", "./issue22295.pkg")
   251  	goCmd(t, "build", "-o", "issue22295.exe", "./issue22295.pkg/main.go")
   252  	run(t, "./issue22295.exe")
   253  }
   254  
   255  func TestIssue24351(t *testing.T) {
   256  	goCmd(t, "build", "-buildmode=plugin", "-o", "issue24351.so", "./issue24351/plugin.go")
   257  	goCmd(t, "build", "-o", "issue24351.exe", "./issue24351/main.go")
   258  	run(t, "./issue24351.exe")
   259  }
   260  
   261  func TestIssue25756(t *testing.T) {
   262  	goCmd(t, "build", "-buildmode=plugin", "-o", "life.so", "./issue25756/plugin")
   263  	goCmd(t, "build", "-o", "issue25756.exe", "./issue25756/main.go")
   264  	// Fails intermittently, but 20 runs should cause the failure
   265  	for n := 20; n > 0; n-- {
   266  		t.Run(fmt.Sprint(n), func(t *testing.T) {
   267  			t.Parallel()
   268  			run(t, "./issue25756.exe")
   269  		})
   270  	}
   271  }
   272  
   273  // Test with main using -buildmode=pie with plugin for issue #43228
   274  func TestIssue25756pie(t *testing.T) {
   275  	goCmd(t, "build", "-buildmode=plugin", "-o", "life.so", "./issue25756/plugin")
   276  	goCmd(t, "build", "-buildmode=pie", "-o", "issue25756pie.exe", "./issue25756/main.go")
   277  	run(t, "./issue25756pie.exe")
   278  }
   279  
   280  func TestMethod(t *testing.T) {
   281  	// Exported symbol's method must be live.
   282  	goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./method/plugin.go")
   283  	goCmd(t, "build", "-o", "method.exe", "./method/main.go")
   284  	run(t, "./method.exe")
   285  }
   286  
   287  func TestMethod2(t *testing.T) {
   288  	goCmd(t, "build", "-buildmode=plugin", "-o", "method2.so", "./method2/plugin.go")
   289  	goCmd(t, "build", "-o", "method2.exe", "./method2/main.go")
   290  	run(t, "./method2.exe")
   291  }
   292  
   293  func TestMethod3(t *testing.T) {
   294  	goCmd(t, "build", "-buildmode=plugin", "-o", "method3.so", "./method3/plugin.go")
   295  	goCmd(t, "build", "-o", "method3.exe", "./method3/main.go")
   296  	run(t, "./method3.exe")
   297  }
   298  
   299  func TestIssue44956(t *testing.T) {
   300  	goCmd(t, "build", "-buildmode=plugin", "-o", "issue44956p1.so", "./issue44956/plugin1.go")
   301  	goCmd(t, "build", "-buildmode=plugin", "-o", "issue44956p2.so", "./issue44956/plugin2.go")
   302  	goCmd(t, "build", "-o", "issue44956.exe", "./issue44956/main.go")
   303  	run(t, "./issue44956.exe")
   304  }
   305  
   306  func TestIssue52937(t *testing.T) {
   307  	goCmd(t, "build", "-buildmode=plugin", "-o", "issue52937.so", "./issue52937/main.go")
   308  }
   309  
   310  func TestIssue53989(t *testing.T) {
   311  	goCmd(t, "build", "-buildmode=plugin", "-o", "issue53989.so", "./issue53989/plugin.go")
   312  	goCmd(t, "build", "-o", "issue53989.exe", "./issue53989/main.go")
   313  	run(t, "./issue53989.exe")
   314  }
   315  
   316  func TestForkExec(t *testing.T) {
   317  	// Issue 38824: importing the plugin package causes it hang in forkExec on darwin.
   318  
   319  	t.Parallel()
   320  	goCmd(t, "build", "-o", "forkexec.exe", "./forkexec/main.go")
   321  
   322  	var cmd *exec.Cmd
   323  	done := make(chan int, 1)
   324  
   325  	go func() {
   326  		for i := 0; i < 100; i++ {
   327  			cmd = exec.Command("./forkexec.exe", "1")
   328  			err := cmd.Run()
   329  			if err != nil {
   330  				t.Errorf("running command failed: %v", err)
   331  				break
   332  			}
   333  		}
   334  		done <- 1
   335  	}()
   336  	select {
   337  	case <-done:
   338  	case <-time.After(5 * time.Minute):
   339  		cmd.Process.Kill()
   340  		t.Fatalf("subprocess hang")
   341  	}
   342  }