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