]> Cypherpunks repositories - gostls13.git/commitdiff
misc/cgo/testcshared: rewrite test.bash in Go
authorChristopher Nelson <nadiasvertex@gmail.com>
Mon, 28 Nov 2016 00:05:01 +0000 (19:05 -0500)
committerAlex Brainman <alex.brainman@gmail.com>
Fri, 18 Aug 2017 03:23:12 +0000 (03:23 +0000)
Change-Id: Id717054cb3c4537452f8ff848445b0c20196a373
Reviewed-on: https://go-review.googlesource.com/33579
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
misc/cgo/testcshared/cshared_test.go [new file with mode: 0644]
misc/cgo/testcshared/test.bash [deleted file]
src/cmd/dist/test.go

diff --git a/misc/cgo/testcshared/cshared_test.go b/misc/cgo/testcshared/cshared_test.go
new file mode 100644 (file)
index 0000000..c7317a4
--- /dev/null
@@ -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 (executable)
index 315a0d4..0000000
+++ /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
index 79338b3721447bd2a791a1dd59c2b8d2b1694432..0b041117ddba9224e4caa5cc8987308087f40757 100644 (file)
@@ -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")