diff --git a/test/fixedbugs/issue29612.dir/main.go b/test/fixedbugs/issue29612.dir/main.go index d860eaac7e93f..97415c445fa34 100644 --- a/test/fixedbugs/issue29612.dir/main.go +++ b/test/fixedbugs/issue29612.dir/main.go @@ -12,8 +12,8 @@ package main import ( "fmt" - ssa1 "./p1/ssa" - ssa2 "./p2/ssa" + ssa1 "issue29612.dir/p1/ssa" + ssa2 "issue29612.dir/p2/ssa" ) func main() { diff --git a/test/run.go b/test/run.go index bd63d7142bc50..95b94b7277281 100644 --- a/test/run.go +++ b/test/run.go @@ -607,20 +607,23 @@ func (t *test) run() { os.Setenv("GOARCH", runtime.GOARCH) } - useTmp := true - runInDir := false + var ( + runInDir = t.tempDir + tempDirIsGOPATH = false + ) runcmd := func(args ...string) ([]byte, error) { cmd := exec.Command(args[0], args[1:]...) var buf bytes.Buffer cmd.Stdout = &buf cmd.Stderr = &buf - cmd.Env = os.Environ() - if useTmp { - cmd.Dir = t.tempDir - cmd.Env = envForDir(cmd.Dir) + cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=") + if runInDir != "" { + cmd.Dir = runInDir + // Set PWD to match Dir to speed up os.Getwd in the child process. + cmd.Env = append(cmd.Env, "PWD="+cmd.Dir) } - if runInDir { - cmd.Dir = t.goDirName() + if tempDirIsGOPATH { + cmd.Env = append(cmd.Env, "GOPATH="+t.tempDir) } var err error @@ -863,13 +866,31 @@ func (t *test) run() { } case "runindir": - // run "go run ." in t.goDirName() - // It's used when test requires go build and run the binary success. - // Example when long import path require (see issue29612.dir) or test - // contains assembly file (see issue15609.dir). - // Verify the expected output. - useTmp = false - runInDir = true + // Make a shallow copy of t.goDirName() in its own module and GOPATH, and + // run "go run ." in it. The module path (and hence import path prefix) of + // the copy is equal to the basename of the source directory. + // + // It's used when test a requires a full 'go build' in order to compile + // the sources, such as when importing multiple packages (issue29612.dir) + // or compiling a package containing assembly files (see issue15609.dir), + // but still needs to be run to verify the expected output. + tempDirIsGOPATH = true + srcDir := t.goDirName() + modName := filepath.Base(srcDir) + gopathSrcDir := filepath.Join(t.tempDir, "src", modName) + runInDir = gopathSrcDir + + if err := overlayDir(gopathSrcDir, srcDir); err != nil { + t.err = err + return + } + + modFile := fmt.Sprintf("module %s\ngo 1.14\n", modName) + if err := ioutil.WriteFile(filepath.Join(gopathSrcDir, "go.mod"), []byte(modFile), 0666); err != nil { + t.err = err + return + } + cmd := []string{goTool(), "run", goGcflags()} if *linkshared { cmd = append(cmd, "-linkshared") @@ -1003,7 +1024,7 @@ func (t *test) run() { // Run Go file if no special go command flags are provided; // otherwise build an executable and run it. // Verify the output. - useTmp = false + runInDir = "" var out []byte var err error if len(flags)+len(args) == 0 && goGcflagsIsEmpty() && !*linkshared && goarch == runtime.GOARCH && goos == runtime.GOOS { @@ -1051,7 +1072,7 @@ func (t *test) run() { defer func() { <-rungatec }() - useTmp = false + runInDir = "" cmd := []string{goTool(), "run", goGcflags()} if *linkshared { cmd = append(cmd, "-linkshared") @@ -1084,7 +1105,7 @@ func (t *test) run() { case "errorcheckoutput": // Run Go file and write its output into temporary Go file. // Compile and errorCheck generated Go file. - useTmp = false + runInDir = "" cmd := []string{goTool(), "run", goGcflags()} if *linkshared { cmd = append(cmd, "-linkshared") @@ -1752,23 +1773,6 @@ func checkShouldTest() { assert(shouldTest("// +build !windows !plan9", "windows", "amd64")) } -// envForDir returns a copy of the environment -// suitable for running in the given directory. -// The environment is the current process's environment -// but with an updated $PWD, so that an os.Getwd in the -// child will be faster. -func envForDir(dir string) []string { - env := os.Environ() - for i, kv := range env { - if strings.HasPrefix(kv, "PWD=") { - env[i] = "PWD=" + dir - return env - } - } - env = append(env, "PWD="+dir) - return env -} - func getenv(key, def string) string { value := os.Getenv(key) if value != "" { @@ -1776,3 +1780,66 @@ func getenv(key, def string) string { } return def } + +// overlayDir makes a minimal-overhead copy of srcRoot in which new files may be added. +func overlayDir(dstRoot, srcRoot string) error { + dstRoot = filepath.Clean(dstRoot) + if err := os.MkdirAll(dstRoot, 0777); err != nil { + return err + } + + srcRoot, err := filepath.Abs(srcRoot) + if err != nil { + return err + } + + return filepath.Walk(srcRoot, func(srcPath string, info os.FileInfo, err error) error { + if err != nil || srcPath == srcRoot { + return err + } + + suffix := strings.TrimPrefix(srcPath, srcRoot) + for len(suffix) > 0 && suffix[0] == filepath.Separator { + suffix = suffix[1:] + } + dstPath := filepath.Join(dstRoot, suffix) + + perm := info.Mode() & os.ModePerm + if info.Mode()&os.ModeSymlink != 0 { + info, err = os.Stat(srcPath) + if err != nil { + return err + } + perm = info.Mode() & os.ModePerm + } + + // Always copy directories (don't symlink them). + // If we add a file in the overlay, we don't want to add it in the original. + if info.IsDir() { + return os.MkdirAll(dstPath, perm|0200) + } + + // If the OS supports symlinks, use them instead of copying bytes. + if err := os.Symlink(srcPath, dstPath); err == nil { + return nil + } + + // Otherwise, copy the bytes. + src, err := os.Open(srcPath) + if err != nil { + return err + } + defer src.Close() + + dst, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm) + if err != nil { + return err + } + + _, err = io.Copy(dst, src) + if closeErr := dst.Close(); err == nil { + err = closeErr + } + return err + }) +}