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