test: make runindir tests pass regardless of whether module mode is in use

The "runindir" tests used "go run", but relied on relative imports
(which are not supported by "go run" in module mode). Instead, such
tests must use fully-qualified imports, which require either a go.mod
file (in module mode) or that the package be in an appropriate
subdirectory of GOPATH/src (in GOPATH mode).

To set up such a directory, we use yet another copy of the same
overlayDir function currently found in the misc subdirectory of this
repository.

Fixes #33912
Updates #30228

Change-Id: If3d7ea2f7942ba496d98aaaf24a90bcdcf4df9f7
Reviewed-on: https://go-review.googlesource.com/c/go/+/225205
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/test/fixedbugs/issue29612.dir/main.go b/test/fixedbugs/issue29612.dir/main.go
index d860eaa..97415c4 100644
--- a/test/fixedbugs/issue29612.dir/main.go
+++ b/test/fixedbugs/issue29612.dir/main.go
@@ -12,8 +12,8 @@
 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 bd63d71..95b94b7 100644
--- a/test/run.go
+++ b/test/run.go
@@ -607,20 +607,23 @@
 		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 @@
 		}
 
 	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 @@
 		// 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 @@
 		defer func() {
 			<-rungatec
 		}()
-		useTmp = false
+		runInDir = ""
 		cmd := []string{goTool(), "run", goGcflags()}
 		if *linkshared {
 			cmd = append(cmd, "-linkshared")
@@ -1084,7 +1105,7 @@
 	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 @@
 	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 @@
 	}
 	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
+	})
+}