github.com/c9s/go@v0.0.0-20180120015821-984e81f64e0c/misc/cgo/testcshared/cshared_test.go (about)

     1  // Copyright 2017 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 cshared_test
     6  
     7  import (
     8  	"debug/elf"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"log"
    12  	"os"
    13  	"os/exec"
    14  	"path"
    15  	"path/filepath"
    16  	"strings"
    17  	"sync"
    18  	"testing"
    19  	"unicode"
    20  )
    21  
    22  // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)).
    23  var cc []string
    24  
    25  // An environment with GOPATH=$(pwd).
    26  var gopathEnv []string
    27  
    28  // ".exe" on Windows.
    29  var exeSuffix string
    30  
    31  var GOOS, GOARCH, GOROOT string
    32  var installdir, androiddir string
    33  var libSuffix, libgoname string
    34  
    35  func TestMain(m *testing.M) {
    36  	GOOS = goEnv("GOOS")
    37  	GOARCH = goEnv("GOARCH")
    38  	GOROOT = goEnv("GOROOT")
    39  
    40  	if _, err := os.Stat(GOROOT); os.IsNotExist(err) {
    41  		log.Fatalf("Unable able to find GOROOT at '%s'", GOROOT)
    42  	}
    43  
    44  	// Directory where cgo headers and outputs will be installed.
    45  	// The installation directory format varies depending on the platform.
    46  	installdir = path.Join("pkg", fmt.Sprintf("%s_%s_testcshared", GOOS, GOARCH))
    47  	switch GOOS {
    48  	case "darwin":
    49  		libSuffix = "dylib"
    50  	case "windows":
    51  		libSuffix = "dll"
    52  	default:
    53  		libSuffix = "so"
    54  		installdir = path.Join("pkg", fmt.Sprintf("%s_%s_testcshared_shared", GOOS, GOARCH))
    55  	}
    56  
    57  	androiddir = fmt.Sprintf("/data/local/tmp/testcshared-%d", os.Getpid())
    58  	if GOOS == "android" {
    59  		cmd := exec.Command("adb", "shell", "mkdir", "-p", androiddir)
    60  		out, err := cmd.CombinedOutput()
    61  		if err != nil {
    62  			log.Fatalf("setupAndroid failed: %v\n%s\n", err, out)
    63  		}
    64  	}
    65  
    66  	libgoname = "libgo." + libSuffix
    67  
    68  	cc = []string{goEnv("CC")}
    69  
    70  	out := goEnv("GOGCCFLAGS")
    71  	quote := '\000'
    72  	start := 0
    73  	lastSpace := true
    74  	backslash := false
    75  	s := string(out)
    76  	for i, c := range s {
    77  		if quote == '\000' && unicode.IsSpace(c) {
    78  			if !lastSpace {
    79  				cc = append(cc, s[start:i])
    80  				lastSpace = true
    81  			}
    82  		} else {
    83  			if lastSpace {
    84  				start = i
    85  				lastSpace = false
    86  			}
    87  			if quote == '\000' && !backslash && (c == '"' || c == '\'') {
    88  				quote = c
    89  				backslash = false
    90  			} else if !backslash && quote == c {
    91  				quote = '\000'
    92  			} else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
    93  				backslash = true
    94  			} else {
    95  				backslash = false
    96  			}
    97  		}
    98  	}
    99  	if !lastSpace {
   100  		cc = append(cc, s[start:])
   101  	}
   102  
   103  	switch GOOS {
   104  	case "darwin":
   105  		// For Darwin/ARM.
   106  		// TODO(crawshaw): can we do better?
   107  		cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...)
   108  	case "android":
   109  		cc = append(cc, "-pie", "-fuse-ld=gold")
   110  	}
   111  	libgodir := GOOS + "_" + GOARCH
   112  	switch GOOS {
   113  	case "darwin":
   114  		if GOARCH == "arm" || GOARCH == "arm64" {
   115  			libgodir += "_shared"
   116  		}
   117  	case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris":
   118  		libgodir += "_shared"
   119  	}
   120  	cc = append(cc, "-I", filepath.Join("pkg", libgodir))
   121  
   122  	// Build an environment with GOPATH=$(pwd)
   123  	dir, err := os.Getwd()
   124  	if err != nil {
   125  		fmt.Fprintln(os.Stderr, err)
   126  		os.Exit(2)
   127  	}
   128  	gopathEnv = append(os.Environ(), "GOPATH="+dir)
   129  
   130  	if GOOS == "windows" {
   131  		exeSuffix = ".exe"
   132  	}
   133  
   134  	st := m.Run()
   135  
   136  	os.Remove(libgoname)
   137  	os.RemoveAll("pkg")
   138  	cleanupHeaders()
   139  	cleanupAndroid()
   140  
   141  	os.Exit(st)
   142  }
   143  
   144  func goEnv(key string) string {
   145  	out, err := exec.Command("go", "env", key).Output()
   146  	if err != nil {
   147  		fmt.Fprintf(os.Stderr, "go env %s failed:\n%s", key, err)
   148  		fmt.Fprintf(os.Stderr, "%s", err.(*exec.ExitError).Stderr)
   149  		os.Exit(2)
   150  	}
   151  	return strings.TrimSpace(string(out))
   152  }
   153  
   154  func cmdToRun(name string) string {
   155  	return "./" + name + exeSuffix
   156  }
   157  
   158  func adbPush(t *testing.T, filename string) {
   159  	if GOOS != "android" {
   160  		return
   161  	}
   162  	args := []string{"adb", "push", filename, fmt.Sprintf("%s/%s", androiddir, filename)}
   163  	cmd := exec.Command(args[0], args[1:]...)
   164  	if out, err := cmd.CombinedOutput(); err != nil {
   165  		t.Fatalf("adb command failed: %v\n%s\n", err, out)
   166  	}
   167  }
   168  
   169  func adbRun(t *testing.T, env []string, adbargs ...string) string {
   170  	if GOOS != "android" {
   171  		t.Fatalf("trying to run adb command when operating system is not android.")
   172  	}
   173  	args := []string{"adb", "shell"}
   174  	// Propagate LD_LIBRARY_PATH to the adb shell invocation.
   175  	for _, e := range env {
   176  		if strings.Index(e, "LD_LIBRARY_PATH=") != -1 {
   177  			adbargs = append([]string{e}, adbargs...)
   178  			break
   179  		}
   180  	}
   181  	shellcmd := fmt.Sprintf("cd %s; %s", androiddir, strings.Join(adbargs, " "))
   182  	args = append(args, shellcmd)
   183  	cmd := exec.Command(args[0], args[1:]...)
   184  	out, err := cmd.CombinedOutput()
   185  	if err != nil {
   186  		t.Fatalf("adb command failed: %v\n%s\n", err, out)
   187  	}
   188  	return strings.Replace(string(out), "\r", "", -1)
   189  }
   190  
   191  func run(t *testing.T, env []string, args ...string) string {
   192  	t.Helper()
   193  	cmd := exec.Command(args[0], args[1:]...)
   194  	cmd.Env = env
   195  	out, err := cmd.CombinedOutput()
   196  	if err != nil {
   197  		t.Fatalf("command failed: %v\n%v\n%s\n", args, err, out)
   198  	} else {
   199  		t.Logf("run: %v", args)
   200  	}
   201  	return string(out)
   202  }
   203  
   204  func runExe(t *testing.T, env []string, args ...string) string {
   205  	t.Helper()
   206  	if GOOS == "android" {
   207  		return adbRun(t, env, args...)
   208  	}
   209  	return run(t, env, args...)
   210  }
   211  
   212  func runCC(t *testing.T, args ...string) string {
   213  	t.Helper()
   214  	// This function is run in parallel, so append to a copy of cc
   215  	// rather than cc itself.
   216  	return run(t, nil, append(append([]string(nil), cc...), args...)...)
   217  }
   218  
   219  func createHeaders() error {
   220  	args := []string{"go", "install", "-i", "-buildmode=c-shared",
   221  		"-installsuffix", "testcshared", "libgo"}
   222  	cmd := exec.Command(args[0], args[1:]...)
   223  	cmd.Env = gopathEnv
   224  	out, err := cmd.CombinedOutput()
   225  	if err != nil {
   226  		return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
   227  	}
   228  
   229  	args = []string{"go", "build", "-buildmode=c-shared",
   230  		"-installsuffix", "testcshared",
   231  		"-o", libgoname,
   232  		filepath.Join("src", "libgo", "libgo.go")}
   233  	cmd = exec.Command(args[0], args[1:]...)
   234  	cmd.Env = gopathEnv
   235  	out, err = cmd.CombinedOutput()
   236  	if err != nil {
   237  		return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
   238  	}
   239  
   240  	if GOOS == "android" {
   241  		args = []string{"adb", "push", libgoname, fmt.Sprintf("%s/%s", androiddir, libgoname)}
   242  		cmd = exec.Command(args[0], args[1:]...)
   243  		out, err = cmd.CombinedOutput()
   244  		if err != nil {
   245  			return fmt.Errorf("adb command failed: %v\n%s\n", err, out)
   246  		}
   247  	}
   248  
   249  	return nil
   250  }
   251  
   252  var (
   253  	headersOnce sync.Once
   254  	headersErr  error
   255  )
   256  
   257  func createHeadersOnce(t *testing.T) {
   258  	headersOnce.Do(func() {
   259  		headersErr = createHeaders()
   260  	})
   261  	if headersErr != nil {
   262  		t.Fatal(headersErr)
   263  	}
   264  }
   265  
   266  func cleanupHeaders() {
   267  	os.Remove("libgo.h")
   268  }
   269  
   270  func cleanupAndroid() {
   271  	if GOOS != "android" {
   272  		return
   273  	}
   274  	cmd := exec.Command("adb", "shell", "rm", "-rf", androiddir)
   275  	out, err := cmd.CombinedOutput()
   276  	if err != nil {
   277  		log.Fatalf("cleanupAndroid failed: %v\n%s\n", err, out)
   278  	}
   279  }
   280  
   281  // test0: exported symbols in shared lib are accessible.
   282  func TestExportedSymbols(t *testing.T) {
   283  	t.Parallel()
   284  
   285  	cmd := "testp0"
   286  	bin := cmdToRun(cmd)
   287  
   288  	createHeadersOnce(t)
   289  
   290  	runCC(t, "-I", installdir, "-o", cmd, "main0.c", libgoname)
   291  	adbPush(t, cmd)
   292  
   293  	defer os.Remove(bin)
   294  
   295  	out := runExe(t, append(gopathEnv, "LD_LIBRARY_PATH=."), bin)
   296  	if strings.TrimSpace(out) != "PASS" {
   297  		t.Error(out)
   298  	}
   299  }
   300  
   301  // test1: shared library can be dynamically loaded and exported symbols are accessible.
   302  func TestExportedSymbolsWithDynamicLoad(t *testing.T) {
   303  	t.Parallel()
   304  
   305  	if GOOS == "windows" {
   306  		t.Logf("Skipping on %s", GOOS)
   307  		return
   308  	}
   309  
   310  	cmd := "testp1"
   311  	bin := cmdToRun(cmd)
   312  
   313  	createHeadersOnce(t)
   314  
   315  	runCC(t, "-o", cmd, "main1.c", "-ldl")
   316  	adbPush(t, cmd)
   317  
   318  	defer os.Remove(bin)
   319  
   320  	out := runExe(t, nil, bin, "./"+libgoname)
   321  	if strings.TrimSpace(out) != "PASS" {
   322  		t.Error(out)
   323  	}
   324  }
   325  
   326  // test2: tests libgo2 which does not export any functions.
   327  func TestUnexportedSymbols(t *testing.T) {
   328  	t.Parallel()
   329  
   330  	if GOOS == "windows" {
   331  		t.Logf("Skipping on %s", GOOS)
   332  		return
   333  	}
   334  
   335  	cmd := "testp2"
   336  	bin := cmdToRun(cmd)
   337  	libname := "libgo2." + libSuffix
   338  
   339  	run(t,
   340  		gopathEnv,
   341  		"go", "build",
   342  		"-buildmode=c-shared",
   343  		"-installsuffix", "testcshared",
   344  		"-o", libname, "libgo2",
   345  	)
   346  	adbPush(t, libname)
   347  
   348  	linkFlags := "-Wl,--no-as-needed"
   349  	if GOOS == "darwin" {
   350  		linkFlags = ""
   351  	}
   352  
   353  	runCC(t, "-o", cmd, "main2.c", linkFlags, libname)
   354  	adbPush(t, cmd)
   355  
   356  	defer os.Remove(libname)
   357  	defer os.Remove(bin)
   358  
   359  	out := runExe(t, append(gopathEnv, "LD_LIBRARY_PATH=."), bin)
   360  
   361  	if strings.TrimSpace(out) != "PASS" {
   362  		t.Error(out)
   363  	}
   364  }
   365  
   366  // test3: tests main.main is exported on android.
   367  func TestMainExportedOnAndroid(t *testing.T) {
   368  	t.Parallel()
   369  
   370  	switch GOOS {
   371  	case "android":
   372  		break
   373  	default:
   374  		t.Logf("Skipping on %s", GOOS)
   375  		return
   376  	}
   377  
   378  	cmd := "testp3"
   379  	bin := cmdToRun(cmd)
   380  
   381  	createHeadersOnce(t)
   382  
   383  	runCC(t, "-o", cmd, "main3.c", "-ldl")
   384  	adbPush(t, cmd)
   385  
   386  	defer os.Remove(bin)
   387  
   388  	out := runExe(t, nil, bin, "./"+libgoname)
   389  	if strings.TrimSpace(out) != "PASS" {
   390  		t.Error(out)
   391  	}
   392  }
   393  
   394  func testSignalHandlers(t *testing.T, pkgname, cfile, cmd string) {
   395  	libname := pkgname + "." + libSuffix
   396  	run(t,
   397  		gopathEnv,
   398  		"go", "build",
   399  		"-buildmode=c-shared",
   400  		"-installsuffix", "testcshared",
   401  		"-o", libname, pkgname,
   402  	)
   403  	adbPush(t, libname)
   404  	runCC(t, "-pthread", "-o", cmd, cfile, "-ldl")
   405  	adbPush(t, cmd)
   406  
   407  	bin := cmdToRun(cmd)
   408  
   409  	defer os.Remove(libname)
   410  	defer os.Remove(bin)
   411  	defer os.Remove(pkgname + ".h")
   412  
   413  	out := runExe(t, nil, bin, "./"+libname)
   414  	if strings.TrimSpace(out) != "PASS" {
   415  		t.Error(run(t, nil, bin, libname, "verbose"))
   416  	}
   417  }
   418  
   419  // test4: test signal handlers
   420  func TestSignalHandlers(t *testing.T) {
   421  	t.Parallel()
   422  	if GOOS == "windows" {
   423  		t.Logf("Skipping on %s", GOOS)
   424  		return
   425  	}
   426  	testSignalHandlers(t, "libgo4", "main4.c", "testp4")
   427  }
   428  
   429  // test5: test signal handlers with os/signal.Notify
   430  func TestSignalHandlersWithNotify(t *testing.T) {
   431  	t.Parallel()
   432  	if GOOS == "windows" {
   433  		t.Logf("Skipping on %s", GOOS)
   434  		return
   435  	}
   436  	testSignalHandlers(t, "libgo5", "main5.c", "testp5")
   437  }
   438  
   439  func TestPIE(t *testing.T) {
   440  	t.Parallel()
   441  
   442  	switch GOOS {
   443  	case "linux", "android":
   444  		break
   445  	default:
   446  		t.Logf("Skipping on %s", GOOS)
   447  		return
   448  	}
   449  
   450  	createHeadersOnce(t)
   451  
   452  	f, err := elf.Open(libgoname)
   453  	if err != nil {
   454  		t.Fatalf("elf.Open failed: %v", err)
   455  	}
   456  	defer f.Close()
   457  
   458  	ds := f.SectionByType(elf.SHT_DYNAMIC)
   459  	if ds == nil {
   460  		t.Fatalf("no SHT_DYNAMIC section")
   461  	}
   462  	d, err := ds.Data()
   463  	if err != nil {
   464  		t.Fatalf("can't read SHT_DYNAMIC contents: %v", err)
   465  	}
   466  	for len(d) > 0 {
   467  		var tag elf.DynTag
   468  		switch f.Class {
   469  		case elf.ELFCLASS32:
   470  			tag = elf.DynTag(f.ByteOrder.Uint32(d[:4]))
   471  			d = d[8:]
   472  		case elf.ELFCLASS64:
   473  			tag = elf.DynTag(f.ByteOrder.Uint64(d[:8]))
   474  			d = d[16:]
   475  		}
   476  		if tag == elf.DT_TEXTREL {
   477  			t.Fatalf("%s has DT_TEXTREL flag", libgoname)
   478  		}
   479  	}
   480  }
   481  
   482  // Test that installing a second time recreates the header files.
   483  func TestCachedInstall(t *testing.T) {
   484  	tmpdir, err := ioutil.TempDir("", "cshared")
   485  	if err != nil {
   486  		t.Fatal(err)
   487  	}
   488  	// defer os.RemoveAll(tmpdir)
   489  
   490  	copyFile(t, filepath.Join(tmpdir, "src", "libgo", "libgo.go"), filepath.Join("src", "libgo", "libgo.go"))
   491  	copyFile(t, filepath.Join(tmpdir, "src", "p", "p.go"), filepath.Join("src", "p", "p.go"))
   492  
   493  	env := append(os.Environ(), "GOPATH="+tmpdir)
   494  
   495  	buildcmd := []string{"go", "install", "-x", "-i", "-buildmode=c-shared", "-installsuffix", "testcshared", "libgo"}
   496  
   497  	cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
   498  	cmd.Env = env
   499  	t.Log(buildcmd)
   500  	out, err := cmd.CombinedOutput()
   501  	t.Logf("%s", out)
   502  	if err != nil {
   503  		t.Fatal(err)
   504  	}
   505  
   506  	var libgoh, ph string
   507  
   508  	walker := func(path string, info os.FileInfo, err error) error {
   509  		if err != nil {
   510  			t.Fatal(err)
   511  		}
   512  		var ps *string
   513  		switch filepath.Base(path) {
   514  		case "libgo.h":
   515  			ps = &libgoh
   516  		case "p.h":
   517  			ps = &ph
   518  		}
   519  		if ps != nil {
   520  			if *ps != "" {
   521  				t.Fatalf("%s found again", *ps)
   522  			}
   523  			*ps = path
   524  		}
   525  		return nil
   526  	}
   527  
   528  	if err := filepath.Walk(tmpdir, walker); err != nil {
   529  		t.Fatal(err)
   530  	}
   531  
   532  	if libgoh == "" {
   533  		t.Fatal("libgo.h not installed")
   534  	}
   535  	if ph == "" {
   536  		t.Fatal("p.h not installed")
   537  	}
   538  
   539  	if err := os.Remove(libgoh); err != nil {
   540  		t.Fatal(err)
   541  	}
   542  	if err := os.Remove(ph); err != nil {
   543  		t.Fatal(err)
   544  	}
   545  
   546  	cmd = exec.Command(buildcmd[0], buildcmd[1:]...)
   547  	cmd.Env = env
   548  	t.Log(buildcmd)
   549  	out, err = cmd.CombinedOutput()
   550  	t.Logf("%s", out)
   551  	if err != nil {
   552  		t.Fatal(err)
   553  	}
   554  
   555  	if _, err := os.Stat(libgoh); err != nil {
   556  		t.Errorf("libgo.h not installed in second run: %v", err)
   557  	}
   558  	if _, err := os.Stat(ph); err != nil {
   559  		t.Errorf("p.h not installed in second run: %v", err)
   560  	}
   561  }
   562  
   563  // copyFile copies src to dst.
   564  func copyFile(t *testing.T, dst, src string) {
   565  	t.Helper()
   566  	data, err := ioutil.ReadFile(src)
   567  	if err != nil {
   568  		t.Fatal(err)
   569  	}
   570  	if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil {
   571  		t.Fatal(err)
   572  	}
   573  	if err := ioutil.WriteFile(dst, data, 0666); err != nil {
   574  		t.Fatal(err)
   575  	}
   576  }