mirror of
https://github.com/golang/go
synced 2024-11-06 09:16:16 -07:00
a7df7b9cdb
Fixes #26730 Change-Id: I3396598282c814e75c0c4ef16f692dbe83d2935e Reviewed-on: https://go-review.googlesource.com/127395 Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
605 lines
13 KiB
Go
605 lines
13 KiB
Go
// Copyright 2017 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package cshared_test
|
|
|
|
import (
|
|
"debug/elf"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"unicode"
|
|
)
|
|
|
|
// C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)).
|
|
var cc []string
|
|
|
|
// An environment with GOPATH=$(pwd).
|
|
var gopathEnv []string
|
|
|
|
// ".exe" on Windows.
|
|
var exeSuffix string
|
|
|
|
var GOOS, GOARCH, GOROOT string
|
|
var installdir, androiddir string
|
|
var libSuffix, libgoname string
|
|
|
|
func TestMain(m *testing.M) {
|
|
GOOS = goEnv("GOOS")
|
|
GOARCH = goEnv("GOARCH")
|
|
GOROOT = goEnv("GOROOT")
|
|
|
|
if _, err := os.Stat(GOROOT); os.IsNotExist(err) {
|
|
log.Fatalf("Unable able to find GOROOT at '%s'", GOROOT)
|
|
}
|
|
|
|
// Directory where cgo headers and outputs will be installed.
|
|
// The installation directory format varies depending on the platform.
|
|
installdir = path.Join("pkg", fmt.Sprintf("%s_%s_testcshared", GOOS, GOARCH))
|
|
switch GOOS {
|
|
case "darwin":
|
|
libSuffix = "dylib"
|
|
case "windows":
|
|
libSuffix = "dll"
|
|
default:
|
|
libSuffix = "so"
|
|
installdir = path.Join("pkg", fmt.Sprintf("%s_%s_testcshared_shared", GOOS, GOARCH))
|
|
}
|
|
|
|
androiddir = fmt.Sprintf("/data/local/tmp/testcshared-%d", os.Getpid())
|
|
if GOOS == "android" {
|
|
args := append(adbCmd(), "shell", "mkdir", "-p", androiddir)
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
log.Fatalf("setupAndroid failed: %v\n%s\n", err, out)
|
|
}
|
|
}
|
|
|
|
libgoname = "libgo." + libSuffix
|
|
|
|
cc = []string{goEnv("CC")}
|
|
|
|
out := goEnv("GOGCCFLAGS")
|
|
quote := '\000'
|
|
start := 0
|
|
lastSpace := true
|
|
backslash := false
|
|
s := string(out)
|
|
for i, c := range s {
|
|
if quote == '\000' && unicode.IsSpace(c) {
|
|
if !lastSpace {
|
|
cc = append(cc, s[start:i])
|
|
lastSpace = true
|
|
}
|
|
} else {
|
|
if lastSpace {
|
|
start = i
|
|
lastSpace = false
|
|
}
|
|
if quote == '\000' && !backslash && (c == '"' || c == '\'') {
|
|
quote = c
|
|
backslash = false
|
|
} else if !backslash && quote == c {
|
|
quote = '\000'
|
|
} else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
|
|
backslash = true
|
|
} else {
|
|
backslash = false
|
|
}
|
|
}
|
|
}
|
|
if !lastSpace {
|
|
cc = append(cc, s[start:])
|
|
}
|
|
|
|
switch GOOS {
|
|
case "darwin":
|
|
// For Darwin/ARM.
|
|
// TODO(crawshaw): can we do better?
|
|
cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...)
|
|
case "android":
|
|
cc = append(cc, "-pie", "-fuse-ld=gold")
|
|
}
|
|
libgodir := GOOS + "_" + GOARCH
|
|
switch GOOS {
|
|
case "darwin":
|
|
if GOARCH == "arm" || GOARCH == "arm64" {
|
|
libgodir += "_shared"
|
|
}
|
|
case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris":
|
|
libgodir += "_shared"
|
|
}
|
|
cc = append(cc, "-I", filepath.Join("pkg", libgodir))
|
|
|
|
// Build an environment with GOPATH=$(pwd)
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(2)
|
|
}
|
|
gopathEnv = append(os.Environ(), "GOPATH="+dir)
|
|
|
|
if GOOS == "windows" {
|
|
exeSuffix = ".exe"
|
|
}
|
|
|
|
st := m.Run()
|
|
|
|
os.Remove(libgoname)
|
|
os.RemoveAll("pkg")
|
|
cleanupHeaders()
|
|
cleanupAndroid()
|
|
|
|
os.Exit(st)
|
|
}
|
|
|
|
func goEnv(key string) string {
|
|
out, err := exec.Command("go", "env", key).Output()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "go env %s failed:\n%s", key, err)
|
|
fmt.Fprintf(os.Stderr, "%s", err.(*exec.ExitError).Stderr)
|
|
os.Exit(2)
|
|
}
|
|
return strings.TrimSpace(string(out))
|
|
}
|
|
|
|
func cmdToRun(name string) string {
|
|
return "./" + name + exeSuffix
|
|
}
|
|
|
|
func adbCmd() []string {
|
|
cmd := []string{"adb"}
|
|
if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
|
|
cmd = append(cmd, strings.Split(flags, " ")...)
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func adbPush(t *testing.T, filename string) {
|
|
if GOOS != "android" {
|
|
return
|
|
}
|
|
args := append(adbCmd(), "push", filename, fmt.Sprintf("%s/%s", androiddir, filename))
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("adb command failed: %v\n%s\n", err, out)
|
|
}
|
|
}
|
|
|
|
func adbRun(t *testing.T, env []string, adbargs ...string) string {
|
|
if GOOS != "android" {
|
|
t.Fatalf("trying to run adb command when operating system is not android.")
|
|
}
|
|
args := append(adbCmd(), "shell")
|
|
// Propagate LD_LIBRARY_PATH to the adb shell invocation.
|
|
for _, e := range env {
|
|
if strings.Index(e, "LD_LIBRARY_PATH=") != -1 {
|
|
adbargs = append([]string{e}, adbargs...)
|
|
break
|
|
}
|
|
}
|
|
shellcmd := fmt.Sprintf("cd %s; %s", androiddir, strings.Join(adbargs, " "))
|
|
args = append(args, shellcmd)
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("adb command failed: %v\n%s\n", err, out)
|
|
}
|
|
return strings.Replace(string(out), "\r", "", -1)
|
|
}
|
|
|
|
func run(t *testing.T, env []string, args ...string) string {
|
|
t.Helper()
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
cmd.Env = env
|
|
|
|
if GOOS != "windows" {
|
|
// TestUnexportedSymbols relies on file descriptor 30
|
|
// being closed when the program starts, so enforce
|
|
// that in all cases. (The first three descriptors are
|
|
// stdin/stdout/stderr, so we just need to make sure
|
|
// that cmd.ExtraFiles[27] exists and is nil.)
|
|
cmd.ExtraFiles = make([]*os.File, 28)
|
|
}
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("command failed: %v\n%v\n%s\n", args, err, out)
|
|
} else {
|
|
t.Logf("run: %v", args)
|
|
}
|
|
return string(out)
|
|
}
|
|
|
|
func runExe(t *testing.T, env []string, args ...string) string {
|
|
t.Helper()
|
|
if GOOS == "android" {
|
|
return adbRun(t, env, args...)
|
|
}
|
|
return run(t, env, args...)
|
|
}
|
|
|
|
func runCC(t *testing.T, args ...string) string {
|
|
t.Helper()
|
|
// This function is run in parallel, so append to a copy of cc
|
|
// rather than cc itself.
|
|
return run(t, nil, append(append([]string(nil), cc...), args...)...)
|
|
}
|
|
|
|
func createHeaders() error {
|
|
args := []string{"go", "install", "-i", "-buildmode=c-shared",
|
|
"-installsuffix", "testcshared", "libgo"}
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
cmd.Env = gopathEnv
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
|
|
}
|
|
|
|
args = []string{"go", "build", "-buildmode=c-shared",
|
|
"-installsuffix", "testcshared",
|
|
"-o", libgoname,
|
|
filepath.Join("src", "libgo", "libgo.go")}
|
|
cmd = exec.Command(args[0], args[1:]...)
|
|
cmd.Env = gopathEnv
|
|
out, err = cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
|
|
}
|
|
|
|
if GOOS == "android" {
|
|
args = append(adbCmd(), "push", libgoname, fmt.Sprintf("%s/%s", androiddir, libgoname))
|
|
cmd = exec.Command(args[0], args[1:]...)
|
|
out, err = cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("adb command failed: %v\n%s\n", err, out)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
headersOnce sync.Once
|
|
headersErr error
|
|
)
|
|
|
|
func createHeadersOnce(t *testing.T) {
|
|
headersOnce.Do(func() {
|
|
headersErr = createHeaders()
|
|
})
|
|
if headersErr != nil {
|
|
t.Fatal(headersErr)
|
|
}
|
|
}
|
|
|
|
func cleanupHeaders() {
|
|
os.Remove("libgo.h")
|
|
}
|
|
|
|
func cleanupAndroid() {
|
|
if GOOS != "android" {
|
|
return
|
|
}
|
|
args := append(adbCmd(), "shell", "rm", "-rf", androiddir)
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
log.Fatalf("cleanupAndroid failed: %v\n%s\n", err, out)
|
|
}
|
|
}
|
|
|
|
// test0: exported symbols in shared lib are accessible.
|
|
func TestExportedSymbols(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cmd := "testp0"
|
|
bin := cmdToRun(cmd)
|
|
|
|
createHeadersOnce(t)
|
|
|
|
runCC(t, "-I", installdir, "-o", cmd, "main0.c", libgoname)
|
|
adbPush(t, cmd)
|
|
|
|
defer os.Remove(bin)
|
|
|
|
out := runExe(t, append(gopathEnv, "LD_LIBRARY_PATH=."), bin)
|
|
if strings.TrimSpace(out) != "PASS" {
|
|
t.Error(out)
|
|
}
|
|
}
|
|
|
|
// test1: shared library can be dynamically loaded and exported symbols are accessible.
|
|
func TestExportedSymbolsWithDynamicLoad(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if GOOS == "windows" {
|
|
t.Logf("Skipping on %s", GOOS)
|
|
return
|
|
}
|
|
|
|
cmd := "testp1"
|
|
bin := cmdToRun(cmd)
|
|
|
|
createHeadersOnce(t)
|
|
|
|
if GOOS != "freebsd" {
|
|
runCC(t, "-o", cmd, "main1.c", "-ldl")
|
|
} else {
|
|
runCC(t, "-o", cmd, "main1.c")
|
|
}
|
|
adbPush(t, cmd)
|
|
|
|
defer os.Remove(bin)
|
|
|
|
out := runExe(t, nil, bin, "./"+libgoname)
|
|
if strings.TrimSpace(out) != "PASS" {
|
|
t.Error(out)
|
|
}
|
|
}
|
|
|
|
// test2: tests libgo2 which does not export any functions.
|
|
func TestUnexportedSymbols(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if GOOS == "windows" {
|
|
t.Logf("Skipping on %s", GOOS)
|
|
return
|
|
}
|
|
|
|
cmd := "testp2"
|
|
bin := cmdToRun(cmd)
|
|
libname := "libgo2." + libSuffix
|
|
|
|
run(t,
|
|
gopathEnv,
|
|
"go", "build",
|
|
"-buildmode=c-shared",
|
|
"-installsuffix", "testcshared",
|
|
"-o", libname, "libgo2",
|
|
)
|
|
adbPush(t, libname)
|
|
|
|
linkFlags := "-Wl,--no-as-needed"
|
|
if GOOS == "darwin" {
|
|
linkFlags = ""
|
|
}
|
|
|
|
runCC(t, "-o", cmd, "main2.c", linkFlags, libname)
|
|
adbPush(t, cmd)
|
|
|
|
defer os.Remove(libname)
|
|
defer os.Remove(bin)
|
|
|
|
out := runExe(t, append(gopathEnv, "LD_LIBRARY_PATH=."), bin)
|
|
|
|
if strings.TrimSpace(out) != "PASS" {
|
|
t.Error(out)
|
|
}
|
|
}
|
|
|
|
// test3: tests main.main is exported on android.
|
|
func TestMainExportedOnAndroid(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
switch GOOS {
|
|
case "android":
|
|
break
|
|
default:
|
|
t.Logf("Skipping on %s", GOOS)
|
|
return
|
|
}
|
|
|
|
cmd := "testp3"
|
|
bin := cmdToRun(cmd)
|
|
|
|
createHeadersOnce(t)
|
|
|
|
runCC(t, "-o", cmd, "main3.c", "-ldl")
|
|
adbPush(t, cmd)
|
|
|
|
defer os.Remove(bin)
|
|
|
|
out := runExe(t, nil, bin, "./"+libgoname)
|
|
if strings.TrimSpace(out) != "PASS" {
|
|
t.Error(out)
|
|
}
|
|
}
|
|
|
|
func testSignalHandlers(t *testing.T, pkgname, cfile, cmd string) {
|
|
libname := pkgname + "." + libSuffix
|
|
run(t,
|
|
gopathEnv,
|
|
"go", "build",
|
|
"-buildmode=c-shared",
|
|
"-installsuffix", "testcshared",
|
|
"-o", libname, pkgname,
|
|
)
|
|
adbPush(t, libname)
|
|
if GOOS != "freebsd" {
|
|
runCC(t, "-pthread", "-o", cmd, cfile, "-ldl")
|
|
} else {
|
|
runCC(t, "-pthread", "-o", cmd, cfile)
|
|
}
|
|
adbPush(t, cmd)
|
|
|
|
bin := cmdToRun(cmd)
|
|
|
|
defer os.Remove(libname)
|
|
defer os.Remove(bin)
|
|
defer os.Remove(pkgname + ".h")
|
|
|
|
out := runExe(t, nil, bin, "./"+libname)
|
|
if strings.TrimSpace(out) != "PASS" {
|
|
t.Error(run(t, nil, bin, libname, "verbose"))
|
|
}
|
|
}
|
|
|
|
// test4: test signal handlers
|
|
func TestSignalHandlers(t *testing.T) {
|
|
t.Parallel()
|
|
if GOOS == "windows" {
|
|
t.Logf("Skipping on %s", GOOS)
|
|
return
|
|
}
|
|
testSignalHandlers(t, "libgo4", "main4.c", "testp4")
|
|
}
|
|
|
|
// test5: test signal handlers with os/signal.Notify
|
|
func TestSignalHandlersWithNotify(t *testing.T) {
|
|
t.Parallel()
|
|
if GOOS == "windows" {
|
|
t.Logf("Skipping on %s", GOOS)
|
|
return
|
|
}
|
|
testSignalHandlers(t, "libgo5", "main5.c", "testp5")
|
|
}
|
|
|
|
func TestPIE(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
switch GOOS {
|
|
case "linux", "android":
|
|
break
|
|
default:
|
|
t.Logf("Skipping on %s", GOOS)
|
|
return
|
|
}
|
|
|
|
createHeadersOnce(t)
|
|
|
|
f, err := elf.Open(libgoname)
|
|
if err != nil {
|
|
t.Fatalf("elf.Open failed: %v", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
ds := f.SectionByType(elf.SHT_DYNAMIC)
|
|
if ds == nil {
|
|
t.Fatalf("no SHT_DYNAMIC section")
|
|
}
|
|
d, err := ds.Data()
|
|
if err != nil {
|
|
t.Fatalf("can't read SHT_DYNAMIC contents: %v", err)
|
|
}
|
|
for len(d) > 0 {
|
|
var tag elf.DynTag
|
|
switch f.Class {
|
|
case elf.ELFCLASS32:
|
|
tag = elf.DynTag(f.ByteOrder.Uint32(d[:4]))
|
|
d = d[8:]
|
|
case elf.ELFCLASS64:
|
|
tag = elf.DynTag(f.ByteOrder.Uint64(d[:8]))
|
|
d = d[16:]
|
|
}
|
|
if tag == elf.DT_TEXTREL {
|
|
t.Fatalf("%s has DT_TEXTREL flag", libgoname)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test that installing a second time recreates the header files.
|
|
func TestCachedInstall(t *testing.T) {
|
|
tmpdir, err := ioutil.TempDir("", "cshared")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// defer os.RemoveAll(tmpdir)
|
|
|
|
copyFile(t, filepath.Join(tmpdir, "src", "libgo", "libgo.go"), filepath.Join("src", "libgo", "libgo.go"))
|
|
copyFile(t, filepath.Join(tmpdir, "src", "p", "p.go"), filepath.Join("src", "p", "p.go"))
|
|
|
|
env := append(os.Environ(), "GOPATH="+tmpdir)
|
|
|
|
buildcmd := []string{"go", "install", "-x", "-i", "-buildmode=c-shared", "-installsuffix", "testcshared", "libgo"}
|
|
|
|
cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
|
|
cmd.Env = env
|
|
t.Log(buildcmd)
|
|
out, err := cmd.CombinedOutput()
|
|
t.Logf("%s", out)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var libgoh, ph string
|
|
|
|
walker := func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var ps *string
|
|
switch filepath.Base(path) {
|
|
case "libgo.h":
|
|
ps = &libgoh
|
|
case "p.h":
|
|
ps = &ph
|
|
}
|
|
if ps != nil {
|
|
if *ps != "" {
|
|
t.Fatalf("%s found again", *ps)
|
|
}
|
|
*ps = path
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if err := filepath.Walk(tmpdir, walker); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if libgoh == "" {
|
|
t.Fatal("libgo.h not installed")
|
|
}
|
|
if ph == "" {
|
|
t.Fatal("p.h not installed")
|
|
}
|
|
|
|
if err := os.Remove(libgoh); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Remove(ph); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cmd = exec.Command(buildcmd[0], buildcmd[1:]...)
|
|
cmd.Env = env
|
|
t.Log(buildcmd)
|
|
out, err = cmd.CombinedOutput()
|
|
t.Logf("%s", out)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := os.Stat(libgoh); err != nil {
|
|
t.Errorf("libgo.h not installed in second run: %v", err)
|
|
}
|
|
if _, err := os.Stat(ph); err != nil {
|
|
t.Errorf("p.h not installed in second run: %v", err)
|
|
}
|
|
}
|
|
|
|
// copyFile copies src to dst.
|
|
func copyFile(t *testing.T, dst, src string) {
|
|
t.Helper()
|
|
data, err := ioutil.ReadFile(src)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := ioutil.WriteFile(dst, data, 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|