Skip to content

Commit c02f3b8

Browse files
author
Bryan C. Mills
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent ea18a1c commit c02f3b8

File tree

1 file changed

+157
-82
lines changed

1 file changed

+157
-82
lines changed

misc/cgo/testcarchive/carchive_test.go

+157-82
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ var exeSuffix string
3636
var GOOS, GOARCH, GOPATH string
3737
var libgodir string
3838

39+
var testWork bool // If true, preserve temporary directories.
40+
3941
func TestMain(m *testing.M) {
42+
flag.BoolVar(&testWork, "testwork", false, "if true, log and preserve the test's temporary working directory")
4043
flag.Parse()
4144
if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" {
4245
fmt.Printf("SKIP - short mode and $GO_BUILDER_NAME not set\n")
@@ -54,7 +57,11 @@ func testMain(m *testing.M) int {
5457
if err != nil {
5558
log.Panic(err)
5659
}
57-
defer os.RemoveAll(GOPATH)
60+
if testWork {
61+
log.Println(GOPATH)
62+
} else {
63+
defer os.RemoveAll(GOPATH)
64+
}
5865
os.Setenv("GOPATH", GOPATH)
5966

6067
// Copy testdata into GOPATH/src/testarchive, along with a go.mod file
@@ -164,6 +171,38 @@ func cmdToRun(name string) []string {
164171
return []string{executor, name}
165172
}
166173

174+
// genHeader writes a C header file for the C-exported declarations found in .go
175+
// source files in dir.
176+
//
177+
// TODO(golang.org/issue/35715): This should be simpler.
178+
func genHeader(t *testing.T, header, dir string) {
179+
t.Helper()
180+
181+
// The 'cgo' command generates a number of additional artifacts,
182+
// but we're only interested in the header.
183+
// Shunt the rest of the outputs to a temporary directory.
184+
objDir, err := ioutil.TempDir(GOPATH, "_obj")
185+
if err != nil {
186+
t.Fatal(err)
187+
}
188+
defer os.RemoveAll(objDir)
189+
190+
files, err := filepath.Glob(filepath.Join(dir, "*.go"))
191+
if err != nil {
192+
t.Fatal(err)
193+
}
194+
195+
cmd := exec.Command("go", "tool", "cgo",
196+
"-objdir", objDir,
197+
"-exportheader", header)
198+
cmd.Args = append(cmd.Args, files...)
199+
t.Log(cmd.Args)
200+
if out, err := cmd.CombinedOutput(); err != nil {
201+
t.Logf("%s", out)
202+
t.Fatal(err)
203+
}
204+
}
205+
167206
func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) {
168207
t.Helper()
169208
cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
@@ -172,10 +211,12 @@ func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) {
172211
t.Logf("%s", out)
173212
t.Fatal(err)
174213
}
175-
defer func() {
176-
os.Remove(libgoa)
177-
os.Remove(libgoh)
178-
}()
214+
if !testWork {
215+
defer func() {
216+
os.Remove(libgoa)
217+
os.Remove(libgoh)
218+
}()
219+
}
179220

180221
ccArgs := append(cc, "-o", exe, "main.c")
181222
if GOOS == "windows" {
@@ -191,7 +232,9 @@ func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) {
191232
t.Logf("%s", out)
192233
t.Fatal(err)
193234
}
194-
defer os.Remove(exe)
235+
if !testWork {
236+
defer os.Remove(exe)
237+
}
195238

196239
binArgs := append(cmdToRun(exe), "arg1", "arg2")
197240
cmd = exec.Command(binArgs[0], binArgs[1:]...)
@@ -227,17 +270,27 @@ func checkLineComments(t *testing.T, hdrname string) {
227270
}
228271

229272
func TestInstall(t *testing.T) {
230-
defer os.RemoveAll(filepath.Join(GOPATH, "pkg"))
273+
if !testWork {
274+
defer os.RemoveAll(filepath.Join(GOPATH, "pkg"))
275+
}
231276

232277
libgoa := "libgo.a"
233278
if runtime.Compiler == "gccgo" {
234279
libgoa = "liblibgo.a"
235280
}
236281

282+
// Generate the p.h header file.
283+
//
284+
// 'go install -i -buildmode=c-archive ./libgo' would do that too, but that
285+
// would also attempt to install transitive standard-library dependencies to
286+
// GOROOT, and we cannot assume that GOROOT is writable. (A non-root user may
287+
// be running this test in a GOROOT owned by root.)
288+
genHeader(t, "p.h", "./p")
289+
237290
testInstall(t, "./testp1"+exeSuffix,
238291
filepath.Join(libgodir, libgoa),
239292
filepath.Join(libgodir, "libgo.h"),
240-
"go", "install", "-i", "-buildmode=c-archive", "./libgo")
293+
"go", "install", "-buildmode=c-archive", "./libgo")
241294

242295
// Test building libgo other than installing it.
243296
// Header files are now present.
@@ -259,12 +312,14 @@ func TestEarlySignalHandler(t *testing.T) {
259312
t.Skip("skipping signal test on Windows")
260313
}
261314

262-
defer func() {
263-
os.Remove("libgo2.a")
264-
os.Remove("libgo2.h")
265-
os.Remove("testp")
266-
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
267-
}()
315+
if !testWork {
316+
defer func() {
317+
os.Remove("libgo2.a")
318+
os.Remove("libgo2.h")
319+
os.Remove("testp")
320+
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
321+
}()
322+
}
268323

269324
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
270325
if out, err := cmd.CombinedOutput(); err != nil {
@@ -297,12 +352,14 @@ func TestEarlySignalHandler(t *testing.T) {
297352
func TestSignalForwarding(t *testing.T) {
298353
checkSignalForwardingTest(t)
299354

300-
defer func() {
301-
os.Remove("libgo2.a")
302-
os.Remove("libgo2.h")
303-
os.Remove("testp")
304-
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
305-
}()
355+
if !testWork {
356+
defer func() {
357+
os.Remove("libgo2.a")
358+
os.Remove("libgo2.h")
359+
os.Remove("testp")
360+
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
361+
}()
362+
}
306363

307364
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
308365
if out, err := cmd.CombinedOutput(); err != nil {
@@ -345,12 +402,14 @@ func TestSignalForwardingExternal(t *testing.T) {
345402
}
346403
checkSignalForwardingTest(t)
347404

348-
defer func() {
349-
os.Remove("libgo2.a")
350-
os.Remove("libgo2.h")
351-
os.Remove("testp")
352-
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
353-
}()
405+
if !testWork {
406+
defer func() {
407+
os.Remove("libgo2.a")
408+
os.Remove("libgo2.h")
409+
os.Remove("testp")
410+
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
411+
}()
412+
}
354413

355414
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
356415
if out, err := cmd.CombinedOutput(); err != nil {
@@ -460,12 +519,14 @@ func TestOsSignal(t *testing.T) {
460519
t.Skip("skipping signal test on Windows")
461520
}
462521

463-
defer func() {
464-
os.Remove("libgo3.a")
465-
os.Remove("libgo3.h")
466-
os.Remove("testp")
467-
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
468-
}()
522+
if !testWork {
523+
defer func() {
524+
os.Remove("libgo3.a")
525+
os.Remove("libgo3.h")
526+
os.Remove("testp")
527+
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
528+
}()
529+
}
469530

470531
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "./libgo3")
471532
if out, err := cmd.CombinedOutput(); err != nil {
@@ -495,12 +556,14 @@ func TestSigaltstack(t *testing.T) {
495556
t.Skip("skipping signal test on Windows")
496557
}
497558

498-
defer func() {
499-
os.Remove("libgo4.a")
500-
os.Remove("libgo4.h")
501-
os.Remove("testp")
502-
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
503-
}()
559+
if !testWork {
560+
defer func() {
561+
os.Remove("libgo4.a")
562+
os.Remove("libgo4.h")
563+
os.Remove("testp")
564+
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
565+
}()
566+
}
504567

505568
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "./libgo4")
506569
if out, err := cmd.CombinedOutput(); err != nil {
@@ -544,13 +607,15 @@ func TestExtar(t *testing.T) {
544607
t.Skip("shell scripts are not executable on iOS hosts")
545608
}
546609

547-
defer func() {
548-
os.Remove("libgo4.a")
549-
os.Remove("libgo4.h")
550-
os.Remove("testar")
551-
os.Remove("testar.ran")
552-
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
553-
}()
610+
if !testWork {
611+
defer func() {
612+
os.Remove("libgo4.a")
613+
os.Remove("libgo4.h")
614+
os.Remove("testar")
615+
os.Remove("testar.ran")
616+
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
617+
}()
618+
}
554619

555620
os.Remove("testar")
556621
dir, err := os.Getwd()
@@ -584,12 +649,22 @@ func TestPIE(t *testing.T) {
584649
t.Skipf("skipping PIE test on %s", GOOS)
585650
}
586651

587-
defer func() {
588-
os.Remove("testp" + exeSuffix)
589-
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
590-
}()
652+
if !testWork {
653+
defer func() {
654+
os.Remove("testp" + exeSuffix)
655+
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
656+
}()
657+
}
658+
659+
// Generate the p.h header file.
660+
//
661+
// 'go install -i -buildmode=c-archive ./libgo' would do that too, but that
662+
// would also attempt to install transitive standard-library dependencies to
663+
// GOROOT, and we cannot assume that GOROOT is writable. (A non-root user may
664+
// be running this test in a GOROOT owned by root.)
665+
genHeader(t, "p.h", "./p")
591666

592-
cmd := exec.Command("go", "install", "-i", "-buildmode=c-archive", "./libgo")
667+
cmd := exec.Command("go", "install", "-buildmode=c-archive", "./libgo")
593668
if out, err := cmd.CombinedOutput(); err != nil {
594669
t.Logf("%s", out)
595670
t.Fatal(err)
@@ -669,11 +744,13 @@ func TestSIGPROF(t *testing.T) {
669744

670745
t.Parallel()
671746

672-
defer func() {
673-
os.Remove("testp6" + exeSuffix)
674-
os.Remove("libgo6.a")
675-
os.Remove("libgo6.h")
676-
}()
747+
if !testWork {
748+
defer func() {
749+
os.Remove("testp6" + exeSuffix)
750+
os.Remove("libgo6.a")
751+
os.Remove("libgo6.h")
752+
}()
753+
}
677754

678755
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo6.a", "./libgo6")
679756
if out, err := cmd.CombinedOutput(); err != nil {
@@ -709,10 +786,12 @@ func TestCompileWithoutShared(t *testing.T) {
709786
// For simplicity, reuse the signal forwarding test.
710787
checkSignalForwardingTest(t)
711788

712-
defer func() {
713-
os.Remove("libgo2.a")
714-
os.Remove("libgo2.h")
715-
}()
789+
if !testWork {
790+
defer func() {
791+
os.Remove("libgo2.a")
792+
os.Remove("libgo2.h")
793+
}()
794+
}
716795

717796
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-gcflags=-shared=false", "-o", "libgo2.a", "./libgo2")
718797
t.Log(cmd.Args)
@@ -751,7 +830,9 @@ func TestCompileWithoutShared(t *testing.T) {
751830
if err != nil {
752831
t.Fatal(err)
753832
}
754-
defer os.Remove(exe)
833+
if !testWork {
834+
defer os.Remove(exe)
835+
}
755836

756837
binArgs := append(cmdToRun(exe), "1")
757838
t.Log(binArgs)
@@ -769,14 +850,15 @@ func TestCompileWithoutShared(t *testing.T) {
769850
}
770851
}
771852

772-
// Test that installing a second time recreates the header files.
853+
// Test that installing a second time recreates the header file.
773854
func TestCachedInstall(t *testing.T) {
774-
defer os.RemoveAll(filepath.Join(GOPATH, "pkg"))
855+
if !testWork {
856+
defer os.RemoveAll(filepath.Join(GOPATH, "pkg"))
857+
}
775858

776-
h1 := filepath.Join(libgodir, "libgo.h")
777-
h2 := filepath.Join(libgodir, "p.h")
859+
h := filepath.Join(libgodir, "libgo.h")
778860

779-
buildcmd := []string{"go", "install", "-i", "-buildmode=c-archive", "./libgo"}
861+
buildcmd := []string{"go", "install", "-buildmode=c-archive", "./libgo"}
780862

781863
cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
782864
t.Log(buildcmd)
@@ -785,17 +867,11 @@ func TestCachedInstall(t *testing.T) {
785867
t.Fatal(err)
786868
}
787869

788-
if _, err := os.Stat(h1); err != nil {
870+
if _, err := os.Stat(h); err != nil {
789871
t.Errorf("libgo.h not installed: %v", err)
790872
}
791-
if _, err := os.Stat(h2); err != nil {
792-
t.Errorf("p.h not installed: %v", err)
793-
}
794873

795-
if err := os.Remove(h1); err != nil {
796-
t.Fatal(err)
797-
}
798-
if err := os.Remove(h2); err != nil {
874+
if err := os.Remove(h); err != nil {
799875
t.Fatal(err)
800876
}
801877

@@ -806,23 +882,22 @@ func TestCachedInstall(t *testing.T) {
806882
t.Fatal(err)
807883
}
808884

809-
if _, err := os.Stat(h1); err != nil {
885+
if _, err := os.Stat(h); err != nil {
810886
t.Errorf("libgo.h not installed in second run: %v", err)
811887
}
812-
if _, err := os.Stat(h2); err != nil {
813-
t.Errorf("p.h not installed in second run: %v", err)
814-
}
815888
}
816889

817890
// Issue 35294.
818891
func TestManyCalls(t *testing.T) {
819892
t.Parallel()
820893

821-
defer func() {
822-
os.Remove("testp7" + exeSuffix)
823-
os.Remove("libgo7.a")
824-
os.Remove("libgo7.h")
825-
}()
894+
if !testWork {
895+
defer func() {
896+
os.Remove("testp7" + exeSuffix)
897+
os.Remove("libgo7.a")
898+
os.Remove("libgo7.h")
899+
}()
900+
}
826901

827902
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo7.a", "./libgo7")
828903
if out, err := cmd.CombinedOutput(); err != nil {

0 commit comments

Comments
 (0)