1
0
mirror of https://github.com/golang/go synced 2024-11-26 04:27:58 -07:00

misc/cgo/testcarchive: avoid writing to GOROOT in tests

Also add a -testwork flag to facilitate debugging the test itself.

Three of the tests of this package invoked 'go install -i
-buildmode=c-archive' in order to generate an archive as well as
multiple C header files.

Unfortunately, the behavior of the '-i' flag is inappropriately broad
for this use-case: it not only generates the library and header files
(as desired), but also attempts to install a number of (unnecessary)
archive files for transitive dependencies to
GOROOT/pkg/$GOOS_$GOARCH_shared, which may not be writable — for
example, if GOROOT is owned by the root user but the test is being run
by a non-root user.

Instead, for now we generate the header files for transitive dependencies
separately by running 'go tool cgo -exportheader'.

In the future, we should consider how to improve the ergonomics for
generating transitive header files without coupling that to
unnecessary library installation.

Updates #28387
Updates #30316
Updates #35715

Change-Id: I3d483f84e22058561efe740aa4885fc3f26137b5
Reviewed-on: https://go-review.googlesource.com/c/go/+/208117
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Bryan C. Mills 2019-11-20 10:19:43 -05:00
parent ea18a1c212
commit c02f3b86b4

View File

@ -36,7 +36,10 @@ var exeSuffix string
var GOOS, GOARCH, GOPATH string var GOOS, GOARCH, GOPATH string
var libgodir string var libgodir string
var testWork bool // If true, preserve temporary directories.
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
flag.BoolVar(&testWork, "testwork", false, "if true, log and preserve the test's temporary working directory")
flag.Parse() flag.Parse()
if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" { if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" {
fmt.Printf("SKIP - short mode and $GO_BUILDER_NAME not set\n") fmt.Printf("SKIP - short mode and $GO_BUILDER_NAME not set\n")
@ -54,7 +57,11 @@ func testMain(m *testing.M) int {
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
defer os.RemoveAll(GOPATH) if testWork {
log.Println(GOPATH)
} else {
defer os.RemoveAll(GOPATH)
}
os.Setenv("GOPATH", GOPATH) os.Setenv("GOPATH", GOPATH)
// Copy testdata into GOPATH/src/testarchive, along with a go.mod file // Copy testdata into GOPATH/src/testarchive, along with a go.mod file
@ -164,6 +171,38 @@ func cmdToRun(name string) []string {
return []string{executor, name} return []string{executor, name}
} }
// genHeader writes a C header file for the C-exported declarations found in .go
// source files in dir.
//
// TODO(golang.org/issue/35715): This should be simpler.
func genHeader(t *testing.T, header, dir string) {
t.Helper()
// The 'cgo' command generates a number of additional artifacts,
// but we're only interested in the header.
// Shunt the rest of the outputs to a temporary directory.
objDir, err := ioutil.TempDir(GOPATH, "_obj")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(objDir)
files, err := filepath.Glob(filepath.Join(dir, "*.go"))
if err != nil {
t.Fatal(err)
}
cmd := exec.Command("go", "tool", "cgo",
"-objdir", objDir,
"-exportheader", header)
cmd.Args = append(cmd.Args, files...)
t.Log(cmd.Args)
if out, err := cmd.CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatal(err)
}
}
func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) { func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) {
t.Helper() t.Helper()
cmd := exec.Command(buildcmd[0], buildcmd[1:]...) cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
@ -172,10 +211,12 @@ func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) {
t.Logf("%s", out) t.Logf("%s", out)
t.Fatal(err) t.Fatal(err)
} }
defer func() { if !testWork {
os.Remove(libgoa) defer func() {
os.Remove(libgoh) os.Remove(libgoa)
}() os.Remove(libgoh)
}()
}
ccArgs := append(cc, "-o", exe, "main.c") ccArgs := append(cc, "-o", exe, "main.c")
if GOOS == "windows" { if GOOS == "windows" {
@ -191,7 +232,9 @@ func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) {
t.Logf("%s", out) t.Logf("%s", out)
t.Fatal(err) t.Fatal(err)
} }
defer os.Remove(exe) if !testWork {
defer os.Remove(exe)
}
binArgs := append(cmdToRun(exe), "arg1", "arg2") binArgs := append(cmdToRun(exe), "arg1", "arg2")
cmd = exec.Command(binArgs[0], binArgs[1:]...) cmd = exec.Command(binArgs[0], binArgs[1:]...)
@ -227,17 +270,27 @@ func checkLineComments(t *testing.T, hdrname string) {
} }
func TestInstall(t *testing.T) { func TestInstall(t *testing.T) {
defer os.RemoveAll(filepath.Join(GOPATH, "pkg")) if !testWork {
defer os.RemoveAll(filepath.Join(GOPATH, "pkg"))
}
libgoa := "libgo.a" libgoa := "libgo.a"
if runtime.Compiler == "gccgo" { if runtime.Compiler == "gccgo" {
libgoa = "liblibgo.a" libgoa = "liblibgo.a"
} }
// Generate the p.h header file.
//
// 'go install -i -buildmode=c-archive ./libgo' would do that too, but that
// would also attempt to install transitive standard-library dependencies to
// GOROOT, and we cannot assume that GOROOT is writable. (A non-root user may
// be running this test in a GOROOT owned by root.)
genHeader(t, "p.h", "./p")
testInstall(t, "./testp1"+exeSuffix, testInstall(t, "./testp1"+exeSuffix,
filepath.Join(libgodir, libgoa), filepath.Join(libgodir, libgoa),
filepath.Join(libgodir, "libgo.h"), filepath.Join(libgodir, "libgo.h"),
"go", "install", "-i", "-buildmode=c-archive", "./libgo") "go", "install", "-buildmode=c-archive", "./libgo")
// Test building libgo other than installing it. // Test building libgo other than installing it.
// Header files are now present. // Header files are now present.
@ -259,12 +312,14 @@ func TestEarlySignalHandler(t *testing.T) {
t.Skip("skipping signal test on Windows") t.Skip("skipping signal test on Windows")
} }
defer func() { if !testWork {
os.Remove("libgo2.a") defer func() {
os.Remove("libgo2.h") os.Remove("libgo2.a")
os.Remove("testp") os.Remove("libgo2.h")
os.RemoveAll(filepath.Join(GOPATH, "pkg")) os.Remove("testp")
}() os.RemoveAll(filepath.Join(GOPATH, "pkg"))
}()
}
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2") cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
@ -297,12 +352,14 @@ func TestEarlySignalHandler(t *testing.T) {
func TestSignalForwarding(t *testing.T) { func TestSignalForwarding(t *testing.T) {
checkSignalForwardingTest(t) checkSignalForwardingTest(t)
defer func() { if !testWork {
os.Remove("libgo2.a") defer func() {
os.Remove("libgo2.h") os.Remove("libgo2.a")
os.Remove("testp") os.Remove("libgo2.h")
os.RemoveAll(filepath.Join(GOPATH, "pkg")) os.Remove("testp")
}() os.RemoveAll(filepath.Join(GOPATH, "pkg"))
}()
}
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2") cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
@ -345,12 +402,14 @@ func TestSignalForwardingExternal(t *testing.T) {
} }
checkSignalForwardingTest(t) checkSignalForwardingTest(t)
defer func() { if !testWork {
os.Remove("libgo2.a") defer func() {
os.Remove("libgo2.h") os.Remove("libgo2.a")
os.Remove("testp") os.Remove("libgo2.h")
os.RemoveAll(filepath.Join(GOPATH, "pkg")) os.Remove("testp")
}() os.RemoveAll(filepath.Join(GOPATH, "pkg"))
}()
}
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2") cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
@ -460,12 +519,14 @@ func TestOsSignal(t *testing.T) {
t.Skip("skipping signal test on Windows") t.Skip("skipping signal test on Windows")
} }
defer func() { if !testWork {
os.Remove("libgo3.a") defer func() {
os.Remove("libgo3.h") os.Remove("libgo3.a")
os.Remove("testp") os.Remove("libgo3.h")
os.RemoveAll(filepath.Join(GOPATH, "pkg")) os.Remove("testp")
}() os.RemoveAll(filepath.Join(GOPATH, "pkg"))
}()
}
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "./libgo3") cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "./libgo3")
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
@ -495,12 +556,14 @@ func TestSigaltstack(t *testing.T) {
t.Skip("skipping signal test on Windows") t.Skip("skipping signal test on Windows")
} }
defer func() { if !testWork {
os.Remove("libgo4.a") defer func() {
os.Remove("libgo4.h") os.Remove("libgo4.a")
os.Remove("testp") os.Remove("libgo4.h")
os.RemoveAll(filepath.Join(GOPATH, "pkg")) os.Remove("testp")
}() os.RemoveAll(filepath.Join(GOPATH, "pkg"))
}()
}
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "./libgo4") cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "./libgo4")
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
@ -544,13 +607,15 @@ func TestExtar(t *testing.T) {
t.Skip("shell scripts are not executable on iOS hosts") t.Skip("shell scripts are not executable on iOS hosts")
} }
defer func() { if !testWork {
os.Remove("libgo4.a") defer func() {
os.Remove("libgo4.h") os.Remove("libgo4.a")
os.Remove("testar") os.Remove("libgo4.h")
os.Remove("testar.ran") os.Remove("testar")
os.RemoveAll(filepath.Join(GOPATH, "pkg")) os.Remove("testar.ran")
}() os.RemoveAll(filepath.Join(GOPATH, "pkg"))
}()
}
os.Remove("testar") os.Remove("testar")
dir, err := os.Getwd() dir, err := os.Getwd()
@ -584,12 +649,22 @@ func TestPIE(t *testing.T) {
t.Skipf("skipping PIE test on %s", GOOS) t.Skipf("skipping PIE test on %s", GOOS)
} }
defer func() { if !testWork {
os.Remove("testp" + exeSuffix) defer func() {
os.RemoveAll(filepath.Join(GOPATH, "pkg")) os.Remove("testp" + exeSuffix)
}() os.RemoveAll(filepath.Join(GOPATH, "pkg"))
}()
}
cmd := exec.Command("go", "install", "-i", "-buildmode=c-archive", "./libgo") // Generate the p.h header file.
//
// 'go install -i -buildmode=c-archive ./libgo' would do that too, but that
// would also attempt to install transitive standard-library dependencies to
// GOROOT, and we cannot assume that GOROOT is writable. (A non-root user may
// be running this test in a GOROOT owned by root.)
genHeader(t, "p.h", "./p")
cmd := exec.Command("go", "install", "-buildmode=c-archive", "./libgo")
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
t.Logf("%s", out) t.Logf("%s", out)
t.Fatal(err) t.Fatal(err)
@ -669,11 +744,13 @@ func TestSIGPROF(t *testing.T) {
t.Parallel() t.Parallel()
defer func() { if !testWork {
os.Remove("testp6" + exeSuffix) defer func() {
os.Remove("libgo6.a") os.Remove("testp6" + exeSuffix)
os.Remove("libgo6.h") os.Remove("libgo6.a")
}() os.Remove("libgo6.h")
}()
}
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo6.a", "./libgo6") cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo6.a", "./libgo6")
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
@ -709,10 +786,12 @@ func TestCompileWithoutShared(t *testing.T) {
// For simplicity, reuse the signal forwarding test. // For simplicity, reuse the signal forwarding test.
checkSignalForwardingTest(t) checkSignalForwardingTest(t)
defer func() { if !testWork {
os.Remove("libgo2.a") defer func() {
os.Remove("libgo2.h") os.Remove("libgo2.a")
}() os.Remove("libgo2.h")
}()
}
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-gcflags=-shared=false", "-o", "libgo2.a", "./libgo2") cmd := exec.Command("go", "build", "-buildmode=c-archive", "-gcflags=-shared=false", "-o", "libgo2.a", "./libgo2")
t.Log(cmd.Args) t.Log(cmd.Args)
@ -751,7 +830,9 @@ func TestCompileWithoutShared(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer os.Remove(exe) if !testWork {
defer os.Remove(exe)
}
binArgs := append(cmdToRun(exe), "1") binArgs := append(cmdToRun(exe), "1")
t.Log(binArgs) t.Log(binArgs)
@ -769,14 +850,15 @@ func TestCompileWithoutShared(t *testing.T) {
} }
} }
// Test that installing a second time recreates the header files. // Test that installing a second time recreates the header file.
func TestCachedInstall(t *testing.T) { func TestCachedInstall(t *testing.T) {
defer os.RemoveAll(filepath.Join(GOPATH, "pkg")) if !testWork {
defer os.RemoveAll(filepath.Join(GOPATH, "pkg"))
}
h1 := filepath.Join(libgodir, "libgo.h") h := filepath.Join(libgodir, "libgo.h")
h2 := filepath.Join(libgodir, "p.h")
buildcmd := []string{"go", "install", "-i", "-buildmode=c-archive", "./libgo"} buildcmd := []string{"go", "install", "-buildmode=c-archive", "./libgo"}
cmd := exec.Command(buildcmd[0], buildcmd[1:]...) cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
t.Log(buildcmd) t.Log(buildcmd)
@ -785,17 +867,11 @@ func TestCachedInstall(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if _, err := os.Stat(h1); err != nil { if _, err := os.Stat(h); err != nil {
t.Errorf("libgo.h not installed: %v", err) t.Errorf("libgo.h not installed: %v", err)
} }
if _, err := os.Stat(h2); err != nil {
t.Errorf("p.h not installed: %v", err)
}
if err := os.Remove(h1); err != nil { if err := os.Remove(h); err != nil {
t.Fatal(err)
}
if err := os.Remove(h2); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -806,23 +882,22 @@ func TestCachedInstall(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if _, err := os.Stat(h1); err != nil { if _, err := os.Stat(h); err != nil {
t.Errorf("libgo.h not installed in second run: %v", err) t.Errorf("libgo.h not installed in second run: %v", err)
} }
if _, err := os.Stat(h2); err != nil {
t.Errorf("p.h not installed in second run: %v", err)
}
} }
// Issue 35294. // Issue 35294.
func TestManyCalls(t *testing.T) { func TestManyCalls(t *testing.T) {
t.Parallel() t.Parallel()
defer func() { if !testWork {
os.Remove("testp7" + exeSuffix) defer func() {
os.Remove("libgo7.a") os.Remove("testp7" + exeSuffix)
os.Remove("libgo7.h") os.Remove("libgo7.a")
}() os.Remove("libgo7.h")
}()
}
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo7.a", "./libgo7") cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo7.a", "./libgo7")
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {