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