github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/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  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"strings"
    15  	"syscall"
    16  	"testing"
    17  	"time"
    18  	"unicode"
    19  )
    20  
    21  // Program to run.
    22  var bin []string
    23  
    24  // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)).
    25  var cc []string
    26  
    27  // An environment with GOPATH=$(pwd).
    28  var gopathEnv []string
    29  
    30  // ".exe" on Windows.
    31  var exeSuffix string
    32  
    33  var GOOS, GOARCH string
    34  
    35  func init() {
    36  	bin = []string{"./testp"}
    37  	GOOS = goEnv("GOOS")
    38  	GOARCH = goEnv("GOARCH")
    39  	execScript := "go_" + GOOS + "_" + GOARCH + "_exec"
    40  	if executor, err := exec.LookPath(execScript); err == nil {
    41  		bin = []string{executor, "./testp"}
    42  	}
    43  
    44  	ccOut := goEnv("CC")
    45  	cc = []string{string(ccOut)}
    46  
    47  	out := goEnv("GOGCCFLAGS")
    48  	quote := '\000'
    49  	start := 0
    50  	lastSpace := true
    51  	backslash := false
    52  	s := string(out)
    53  	for i, c := range s {
    54  		if quote == '\000' && unicode.IsSpace(c) {
    55  			if !lastSpace {
    56  				cc = append(cc, s[start:i])
    57  				lastSpace = true
    58  			}
    59  		} else {
    60  			if lastSpace {
    61  				start = i
    62  				lastSpace = false
    63  			}
    64  			if quote == '\000' && !backslash && (c == '"' || c == '\'') {
    65  				quote = c
    66  				backslash = false
    67  			} else if !backslash && quote == c {
    68  				quote = '\000'
    69  			} else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
    70  				backslash = true
    71  			} else {
    72  				backslash = false
    73  			}
    74  		}
    75  	}
    76  	if !lastSpace {
    77  		cc = append(cc, s[start:])
    78  	}
    79  
    80  	if GOOS == "darwin" {
    81  		cc = append(cc, "-Wl,-no_pie")
    82  
    83  		// For Darwin/ARM.
    84  		// TODO(crawshaw): can we do better?
    85  		cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...)
    86  	}
    87  	cc = append(cc, "-I", filepath.Join("pkg", GOOS+"_"+GOARCH))
    88  
    89  	// Build an environment with GOPATH=$(pwd)
    90  	env := os.Environ()
    91  	var n []string
    92  	for _, e := range env {
    93  		if !strings.HasPrefix(e, "GOPATH=") {
    94  			n = append(n, e)
    95  		}
    96  	}
    97  	dir, err := os.Getwd()
    98  	if err != nil {
    99  		fmt.Fprintln(os.Stderr, err)
   100  		os.Exit(2)
   101  	}
   102  	n = append(n, "GOPATH="+dir)
   103  	gopathEnv = n
   104  
   105  	if GOOS == "windows" {
   106  		exeSuffix = ".exe"
   107  	}
   108  }
   109  
   110  func goEnv(key string) string {
   111  	out, err := exec.Command("go", "env", key).Output()
   112  	if err != nil {
   113  		fmt.Fprintf(os.Stderr, "go env %s failed:\n%s", key, err)
   114  		fmt.Fprintf(os.Stderr, "%s", err.(*exec.ExitError).Stderr)
   115  		os.Exit(2)
   116  	}
   117  	return strings.TrimSpace(string(out))
   118  }
   119  
   120  func compilemain(t *testing.T, libgo string) {
   121  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main.c")
   122  	if GOOS == "windows" {
   123  		ccArgs = append(ccArgs, "main_windows.c", libgo, "-lntdll", "-lws2_32")
   124  	} else {
   125  		ccArgs = append(ccArgs, "main_unix.c", libgo)
   126  	}
   127  	t.Log(ccArgs)
   128  
   129  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   130  		t.Logf("%s", out)
   131  		t.Fatal(err)
   132  	}
   133  }
   134  
   135  func TestInstall(t *testing.T) {
   136  	defer func() {
   137  		os.Remove("libgo.a")
   138  		os.Remove("libgo.h")
   139  		os.Remove("testp")
   140  		os.RemoveAll("pkg")
   141  	}()
   142  
   143  	cmd := exec.Command("go", "install", "-buildmode=c-archive", "libgo")
   144  	cmd.Env = gopathEnv
   145  	if out, err := cmd.CombinedOutput(); err != nil {
   146  		t.Logf("%s", out)
   147  		t.Fatal(err)
   148  	}
   149  
   150  	compilemain(t, filepath.Join("pkg", GOOS+"_"+GOARCH, "libgo.a"))
   151  
   152  	binArgs := append(bin, "arg1", "arg2")
   153  	if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil {
   154  		t.Logf("%s", out)
   155  		t.Fatal(err)
   156  	}
   157  
   158  	os.Remove("libgo.a")
   159  	os.Remove("libgo.h")
   160  	os.Remove("testp")
   161  
   162  	// Test building libgo other than installing it.
   163  	// Header files are now present.
   164  	cmd = exec.Command("go", "build", "-buildmode=c-archive", filepath.Join("src", "libgo", "libgo.go"))
   165  	cmd.Env = gopathEnv
   166  	if out, err := cmd.CombinedOutput(); err != nil {
   167  		t.Logf("%s", out)
   168  		t.Fatal(err)
   169  	}
   170  
   171  	compilemain(t, "libgo.a")
   172  
   173  	if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil {
   174  		t.Logf("%s", out)
   175  		t.Fatal(err)
   176  	}
   177  
   178  	os.Remove("libgo.a")
   179  	os.Remove("libgo.h")
   180  	os.Remove("testp")
   181  
   182  	cmd = exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo.a", "libgo")
   183  	cmd.Env = gopathEnv
   184  	if out, err := cmd.CombinedOutput(); err != nil {
   185  		t.Logf("%s", out)
   186  		t.Fatal(err)
   187  	}
   188  
   189  	compilemain(t, "libgo.a")
   190  
   191  	if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil {
   192  		t.Logf("%s", out)
   193  		t.Fatal(err)
   194  	}
   195  }
   196  
   197  func TestEarlySignalHandler(t *testing.T) {
   198  	switch GOOS {
   199  	case "darwin":
   200  		switch GOARCH {
   201  		case "arm", "arm64":
   202  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   203  		}
   204  	case "windows":
   205  		t.Skip("skipping signal test on Windows")
   206  	}
   207  
   208  	defer func() {
   209  		os.Remove("libgo2.a")
   210  		os.Remove("libgo2.h")
   211  		os.Remove("testp")
   212  		os.RemoveAll("pkg")
   213  	}()
   214  
   215  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   216  	cmd.Env = gopathEnv
   217  	if out, err := cmd.CombinedOutput(); err != nil {
   218  		t.Logf("%s", out)
   219  		t.Fatal(err)
   220  	}
   221  
   222  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main2.c", "libgo2.a")
   223  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   224  		t.Logf("%s", out)
   225  		t.Fatal(err)
   226  	}
   227  
   228  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   229  		t.Logf("%s", out)
   230  		t.Fatal(err)
   231  	}
   232  }
   233  
   234  func TestSignalForwarding(t *testing.T) {
   235  	switch GOOS {
   236  	case "darwin":
   237  		switch GOARCH {
   238  		case "arm", "arm64":
   239  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   240  		}
   241  	case "windows":
   242  		t.Skip("skipping signal test on Windows")
   243  	}
   244  
   245  	defer func() {
   246  		os.Remove("libgo2.a")
   247  		os.Remove("libgo2.h")
   248  		os.Remove("testp")
   249  		os.RemoveAll("pkg")
   250  	}()
   251  
   252  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   253  	cmd.Env = gopathEnv
   254  	if out, err := cmd.CombinedOutput(); err != nil {
   255  		t.Logf("%s", out)
   256  		t.Fatal(err)
   257  	}
   258  
   259  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
   260  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   261  		t.Logf("%s", out)
   262  		t.Fatal(err)
   263  	}
   264  
   265  	cmd = exec.Command(bin[0], append(bin[1:], "1")...)
   266  
   267  	out, err := cmd.CombinedOutput()
   268  
   269  	if err == nil {
   270  		t.Logf("%s", out)
   271  		t.Error("test program succeeded unexpectedly")
   272  	} else if ee, ok := err.(*exec.ExitError); !ok {
   273  		t.Logf("%s", out)
   274  		t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
   275  	} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
   276  		t.Logf("%s", out)
   277  		t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
   278  	} else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV {
   279  		t.Logf("%s", out)
   280  		t.Errorf("got %v; expected SIGSEGV", ee)
   281  	}
   282  }
   283  
   284  func TestSignalForwardingExternal(t *testing.T) {
   285  	switch GOOS {
   286  	case "darwin":
   287  		switch GOARCH {
   288  		case "arm", "arm64":
   289  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   290  		}
   291  	case "windows":
   292  		t.Skip("skipping signal test on Windows")
   293  	}
   294  
   295  	defer func() {
   296  		os.Remove("libgo2.a")
   297  		os.Remove("libgo2.h")
   298  		os.Remove("testp")
   299  		os.RemoveAll("pkg")
   300  	}()
   301  
   302  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   303  	cmd.Env = gopathEnv
   304  	if out, err := cmd.CombinedOutput(); err != nil {
   305  		t.Logf("%s", out)
   306  		t.Fatal(err)
   307  	}
   308  
   309  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
   310  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   311  		t.Logf("%s", out)
   312  		t.Fatal(err)
   313  	}
   314  
   315  	// We want to send the process a signal and see if it dies.
   316  	// Normally the signal goes to the C thread, the Go signal
   317  	// handler picks it up, sees that it is running in a C thread,
   318  	// and the program dies. Unfortunately, occasionally the
   319  	// signal is delivered to a Go thread, which winds up
   320  	// discarding it because it was sent by another program and
   321  	// there is no Go handler for it. To avoid this, run the
   322  	// program several times in the hopes that it will eventually
   323  	// fail.
   324  	const tries = 20
   325  	for i := 0; i < tries; i++ {
   326  		cmd = exec.Command(bin[0], append(bin[1:], "2")...)
   327  
   328  		stderr, err := cmd.StderrPipe()
   329  		if err != nil {
   330  			t.Fatal(err)
   331  		}
   332  		defer stderr.Close()
   333  
   334  		r := bufio.NewReader(stderr)
   335  
   336  		err = cmd.Start()
   337  
   338  		if err != nil {
   339  			t.Fatal(err)
   340  		}
   341  
   342  		// Wait for trigger to ensure that the process is started.
   343  		ok, err := r.ReadString('\n')
   344  
   345  		// Verify trigger.
   346  		if err != nil || ok != "OK\n" {
   347  			t.Fatalf("Did not receive OK signal")
   348  		}
   349  
   350  		// Give the program a chance to enter the sleep function.
   351  		time.Sleep(time.Millisecond)
   352  
   353  		cmd.Process.Signal(syscall.SIGSEGV)
   354  
   355  		err = cmd.Wait()
   356  
   357  		if err == nil {
   358  			continue
   359  		}
   360  
   361  		if ee, ok := err.(*exec.ExitError); !ok {
   362  			t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
   363  		} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
   364  			t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
   365  		} else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV {
   366  			t.Errorf("got %v; expected SIGSEGV", ee)
   367  		} else {
   368  			// We got the error we expected.
   369  			return
   370  		}
   371  	}
   372  
   373  	t.Errorf("program succeeded unexpectedly %d times", tries)
   374  }
   375  
   376  func TestOsSignal(t *testing.T) {
   377  	switch GOOS {
   378  	case "windows":
   379  		t.Skip("skipping signal test on Windows")
   380  	}
   381  
   382  	defer func() {
   383  		os.Remove("libgo3.a")
   384  		os.Remove("libgo3.h")
   385  		os.Remove("testp")
   386  		os.RemoveAll("pkg")
   387  	}()
   388  
   389  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "libgo3")
   390  	cmd.Env = gopathEnv
   391  	if out, err := cmd.CombinedOutput(); err != nil {
   392  		t.Logf("%s", out)
   393  		t.Fatal(err)
   394  	}
   395  
   396  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main3.c", "libgo3.a")
   397  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   398  		t.Logf("%s", out)
   399  		t.Fatal(err)
   400  	}
   401  
   402  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   403  		t.Logf("%s", out)
   404  		t.Fatal(err)
   405  	}
   406  }
   407  
   408  func TestSigaltstack(t *testing.T) {
   409  	switch GOOS {
   410  	case "windows":
   411  		t.Skip("skipping signal test on Windows")
   412  	}
   413  
   414  	defer func() {
   415  		os.Remove("libgo4.a")
   416  		os.Remove("libgo4.h")
   417  		os.Remove("testp")
   418  		os.RemoveAll("pkg")
   419  	}()
   420  
   421  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "libgo4")
   422  	cmd.Env = gopathEnv
   423  	if out, err := cmd.CombinedOutput(); err != nil {
   424  		t.Logf("%s", out)
   425  		t.Fatal(err)
   426  	}
   427  
   428  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main4.c", "libgo4.a")
   429  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   430  		t.Logf("%s", out)
   431  		t.Fatal(err)
   432  	}
   433  
   434  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   435  		t.Logf("%s", out)
   436  		t.Fatal(err)
   437  	}
   438  }
   439  
   440  const testar = `#!/usr/bin/env bash
   441  while expr $1 : '[-]' >/dev/null; do
   442    shift
   443  done
   444  echo "testar" > $1
   445  echo "testar" > PWD/testar.ran
   446  `
   447  
   448  func TestExtar(t *testing.T) {
   449  	switch GOOS {
   450  	case "windows":
   451  		t.Skip("skipping signal test on Windows")
   452  	}
   453  
   454  	defer func() {
   455  		os.Remove("libgo4.a")
   456  		os.Remove("libgo4.h")
   457  		os.Remove("testar")
   458  		os.Remove("testar.ran")
   459  		os.RemoveAll("pkg")
   460  	}()
   461  
   462  	os.Remove("testar")
   463  	dir, err := os.Getwd()
   464  	if err != nil {
   465  		t.Fatal(err)
   466  	}
   467  	s := strings.Replace(testar, "PWD", dir, 1)
   468  	if err := ioutil.WriteFile("testar", []byte(s), 0777); err != nil {
   469  		t.Fatal(err)
   470  	}
   471  
   472  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-ldflags=-extar="+filepath.Join(dir, "testar"), "-o", "libgo4.a", "libgo4")
   473  	cmd.Env = gopathEnv
   474  	if out, err := cmd.CombinedOutput(); err != nil {
   475  		t.Logf("%s", out)
   476  		t.Fatal(err)
   477  	}
   478  
   479  	if _, err := os.Stat("testar.ran"); err != nil {
   480  		if os.IsNotExist(err) {
   481  			t.Error("testar does not exist after go build")
   482  		} else {
   483  			t.Errorf("error checking testar: %v", err)
   484  		}
   485  	}
   486  }