Skip to content

Commit 6d5f8b1

Browse files
author
Bryan C. Mills
committed
misc/cgo/testshared: do not write to GOROOT
Instead of installing shared libraries to GOROOT/pkg, clone the necessary files into a new GOROOT and run there. Given that we now have a build cache, ideally we should not need to install into GOROOT/pkg at all, but we can't fix that during the 1.14 code freeze. Updates #28387 Updates #28553 Updates #30316 Change-Id: I83084a8ca29a5dffcd586c7fccc3f172cac57cc6 Reviewed-on: https://go-review.googlesource.com/c/go/+/208482 Run-TryBot: Bryan C. Mills <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent e16f64c commit 6d5f8b1

File tree

1 file changed

+127
-49
lines changed

1 file changed

+127
-49
lines changed

misc/cgo/testshared/shared_test.go

+127-49
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,32 @@ import (
99
"bytes"
1010
"debug/elf"
1111
"encoding/binary"
12-
"errors"
1312
"flag"
1413
"fmt"
1514
"go/build"
1615
"io"
1716
"io/ioutil"
1817
"log"
19-
"math/rand"
2018
"os"
2119
"os/exec"
2220
"path/filepath"
2321
"regexp"
2422
"runtime"
23+
"sort"
2524
"strings"
2625
"testing"
2726
"time"
2827
)
2928

30-
var gopathInstallDir, gorootInstallDir, suffix string
29+
var gopathInstallDir, gorootInstallDir string
3130

3231
// This is the smallest set of packages we can link into a shared
3332
// library (runtime/cgo is built implicitly).
3433
var minpkgs = []string{"runtime", "sync/atomic"}
3534
var soname = "libruntime,sync-atomic.so"
3635

3736
var testX = flag.Bool("testx", false, "if true, pass -x to 'go' subcommands invoked by the test")
37+
var testWork = flag.Bool("testwork", false, "if true, log and do not delete the temporary working directory")
3838

