github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/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  
   270  func TestSignalForwardingExternal(t *testing.T) {
   271  	switch GOOS {
   272  	case "darwin":
   273  		switch GOARCH {
   274  		case "arm", "arm64":
   275  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   276  		}
   277  	case "windows":
   278  		t.Skip("skipping signal test on Windows")
   279  	}
   280  
   281  	defer func() {
   282  		os.Remove("libgo2.a")
   283  		os.Remove("libgo2.h")
   284  		os.Remove("testp")
   285  		os.RemoveAll("pkg")
   286  	}()
   287  
   288  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   289  	cmd.Env = gopathEnv
   290  	if out, err := cmd.CombinedOutput(); err != nil {
   291  		t.Logf("%s", out)
   292  		t.Fatal(err)
   293  	}
   294  
   295  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
   296  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   297  		t.Logf("%s", out)
   298  		t.Fatal(err)
   299  	}
   300  
   301  	// We want to send the process a signal and see if it dies.
   302  	// Normally the signal goes to the C thread, the Go signal
   303  	// handler picks it up, sees that it is running in a C thread,
   304  	// and the program dies. Unfortunately, occasionally the
   305  	// signal is delivered to a Go thread, which winds up
   306  	// discarding it because it was sent by another program and
   307  	// there is no Go handler for it. To avoid this, run the
   308  	// program several times in the hopes that it will eventually
   309  	// fail.
   310  	const tries = 20
   311  	for i := 0; i < tries; i++ {
   312  		cmd = exec.Command(bin[0], append(bin[1:], "2")...)
   313  
   314  		stderr, err := cmd.StderrPipe()
   315  		if err != nil {
   316  			t.Fatal(err)
   317  		}
   318  		defer stderr.Close()
   319  
   320  		r := bufio.NewReader(stderr)
   321  
   322  		err = cmd.Start()
   323  
   324  		if err != nil {
   325  			t.Fatal(err)
   326  		}
   327  
   328  		// Wait for trigger to ensure that the process is started.
   329  		ok, err := r.ReadString('\n')
   330  
   331  		// Verify trigger.
   332  		if err != nil || ok != "OK\n" {
   333  			t.Fatalf("Did not receive OK signal")
   334  		}
   335  
   336  		// Give the program a chance to enter the sleep function.
   337  		time.Sleep(time.Millisecond)
   338  
   339  		cmd.Process.Signal(syscall.SIGSEGV)
   340  
   341  		err = cmd.Wait()
   342  
   343  		if err == nil {
   344  			continue
   345  		}
   346  
   347  		if ee, ok := err.(*exec.ExitError); !ok {
   348  			t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
   349  		} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
   350  			t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
   351  		} else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV {
   352  			t.Errorf("got %v; expected SIGSEGV", ee)
   353  		} else {
   354  			// We got the error we expected.
   355  			return
   356  		}
   357  	}
   358  
   359  	t.Errorf("program succeeded unexpectedly %d times", tries)
   360  }
   361  
   362  func TestOsSignal(t *testing.T) {
   363  	switch GOOS {
   364  	case "windows":
   365  		t.Skip("skipping signal test on Windows")
   366  	}
   367  
   368  	defer func() {
   369  		os.Remove("libgo3.a")
   370  		os.Remove("libgo3.h")
   371  		os.Remove("testp")
   372  		os.RemoveAll("pkg")
   373  	}()
   374  
   375  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "libgo3")
   376  	cmd.Env = gopathEnv
   377  	if out, err := cmd.CombinedOutput(); err != nil {
   378  		t.Logf("%s", out)
   379  		t.Fatal(err)
   380  	}
   381  
   382  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main3.c", "libgo3.a")
   383  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   384  		t.Logf("%s", out)
   385  		t.Fatal(err)
   386  	}
   387  
   388  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   389  		t.Logf("%s", out)
   390  		t.Fatal(err)
   391  	}
   392  }
   393  
   394  func TestSigaltstack(t *testing.T) {
   395  	switch GOOS {
   396  	case "windows":
   397  		t.Skip("skipping signal test on Windows")
   398  	}
   399  
   400  	defer func() {
   401  		os.Remove("libgo4.a")
   402  		os.Remove("libgo4.h")
   403  		os.Remove("testp")
   404  		os.RemoveAll("pkg")
   405  	}()
   406  
   407  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "libgo4")
   408  	cmd.Env = gopathEnv
   409  	if out, err := cmd.CombinedOutput(); err != nil {
   410  		t.Logf("%s", out)
   411  		t.Fatal(err)
   412  	}
   413  
   414  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main4.c", "libgo4.a")
   415  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   416  		t.Logf("%s", out)
   417  		t.Fatal(err)
   418  	}
   419  
   420  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   421  		t.Logf("%s", out)
   422  		t.Fatal(err)
   423  	}
   424  }
   425  
   426  const testar = `#!/usr/bin/env bash
   427  while expr $1 : '[-]' >/dev/null; do
   428    shift
   429  done
   430  echo "testar" > $1
   431  echo "testar" > PWD/testar.ran
   432  `
   433  
   434  func TestExtar(t *testing.T) {
   435  	switch GOOS {
   436  	case "windows":
   437  		t.Skip("skipping signal test on Windows")
   438  	}
   439  
   440  	defer func() {
   441  		os.Remove("libgo4.a")
   442  		os.Remove("libgo4.h")
   443  		os.Remove("testar")
   444  		os.Remove("testar.ran")
   445  		os.RemoveAll("pkg")
   446  	}()
   447  
   448  	os.Remove("testar")
   449  	dir, err := os.Getwd()
   450  	if err != nil {
   451  		t.Fatal(err)
   452  	}
   453  	s := strings.Replace(testar, "PWD", dir, 1)
   454  	if err := ioutil.WriteFile("testar", []byte(s), 0777); err != nil {
   455  		t.Fatal(err)
   456  	}
   457  
   458  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-ldflags=-extar="+filepath.Join(dir, "testar"), "-o", "libgo4.a", "libgo4")
   459  	cmd.Env = gopathEnv
   460  	if out, err := cmd.CombinedOutput(); err != nil {
   461  		t.Logf("%s", out)
   462  		t.Fatal(err)
   463  	}
   464  
   465  	if _, err := os.Stat("testar.ran"); err != nil {
   466  		if os.IsNotExist(err) {
   467  			t.Error("testar does not exist after go build")
   468  		} else {
   469  			t.Errorf("error checking testar: %v", err)
   470  		}
   471  	}
   472  }
   473  
   474  func TestPIE(t *testing.T) {
   475  	switch GOOS {
   476  	case "windows", "darwin", "plan9":
   477  		t.Skipf("skipping PIE test on %s", GOOS)
   478  	}
   479  
   480  	defer func() {
   481  		os.Remove("testp" + exeSuffix)
   482  		os.RemoveAll("pkg")
   483  	}()
   484  
   485  	cmd := exec.Command("go", "install", "-buildmode=c-archive", "libgo")
   486  	cmd.Env = gopathEnv
   487  	if out, err := cmd.CombinedOutput(); err != nil {
   488  		t.Logf("%s", out)
   489  		t.Fatal(err)
   490  	}
   491  
   492  	ccArgs := append(cc, "-fPIE", "-pie", "-o", "testp"+exeSuffix, "main.c", "main_unix.c", filepath.Join("pkg", libgodir, "libgo.a"))
   493  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   494  		t.Logf("%s", out)
   495  		t.Fatal(err)
   496  	}
   497  
   498  	binArgs := append(bin, "arg1", "arg2")
   499  	if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil {
   500  		t.Logf("%s", out)
   501  		t.Fatal(err)
   502  	}
   503  
   504  	f, err := elf.Open("testp" + exeSuffix)
   505  	if err != nil {
   506  		t.Fatal("elf.Open failed: ", err)
   507  	}
   508  	defer f.Close()
   509  	if hasDynTag(t, f, elf.DT_TEXTREL) {
   510  		t.Errorf("%s has DT_TEXTREL flag", "testp"+exeSuffix)
   511  	}
   512  }
   513  
   514  func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool {
   515  	ds := f.SectionByType(elf.SHT_DYNAMIC)
   516  	if ds == nil {
   517  		t.Error("no SHT_DYNAMIC section")
   518  		return false
   519  	}
   520  	d, err := ds.Data()
   521  	if err != nil {
   522  		t.Errorf("can't read SHT_DYNAMIC contents: %v", err)
   523  		return false
   524  	}
   525  	for len(d) > 0 {
   526  		var t elf.DynTag
   527  		switch f.Class {
   528  		case elf.ELFCLASS32:
   529  			t = elf.DynTag(f.ByteOrder.Uint32(d[:4]))
   530  			d = d[8:]
   531  		case elf.ELFCLASS64:
   532  			t = elf.DynTag(f.ByteOrder.Uint64(d[:8]))
   533  			d = d[16:]
   534  		}
   535  		if t == tag {
   536  			return true
   537  		}
   538  	}
   539  	return false
   540  }