From: Christopher Nelson Date: Mon, 28 Nov 2016 00:05:01 +0000 (-0500) Subject: misc/cgo/testcshared: rewrite test.bash in Go X-Git-Tag: go1.10beta1~1484 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=ef94870cc87720e4e2950043ce3875315c4c7c96;p=gostls13.git misc/cgo/testcshared: rewrite test.bash in Go Change-Id: Id717054cb3c4537452f8ff848445b0c20196a373 Reviewed-on: https://go-review.googlesource.com/33579 TryBot-Result: Gobot Gobot Reviewed-by: Alex Brainman --- diff --git a/misc/cgo/testcshared/cshared_test.go b/misc/cgo/testcshared/cshared_test.go new file mode 100644 index 0000000000..c7317a43eb --- /dev/null +++ b/misc/cgo/testcshared/cshared_test.go @@ -0,0 +1,477 @@ +// 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" + "log" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + "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, ldlibrarypath string +var libSuffix, libgoname string + +func init() { + 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_shared", GOOS, GOARCH)) + switch GOOS { + case "darwin": + libSuffix = "dylib" + installdir = path.Join("pkg", fmt.Sprintf("%s_%s_testcshared", GOOS, GOARCH)) + case "windows": + libSuffix = "dll" + default: + libSuffix = "so" + } + + androiddir = fmt.Sprintf("/data/local/tmp/testcshared-%d", os.Getpid()) + libgoname = "libgo." + libSuffix + + ccOut := goEnv("CC") + cc = []string{string(ccOut)} + + 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:]) + } + + if GOOS == "darwin" { + // For Darwin/ARM. + // TODO(crawshaw): can we do better? + cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...) + } + 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) + ldlibrarypath = "LD_LIBRARY_PATH=" + dir + + if GOOS == "windows" { + exeSuffix = ".exe" + } +} + +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 []string{"./" + name + exeSuffix} +} + +func adbPush(t *testing.T, filename string) { + if GOOS != "android" { + return + } + args := append(cmdToRun("adb"), "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, adbargs ...string) string { + if GOOS != "android" { + t.Fatalf("trying to run adb command when operating system is not android.") + } + args := append(cmdToRun("adb"), "shell") + args = append(args, adbargs...) + 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 runwithenv(t *testing.T, env []string, args ...string) string { + if GOOS == "android" { + return adbRun(t, args...) + } + + cmd := exec.Command(args[0], args[1:]...) + cmd.Env = env + 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 run(t *testing.T, args ...string) string { + if GOOS == "android" { + return adbRun(t, args...) + } + + cmd := exec.Command(args[0], args[1:]...) + 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 runwithldlibrarypath(t *testing.T, args ...string) string { + return runwithenv(t, append(gopathEnv, ldlibrarypath), args...) +} + +func rungocmd(t *testing.T, args ...string) string { + return runwithenv(t, gopathEnv, args...) +} + +func createHeaders(t *testing.T) { + rungocmd(t, + "go", "install", + "-buildmode=c-shared", "-installsuffix", + "testcshared", "libgo", + ) + + rungocmd(t, + "go", "build", + "-buildmode=c-shared", "-installsuffix", + "testcshared", "-o", libgoname, + filepath.Join("src", "libgo", "libgo.go"), + ) + adbPush(t, libgoname) + + if GOOS == "linux" || GOOS == "android" { + f, err := elf.Open(libgoname) + if err != nil { + t.Fatal("elf.Open failed: ", err) + } + defer f.Close() + if hasDynTag(t, f, elf.DT_TEXTREL) { + t.Fatalf("%s has DT_TEXTREL flag", libgoname) + } + } +} + +func cleanupHeaders() { + os.Remove("libgo.h") +} + +func setupAndroid(t *testing.T) { + if GOOS != "android" { + return + } + adbRun(t, "mkdir", "-p", androiddir) +} + +func cleanupAndroid(t *testing.T) { + if GOOS != "android" { + return + } + adbRun(t, "rm", "-rf", androiddir) +} + +// test0: exported symbols in shared lib are accessible. +func TestExportedSymbols(t *testing.T) { + cmd := "testp" + bin := cmdToRun(cmd) + + setupAndroid(t) + defer cleanupAndroid(t) + createHeaders(t) + defer cleanupHeaders() + + run(t, append(cc, "-I", installdir, "-o", cmd, "main0.c", libgoname)...) + adbPush(t, cmd) + + defer os.Remove(libgoname) + defer os.Remove("testp") + + out := runwithldlibrarypath(t, 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) { + cmd := "testp" + bin := cmdToRun(cmd) + + setupAndroid(t) + defer cleanupAndroid(t) + createHeaders(t) + defer cleanupHeaders() + + run(t, append(cc, "-o", cmd, "main1.c", "-ldl")...) + adbPush(t, cmd) + + defer os.Remove(libgoname) + defer os.Remove(cmd) + + out := run(t, append(bin, "./"+libgoname)...) + if strings.TrimSpace(out) != "PASS" { + t.Error(out) + } +} + +// test2: tests libgo2 which does not export any functions. +func TestUnexportedSymbols(t *testing.T) { + cmd := "testp2" + libname := "libgo2." + libSuffix + bin := cmdToRun(cmd) + + setupAndroid(t) + defer cleanupAndroid(t) + + rungocmd(t, + "go", "build", + "-buildmode=c-shared", + "-installsuffix", "testcshared", + "-o", libname, "libgo2", + ) + adbPush(t, libname) + + linkFlags := "-Wl,--no-as-needed" + if GOOS == "darwin" { + linkFlags = "" + } + + run(t, append( + cc, "-o", cmd, + "main2.c", linkFlags, + libname, + )...) + adbPush(t, cmd) + + defer os.Remove(libname) + defer os.Remove(cmd) + + out := runwithldlibrarypath(t, bin...) + + if strings.TrimSpace(out) != "PASS" { + t.Error(out) + } +} + +// test3: tests main.main is exported on android. +func TestMainExportedOnAndroid(t *testing.T) { + if GOOS != "android" { + return + } + + cmd := "testp3" + bin := cmdToRun(cmd) + + setupAndroid(t) + defer cleanupAndroid(t) + createHeaders(t) + defer cleanupHeaders() + + run(t, append(cc, "-o", cmd, "main3.c", "-ldl")...) + adbPush(t, cmd) + + defer os.Remove(libgoname) + defer os.Remove(cmd) + + out := run(t, append(bin, "./"+libgoname)...) + if strings.TrimSpace(out) != "PASS" { + t.Error(out) + } +} + +// test4: test signal handlers +func TestSignalHandlers(t *testing.T) { + cmd := "testp4" + libname := "libgo4." + libSuffix + bin := cmdToRun(cmd) + + setupAndroid(t) + defer cleanupAndroid(t) + + rungocmd(t, + "go", "build", + "-buildmode=c-shared", + "-installsuffix", "testcshared", + "-o", libname, "libgo4", + ) + adbPush(t, libname) + run(t, append( + cc, "-pthread", "-o", cmd, + "main4.c", "-ldl", + )...) + adbPush(t, cmd) + + defer os.Remove(libname) + defer os.Remove(cmd) + defer os.Remove("libgo4.h") + + out := run(t, append(bin, "./"+libname)...) + + if strings.TrimSpace(out) != "PASS" { + t.Error(run(t, append(bin, libname, "verbose")...)) + } +} + +// test5: test signal handlers with os/signal.Notify +func TestSignalHandlersWithNotify(t *testing.T) { + cmd := "testp5" + libname := "libgo5." + libSuffix + bin := cmdToRun(cmd) + + setupAndroid(t) + defer cleanupAndroid(t) + + rungocmd(t, + "go", "build", + "-buildmode=c-shared", + "-installsuffix", "testcshared", + "-o", libname, "libgo5", + ) + adbPush(t, libname) + run(t, append( + cc, "-pthread", "-o", cmd, + "main5.c", "-ldl", + )...) + adbPush(t, cmd) + + defer os.Remove(libname) + defer os.Remove(cmd) + defer os.Remove("libgo5.h") + + out := run(t, append(bin, "./"+libname)...) + + if strings.TrimSpace(out) != "PASS" { + t.Error(run(t, append(bin, libname, "verbose")...)) + } +} + +func TestPIE(t *testing.T) { + switch GOOS { + case "linux", "android": + break + default: + t.Logf("Skipping TestPIE on %s", GOOS) + return + } + + defer func() { + os.RemoveAll("pkg") + }() + + createHeaders(t) + defer cleanupHeaders() + + f, err := elf.Open(libgoname) + if err != nil { + t.Fatal("elf.Open failed: ", err) + } + defer f.Close() + if hasDynTag(t, f, elf.DT_TEXTREL) { + t.Errorf("%s has DT_TEXTREL flag", libgoname) + } +} + +func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool { + ds := f.SectionByType(elf.SHT_DYNAMIC) + if ds == nil { + t.Error("no SHT_DYNAMIC section") + return false + } + d, err := ds.Data() + if err != nil { + t.Errorf("can't read SHT_DYNAMIC contents: %v", err) + return false + } + for len(d) > 0 { + var t elf.DynTag + switch f.Class { + case elf.ELFCLASS32: + t = elf.DynTag(f.ByteOrder.Uint32(d[:4])) + d = d[8:] + case elf.ELFCLASS64: + t = elf.DynTag(f.ByteOrder.Uint64(d[:8])) + d = d[16:] + } + if t == tag { + return true + } + } + return false +} diff --git a/misc/cgo/testcshared/test.bash b/misc/cgo/testcshared/test.bash deleted file mode 100755 index 315a0d4036..0000000000 --- a/misc/cgo/testcshared/test.bash +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2015 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. - -# For testing Android, this script requires adb to push and run compiled -# binaries on a target device. - -set -e - -if [ ! -f src/libgo/libgo.go ]; then - cwd=$(pwd) - echo "misc/cgo/testcshared/test.bash is running in $cwd" 1>&2 - exit 1 -fi - -goos=$(go env GOOS) -goarch=$(go env GOARCH) -goroot=$(go env GOROOT) -if [ ! -d "$goroot" ]; then - echo 'misc/cgo/testcshared/test.bash cannot find GOROOT' 1>&2 - echo '$GOROOT:' "$GOROOT" 1>&2 - echo 'go env GOROOT:' "$goroot" 1>&2 - exit 1 -fi - -# Directory where cgo headers and outputs will be installed. -# The installation directory format varies depending on the platform. -installdir=pkg/${goos}_${goarch}_testcshared_shared -if [ "${goos}" = "darwin" ]; then - installdir=pkg/${goos}_${goarch}_testcshared -fi - -# Temporary directory on the android device. -androidpath=/data/local/tmp/testcshared-$$ - -function cleanup() { - rm -f libgo.$libext libgo2.$libext libgo4.$libext libgo5.$libext - rm -f libgo.h libgo4.h libgo5.h - rm -f testp testp2 testp3 testp4 testp5 - rm -rf pkg "${goroot}/${installdir}" - - if [ "$goos" = "android" ]; then - adb shell rm -rf "$androidpath" - fi -} -trap cleanup EXIT - -if [ "$goos" = "android" ]; then - adb shell mkdir -p "$androidpath" -fi - -function run() { - case "$goos" in - "android") - local args=$@ - output=$(adb shell "cd ${androidpath}; $@") - output=$(echo $output|tr -d '\r') - case $output in - *PASS) echo "PASS";; - *) echo "$output";; - esac - ;; - *) - echo $(env $@) - ;; - esac -} - -function binpush() { - bin=${1} - if [ "$goos" = "android" ]; then - adb push "$bin" "${androidpath}/${bin}" 2>/dev/null - fi -} - -rm -rf pkg - -suffix="-installsuffix testcshared" - -libext="so" -if [ "$goos" = "darwin" ]; then - libext="dylib" -fi - -# Create the header files. -GOPATH=$(pwd) go install -buildmode=c-shared $suffix libgo - -GOPATH=$(pwd) go build -buildmode=c-shared $suffix -o libgo.$libext src/libgo/libgo.go -binpush libgo.$libext - -if [ "$goos" = "linux" ] || [ "$goos" = "android" ] ; then - if readelf -d libgo.$libext | grep TEXTREL >/dev/null; then - echo "libgo.$libext has TEXTREL set" - exit 1 - fi -fi - -GOGCCFLAGS=$(go env GOGCCFLAGS) -if [ "$goos" = "android" ]; then - GOGCCFLAGS="${GOGCCFLAGS} -pie -fuse-ld=gold" -fi - -status=0 - -# test0: exported symbols in shared lib are accessible. -# TODO(iant): using _shared here shouldn't really be necessary. -$(go env CC) ${GOGCCFLAGS} -I ${installdir} -o testp main0.c ./libgo.$libext -binpush testp - -output=$(run LD_LIBRARY_PATH=. ./testp) -if [ "$output" != "PASS" ]; then - echo "FAIL test0 got ${output}" - status=1 -fi - -# test1: shared library can be dynamically loaded and exported symbols are accessible. -$(go env CC) ${GOGCCFLAGS} -o testp main1.c -ldl -binpush testp -output=$(run ./testp ./libgo.$libext) -if [ "$output" != "PASS" ]; then - echo "FAIL test1 got ${output}" - status=1 -fi - -# test2: tests libgo2 which does not export any functions. -GOPATH=$(pwd) go build -buildmode=c-shared $suffix -o libgo2.$libext libgo2 -binpush libgo2.$libext -linkflags="-Wl,--no-as-needed" -if [ "$goos" = "darwin" ]; then - linkflags="" -fi -$(go env CC) ${GOGCCFLAGS} -o testp2 main2.c $linkflags libgo2.$libext -binpush testp2 -output=$(run LD_LIBRARY_PATH=. ./testp2) -if [ "$output" != "PASS" ]; then - echo "FAIL test2 got ${output}" - status=1 -fi - -# test3: tests main.main is exported on android. -if [ "$goos" = "android" ]; then - $(go env CC) ${GOGCCFLAGS} -o testp3 main3.c -ldl - binpush testp3 - output=$(run ./testp ./libgo.so) - if [ "$output" != "PASS" ]; then - echo "FAIL test3 got ${output}" - status=1 - fi -fi - -# test4: tests signal handlers -GOPATH=$(pwd) go build -buildmode=c-shared $suffix -o libgo4.$libext libgo4 -binpush libgo4.$libext -$(go env CC) ${GOGCCFLAGS} -pthread -o testp4 main4.c -ldl -binpush testp4 -output=$(run ./testp4 ./libgo4.$libext 2>&1) -if test "$output" != "PASS"; then - echo "FAIL test4 got ${output}" - if test "$goos" != "android"; then - echo "re-running test4 in verbose mode" - ./testp4 ./libgo4.$libext verbose - fi - status=1 -fi - -# test5: tests signal handlers with os/signal.Notify -GOPATH=$(pwd) go build -buildmode=c-shared $suffix -o libgo5.$libext libgo5 -binpush libgo5.$libext -$(go env CC) ${GOGCCFLAGS} -pthread -o testp5 main5.c -ldl -binpush testp5 -output=$(run ./testp5 ./libgo5.$libext 2>&1) -if test "$output" != "PASS"; then - echo "FAIL test5 got ${output}" - if test "$goos" != "android"; then - echo "re-running test5 in verbose mode" - ./testp5 ./libgo5.$libext verbose - fi - status=1 -fi - -if test "$libext" = "dylib"; then - # make sure dylibs are well-formed - if ! otool -l libgo*.dylib >/dev/null; then - status=1 - fi -fi - -if test $status = 0; then - echo "ok" -fi - -exit $status diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index 79338b3721..0b041117dd 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -608,7 +608,7 @@ func (t *tester) registerTests() { t.registerHostTest("testcarchive", "../misc/cgo/testcarchive", "misc/cgo/testcarchive", "carchive_test.go") } if t.supportedBuildmode("c-shared") { - t.registerTest("testcshared", "../misc/cgo/testcshared", "./test.bash") + t.registerHostTest("testcshared", "../misc/cgo/testcshared", "misc/cgo/testcshared", "cshared_test.go") } if t.supportedBuildmode("shared") { t.registerTest("testshared", "../misc/cgo/testshared", "go", "test")