github.com/hlts2/go@v0.0.0-20170904000733-812b34efaed8/misc/cgo/testcarchive/carchive_test.go (about)

     1  // Copyright 2016 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 carchive_test
     6  
     7  import (
     8  	"bufio"
     9  	"debug/elf"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strings"
    16  	"syscall"
    17  	"testing"
    18  	"time"
    19  	"unicode"
    20  )
    21  
    22  // Program to run.
    23  var bin []string
    24  
    25  // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)).
    26  var cc []string
    27  
    28  // An environment with GOPATH=$(pwd).
    29  var gopathEnv []string
    30  
    31  // ".exe" on Windows.
    32  var exeSuffix string
    33  
    34  var GOOS, GOARCH string
    35  var libgodir string
    36  
    37  func init() {
    38  	GOOS = goEnv("GOOS")
    39  	GOARCH = goEnv("GOARCH")
    40  	bin = cmdToRun("./testp")
    41  
    42  	ccOut := goEnv("CC")
    43  	cc = []string{string(ccOut)}
    44  
    45  	out := goEnv("GOGCCFLAGS")
    46  	quote := '\000'
    47  	start := 0
    48  	lastSpace := true
    49  	backslash := false
    50  	s := string(out)
    51  	for i, c := range s {
    52  		if quote == '\000' && unicode.IsSpace(c) {
    53  			if !lastSpace {
    54  				cc = append(cc, s[start:i])
    55  				lastSpace = true
    56  			}
    57  		} else {
    58  			if lastSpace {
    59  				start = i
    60  				lastSpace = false
    61  			}
    62  			if quote == '\000' && !backslash && (c == '"' || c == '\'') {
    63  				quote = c
    64  				backslash = false
    65  			} else if !backslash && quote == c {
    66  				quote = '\000'
    67  			} else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
    68  				backslash = true
    69  			} else {
    70  				backslash = false
    71  			}
    72  		}
    73  	}
    74  	if !lastSpace {
    75  		cc = append(cc, s[start:])
    76  	}
    77  
    78  	if GOOS == "darwin" {
    79  		// For Darwin/ARM.
    80  		// TODO(crawshaw): can we do better?
    81  		cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...)
    82  	}
    83  	libgodir = GOOS + "_" + GOARCH
    84  	switch GOOS {
    85  	case "darwin":
    86  		if GOARCH == "arm" || GOARCH == "arm64" {
    87  			libgodir += "_shared"
    88  		}
    89  	case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris":
    90  		libgodir += "_shared"
    91  	}
    92  	cc = append(cc, "-I", filepath.Join("pkg", libgodir))
    93  
    94  	// Build an environment with GOPATH=$(pwd)
    95  	env := os.Environ()
    96  	var n []string
    97  	for _, e := range env {
    98  		if !strings.HasPrefix(e, "GOPATH=") {
    99  			n = append(n, e)
   100  		}
   101  	}
   102  	dir, err := os.Getwd()
   103  	if err != nil {
   104  		fmt.Fprintln(os.Stderr, err)
   105  		os.Exit(2)
   106  	}
   107  	n = append(n, "GOPATH="+dir)
   108  	gopathEnv = n
   109  
   110  	if GOOS == "windows" {
   111  		exeSuffix = ".exe"
   112  	}
   113  }
   114  
   115  func goEnv(key string) string {
   116  	out, err := exec.Command("go", "env", key).Output()
   117  	if err != nil {
   118  		fmt.Fprintf(os.Stderr, "go env %s failed:\n%s\n", key, err)
   119  		if ee, ok := err.(*exec.ExitError); ok {
   120  			fmt.Fprintf(os.Stderr, "%s", ee.Stderr)
   121  		}
   122  		os.Exit(2)
   123  	}
   124  	return strings.TrimSpace(string(out))
   125  }
   126  
   127  func cmdToRun(name string) []string {
   128  	execScript := "go_" + goEnv("GOOS") + "_" + goEnv("GOARCH") + "_exec"
   129  	executor, err := exec.LookPath(execScript)
   130  	if err != nil {
   131  		return []string{name}
   132  	}
   133  	return []string{executor, name}
   134  }
   135  
   136  func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) {
   137  	cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
   138  	cmd.Env = gopathEnv
   139  	if out, err := cmd.CombinedOutput(); err != nil {
   140  		t.Logf("%s", out)
   141  		t.Fatal(err)
   142  	}
   143  	defer func() {
   144  		os.Remove(libgoa)
   145  		os.Remove(libgoh)
   146  	}()
   147  
   148  	ccArgs := append(cc, "-o", exe, "main.c")
   149  	if GOOS == "windows" {
   150  		ccArgs = append(ccArgs, "main_windows.c", libgoa, "-lntdll", "-lws2_32", "-lwinmm")
   151  	} else {
   152  		ccArgs = append(ccArgs, "main_unix.c", libgoa)
   153  	}
   154  	t.Log(ccArgs)
   155  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   156  		t.Logf("%s", out)
   157  		t.Fatal(err)
   158  	}
   159  	defer os.Remove(exe)
   160  
   161  	binArgs := append(cmdToRun(exe), "arg1", "arg2")
   162  	if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil {
   163  		t.Logf("%s", out)
   164  		t.Fatal(err)
   165  	}
   166  }
   167  
   168  func TestInstall(t *testing.T) {
   169  	defer os.RemoveAll("pkg")
   170  
   171  	testInstall(t, "./testp1"+exeSuffix,
   172  		filepath.Join("pkg", libgodir, "libgo.a"),
   173  		filepath.Join("pkg", libgodir, "libgo.h"),
   174  		"go", "install", "-buildmode=c-archive", "libgo")
   175  
   176  	// Test building libgo other than installing it.
   177  	// Header files are now present.
   178  	testInstall(t, "./testp2"+exeSuffix, "libgo.a", "libgo.h",
   179  		"go", "build", "-buildmode=c-archive", filepath.Join("src", "libgo", "libgo.go"))
   180  
   181  	testInstall(t, "./testp3"+exeSuffix, "libgo.a", "libgo.h",
   182  		"go", "build", "-buildmode=c-archive", "-o", "libgo.a", "libgo")
   183  }
   184  
   185  func TestEarlySignalHandler(t *testing.T) {
   186  	switch GOOS {
   187  	case "darwin":
   188  		switch GOARCH {
   189  		case "arm", "arm64":
   190  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   191  		}
   192  	case "windows":
   193  		t.Skip("skipping signal test on Windows")
   194  	}
   195  
   196  	defer func() {
   197  		os.Remove("libgo2.a")
   198  		os.Remove("libgo2.h")
   199  		os.Remove("testp")
   200  		os.RemoveAll("pkg")
   201  	}()
   202  
   203  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   204  	cmd.Env = gopathEnv
   205  	if out, err := cmd.CombinedOutput(); err != nil {
   206  		t.Logf("%s", out)
   207  		t.Fatal(err)
   208  	}
   209  
   210  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main2.c", "libgo2.a")
   211  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   212  		t.Logf("%s", out)
   213  		t.Fatal(err)
   214  	}
   215  
   216  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   217  		t.Logf("%s", out)
   218  		t.Fatal(err)
   219  	}
   220  }
   221  
   222  func TestSignalForwarding(t *testing.T) {
   223  	checkSignalForwardingTest(t)
   224  
   225  	defer func() {
   226  		os.Remove("libgo2.a")
   227  		os.Remove("libgo2.h")
   228  		os.Remove("testp")
   229  		os.RemoveAll("pkg")
   230  	}()
   231  
   232  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   233  	cmd.Env = gopathEnv
   234  	if out, err := cmd.CombinedOutput(); err != nil {
   235  		t.Logf("%s", out)
   236  		t.Fatal(err)
   237  	}
   238  
   239  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
   240  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   241  		t.Logf("%s", out)
   242  		t.Fatal(err)
   243  	}
   244  
   245  	cmd = exec.Command(bin[0], append(bin[1:], "1")...)
   246  
   247  	out, err := cmd.CombinedOutput()
   248  	t.Logf("%s", out)
   249  	expectSignal(t, err, syscall.SIGSEGV)
   250  
   251  	// Test SIGPIPE forwarding
   252  	cmd = exec.Command(bin[0], append(bin[1:], "3")...)
   253  
   254  	out, err = cmd.CombinedOutput()
   255  	t.Logf("%s", out)
   256  	expectSignal(t, err, syscall.SIGPIPE)
   257  }
   258  
   259  func TestSignalForwardingExternal(t *testing.T) {
   260  	checkSignalForwardingTest(t)
   261  
   262  	defer func() {
   263  		os.Remove("libgo2.a")
   264  		os.Remove("libgo2.h")
   265  		os.Remove("testp")
   266  		os.RemoveAll("pkg")
   267  	}()
   268  
   269  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   270  	cmd.Env = gopathEnv
   271  	if out, err := cmd.CombinedOutput(); err != nil {
   272  		t.Logf("%s", out)
   273  		t.Fatal(err)
   274  	}
   275  
   276  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
   277  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   278  		t.Logf("%s", out)
   279  		t.Fatal(err)
   280  	}
   281  
   282  	// We want to send the process a signal and see if it dies.
   283  	// Normally the signal goes to the C thread, the Go signal
   284  	// handler picks it up, sees that it is running in a C thread,
   285  	// and the program dies. Unfortunately, occasionally the
   286  	// signal is delivered to a Go thread, which winds up
   287  	// discarding it because it was sent by another program and
   288  	// there is no Go handler for it. To avoid this, run the
   289  	// program several times in the hopes that it will eventually
   290  	// fail.
   291  	const tries = 20
   292  	for i := 0; i < tries; i++ {
   293  		cmd = exec.Command(bin[0], append(bin[1:], "2")...)
   294  
   295  		stderr, err := cmd.StderrPipe()
   296  		if err != nil {
   297  			t.Fatal(err)
   298  		}
   299  		defer stderr.Close()
   300  
   301  		r := bufio.NewReader(stderr)
   302  
   303  		err = cmd.Start()
   304  
   305  		if err != nil {
   306  			t.Fatal(err)
   307  		}
   308  
   309  		// Wait for trigger to ensure that the process is started.
   310  		ok, err := r.ReadString('\n')
   311  
   312  		// Verify trigger.
   313  		if err != nil || ok != "OK\n" {
   314  			t.Fatalf("Did not receive OK signal")
   315  		}
   316  
   317  		// Give the program a chance to enter the sleep function.
   318  		time.Sleep(time.Millisecond)
   319  
   320  		cmd.Process.Signal(syscall.SIGSEGV)
   321  
   322  		err = cmd.Wait()
   323  
   324  		if err == nil {
   325  			continue
   326  		}
   327  
   328  		if expectSignal(t, err, syscall.SIGSEGV) {
   329  			return
   330  		}
   331  	}
   332  
   333  	t.Errorf("program succeeded unexpectedly %d times", tries)
   334  }
   335  
   336  // checkSignalForwardingTest calls t.Skip if the SignalForwarding test
   337  // doesn't work on this platform.
   338  func checkSignalForwardingTest(t *testing.T) {
   339  	switch GOOS {
   340  	case "darwin":
   341  		switch GOARCH {
   342  		case "arm", "arm64":
   343  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   344  		}
   345  	case "windows":
   346  		t.Skip("skipping signal test on Windows")
   347  	}
   348  }
   349  
   350  // expectSignal checks that err, the exit status of a test program,
   351  // shows a failure due to a specific signal. Returns whether we found
   352  // the expected signal.
   353  func expectSignal(t *testing.T, err error, sig syscall.Signal) bool {
   354  	if err == nil {
   355  		t.Error("test program succeeded unexpectedly")
   356  	} else if ee, ok := err.(*exec.ExitError); !ok {
   357  		t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
   358  	} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
   359  		t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
   360  	} else if !ws.Signaled() || ws.Signal() != sig {
   361  		t.Errorf("got %v; expected signal %v", ee, sig)
   362  	} else {
   363  		return true
   364  	}
   365  	return false
   366  }
   367  
   368  func TestOsSignal(t *testing.T) {
   369  	switch GOOS {
   370  	case "windows":
   371  		t.Skip("skipping signal test on Windows")
   372  	}
   373  
   374  	defer func() {
   375  		os.Remove("libgo3.a")
   376  		os.Remove("libgo3.h")
   377  		os.Remove("testp")
   378  		os.RemoveAll("pkg")
   379  	}()
   380  
   381  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "libgo3")
   382  	cmd.Env = gopathEnv
   383  	if out, err := cmd.CombinedOutput(); err != nil {
   384  		t.Logf("%s", out)
   385  		t.Fatal(err)
   386  	}
   387  
   388  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main3.c", "libgo3.a")
   389  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   390  		t.Logf("%s", out)
   391  		t.Fatal(err)
   392  	}
   393  
   394  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   395  		t.Logf("%s", out)
   396  		t.Fatal(err)
   397  	}
   398  }
   399  
   400  func TestSigaltstack(t *testing.T) {
   401  	switch GOOS {
   402  	case "windows":
   403  		t.Skip("skipping signal test on Windows")
   404  	}
   405  
   406  	defer func() {
   407  		os.Remove("libgo4.a")
   408  		os.Remove("libgo4.h")
   409  		os.Remove("testp")
   410  		os.RemoveAll("pkg")
   411  	}()
   412  
   413  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "libgo4")
   414  	cmd.Env = gopathEnv
   415  	if out, err := cmd.CombinedOutput(); err != nil {
   416  		t.Logf("%s", out)
   417  		t.Fatal(err)
   418  	}
   419  
   420  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main4.c", "libgo4.a")
   421  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   422  		t.Logf("%s", out)
   423  		t.Fatal(err)
   424  	}
   425  
   426  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   427  		t.Logf("%s", out)
   428  		t.Fatal(err)
   429  	}
   430  }
   431  
   432  const testar = `#!/usr/bin/env bash
   433  while expr $1 : '[-]' >/dev/null; do
   434    shift
   435  done
   436  echo "testar" > $1
   437  echo "testar" > PWD/testar.ran
   438  `
   439  
   440  func TestExtar(t *testing.T) {
   441  	switch GOOS {
   442  	case "windows":
   443  		t.Skip("skipping signal test on Windows")
   444  	}
   445  
   446  	defer func() {
   447  		os.Remove("libgo4.a")
   448  		os.Remove("libgo4.h")
   449  		os.Remove("testar")
   450  		os.Remove("testar.ran")
   451  		os.RemoveAll("pkg")
   452  	}()
   453  
   454  	os.Remove("testar")
   455  	dir, err := os.Getwd()
   456  	if err != nil {
   457  		t.Fatal(err)
   458  	}
   459  	s := strings.Replace(testar, "PWD", dir, 1)
   460  	if err := ioutil.WriteFile("testar", []byte(s), 0777); err != nil {
   461  		t.Fatal(err)
   462  	}
   463  
   464  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-ldflags=-extar="+filepath.Join(dir, "testar"), "-o", "libgo4.a", "libgo4")
   465  	cmd.Env = gopathEnv
   466  	if out, err := cmd.CombinedOutput(); err != nil {
   467  		t.Logf("%s", out)
   468  		t.Fatal(err)
   469  	}
   470  
   471  	if _, err := os.Stat("testar.ran"); err != nil {
   472  		if os.IsNotExist(err) {
   473  			t.Error("testar does not exist after go build")
   474  		} else {
   475  			t.Errorf("error checking testar: %v", err)
   476  		}
   477  	}
   478  }
   479  
   480  func TestPIE(t *testing.T) {
   481  	switch GOOS {
   482  	case "windows", "darwin", "plan9":
   483  		t.Skipf("skipping PIE test on %s", GOOS)
   484  	}
   485  
   486  	defer func() {
   487  		os.Remove("testp" + exeSuffix)
   488  		os.RemoveAll("pkg")
   489  	}()
   490  
   491  	cmd := exec.Command("go", "install", "-buildmode=c-archive", "libgo")
   492  	cmd.Env = gopathEnv
   493  	if out, err := cmd.CombinedOutput(); err != nil {
   494  		t.Logf("%s", out)
   495  		t.Fatal(err)
   496  	}
   497  
   498  	ccArgs := append(cc, "-fPIE", "-pie", "-o", "testp"+exeSuffix, "main.c", "main_unix.c", filepath.Join("pkg", libgodir, "libgo.a"))
   499  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   500  		t.Logf("%s", out)
   501  		t.Fatal(err)
   502  	}
   503  
   504  	binArgs := append(bin, "arg1", "arg2")
   505  	if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil {
   506  		t.Logf("%s", out)
   507  		t.Fatal(err)
   508  	}
   509  
   510  	f, err := elf.Open("testp" + exeSuffix)
   511  	if err != nil {
   512  		t.Fatal("elf.Open failed: ", err)
   513  	}
   514  	defer f.Close()
   515  	if hasDynTag(t, f, elf.DT_TEXTREL) {
   516  		t.Errorf("%s has DT_TEXTREL flag", "testp"+exeSuffix)
   517  	}
   518  }
   519  
   520  func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool {
   521  	ds := f.SectionByType(elf.SHT_DYNAMIC)
   522  	if ds == nil {
   523  		t.Error("no SHT_DYNAMIC section")
   524  		return false
   525  	}
   526  	d, err := ds.Data()
   527  	if err != nil {
   528  		t.Errorf("can't read SHT_DYNAMIC contents: %v", err)
   529  		return false
   530  	}
   531  	for len(d) > 0 {
   532  		var t elf.DynTag
   533  		switch f.Class {
   534  		case elf.ELFCLASS32:
   535  			t = elf.DynTag(f.ByteOrder.Uint32(d[:4]))
   536  			d = d[8:]
   537  		case elf.ELFCLASS64:
   538  			t = elf.DynTag(f.ByteOrder.Uint64(d[:8]))
   539  			d = d[16:]
   540  		}
   541  		if t == tag {
   542  			return true
   543  		}
   544  	}
   545  	return false
   546  }
   547  
   548  func TestSIGPROF(t *testing.T) {
   549  	switch GOOS {
   550  	case "windows", "plan9":
   551  		t.Skipf("skipping SIGPROF test on %s", GOOS)
   552  	}
   553  
   554  	t.Parallel()
   555  
   556  	defer func() {
   557  		os.Remove("testp6" + exeSuffix)
   558  		os.Remove("libgo6.a")
   559  		os.Remove("libgo6.h")
   560  	}()
   561  
   562  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo6.a", "libgo6")
   563  	cmd.Env = gopathEnv
   564  	if out, err := cmd.CombinedOutput(); err != nil {
   565  		t.Logf("%s", out)
   566  		t.Fatal(err)
   567  	}
   568  
   569  	ccArgs := append(cc, "-o", "testp6"+exeSuffix, "main6.c", "libgo6.a")
   570  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   571  		t.Logf("%s", out)
   572  		t.Fatal(err)
   573  	}
   574  
   575  	argv := cmdToRun("./testp6")
   576  	cmd = exec.Command(argv[0], argv[1:]...)
   577  	if out, err := cmd.CombinedOutput(); err != nil {
   578  		t.Logf("%s", out)
   579  		t.Fatal(err)
   580  	}
   581  }
   582  
   583  // TestCompileWithoutShared tests that if we compile code without the
   584  // -shared option, we can put it into an archive. When we use the go
   585  // tool with -buildmode=c-archive, it passes -shared to the compiler,
   586  // so we override that. The go tool doesn't work this way, but Bazel
   587  // will likely do it in the future. And it ought to work. This test
   588  // was added because at one time it did not work on PPC GNU/Linux.
   589  func TestCompileWithoutShared(t *testing.T) {
   590  	// For simplicity, reuse the signal forwarding test.
   591  	checkSignalForwardingTest(t)
   592  
   593  	defer func() {
   594  		os.Remove("libgo2.a")
   595  		os.Remove("libgo2.h")
   596  	}()
   597  
   598  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-gcflags=-shared=false", "-o", "libgo2.a", "libgo2")
   599  	cmd.Env = gopathEnv
   600  	t.Log(cmd.Args)
   601  	out, err := cmd.CombinedOutput()
   602  	t.Logf("%s", out)
   603  	if err != nil {
   604  		t.Fatal(err)
   605  	}
   606  
   607  	exe := "./testnoshared" + exeSuffix
   608  	ccArgs := append(cc, "-o", exe, "main5.c", "libgo2.a")
   609  	t.Log(ccArgs)
   610  	out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
   611  	t.Logf("%s", out)
   612  	if err != nil {
   613  		t.Fatal(err)
   614  	}
   615  	defer os.Remove(exe)
   616  
   617  	binArgs := append(cmdToRun(exe), "3")
   618  	t.Log(binArgs)
   619  	out, err = exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput()
   620  	t.Logf("%s", out)
   621  	expectSignal(t, err, syscall.SIGPIPE)
   622  }