cmd/go: in module mode, populate PackagePublic.Root with the module root

'go test' uses the Root field to determine the set of files that
invalidate test results, and there is no other sensible meaning of
“root” for code within a module.

Fixes #29111

Change-Id: Icf1be90a26d22665613e42cb968087b63c36e74c
Reviewed-on: https://go-review.googlesource.com/c/go/+/154100
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go
index 35b0790..4eb4ba6 100644
--- a/src/cmd/go/internal/load/pkg.go
+++ b/src/cmd/go/internal/load/pkg.go
@@ -64,7 +64,7 @@
 	Doc           string                `json:",omitempty"` // package documentation string
 	Target        string                `json:",omitempty"` // installed target for this package (may be executable)
 	Shlib         string                `json:",omitempty"` // the shared library that contains this package (only set when -linkshared)
-	Root          string                `json:",omitempty"` // Go root or Go path dir containing this package
+	Root          string                `json:",omitempty"` // Go root, Go path dir, or module root dir containing this package
 	ConflictDir   string                `json:",omitempty"` // Dir is hidden by this other directory
 	ForTest       string                `json:",omitempty"` // package is only for use in named test
 	Export        string                `json:",omitempty"` // file containing export data (set by go list -export)
@@ -647,6 +647,11 @@
 				buildMode = build.ImportComment
 			}
 			data.p, data.err = cfg.BuildContext.ImportDir(r.dir, buildMode)
+			if data.p.Root == "" && cfg.ModulesEnabled {
+				if info := ModPackageModuleInfo(path); info != nil {
+					data.p.Root = info.Dir
+				}
+			}
 		} else if r.err != nil {
 			data.p = new(build.Package)
 			data.err = fmt.Errorf("unknown import path %q: %v", r.path, r.err)
diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go
index eed2d43..cc7c456 100644
--- a/src/cmd/go/internal/test/test.go
+++ b/src/cmd/go/internal/test/test.go
@@ -1250,6 +1250,15 @@
 		return false
 	}
 
+	if a.Package.Root == "" {
+		// Caching does not apply to tests outside of any module, GOPATH, or GOROOT.
+		if cache.DebugTest {
+			fmt.Fprintf(os.Stderr, "testcache: caching disabled for package outside of module root, GOPATH, or GOROOT: %s\n", a.Package.ImportPath)
+		}
+		c.disableCache = true
+		return false
+	}
+
 	var cacheArgs []string
 	for _, arg := range testArgs {
 		i := strings.Index(arg, "=")
@@ -1437,8 +1446,8 @@
 			if !filepath.IsAbs(name) {
 				name = filepath.Join(pwd, name)
 			}
-			if !inDir(name, a.Package.Root) {
-				// Do not recheck files outside the GOPATH or GOROOT root.
+			if a.Package.Root == "" || !inDir(name, a.Package.Root) {
+				// Do not recheck files outside the module, GOPATH, or GOROOT root.
 				break
 			}
 			fmt.Fprintf(h, "stat %s %x\n", name, hashStat(name))
@@ -1446,8 +1455,8 @@
 			if !filepath.IsAbs(name) {
 				name = filepath.Join(pwd, name)
 			}
-			if !inDir(name, a.Package.Root) {
-				// Do not recheck files outside the GOPATH or GOROOT root.
+			if a.Package.Root == "" || !inDir(name, a.Package.Root) {
+				// Do not recheck files outside the module, GOPATH, or GOROOT root.
 				break
 			}
 			fh, err := hashOpen(name)
diff --git a/src/cmd/go/testdata/script/build_cache_output.txt b/src/cmd/go/testdata/script/build_cache_output.txt
index 89e3ff0..0d94bf6 100644
--- a/src/cmd/go/testdata/script/build_cache_output.txt
+++ b/src/cmd/go/testdata/script/build_cache_output.txt
@@ -1,4 +1,5 @@
 env GO111MODULE=off
+env GODEBUG=gocachetest=1
 
 [!gc] skip
 [short] skip # clears cache, rebuilds too much
@@ -32,7 +33,7 @@
 stderr '\d+ symbols' # from linker
 
 # Running a test should run the compiler, linker, and the test the first time.
-go test -v -x -gcflags=-m -ldflags=-v p_test.go
+go test -v -x -gcflags=-m -ldflags=-v p
 stderr 'compile( |\.exe"?)'
 stderr 'p_test.go:.*can inline Test' # from compile of p_test
 stderr 'testmain\.go:.*inlin' # from compile of testmain
@@ -42,7 +43,7 @@
 stdout 'TEST' # from test
 
 # ... but not the second, even though it still prints the compiler, linker, and test output.
-go test -v -x -gcflags=-m -ldflags=-v p_test.go
+go test -v -x -gcflags=-m -ldflags=-v p
 ! stderr 'compile( |\.exe"?)'
 stderr 'p_test.go:.*can inline Test' # from compile of p_test
 stderr 'testmain\.go:.*inlin' # from compile of testmain
@@ -60,7 +61,7 @@
 package main
 func main() {}
 
--- p_test.go --
+-- p/p_test.go --
 package p
 import "testing"
 func Test(t *testing.T) {println("TEST")}
diff --git a/src/cmd/go/testdata/script/mod_test_cached.txt b/src/cmd/go/testdata/script/mod_test_cached.txt
new file mode 100644
index 0000000..ffd573c
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_test_cached.txt
@@ -0,0 +1,77 @@
+[short] skip
+
+env GO111MODULE=on
+env GOCACHE=$WORK/gocache
+env GODEBUG=gocachetest=1
+
+# The first run of a test should not be cached.
+# The second run should be.
+go test -run=WriteTmp .
+! stdout '(cached)'
+go test -run=WriteTmp .
+stdout '(cached)'
+
+# 'go test' without arguments should never be cached.
+go test -run=WriteTmp
+! stdout '(cached)'
+go test -run=WriteTmp
+! stdout '(cached)'
+
+# We should never cache a test run from command-line files.
+go test -run=WriteTmp ./foo_test.go
+! stdout '(cached)'
+go test -run=WriteTmp ./foo_test.go
+! stdout '(cached)'
+
+[!exec:sleep] stop
+# The go command refuses to cache access to files younger than 2s, so sleep that long.
+exec sleep 2
+
+# Touching a file that the test reads from within its testdata should invalidate the cache.
+go test -run=ReadTestdata .
+! stdout '(cached)'
+go test -run=ReadTestdata .
+stdout '(cached)'
+cp testdata/bar.txt testdata/foo.txt
+go test -run=ReadTestdata .
+! stdout '(cached)'
+
+-- go.mod --
+module golang.org/issue/29111/foo
+
+-- foo.go --
+package foo
+
+-- testdata/foo.txt --
+foo
+-- testdata/bar.txt --
+bar
+
+-- foo_test.go --
+package foo_test
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+func TestWriteTmp(t *testing.T) {
+	dir, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+	err = ioutil.WriteFile(filepath.Join(dir, "x"), nil, 0666)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestReadTestdata(t *testing.T) {
+	_, err := ioutil.ReadFile("testdata/foo.txt")
+	if err != nil {
+		t.Fatal(err)
+	}
+}