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