3939
// run runs a command and calls t.Errorf if it fails.
4040
func run(t *testing.T, msg string, args ...string) {
@@ -47,7 +47,7 @@ func run(t *testing.T, msg string, args ...string) {
4747
// goCmd invokes the go tool with the installsuffix set up by TestMain. It calls
4848
// t.Fatalf if the command fails.
4949
func goCmd(t *testing.T, args ...string) string {
50-
newargs := []string{args[0], "-installsuffix=" + suffix}
50+
newargs := []string{args[0]}
5151
if *testX {
5252
newargs = append(newargs, "-x")
5353
}
@@ -67,7 +67,8 @@ func goCmd(t *testing.T, args ...string) string {
6767
t.Helper()
6868
t.Fatalf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, stderr)
6969
} else {
70-
log.Fatalf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, stderr)
70+
// Panic instead of using log.Fatalf so that deferred cleanup may run in testMain.
71+
log.Panicf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, stderr)
7172
}
7273
}
7374
if testing.Verbose() && t != nil {
@@ -81,73 +82,61 @@ func goCmd(t *testing.T, args ...string) string {
8182

8283
// TestMain calls testMain so that the latter can use defer (TestMain exits with os.Exit).
8384
func testMain(m *testing.M) (int, error) {
84-
// Because go install -buildmode=shared $standard_library_package always
85-
// installs into $GOROOT, here are some gymnastics to come up with a unique
86-
// installsuffix to use in this test that we can clean up afterwards.
87-
myContext := build.Default
88-
runtimeP, err := myContext.Import("runtime", ".", build.ImportComment)
89-
if err != nil {
90-
return 0, fmt.Errorf("import failed: %v", err)
91-
}
92-
for i := 0; i < 10000; i++ {
93-
try := fmt.Sprintf("%s_%d_dynlink", runtimeP.PkgTargetRoot, rand.Int63())
94-
err = os.Mkdir(try, 0700)
95-
if os.IsExist(err) {
96-
continue
97-
}
98-
if err == nil {
99-
gorootInstallDir = try
100-
}
101-
break
102-
}
85+
workDir, err := ioutil.TempDir("", "shared_test")
10386
if err != nil {
104-
return 0, fmt.Errorf("can't create temporary directory: %v", err)
87+
return 0, err
10588
}
106-
if gorootInstallDir == "" {
107-
return 0, errors.New("could not create temporary directory after 10000 tries")
89+
if *testWork || testing.Verbose() {
90+
fmt.Printf("+ mkdir -p %s\n", workDir)
10891
}
109-
if testing.Verbose() {
110-
fmt.Printf("+ mkdir -p %s\n", gorootInstallDir)
92+
if !*testWork {
93+
defer os.RemoveAll(workDir)
11194
}
112-
defer os.RemoveAll(gorootInstallDir)
11395

11496
// Some tests need to edit the source in GOPATH, so copy this directory to a
11597
// temporary directory and chdir to that.
116-
gopath, err := ioutil.TempDir("", "testshared")
98+
gopath := filepath.Join(workDir, "gopath")
99+
modRoot, err := cloneTestdataModule(gopath)
117100
if err != nil {
118-
return 0, fmt.Errorf("TempDir failed: %v", err)
119-
}
120-
if testing.Verbose() {
121-
fmt.Printf("+ mkdir -p %s\n", gopath)
122-
}
123-
defer os.RemoveAll(gopath)
124-
125-
modRoot := filepath.Join(gopath, "src", "testshared")
126-
if err := overlayDir(modRoot, "testdata"); err != nil {
127101
return 0, err
128102
}
129103
if testing.Verbose() {
104+
fmt.Printf("+ export GOPATH=%s\n", gopath)
130105
fmt.Printf("+ cd %s\n", modRoot)
131106
}
107+
os.Setenv("GOPATH", gopath)
132108
os.Chdir(modRoot)
133109
os.Setenv("PWD", modRoot)
134-
if err := ioutil.WriteFile("go.mod", []byte("module testshared\n"), 0666); err != nil {
110+
111+
// The test also needs to install libraries into GOROOT/pkg, so copy the
112+
// subset of GOROOT that we need.
113+
//
114+
// TODO(golang.org/issue/28553): Rework -buildmode=shared so that it does not
115+
// need to write to GOROOT.
116+
goroot := filepath.Join(workDir, "goroot")
117+
if err := cloneGOROOTDeps(goroot); err != nil {
135118
return 0, err
136119
}
137-
138-
os.Setenv("GOPATH", gopath)
139120
if testing.Verbose() {
140-
fmt.Printf("+ export GOPATH=%s\n", gopath)
121+
fmt.Fprintf(os.Stderr, "+ export GOROOT=%s\n", goroot)
141122
}
123+
os.Setenv("GOROOT", goroot)
124+
125+
myContext := build.Default
126+
myContext.GOROOT = goroot
142127
myContext.GOPATH = gopath
128+
runtimeP, err := myContext.Import("runtime", ".", build.ImportComment)
129+
if err != nil {
130+
return 0, fmt.Errorf("import failed: %v", err)
131+
}
132+
gorootInstallDir = runtimeP.PkgTargetRoot + "_dynlink"
143133

144134
// All tests depend on runtime being built into a shared library. Because
145135
// that takes a few seconds, do it here and have all tests use the version
146136
// built here.
147-
suffix = strings.Split(filepath.Base(gorootInstallDir), "_")[2]
148137
goCmd(nil, append([]string{"install", "-buildmode=shared"}, minpkgs...)...)
149138

150-
myContext.InstallSuffix = suffix + "_dynlink"
139+
myContext.InstallSuffix = "_dynlink"
151140
depP, err := myContext.Import("./depBase", ".", build.ImportComment)
152141
if err != nil {
153142
return 0, fmt.Errorf("import failed: %v", err)
@@ -175,6 +164,75 @@ func TestMain(m *testing.M) {
175164
os.Exit(exitCode)
176165
}
177166

167+
// cloneTestdataModule clones the packages from src/testshared into gopath.
168+
// It returns the directory within gopath at which the module root is located.
169+
func cloneTestdataModule(gopath string) (string, error) {
170+
modRoot := filepath.Join(gopath, "src", "testshared")
171+
if err := overlayDir(modRoot, "testdata"); err != nil {
172+
return "", err
173+
}
174+
if err := ioutil.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module testshared\n"), 0644); err != nil {
175+
return "", err
176+
}
177+
return modRoot, nil
178+
}
179+
180+
// cloneGOROOTDeps copies (or symlinks) the portions of GOROOT/src and
181+
// GOROOT/pkg relevant to this test into the given directory.
182+
// It must be run from within the testdata module.
183+
func cloneGOROOTDeps(goroot string) error {
184+
oldGOROOT := strings.TrimSpace(goCmd(nil, "env", "GOROOT"))
185+
if oldGOROOT == "" {
186+
return fmt.Errorf("go env GOROOT returned an empty string")
187+
}
188+
189+
// Before we clone GOROOT, figure out which packages we need to copy over.
190+
listArgs := []string{
191+
"list",
192+
"-deps",
193+
"-f", "{{if and .Standard (not .ForTest)}}{{.ImportPath}}{{end}}",
194+
}
195+
stdDeps := goCmd(nil, append(listArgs, minpkgs...)...)
196+
testdataDeps := goCmd(nil, append(listArgs, "-test", "./...")...)
197+
198+
pkgs := append(strings.Split(strings.TrimSpace(stdDeps), "\n"),
199+
strings.Split(strings.TrimSpace(testdataDeps), "\n")...)
200+
sort.Strings(pkgs)
201+
var pkgRoots []string
202+
for _, pkg := range pkgs {
203+
parentFound := false
204+
for _, prev := range pkgRoots {
205+
if strings.HasPrefix(pkg, prev) {
206+
// We will copy in the source for pkg when we copy in prev.
207+
parentFound = true
208+
break
209+
}
210+
}
211+
if !parentFound {
212+
pkgRoots = append(pkgRoots, pkg)
213+
}
214+
}
215+
216+
gorootDirs := []string{
217+
"pkg/tool",
218+
"pkg/include",
219+
}
220+
for _, pkg := range pkgRoots {
221+
gorootDirs = append(gorootDirs, filepath.Join("src", pkg))
222+
}
223+
224+
for _, dir := range gorootDirs {
225+
if testing.Verbose() {
226+
fmt.Fprintf(os.Stderr, "+ cp -r %s %s\n", filepath.Join(goroot, dir), filepath.Join(oldGOROOT, dir))
227+
}
228+
if err := overlayDir(filepath.Join(goroot, dir), filepath.Join(oldGOROOT, dir)); err != nil {
229+
return err
230+
}
231+
}
232+
233+
return nil
234+
}
235+
178236
// The shared library was built at the expected location.
179237
func TestSOBuilt(t *testing.T) {
180238
_, err := os.Stat(filepath.Join(gorootInstallDir, soname))
@@ -223,6 +281,7 @@ func TestNoTextrel(t *testing.T) {
223281
}
224282

225283
// The shared library does not contain symbols called ".dup"
284+
// (See golang.org/issue/14841.)
226285
func TestNoDupSymbols(t *testing.T) {
227286
sopath := filepath.Join(gorootInstallDir, soname)
228287
f, err := elf.Open(sopath)
@@ -699,7 +758,7 @@ func resetFileStamps() {
699758
}
700759
reset := func(path string) {
701760
if err := filepath.Walk(path, chtime); err != nil {
702-
log.Fatalf("resetFileStamps failed: %v", err)
761+
log.Panicf("resetFileStamps failed: %v", err)
703762
}
704763

705764
}
@@ -712,6 +771,7 @@ func resetFileStamps() {
712771
// touch changes path and returns a function that changes it back.
713772
// It also sets the time of the file, so that we can see if it is rewritten.
714773
func touch(t *testing.T, path string) (cleanup func()) {
774+
t.Helper()
715775
data, err := ioutil.ReadFile(path)
716776
if err != nil {
717777
t.Fatal(err)
@@ -740,14 +800,32 @@ func touch(t *testing.T, path string) (cleanup func()) {
740800
// assume it's a text file
741801
data = append(data, '\n')
742802
}
743-
if err := ioutil.WriteFile(path, data, 0666); err != nil {
803+
804+
// If the file is still a symlink from an overlay, delete it so that we will
805+
// replace it with a regular file instead of overwriting the symlinked one.
806+
fi, err := os.Lstat(path)
807+
if err == nil && !fi.Mode().IsRegular() {
808+
fi, err = os.Stat(path)
809+
if err := os.Remove(path); err != nil {
810+
t.Fatal(err)
811+
}
812+
}
813+
if err != nil {
814+
t.Fatal(err)
815+
}
816+
817+
// If we're replacing a symlink to a read-only file, make the new file
818+
// user-writable.
819+
perm := fi.Mode().Perm() | 0200
820+
821+
if err := ioutil.WriteFile(path, data, perm); err != nil {
744822
t.Fatal(err)
745823
}
746824
if err := os.Chtimes(path, nearlyNew, nearlyNew); err != nil {
747825
t.Fatal(err)
748826
}
749827
return func() {
750-
if err := ioutil.WriteFile(path, old, 0666); err != nil {
828+
if err := ioutil.WriteFile(path, old, perm); err != nil {
751829
t.Fatal(err)
752830
}
753831
}

0 commit comments

Comments
 (0)