github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/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", key, err)
   119  		fmt.Fprintf(os.Stderr, "%s", err.(*exec.ExitError).Stderr)
   120  		os.Exit(2)
   121  	}
   122  	return strings.TrimSpace(string(out))
   123  }
   124  
   125  func cmdToRun(name string) []string {
   126  	execScript := "go_" + goEnv("GOOS") + "_" + goEnv("GOARCH") + "_exec"
   127  	executor, err := exec.LookPath(execScript)
   128  	if err != nil {
   129  		return []string{name}
   130  	}
   131  	return []string{executor, name}
   132  }
   133  
   134  func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) {
   135  	cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
   136  	cmd.Env = gopathEnv
   137  	if out, err := cmd.CombinedOutput(); err != nil {
   138  		t.Logf("%s", out)
   139  		t.Fatal(err)
   140  	}
   141  	defer func() {
   142  		os.Remove(libgoa)
   143  		os.Remove(libgoh)
   144  	}()
   145  
   146  	ccArgs := append(cc, "-o", exe, "main.c")
   147  	if GOOS == "windows" {
   148  		ccArgs = append(ccArgs, "main_windows.c", libgoa, "-lntdll", "-lws2_32", "-lwinmm")
   149  	} else {
   150  		ccArgs = append(ccArgs, "main_unix.c", libgoa)
   151  	}
   152  	t.Log(ccArgs)
   153  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   154  		t.Logf("%s", out)
   155  		t.Fatal(err)
   156  	}
   157  	defer os.Remove(exe)
   158  
   159  	binArgs := append(cmdToRun(exe), "arg1", "arg2")
   160  	if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil {
   161  		t.Logf("%s", out)
   162  		t.Fatal(err)
   163  	}
   164  }
   165  
   166  func TestInstall(t *testing.T) {
   167  	defer os.RemoveAll("pkg")
   168  
   169  	testInstall(t, "./testp1"+exeSuffix,
   170  		filepath.Join("pkg", libgodir, "libgo.a"),
   171  		filepath.Join("pkg", libgodir, "libgo.h"),
   172  		"go", "install", "-buildmode=c-archive", "libgo")
   173  
   174  	// Test building libgo other than installing it.
   175  	// Header files are now present.
   176  	testInstall(t, "./testp2"+exeSuffix, "libgo.a", "libgo.h",
   177  		"go", "build", "-buildmode=c-archive", filepath.Join("src", "libgo", "libgo.go"))
   178  
   179  	testInstall(t, "./testp3"+exeSuffix, "libgo.a", "libgo.h",
   180  		"go", "build", "-buildmode=c-archive", "-o", "libgo.a", "libgo")
   181  }
   182  
   183  func TestEarlySignalHandler(t *testing.T) {
   184  	switch GOOS {
   185  	case "darwin":
   186  		switch GOARCH {
   187  		case "arm", "arm64":
   188  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   189  		}
   190  	case "windows":
   191  		t.Skip("skipping signal test on Windows")
   192  	}
   193  
   194  	defer func() {
   195  		os.Remove("libgo2.a")
   196  		os.Remove("libgo2.h")
   197  		os.Remove("testp")
   198  		os.RemoveAll("pkg")
   199  	}()
   200  
   201  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   202  	cmd.Env = gopathEnv
   203  	if out, err := cmd.CombinedOutput(); err != nil {
   204  		t.Logf("%s", out)
   205  		t.Fatal(err)
   206  	}
   207  
   208  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main2.c", "libgo2.a")
   209  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   210  		t.Logf("%s", out)
   211  		t.Fatal(err)
   212  	}
   213  
   214  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   215  		t.Logf("%s", out)
   216  		t.Fatal(err)
   217  	}
   218  }
   219  
   220  func TestSignalForwarding(t *testing.T) {
   221  	switch GOOS {
   222  	case "darwin":
   223  		switch GOARCH {
   224  		case "arm", "arm64":
   225  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   226  		}
   227  	case "windows":
   228  		t.Skip("skipping signal test on Windows")
   229  	}
   230  
   231  	defer func() {
   232  		os.Remove("libgo2.a")
   233  		os.Remove("libgo2.h")
   234  		os.Remove("testp")
   235  		os.RemoveAll("pkg")
   236  	}()
   237  
   238  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   239  	cmd.Env = gopathEnv
   240  	if out, err := cmd.CombinedOutput(); err != nil {
   241  		t.Logf("%s", out)
   242  		t.Fatal(err)
   243  	}
   244  
   245  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
   246  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   247  		t.Logf("%s", out)
   248  		t.Fatal(err)
   249  	}
   250  
   251  	cmd = exec.Command(bin[0], append(bin[1:], "1")...)
   252  
   253  	out, err := cmd.CombinedOutput()
   254  
   255  	if err == nil {
   256  		t.Logf("%s", out)
   257  		t.Error("test program succeeded unexpectedly")
   258  	} else if ee, ok := err.(*exec.ExitError); !ok {
   259  		t.Logf("%s", out)
   260  		t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
   261  	} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
   262  		t.Logf("%s", out)
   263  		t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
   264  	} else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV {
   265  		t.Logf("%s", out)
   266  		t.Errorf("got %v; expected SIGSEGV", ee)
   267  	}
   268  
   269  	// Test SIGPIPE forwarding
   270  	cmd = exec.Command(bin[0], append(bin[1:], "3")...)
   271  
   272  	out, err = cmd.CombinedOutput()
   273  
   274  	if err == nil {
   275  		t.Logf("%s", out)
   276  		t.Error("test program succeeded unexpectedly")
   277  	} else if ee, ok := err.(*exec.ExitError); !ok {
   278  		t.Logf("%s", out)
   279  		t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
   280  	} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
   281  		t.Logf("%s", out)
   282  		t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
   283  	} else if !ws.Signaled() || ws.Signal() != syscall.SIGPIPE {
   284  		t.Logf("%s", out)
   285  		t.Errorf("got %v; expected SIGPIPE", ee)
   286  	}
   287  }
   288  
   289  func TestSignalForwardingExternal(t *testing.T) {
   290  	switch GOOS {
   291  	case "darwin":
   292  		switch GOARCH {
   293  		case "arm", "arm64":
   294  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   295  		}
   296  	case "windows":
   297  		t.Skip("skipping signal test on Windows")
   298  	}
   299  
   300  	defer func() {
   301  		os.Remove("libgo2.a")
   302  		os.Remove("libgo2.h")
   303  		os.Remove("testp")
   304  		os.RemoveAll("pkg")
   305  	}()
   306  
   307  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   308  	cmd.Env = gopathEnv
   309  	if out, err := cmd.CombinedOutput(); err != nil {
   310  		t.Logf("%s", out)
   311  		t.Fatal(err)
   312  	}
   313  
   314  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
   315  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   316  		t.Logf("%s", out)
   317  		t.Fatal(err)
   318  	}
   319  
   320  	// We want to send the process a signal and see if it dies.
   321  	// Normally the signal goes to the C thread, the Go signal
   322  	// handler picks it up, sees that it is running in a C thread,
   323  	// and the program dies. Unfortunately, occasionally the
   324  	// signal is delivered to a Go thread, which winds up
   325  	// discarding it because it was sent by another program and
   326  	// there is no Go handler for it. To avoid this, run the
   327  	// program several times in the hopes that it will eventually
   328  	// fail.
   329  	const tries = 20
   330  	for i := 0; i < tries; i++ {
   331  		cmd = exec.Command(bin[0], append(bin[1:], "2")...)
   332  
   333  		stderr, err := cmd.StderrPipe()
   334  		if err != nil {
   335  			t.Fatal(err)
   336  		}
   337  		defer stderr.Close()
   338  
   339  		r := bufio.NewReader(stderr)
   340  
   341  		err = cmd.Start()
   342  
   343  		if err != nil {
   344  			t.Fatal(err)
   345  		}
   346  
   347  		// Wait for trigger to ensure that the process is started.
   348  		ok, err := r.ReadString('\n')
   349  
   350  		// Verify trigger.
   351  		if err != nil || ok != "OK\n" {
   352  			t.Fatalf("Did not receive OK signal")
   353  		}
   354  
   355  		// Give the program a chance to enter the sleep function.
   356  		time.Sleep(time.Millisecond)
   357  
   358  		cmd.Process.Signal(syscall.SIGSEGV)
   359  
   360  		err = cmd.Wait()
   361  
   362  		if err == nil {
   363  			continue
   364  		}
   365  
   366  		if ee, ok := err.(*exec.ExitError); !ok {
   367  			t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
   368  		} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
   369  			t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
   370  		} else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV {
   371  			t.Errorf("got %v; expected SIGSEGV", ee)
   372  		} else {
   373  			// We got the error we expected.
   374  			return
   375  		}
   376  	}
   377  
   378  	t.Errorf("program succeeded unexpectedly %d times", tries)
   379  }
   380  
   381  func TestOsSignal(t *testing.T) {
   382  	switch GOOS {
   383  	case "windows":
   384  		t.Skip("skipping signal test on Windows")
   385  	}
   386  
   387  	defer func() {
   388  		os.Remove("libgo3.a")
   389  		os.Remove("libgo3.h")
   390  		os.Remove("testp")
   391  		os.RemoveAll("pkg")
   392  	}()
   393  
   394  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "libgo3")
   395  	cmd.Env = gopathEnv
   396  	if out, err := cmd.CombinedOutput(); err != nil {
   397  		t.Logf("%s", out)
   398  		t.Fatal(err)
   399  	}
   400  
   401  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main3.c", "libgo3.a")
   402  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   403  		t.Logf("%s", out)
   404  		t.Fatal(err)
   405  	}
   406  
   407  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   408  		t.Logf("%s", out)
   409  		t.Fatal(err)
   410  	}
   411  }
   412  
   413  func TestSigaltstack(t *testing.T) {
   414  	switch GOOS {
   415  	case "windows":
   416  		t.Skip("skipping signal test on Windows")
   417  	}
   418  
   419  	defer func() {
   420  		os.Remove("libgo4.a")
   421  		os.Remove("libgo4.h")
   422  		os.Remove("testp")
   423  		os.RemoveAll("pkg")
   424  	}()
   425  
   426  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "libgo4")
   427  	cmd.Env = gopathEnv
   428  	if out, err := cmd.CombinedOutput(); err != nil {
   429  		t.Logf("%s", out)
   430  		t.Fatal(err)
   431  	}
   432  
   433  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main4.c", "libgo4.a")
   434  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   435  		t.Logf("%s", out)
   436  		t.Fatal(err)
   437  	}
   438  
   439  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   440  		t.Logf("%s", out)
   441  		t.Fatal(err)
   442  	}
   443  }
   444  
   445  const testar = `#!/usr/bin/env bash
   446  while expr $1 : '[-]' >/dev/null; do
   447    shift
   448  done
   449  echo "testar" > $1
   450  echo "testar" > PWD/testar.ran
   451  `
   452  
   453  func TestExtar(t *testing.T) {
   454  	switch GOOS {
   455  	case "windows":
   456  		t.Skip("skipping signal test on Windows")
   457  	}
   458  
   459  	defer func() {
   460  		os.Remove("libgo4.a")
   461  		os.Remove("libgo4.h")
   462  		os.Remove("testar")
   463  		os.Remove("testar.ran")
   464  		os.RemoveAll("pkg")
   465  	}()
   466  
   467  	os.Remove("testar")
   468  	dir, err := os.Getwd()
   469  	if err != nil {
   470  		t.Fatal(err)
   471  	}
   472  	s := strings.Replace(testar, "PWD", dir, 1)
   473  	if err := ioutil.WriteFile("testar", []byte(s), 0777); err != nil {
   474  		t.Fatal(err)
   475  	}
   476  
   477  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-ldflags=-extar="+filepath.Join(dir, "testar"), "-o", "libgo4.a", "libgo4")
   478  	cmd.Env = gopathEnv
   479  	if out, err := cmd.CombinedOutput(); err != nil {
   480  		t.Logf("%s", out)
   481  		t.Fatal(err)
   482  	}
   483  
   484  	if _, err := os.Stat("testar.ran"); err != nil {
   485  		if os.IsNotExist(err) {
   486  			t.Error("testar does not exist after go build")
   487  		} else {
   488  			t.Errorf("error checking testar: %v", err)
   489  		}
   490  	}
   491  }
   492  
   493  func TestPIE(t *testing.T) {
   494  	switch GOOS {
   495  	case "windows", "darwin", "plan9":
   496  		t.Skipf("skipping PIE test on %s", GOOS)
   497  	}
   498  
   499  	defer func() {
   500  		os.Remove("testp" + exeSuffix)
   501  		os.RemoveAll("pkg")
   502  	}()
   503  
   504  	cmd := exec.Command("go", "install", "-buildmode=c-archive", "libgo")
   505  	cmd.Env = gopathEnv
   506  	if out, err := cmd.CombinedOutput(); err != nil {
   507  		t.Logf("%s", out)
   508  		t.Fatal(err)
   509  	}
   510  
   511  	ccArgs := append(cc, "-fPIE", "-pie", "-o", "testp"+exeSuffix, "main.c", "main_unix.c", filepath.Join("pkg", libgodir, "libgo.a"))
   512  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   513  		t.Logf("%s", out)
   514  		t.Fatal(err)
   515  	}
   516  
   517  	binArgs := append(bin, "arg1", "arg2")
   518  	if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil {
   519  		t.Logf("%s", out)
   520  		t.Fatal(err)
   521  	}
   522  
   523  	f, err := elf.Open("testp" + exeSuffix)
   524  	if err != nil {
   525  		t.Fatal("elf.Open failed: ", err)
   526  	}
   527  	defer f.Close()
   528  	if hasDynTag(t, f, elf.DT_TEXTREL) {
   529  		t.Errorf("%s has DT_TEXTREL flag", "testp"+exeSuffix)
   530  	}
   531  }
   532  
   533  func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool {
   534  	ds := f.SectionByType(elf.SHT_DYNAMIC)
   535  	if ds == nil {
   536  		t.Error("no SHT_DYNAMIC section")
   537  		return false
   538  	}
   539  	d, err := ds.Data()
   540  	if err != nil {
   541  		t.Errorf("can't read SHT_DYNAMIC contents: %v", err)
   542  		return false
   543  	}
   544  	for len(d) > 0 {
   545  		var t elf.DynTag
   546  		switch f.Class {
   547  		case elf.ELFCLASS32:
   548  			t = elf.DynTag(f.ByteOrder.Uint32(d[:4]))
   549  			d = d[8:]
   550  		case elf.ELFCLASS64:
   551  			t = elf.DynTag(f.ByteOrder.Uint64(d[:8]))
   552  			d = d[16:]
   553  		}
   554  		if t == tag {
   555  			return true
   556  		}
   557  	}
   558  	return false
   559  }
   560  
   561  func TestSIGPROF(t *testing.T) {
   562  	switch GOOS {
   563  	case "windows", "plan9":
   564  		t.Skipf("skipping SIGPROF test on %s", GOOS)
   565  	}
   566  
   567  	t.Parallel()
   568  
   569  	defer func() {
   570  		os.Remove("testp6" + exeSuffix)
   571  		os.Remove("libgo6.a")
   572  		os.Remove("libgo6.h")
   573  	}()
   574  
   575  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo6.a", "libgo6")
   576  	cmd.Env = gopathEnv
   577  	if out, err := cmd.CombinedOutput(); err != nil {
   578  		t.Logf("%s", out)
   579  		t.Fatal(err)
   580  	}
   581  
   582  	ccArgs := append(cc, "-o", "testp6"+exeSuffix, "main6.c", "libgo6.a")
   583  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   584  		t.Logf("%s", out)
   585  		t.Fatal(err)
   586  	}
   587  
   588  	argv := cmdToRun("./testp6")
   589  	cmd = exec.Command(argv[0], argv[1:]...)
   590  	if out, err := cmd.CombinedOutput(); err != nil {
   591  		t.Logf("%s", out)
   592  		t.Fatal(err)
   593  	}
   594  }