go/packages/packagestest: Testing with multiple drivers

This extracts some of the test code from packages, and adds the ability to run
the same test with multiple drivers.
This should generally be useful for all tools that run on top of go/packages as
well when writing tests for them.

Change-Id: I88c596ad07c0782270c5798d92ae29f7549943cf
Reviewed-on: https://go-review.googlesource.com/c/140118
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Matloob <matloob@golang.org>
diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go
index 85fac18..778d631 100644
--- a/go/packages/packages_test.go
+++ b/go/packages/packages_test.go
@@ -13,7 +13,6 @@
 	"go/parser"
 	"go/token"
 	"go/types"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"reflect"
@@ -23,13 +22,9 @@
 	"testing"
 
 	"golang.org/x/tools/go/packages"
+	"golang.org/x/tools/go/packages/packagestest"
 )
 
-func init() {
-	// Insulate the tests from the users' environment.
-	os.Setenv("GOPACKAGESDRIVER", "off")
-}
-
 // TODO(matloob): remove this once Go 1.12 is released as we will end support
 // for versions of go list before Go 1.10.4.
 var usesOldGolist = false
@@ -71,27 +66,26 @@
 	}
 }
 
-func TestLoadImportsGraph(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go":             `package a; const A = 1`,
-		"src/b/b.go":             `package b; import ("a"; _ "errors"); var B = a.A`,
-		"src/c/c.go":             `package c; import (_ "b"; _ "unsafe")`,
-		"src/c/c2.go":            "// +build ignore\n\n" + `package c; import _ "fmt"`,
-		"src/subdir/d/d.go":      `package d`,
-		"src/subdir/d/d_test.go": `package d; import _ "math/bits"`,
-		"src/subdir/d/x_test.go": `package d_test; import _ "subdir/d"`, // TODO(adonovan): test bad import here
-		"src/subdir/e/d.go":      `package e`,
-		"src/e/e.go":             `package main; import _ "b"`,
-		"src/e/e2.go":            `package main; import _ "c"`,
-		"src/f/f.go":             `package f`,
-	})
-	defer cleanup()
-
-	cfg := &packages.Config{
-		Mode: packages.LoadImports,
-		Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, "c", "subdir/d", "e")
+func TestLoadImportsGraph(t *testing.T) { packagestest.TestAll(t, testLoadImportsGraph) }
+func testLoadImportsGraph(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go":             `package a; const A = 1`,
+			"b/b.go":             `package b; import ("golang.org/fake/a"; _ "errors"); var B = a.A`,
+			"c/c.go":             `package c; import (_ "golang.org/fake/b"; _ "unsafe")`,
+			"c/c2.go":            "// +build ignore\n\n" + `package c; import _ "fmt"`,
+			"subdir/d/d.go":      `package d`,
+			"subdir/d/d_test.go": `package d; import _ "math/bits"`,
+			"subdir/d/x_test.go": `package d_test; import _ "golang.org/fake/subdir/d"`, // TODO(adonovan): test bad import here
+			"subdir/e/d.go":      `package e`,
+			"e/e.go":             `package main; import _ "golang.org/fake/b"`,
+			"e/e2.go":            `package main; import _ "golang.org/fake/c"`,
+			"f/f.go":             `package f`,
+		}}})
+	defer exported.Cleanup()
+	exported.Config.Mode = packages.LoadImports
+	initial, err := packages.Load(exported.Config, "golang.org/fake/c", "golang.org/fake/subdir/d", "golang.org/fake/e")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -99,27 +93,27 @@
 	// Check graph topology.
 	graph, all := importGraph(initial)
 	wantGraph := `
-  a
-  b
-* c
-* e
   errors
-* subdir/d
+  golang.org/fake/a
+  golang.org/fake/b
+* golang.org/fake/c
+* golang.org/fake/e
+* golang.org/fake/subdir/d
   unsafe
-  b -> a
-  b -> errors
-  c -> b
-  c -> unsafe
-  e -> b
-  e -> c
+  golang.org/fake/b -> errors
+  golang.org/fake/b -> golang.org/fake/a
+  golang.org/fake/c -> golang.org/fake/b
+  golang.org/fake/c -> unsafe
+  golang.org/fake/e -> golang.org/fake/b
+  golang.org/fake/e -> golang.org/fake/c
 `[1:]
 
 	if graph != wantGraph {
 		t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
 	}
 
-	cfg.Tests = true
-	initial, err = packages.Load(cfg, "c", "subdir/d", "e")
+	exported.Config.Tests = true
+	initial, err = packages.Load(exported.Config, "golang.org/fake/c", "golang.org/fake/subdir/d", "golang.org/fake/e")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -127,30 +121,30 @@
 	// Check graph topology.
 	graph, all = importGraph(initial)
 	wantGraph = `
-  a
-  b
-* c
-* e
   errors
+  golang.org/fake/a
+  golang.org/fake/b
+* golang.org/fake/c
+* golang.org/fake/e
+* golang.org/fake/subdir/d
+* golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+* golang.org/fake/subdir/d.test
+* golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test]
   math/bits
-* subdir/d
-* subdir/d [subdir/d.test]
-* subdir/d.test
-* subdir/d_test [subdir/d.test]
   unsafe
-  b -> a
-  b -> errors
-  c -> b
-  c -> unsafe
-  e -> b
-  e -> c
-  subdir/d [subdir/d.test] -> math/bits
-  subdir/d.test -> os (pruned)
-  subdir/d.test -> subdir/d [subdir/d.test]
-  subdir/d.test -> subdir/d_test [subdir/d.test]
-  subdir/d.test -> testing (pruned)
-  subdir/d.test -> testing/internal/testdeps (pruned)
-  subdir/d_test [subdir/d.test] -> subdir/d [subdir/d.test]
+  golang.org/fake/b -> errors
+  golang.org/fake/b -> golang.org/fake/a
+  golang.org/fake/c -> golang.org/fake/b
+  golang.org/fake/c -> unsafe
+  golang.org/fake/e -> golang.org/fake/b
+  golang.org/fake/e -> golang.org/fake/c
+  golang.org/fake/subdir/d [golang.org/fake/subdir/d.test] -> math/bits
+  golang.org/fake/subdir/d.test -> golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+  golang.org/fake/subdir/d.test -> golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test]
+  golang.org/fake/subdir/d.test -> os (pruned)
+  golang.org/fake/subdir/d.test -> testing (pruned)
+  golang.org/fake/subdir/d.test -> testing/internal/testdeps (pruned)
+  golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test] -> golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
 `[1:]
 
 	if graph != wantGraph {
@@ -164,13 +158,13 @@
 		wantKind string
 		wantSrcs string
 	}{
-		{"a", "a", "package", "a.go"},
-		{"b", "b", "package", "b.go"},
-		{"c", "c", "package", "c.go"}, // c2.go is ignored
-		{"e", "main", "command", "e.go e2.go"},
+		{"golang.org/fake/a", "a", "package", "a.go"},
+		{"golang.org/fake/b", "b", "package", "b.go"},
+		{"golang.org/fake/c", "c", "package", "c.go"}, // c2.go is ignored
+		{"golang.org/fake/e", "main", "command", "e.go e2.go"},
 		{"errors", "errors", "package", "errors.go"},
-		{"subdir/d", "d", "package", "d.go"},
-		{"subdir/d.test", "main", "command", "0.go"},
+		{"golang.org/fake/subdir/d", "d", "package", "d.go"},
+		{"golang.org/fake/subdir/d.test", "main", "command", "0.go"},
 		{"unsafe", "unsafe", "package", ""},
 	} {
 		p, ok := all[test.id]
@@ -199,7 +193,7 @@
 	}
 
 	// Test an ad-hoc package, analogous to "go run hello.go".
-	if initial, err := packages.Load(cfg, filepath.Join(tmp, "src/c/c.go")); len(initial) == 0 {
+	if initial, err := packages.Load(exported.Config, exported.File("golang.org/fake", "c/c.go")); len(initial) == 0 {
 		t.Errorf("failed to obtain metadata for ad-hoc package: %s", err)
 	} else {
 		got := fmt.Sprintf("%s %s", initial[0].ID, srcs(initial[0]))
@@ -213,25 +207,25 @@
 	// TODO(adonovan): test "all" returns everything in the current module.
 	{
 		// "..." (subdirectory)
-		initial, err = packages.Load(cfg, "subdir/...")
+		initial, err = packages.Load(exported.Config, "golang.org/fake/subdir/...")
 		if err != nil {
 			t.Fatal(err)
 		}
 		graph, all = importGraph(initial)
 		wantGraph = `
+* golang.org/fake/subdir/d
+* golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+* golang.org/fake/subdir/d.test
+* golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test]
+* golang.org/fake/subdir/e
   math/bits
-* subdir/d
-* subdir/d [subdir/d.test]
-* subdir/d.test
-* subdir/d_test [subdir/d.test]
-* subdir/e
-  subdir/d [subdir/d.test] -> math/bits
-  subdir/d.test -> os (pruned)
-  subdir/d.test -> subdir/d [subdir/d.test]
-  subdir/d.test -> subdir/d_test [subdir/d.test]
-  subdir/d.test -> testing (pruned)
-  subdir/d.test -> testing/internal/testdeps (pruned)
-  subdir/d_test [subdir/d.test] -> subdir/d [subdir/d.test]
+  golang.org/fake/subdir/d [golang.org/fake/subdir/d.test] -> math/bits
+  golang.org/fake/subdir/d.test -> golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+  golang.org/fake/subdir/d.test -> golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test]
+  golang.org/fake/subdir/d.test -> os (pruned)
+  golang.org/fake/subdir/d.test -> testing (pruned)
+  golang.org/fake/subdir/d.test -> testing/internal/testdeps (pruned)
+  golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test] -> golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
 `[1:]
 
 		if graph != wantGraph {
@@ -240,21 +234,21 @@
 	}
 }
 
-func TestLoadImportsTestVariants(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go":       `package a; import _ "b"`,
-		"src/b/b.go":       `package b`,
-		"src/b/b_test.go":  `package b`,
-		"src/b/bx_test.go": `package b_test; import _ "a"`,
-	})
-	defer cleanup()
+func TestLoadImportsTestVariants(t *testing.T) { packagestest.TestAll(t, testLoadImportsTestVariants) }
+func testLoadImportsTestVariants(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go":       `package a; import _ "golang.org/fake/b"`,
+			"b/b.go":       `package b`,
+			"b/b_test.go":  `package b`,
+			"b/bx_test.go": `package b_test; import _ "golang.org/fake/a"`,
+		}}})
+	defer exported.Cleanup()
+	exported.Config.Mode = packages.LoadImports
+	exported.Config.Tests = true
 
-	cfg := &packages.Config{
-		Mode:  packages.LoadImports,
-		Env:   append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-		Tests: true,
-	}
-	initial, err := packages.Load(cfg, "a", "b")
+	initial, err := packages.Load(exported.Config, "golang.org/fake/a", "golang.org/fake/b")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -262,20 +256,20 @@
 	// Check graph topology.
 	graph, _ := importGraph(initial)
 	wantGraph := `
-* a
-  a [b.test]
-* b
-* b [b.test]
-* b.test
-* b_test [b.test]
-  a -> b
-  a [b.test] -> b [b.test]
-  b.test -> b [b.test]
-  b.test -> b_test [b.test]
-  b.test -> os (pruned)
-  b.test -> testing (pruned)
-  b.test -> testing/internal/testdeps (pruned)
-  b_test [b.test] -> a [b.test]
+* golang.org/fake/a
+  golang.org/fake/a [golang.org/fake/b.test]
+* golang.org/fake/b
+* golang.org/fake/b [golang.org/fake/b.test]
+* golang.org/fake/b.test
+* golang.org/fake/b_test [golang.org/fake/b.test]
+  golang.org/fake/a -> golang.org/fake/b
+  golang.org/fake/a [golang.org/fake/b.test] -> golang.org/fake/b [golang.org/fake/b.test]
+  golang.org/fake/b.test -> golang.org/fake/b [golang.org/fake/b.test]
+  golang.org/fake/b.test -> golang.org/fake/b_test [golang.org/fake/b.test]
+  golang.org/fake/b.test -> os (pruned)
+  golang.org/fake/b.test -> testing (pruned)
+  golang.org/fake/b.test -> testing/internal/testdeps (pruned)
+  golang.org/fake/b_test [golang.org/fake/b.test] -> golang.org/fake/a [golang.org/fake/b.test]
 `[1:]
 
 	if graph != wantGraph {
@@ -284,19 +278,18 @@
 }
 
 func TestLoadAbsolutePath(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"gopatha/src/a/a.go": `package a`,
-		"gopathb/src/b/b.go": `package b`,
-	})
-	defer cleanup()
+	exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
+		Name: "golang.org/gopatha",
+		Files: map[string]interface{}{
+			"a/a.go": `package a`,
+		}}, {
+		Name: "golang.org/gopathb",
+		Files: map[string]interface{}{
+			"b/b.go": `package b`,
+		}}})
+	defer exported.Cleanup()
 
-	cfg := &packages.Config{
-		Mode: packages.LoadImports,
-		Env: append(os.Environ(),
-			"GOPATH="+filepath.Join(tmp, "gopatha")+string(filepath.ListSeparator)+filepath.Join(tmp, "gopathb"),
-			"GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, filepath.Join(tmp, "gopatha", "src", "a"), filepath.Join(tmp, "gopathb", "src", "b"))
+	initial, err := packages.Load(exported.Config, filepath.Dir(exported.File("golang.org/gopatha", "a/a.go")), filepath.Dir(exported.File("golang.org/gopathb", "b/b.go")))
 	if err != nil {
 		t.Fatalf("failed to load imports: %v", err)
 	}
@@ -305,39 +298,37 @@
 	for _, p := range initial {
 		got = append(got, p.ID)
 	}
-	if !reflect.DeepEqual(got, []string{"a", "b"}) {
+	if !reflect.DeepEqual(got, []string{"golang.org/gopatha/a", "golang.org/gopathb/b"}) {
 		t.Fatalf("initial packages loaded: got [%s], want [a b]", got)
 	}
 }
 
 func TestVendorImports(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go":          `package a; import _ "b"; import _ "c";`,
-		"src/a/vendor/b/b.go": `package b; import _ "c"`,
-		"src/c/c.go":          `package c; import _ "b"`,
-		"src/c/vendor/b/b.go": `package b`,
-	})
-	defer cleanup()
-
-	cfg := &packages.Config{
-		Mode: packages.LoadImports,
-		Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, "a", "c")
+	exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go":          `package a; import _ "b"; import _ "golang.org/fake/c";`,
+			"a/vendor/b/b.go": `package b; import _ "golang.org/fake/c"`,
+			"c/c.go":          `package c; import _ "b"`,
+			"c/vendor/b/b.go": `package b`,
+		}}})
+	defer exported.Cleanup()
+	exported.Config.Mode = packages.LoadImports
+	initial, err := packages.Load(exported.Config, "golang.org/fake/a", "golang.org/fake/c")
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	graph, all := importGraph(initial)
 	wantGraph := `
-* a
-  a/vendor/b
-* c
-  c/vendor/b
-  a -> a/vendor/b
-  a -> c
-  a/vendor/b -> c
-  c -> c/vendor/b
+* golang.org/fake/a
+  golang.org/fake/a/vendor/b
+* golang.org/fake/c
+  golang.org/fake/c/vendor/b
+  golang.org/fake/a -> golang.org/fake/a/vendor/b
+  golang.org/fake/a -> golang.org/fake/c
+  golang.org/fake/a/vendor/b -> golang.org/fake/c
+  golang.org/fake/c -> golang.org/fake/c/vendor/b
 `[1:]
 	if graph != wantGraph {
 		t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
@@ -347,10 +338,10 @@
 		pattern     string
 		wantImports string
 	}{
-		{"a", "b:a/vendor/b c:c"},
-		{"c", "b:c/vendor/b"},
-		{"a/vendor/b", "c:c"},
-		{"c/vendor/b", ""},
+		{"golang.org/fake/a", "b:golang.org/fake/a/vendor/b golang.org/fake/c:golang.org/fake/c"},
+		{"golang.org/fake/c", "b:golang.org/fake/c/vendor/b"},
+		{"golang.org/fake/a/vendor/b", "golang.org/fake/c:golang.org/fake/c"},
+		{"golang.org/fake/c/vendor/b", ""},
 	} {
 		// Test the import paths.
 		pkg := all[test.pattern]
@@ -372,46 +363,49 @@
 	return keys
 }
 
-func TestConfigDir(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go":   `package a; const Name = "a" `,
-		"src/a/b/b.go": `package b; const Name = "a/b"`,
-		"src/b/b.go":   `package b; const Name = "b"`,
-	})
-	defer cleanup()
+func TestConfigDir(t *testing.T) { packagestest.TestAll(t, testConfigDir) }
+func testConfigDir(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go":   `package a; const Name = "a" `,
+			"a/b/b.go": `package b; const Name = "a/b"`,
+			"b/b.go":   `package b; const Name = "b"`,
+		}}})
+	defer exported.Cleanup()
+	aDir := filepath.Dir(exported.File("golang.org/fake", "a/a.go"))
+	bDir := filepath.Dir(exported.File("golang.org/fake", "b/b.go"))
+	baseDir := filepath.Dir(aDir)
 
 	for _, test := range []struct {
-		dir         string
-		pattern     string
-		want        string // value of Name constant
-		errContains string
+		dir     string
+		pattern string
+		want    string // value of Name constant
+		fails   bool
 	}{
-		{pattern: "a", want: `"a"`},
-		{pattern: "b", want: `"b"`},
-		{pattern: "./a", errContains: "cannot find"},
-		{pattern: "./b", errContains: "cannot find"},
-		{dir: filepath.Join(tmp, "/src"), pattern: "a", want: `"a"`},
-		{dir: filepath.Join(tmp, "/src"), pattern: "b", want: `"b"`},
-		{dir: filepath.Join(tmp, "/src"), pattern: "./a", want: `"a"`},
-		{dir: filepath.Join(tmp, "/src"), pattern: "./b", want: `"b"`},
-		{dir: filepath.Join(tmp, "/src/a"), pattern: "a", want: `"a"`},
-		{dir: filepath.Join(tmp, "/src/a"), pattern: "b", want: `"b"`},
-		{dir: filepath.Join(tmp, "/src/a"), pattern: "./a", errContains: "cannot find"},
-		{dir: filepath.Join(tmp, "/src/a"), pattern: "./b", want: `"a/b"`},
+		{dir: bDir, pattern: "golang.org/fake/a", want: `"a"`},
+		{dir: bDir, pattern: "golang.org/fake/b", want: `"b"`},
+		{dir: bDir, pattern: "./a", fails: true},
+		{dir: bDir, pattern: "./b", fails: true},
+		{dir: baseDir, pattern: "golang.org/fake/a", want: `"a"`},
+		{dir: baseDir, pattern: "golang.org/fake/b", want: `"b"`},
+		{dir: baseDir, pattern: "./a", want: `"a"`},
+		{dir: baseDir, pattern: "./b", want: `"b"`},
+		{dir: aDir, pattern: "golang.org/fake/a", want: `"a"`},
+		{dir: aDir, pattern: "golang.org/fake/b", want: `"b"`},
+		{dir: aDir, pattern: "./a", fails: true},
+		{dir: aDir, pattern: "./b", want: `"a/b"`},
 	} {
-		cfg := &packages.Config{
-			Mode: packages.LoadSyntax, // Use LoadSyntax to ensure that files can be opened.
-			Dir:  test.dir,
-			Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-		}
-
-		initial, err := packages.Load(cfg, test.pattern)
-		var got, errMsg string
+		exported.Config.Mode = packages.LoadSyntax // Use LoadSyntax to ensure that files can be opened.
+		exported.Config.Dir = test.dir
+		initial, err := packages.Load(exported.Config, test.pattern)
+		var got string
+		fails := false
 		if err != nil {
-			errMsg = err.Error()
+			fails = true
 		} else if len(initial) > 0 {
 			if len(initial[0].Errors) > 0 {
-				errMsg = initial[0].Errors[0].Error()
+				fails = true
 			} else if c := constant(initial[0], "Name"); c != nil {
 				got = c.Val().String()
 			}
@@ -420,35 +414,42 @@
 			t.Errorf("dir %q, pattern %q: got %s, want %s",
 				test.dir, test.pattern, got, test.want)
 		}
-		if !strings.Contains(errMsg, test.errContains) {
-			t.Errorf("dir %q, pattern %q: error %s, want %s",
-				test.dir, test.pattern, errMsg, test.errContains)
+		if fails != test.fails {
+			// TODO: remove when go#28023 is fixed
+			if test.fails && strings.HasPrefix(test.pattern, "./") && exporter == packagestest.Modules {
+				// Currently go list in module mode does not handle missing directories correctly.
+				continue
+			}
+			t.Errorf("dir %q, pattern %q: error %v, want %v",
+				test.dir, test.pattern, fails, test.fails)
 		}
 	}
-
 }
 
-func TestConfigFlags(t *testing.T) {
+func TestConfigFlags(t *testing.T) { packagestest.TestAll(t, testConfigFlags) }
+func testConfigFlags(t *testing.T, exporter packagestest.Exporter) {
 	// Test satisfying +build line tags, with -tags flag.
-	tmp, cleanup := makeTree(t, map[string]string{
-		// package a
-		"src/a/a.go": `package a; import _ "a/b"`,
-		"src/a/b.go": `// +build tag
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			// package a
+			"a/a.go": `package a; import _ "golang.org/fake/a/b"`,
+			"a/b.go": `// +build tag
 
 package a`,
-		"src/a/c.go": `// +build tag tag2
+			"a/c.go": `// +build tag tag2
 
 package a`,
-		"src/a/d.go": `// +build tag,tag2
+			"a/d.go": `// +build tag,tag2
 
 package a`,
-		// package a/b
-		"src/a/b/a.go": `package b`,
-		"src/a/b/b.go": `// +build tag
+			// package a/b
+			"a/b/a.go": `package b`,
+			"a/b/b.go": `// +build tag
 
 package b`,
-	})
-	defer cleanup()
+		}}})
+	defer exported.Cleanup()
 
 	for _, test := range []struct {
 		pattern        string
@@ -456,18 +457,15 @@
 		wantSrcs       string
 		wantImportSrcs string
 	}{
-		{`a`, []string{}, "a.go", "a.go"},
-		{`a`, []string{`-tags=tag`}, "a.go b.go c.go", "a.go b.go"},
-		{`a`, []string{`-tags=tag2`}, "a.go c.go", "a.go"},
-		{`a`, []string{`-tags=tag tag2`}, "a.go b.go c.go d.go", "a.go b.go"},
+		{`golang.org/fake/a`, []string{}, "a.go", "a.go"},
+		{`golang.org/fake/a`, []string{`-tags=tag`}, "a.go b.go c.go", "a.go b.go"},
+		{`golang.org/fake/a`, []string{`-tags=tag2`}, "a.go c.go", "a.go"},
+		{`golang.org/fake/a`, []string{`-tags=tag tag2`}, "a.go b.go c.go d.go", "a.go b.go"},
 	} {
-		cfg := &packages.Config{
-			Mode:       packages.LoadImports,
-			BuildFlags: test.tags,
-			Env:        append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-		}
+		exported.Config.Mode = packages.LoadImports
+		exported.Config.BuildFlags = test.tags
 
-		initial, err := packages.Load(cfg, test.pattern)
+		initial, err := packages.Load(exported.Config, test.pattern)
 		if err != nil {
 			t.Error(err)
 			continue
@@ -489,35 +487,35 @@
 	}
 }
 
-func TestLoadTypes(t *testing.T) {
+func TestLoadTypes(t *testing.T) { packagestest.TestAll(t, testLoadTypes) }
+func testLoadTypes(t *testing.T, exporter packagestest.Exporter) {
 	// In LoadTypes and LoadSyntax modes, the compiler will
 	// fail to generate an export data file for c, because it has
 	// a type error.  The loader should fall back loading a and c
 	// from source, but use the export data for b.
 
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go": `package a; import "b"; import "c"; const A = "a" + b.B + c.C`,
-		"src/b/b.go": `package b; const B = "b"`,
-		"src/c/c.go": `package c; const C = "c" + 1`,
-	})
-	defer cleanup()
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a; import "golang.org/fake/b"; import "golang.org/fake/c"; const A = "a" + b.B + c.C`,
+			"b/b.go": `package b; const B = "b"`,
+			"c/c.go": `package c; const C = "c" + 1`,
+		}}})
+	defer exported.Cleanup()
 
-	cfg := &packages.Config{
-		Mode: packages.LoadTypes,
-		Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, "a")
+	exported.Config.Mode = packages.LoadTypes
+	initial, err := packages.Load(exported.Config, "golang.org/fake/a")
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	graph, all := importGraph(initial)
 	wantGraph := `
-* a
-  b
-  c
-  a -> b
-  a -> c
+* golang.org/fake/a
+  golang.org/fake/b
+  golang.org/fake/c
+  golang.org/fake/a -> golang.org/fake/b
+  golang.org/fake/a -> golang.org/fake/c
 `[1:]
 	if graph != wantGraph {
 		t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
@@ -527,9 +525,9 @@
 		id         string
 		wantSyntax bool
 	}{
-		{"a", true},  // need src, no export data for c
-		{"b", false}, // use export data
-		{"c", true},  // need src, no export data for c
+		{"golang.org/fake/a", true},  // need src, no export data for c
+		{"golang.org/fake/b", false}, // use export data
+		{"golang.org/fake/c", true},  // need src, no export data for c
 	} {
 		if usesOldGolist && !test.wantSyntax {
 			// legacy go list always upgrades to LoadAllSyntax, syntax will be filled in.
@@ -557,39 +555,39 @@
 	}
 }
 
-func TestLoadSyntaxOK(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go": `package a; import "b"; const A = "a" + b.B`,
-		"src/b/b.go": `package b; import "c"; const B = "b" + c.C`,
-		"src/c/c.go": `package c; import "d"; const C = "c" + d.D`,
-		"src/d/d.go": `package d; import "e"; const D = "d" + e.E`,
-		"src/e/e.go": `package e; import "f"; const E = "e" + f.F`,
-		"src/f/f.go": `package f; const F = "f"`,
-	})
-	defer cleanup()
+func TestLoadSyntaxOK(t *testing.T) { packagestest.TestAll(t, testLoadSyntaxOK) }
+func testLoadSyntaxOK(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a; import "golang.org/fake/b"; const A = "a" + b.B`,
+			"b/b.go": `package b; import "golang.org/fake/c"; const B = "b" + c.C`,
+			"c/c.go": `package c; import "golang.org/fake/d"; const C = "c" + d.D`,
+			"d/d.go": `package d; import "golang.org/fake/e"; const D = "d" + e.E`,
+			"e/e.go": `package e; import "golang.org/fake/f"; const E = "e" + f.F`,
+			"f/f.go": `package f; const F = "f"`,
+		}}})
+	defer exported.Cleanup()
 
-	cfg := &packages.Config{
-		Mode: packages.LoadSyntax,
-		Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, "a", "c")
+	exported.Config.Mode = packages.LoadSyntax
+	initial, err := packages.Load(exported.Config, "golang.org/fake/a", "golang.org/fake/c")
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	graph, all := importGraph(initial)
 	wantGraph := `
-* a
-  b
-* c
-  d
-  e
-  f
-  a -> b
-  b -> c
-  c -> d
-  d -> e
-  e -> f
+* golang.org/fake/a
+  golang.org/fake/b
+* golang.org/fake/c
+  golang.org/fake/d
+  golang.org/fake/e
+  golang.org/fake/f
+  golang.org/fake/a -> golang.org/fake/b
+  golang.org/fake/b -> golang.org/fake/c
+  golang.org/fake/c -> golang.org/fake/d
+  golang.org/fake/d -> golang.org/fake/e
+  golang.org/fake/e -> golang.org/fake/f
 `[1:]
 	if graph != wantGraph {
 		t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
@@ -600,12 +598,12 @@
 		wantSyntax   bool
 		wantComplete bool
 	}{
-		{"a", true, true},   // source package
-		{"b", true, true},   // source package
-		{"c", true, true},   // source package
-		{"d", false, true},  // export data package
-		{"e", false, false}, // export data package
-		{"f", false, false}, // export data package
+		{"golang.org/fake/a", true, true},   // source package
+		{"golang.org/fake/b", true, true},   // source package
+		{"golang.org/fake/c", true, true},   // source package
+		{"golang.org/fake/d", false, true},  // export data package
+		{"golang.org/fake/e", false, false}, // export data package
+		{"golang.org/fake/f", false, false}, // export data package
 	} {
 		// TODO(matloob): The legacy go list based support loads
 		// everything from source because it doesn't do a build
@@ -643,27 +641,30 @@
 	}
 
 	// Check value of constant.
-	aA := constant(all["a"], "A")
-	if got, want := fmt.Sprintf("%v %v", aA, aA.Val()), `const a.A untyped string "abcdef"`; got != want {
+	aA := constant(all["golang.org/fake/a"], "A")
+	if aA == nil {
+		t.Fatalf("a.A: got nil")
+	}
+	if got, want := fmt.Sprintf("%v %v", aA, aA.Val()), `const golang.org/fake/a.A untyped string "abcdef"`; got != want {
 		t.Errorf("a.A: got %s, want %s", got, want)
 	}
 }
 
-func TestLoadDiamondTypes(t *testing.T) {
+func TestLoadDiamondTypes(t *testing.T) { packagestest.TestAll(t, testLoadDiamondTypes) }
+func testLoadDiamondTypes(t *testing.T, exporter packagestest.Exporter) {
 	// We make a diamond dependency and check the type d.D is the same through both paths
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go": `package a; import ("b"; "c"); var _ = b.B == c.C`,
-		"src/b/b.go": `package b; import "d"; var B d.D`,
-		"src/c/c.go": `package c; import "d"; var C d.D`,
-		"src/d/d.go": `package d; type D int`,
-	})
-	defer cleanup()
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a; import ("golang.org/fake/b"; "golang.org/fake/c"); var _ = b.B == c.C`,
+			"b/b.go": `package b; import "golang.org/fake/d"; var B d.D`,
+			"c/c.go": `package c; import "golang.org/fake/d"; var C d.D`,
+			"d/d.go": `package d; type D int`,
+		}}})
+	defer exported.Cleanup()
 
-	cfg := &packages.Config{
-		Mode: packages.LoadSyntax,
-		Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, "a")
+	exported.Config.Mode = packages.LoadSyntax
+	initial, err := packages.Load(exported.Config, "golang.org/fake/a")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -675,40 +676,40 @@
 
 	graph, _ := importGraph(initial)
 	wantGraph := `
-* a
-  b
-  c
-  d
-  a -> b
-  a -> c
-  b -> d
-  c -> d
+* golang.org/fake/a
+  golang.org/fake/b
+  golang.org/fake/c
+  golang.org/fake/d
+  golang.org/fake/a -> golang.org/fake/b
+  golang.org/fake/a -> golang.org/fake/c
+  golang.org/fake/b -> golang.org/fake/d
+  golang.org/fake/c -> golang.org/fake/d
 `[1:]
 	if graph != wantGraph {
 		t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
 	}
 }
 
-func TestLoadSyntaxError(t *testing.T) {
+func TestLoadSyntaxError(t *testing.T) { packagestest.TestAll(t, testLoadSyntaxError) }
+func testLoadSyntaxError(t *testing.T, exporter packagestest.Exporter) {
 	// A type error in a lower-level package (e) prevents go list
 	// from producing export data for all packages that depend on it
 	// [a-e]. Only f should be loaded from export data, and the rest
 	// should be IllTyped.
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go": `package a; import "b"; const A = "a" + b.B`,
-		"src/b/b.go": `package b; import "c"; const B = "b" + c.C`,
-		"src/c/c.go": `package c; import "d"; const C = "c" + d.D`,
-		"src/d/d.go": `package d; import "e"; const D = "d" + e.E`,
-		"src/e/e.go": `package e; import "f"; const E = "e" + f.F + 1`, // type error
-		"src/f/f.go": `package f; const F = "f"`,
-	})
-	defer cleanup()
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a; import "golang.org/fake/b"; const A = "a" + b.B`,
+			"b/b.go": `package b; import "golang.org/fake/c"; const B = "b" + c.C`,
+			"c/c.go": `package c; import "golang.org/fake/d"; const C = "c" + d.D`,
+			"d/d.go": `package d; import "golang.org/fake/e"; const D = "d" + e.E`,
+			"e/e.go": `package e; import "golang.org/fake/f"; const E = "e" + f.F + 1`, // type error
+			"f/f.go": `package f; const F = "f"`,
+		}}})
+	defer exported.Cleanup()
 
-	cfg := &packages.Config{
-		Mode: packages.LoadSyntax,
-		Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, "a", "c")
+	exported.Config.Mode = packages.LoadSyntax
+	initial, err := packages.Load(exported.Config, "golang.org/fake/a", "golang.org/fake/c")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -723,12 +724,12 @@
 		wantSyntax   bool
 		wantIllTyped bool
 	}{
-		{"a", true, true},
-		{"b", true, true},
-		{"c", true, true},
-		{"d", true, true},
-		{"e", true, true},
-		{"f", false, false},
+		{"golang.org/fake/a", true, true},
+		{"golang.org/fake/b", true, true},
+		{"golang.org/fake/c", true, true},
+		{"golang.org/fake/d", true, true},
+		{"golang.org/fake/e", true, true},
+		{"golang.org/fake/f", false, false},
 	} {
 		if usesOldGolist && !test.wantSyntax {
 			// legacy go list always upgrades to LoadAllSyntax, syntax will be filled in.
@@ -758,23 +759,28 @@
 	}
 
 	// Check value of constant.
-	aA := constant(all["a"], "A")
-	if got, want := aA.String(), `const a.A invalid type`; got != want {
+	aA := constant(all["golang.org/fake/a"], "A")
+	if aA == nil {
+		t.Fatalf("a.A: got nil")
+	}
+	if got, want := aA.String(), `const golang.org/fake/a.A invalid type`; got != want {
 		t.Errorf("a.A: got %s, want %s", got, want)
 	}
 }
 
 // This function tests use of the ParseFile hook to modify
 // the AST after parsing.
-func TestParseFileModifyAST(t *testing.T) {
-	type M = map[string]string
+func TestParseFileModifyAST(t *testing.T) { packagestest.TestAll(t, testParseFileModifyAST) }
+func testParseFileModifyAST(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a; const A = "a" `,
+		}}})
+	defer exported.Cleanup()
 
-	tmp, cleanup := makeTree(t, M{
-		"src/a/a.go": `package a; const A = "a" `,
-	})
-	defer cleanup()
-
-	parseFile := func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
+	exported.Config.Mode = packages.LoadAllSyntax
+	exported.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
 		const mode = parser.AllErrors | parser.ParseComments
 		f, err := parser.ParseFile(fset, filename, src, mode)
 		// modify AST to change `const A = "a"` to `const A = "b"`
@@ -782,12 +788,7 @@
 		spec.Values[0].(*ast.BasicLit).Value = `"b"`
 		return f, err
 	}
-	cfg := &packages.Config{
-		Mode:      packages.LoadAllSyntax,
-		Env:       append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-		ParseFile: parseFile,
-	}
-	initial, err := packages.Load(cfg, "a")
+	initial, err := packages.Load(exported.Config, "golang.org/fake/a")
 	if err != nil {
 		t.Error(err)
 	}
@@ -800,15 +801,17 @@
 	}
 }
 
-// This function tests config.Overlay functionality.
-func TestOverlay(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go": `package a; import "b"; const A = "a" + b.B`,
-		"src/b/b.go": `package b; import "c"; const B = "b" + c.C`,
-		"src/c/c.go": `package c; const C = "c"`,
-		"src/d/d.go": `package d; const D = "d"`,
-	})
-	defer cleanup()
+func TestOverlay(t *testing.T) { packagestest.TestAll(t, testOverlay) }
+func testOverlay(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a; import "golang.org/fake/b"; const A = "a" + b.B`,
+			"b/b.go": `package b; import "golang.org/fake/c"; const B = "b" + c.C`,
+			"c/c.go": `package c; const C = "c"`,
+			"d/d.go": `package d; const D = "d"`,
+		}}})
+	defer exported.Cleanup()
 
 	for i, test := range []struct {
 		overlay  map[string][]byte
@@ -817,19 +820,14 @@
 	}{
 		{nil, `"abc"`, nil},                 // default
 		{map[string][]byte{}, `"abc"`, nil}, // empty overlay
-		{map[string][]byte{filepath.Join(tmp, "src/c/c.go"): []byte(`package c; const C = "C"`)}, `"abC"`, nil},
-		{map[string][]byte{filepath.Join(tmp, "src/b/b.go"): []byte(`package b; import "c"; const B = "B" + c.C`)}, `"aBc"`, nil},
-		{map[string][]byte{filepath.Join(tmp, "src/b/b.go"): []byte(`package b; import "d"; const B = "B" + d.D`)}, `unknown`,
+		{map[string][]byte{exported.File("golang.org/fake", "c/c.go"): []byte(`package c; const C = "C"`)}, `"abC"`, nil},
+		{map[string][]byte{exported.File("golang.org/fake", "b/b.go"): []byte(`package b; import "golang.org/fake/c"; const B = "B" + c.C`)}, `"aBc"`, nil},
+		{map[string][]byte{exported.File("golang.org/fake", "b/b.go"): []byte(`package b; import "d"; const B = "B" + d.D`)}, `unknown`,
 			[]string{`could not import d (no metadata for d)`}},
 	} {
-		var parseFile func(fset *token.FileSet, filename string, src []byte) (*ast.File, error)
-		cfg := &packages.Config{
-			Mode:      packages.LoadAllSyntax,
-			Env:       append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-			ParseFile: parseFile,
-			Overlay:   test.overlay,
-		}
-		initial, err := packages.Load(cfg, "a")
+		exported.Config.Overlay = test.overlay
+		exported.Config.Mode = packages.LoadAllSyntax
+		initial, err := packages.Load(exported.Config, "golang.org/fake/a")
 		if err != nil {
 			t.Error(err)
 			continue
@@ -837,7 +835,12 @@
 
 		// Check value of a.A.
 		a := initial[0]
-		got := constant(a, "A").Val().String()
+		aA := constant(a, "A")
+		if aA == nil {
+			t.Errorf("%d. a.A: got nil", i)
+			continue
+		}
+		got := aA.Val().String()
 		if got != test.want {
 			t.Errorf("%d. a.A: got %s, want %s", i, got, test.want)
 		}
@@ -854,15 +857,21 @@
 }
 
 func TestLoadAllSyntaxImportErrors(t *testing.T) {
+	packagestest.TestAll(t, testLoadAllSyntaxImportErrors)
+}
+func testLoadAllSyntaxImportErrors(t *testing.T, exporter packagestest.Exporter) {
 	// TODO(matloob): Remove this once go list -e -compiled is fixed. See golang.org/issue/26755
 	t.Skip("go list -compiled -e fails with non-zero exit status for empty packages")
 
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/unicycle/unicycle.go": `package unicycle; import _ "unicycle"`,
-		"src/bicycle1/bicycle1.go": `package bicycle1; import _ "bicycle2"`,
-		"src/bicycle2/bicycle2.go": `package bicycle2; import _ "bicycle1"`,
-		"src/bad/bad.go":           `not a package declaration`,
-		"src/root/root.go": `package root
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"unicycle/unicycle.go": `package unicycle; import _ "unicycle"`,
+			"bicycle1/bicycle1.go": `package bicycle1; import _ "bicycle2"`,
+			"bicycle2/bicycle2.go": `package bicycle2; import _ "bicycle1"`,
+			"bad/bad.go":           `not a package declaration`,
+			"empty/README.txt":     `not a go file`,
+			"root/root.go": `package root
 import (
 	_ "bicycle1"
 	_ "unicycle"
@@ -870,16 +879,11 @@
 	_ "empty"
 	_ "bad"
 )`,
-	})
-	defer cleanup()
+		}}})
+	defer exported.Cleanup()
 
-	os.Mkdir(filepath.Join(tmp, "src/empty"), 0777) // create an existing but empty package
-
-	cfg := &packages.Config{
-		Mode: packages.LoadAllSyntax,
-		Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, "root")
+	exported.Config.Mode = packages.LoadAllSyntax
+	initial, err := packages.Load(exported.Config, "root")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -937,21 +941,25 @@
 	}
 }
 
-func TestAbsoluteFilenames(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go":          `package a; const A = 1`,
-		"src/b/b.go":          `package b; import ("a"; _ "errors"); var B = a.A`,
-		"src/b/vendor/a/a.go": `package a; const A = 1`,
-		"src/c/c.go":          `package c; import (_ "b"; _ "unsafe")`,
-		"src/c/c2.go":         "// +build ignore\n\n" + `package c; import _ "fmt"`,
-		"src/subdir/d/d.go":   `package d`,
-		"src/subdir/e/d.go":   `package e`,
-		"src/e/e.go":          `package main; import _ "b"`,
-		"src/e/e2.go":         `package main; import _ "c"`,
-		"src/f/f.go":          `package f`,
-		"src/f/f.s":           ``,
-	})
-	defer cleanup()
+func TestAbsoluteFilenames(t *testing.T) { packagestest.TestAll(t, testAbsoluteFilenames) }
+func testAbsoluteFilenames(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go":          `package a; const A = 1`,
+			"b/b.go":          `package b; import ("golang.org/fake/a"; _ "errors"); var B = a.A`,
+			"b/vendor/a/a.go": `package a; const A = 1`,
+			"c/c.go":          `package c; import (_ "golang.org/fake/b"; _ "unsafe")`,
+			"c/c2.go":         "// +build ignore\n\n" + `package c; import _ "fmt"`,
+			"subdir/d/d.go":   `package d`,
+			"subdir/e/d.go":   `package e`,
+			"e/e.go":          `package main; import _ "golang.org/fake/b"`,
+			"e/e2.go":         `package main; import _ "golang.org/fake/c"`,
+			"f/f.go":          `package f`,
+			"f/f.s":           ``,
+		}}})
+	defer exported.Cleanup()
+	exported.Config.Dir = filepath.Dir(filepath.Dir(exported.File("golang.org/fake", "a/a.go")))
 
 	checkFile := func(filename string) {
 		if !filepath.IsAbs(filename) {
@@ -967,14 +975,14 @@
 		want    string
 	}{
 		// Import paths
-		{"a", "a.go"},
-		{"b/vendor/a", "a.go"},
-		{"b", "b.go"},
-		{"c", "c.go"},
-		{"subdir/d", "d.go"},
-		{"subdir/e", "d.go"},
-		{"e", "e.go e2.go"},
-		{"f", "f.go f.s"},
+		{"golang.org/fake/a", "a.go"},
+		{"golang.org/fake/b/vendor/a", "a.go"},
+		{"golang.org/fake/b", "b.go"},
+		{"golang.org/fake/c", "c.go"},
+		{"golang.org/fake/subdir/d", "d.go"},
+		{"golang.org/fake/subdir/e", "d.go"},
+		{"golang.org/fake/e", "e.go e2.go"},
+		{"golang.org/fake/f", "f.go f.s"},
 		// Relative paths
 		{"./a", "a.go"},
 		{"./b/vendor/a", "a.go"},
@@ -985,12 +993,8 @@
 		{"./e", "e.go e2.go"},
 		{"./f", "f.go f.s"},
 	} {
-		cfg := &packages.Config{
-			Mode: packages.LoadFiles,
-			Dir:  filepath.Join(tmp, "src"),
-			Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-		}
-		pkgs, err := packages.Load(cfg, test.pattern)
+		exported.Config.Mode = packages.LoadFiles
+		pkgs, err := packages.Load(exported.Config, test.pattern)
 		if err != nil {
 			t.Errorf("pattern %s: %v", test.pattern, err)
 			continue
@@ -1013,29 +1017,28 @@
 	}
 }
 
-func TestFile(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go": `package a; import "b"`,
-		"src/b/b.go": `package b; import "c"`,
-		"src/c/c.go": `package c`,
-	})
-	defer cleanup()
-
-	cfg := &packages.Config{
-		Mode: packages.LoadImports,
-		Dir:  tmp,
-		Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, "file=src/b/b.go")
+func TestContains(t *testing.T) { packagestest.TestAll(t, testContains) }
+func testContains(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a; import "golang.org/fake/b"`,
+			"b/b.go": `package b; import "golang.org/fake/c"`,
+			"c/c.go": `package c`,
+		}}})
+	defer exported.Cleanup()
+	bFile := exported.File("golang.org/fake", "b/b.go")
+	exported.Config.Mode = packages.LoadImports
+	initial, err := packages.Load(exported.Config, "file="+bFile)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	graph, _ := importGraph(initial)
 	wantGraph := `
-* b
-  c
-  b -> c
+* golang.org/fake/b
+  golang.org/fake/c
+  golang.org/fake/b -> golang.org/fake/c
 `[1:]
 	if graph != wantGraph {
 		t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
@@ -1046,19 +1049,20 @@
 // application determines the Sizes function used by the type checker.
 // This behavior is a stop-gap until we make the build system's query
 // too report the correct sizes function for the actual configuration.
-func TestSizes(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go": `package a; import "unsafe"; const WordSize = 8*unsafe.Sizeof(int(0))`,
-	})
-	defer cleanup()
+func TestSizes(t *testing.T) { packagestest.TestAll(t, testSizes) }
+func testSizes(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a; import "unsafe"; const WordSize = 8*unsafe.Sizeof(int(0))`,
+		}}})
+	defer exported.Cleanup()
 
+	exported.Config.Mode = packages.LoadSyntax
+	savedEnv := exported.Config.Env
 	for arch, wantWordSize := range map[string]int64{"386": 32, "amd64": 64} {
-		cfg := &packages.Config{
-			Mode: packages.LoadSyntax,
-			Dir:  tmp,
-			Env:  append(os.Environ(), "GOARCH="+arch, "GOPATH="+tmp, "GO111MODULE=off"),
-		}
-		initial, err := packages.Load(cfg, "a")
+		exported.Config.Env = append(savedEnv, "GOARCH="+arch)
+		initial, err := packages.Load(exported.Config, "golang.org/fake/a")
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -1076,61 +1080,60 @@
 // TestContains_FallbackSticks ensures that when there are both contains and non-contains queries
 // the decision whether to fallback to the pre-1.11 go list sticks across both sets of calls to
 // go list.
-func TestContains_FallbackSticks(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go": `package a; import "b"`,
-		"src/b/b.go": `package b; import "c"`,
-		"src/c/c.go": `package c`,
-	})
-	defer cleanup()
+func TestContains_FallbackSticks(t *testing.T) { packagestest.TestAll(t, testContains_FallbackSticks) }
+func testContains_FallbackSticks(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a; import "golang.org/fake/b"`,
+			"b/b.go": `package b; import "golang.org/fake/c"`,
+			"c/c.go": `package c`,
+		}}})
+	defer exported.Cleanup()
 
-	cfg := &packages.Config{
-		Mode: packages.LoadImports,
-		Dir:  tmp,
-		Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, "a", "file=src/b/b.go")
+	exported.Config.Mode = packages.LoadImports
+	bFile := exported.File("golang.org/fake", "b/b.go")
+	initial, err := packages.Load(exported.Config, "golang.org/fake/a", "file="+bFile)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	graph, _ := importGraph(initial)
 	wantGraph := `
-* a
-* b
-  c
-  a -> b
-  b -> c
+* golang.org/fake/a
+* golang.org/fake/b
+  golang.org/fake/c
+  golang.org/fake/a -> golang.org/fake/b
+  golang.org/fake/b -> golang.org/fake/c
 `[1:]
 	if graph != wantGraph {
 		t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
 	}
 }
 
-func TestName(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/needle/needle.go":       `package needle; import "c"`,
-		"src/b/needle/needle.go":       `package needle;`,
-		"src/c/c.go":                   `package c;`,
-		"src/irrelevant/irrelevant.go": `package irrelevant;`,
-	})
-	defer cleanup()
+func TestName(t *testing.T) { packagestest.TestAll(t, testName) }
+func testName(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/needle/needle.go":       `package needle; import "golang.org/fake/c"`,
+			"b/needle/needle.go":       `package needle;`,
+			"c/c.go":                   `package c;`,
+			"irrelevant/irrelevant.go": `package irrelevant;`,
+		}}})
+	defer exported.Cleanup()
 
-	cfg := &packages.Config{
-		Mode: packages.LoadImports,
-		Dir:  tmp,
-		Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, "name=needle")
+	exported.Config.Mode = packages.LoadImports
+	initial, err := packages.Load(exported.Config, "name=needle")
 	if err != nil {
 		t.Fatal(err)
 	}
 	graph, _ := importGraph(initial)
 	wantGraph := `
-* a/needle
-* b/needle
-  c
-  a/needle -> c
+* golang.org/fake/a/needle
+* golang.org/fake/b/needle
+  golang.org/fake/c
+  golang.org/fake/a/needle -> golang.org/fake/c
 `[1:]
 	if graph != wantGraph {
 		t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
@@ -1142,11 +1145,12 @@
 		t.Skip("pre-modules version of Go")
 	}
 
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/localmod/go.mod":     `module test`,
-		"src/localmod/pkg/pkg.go": `package pkg;`,
-	})
-	defer cleanup()
+	exported := packagestest.Export(t, packagestest.Modules, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"pkg/pkg.go": `package pkg`,
+		}}})
+	defer exported.Cleanup()
 
 	wd, err := os.Getwd()
 	if err != nil {
@@ -1156,13 +1160,9 @@
 	// - pkg/mod/github.com/heschik/tools-testrepo@v1.0.0/pkg
 	// - pkg/mod/github.com/heschik/tools-testrepo/v2@v2.0.0/pkg
 	// - src/b/pkg
-	cfg := &packages.Config{
-		Mode: packages.LoadImports,
-		Dir:  filepath.Join(tmp, "src/localmod"),
-		Env:  append(os.Environ(), "GOPATH="+wd+"/testdata/TestName_Modules", "GO111MODULE=on"),
-	}
-
-	initial, err := packages.Load(cfg, "name=pkg")
+	exported.Config.Mode = packages.LoadImports
+	exported.Config.Env = append(exported.Config.Env, "GOPATH="+wd+"/testdata/TestName_Modules")
+	initial, err := packages.Load(exported.Config, "name=pkg")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1170,7 +1170,7 @@
 	wantGraph := `
 * github.com/heschik/tools-testrepo/pkg
 * github.com/heschik/tools-testrepo/v2/pkg
-* test/pkg
+* golang.org/fake/pkg
 `[1:]
 	if graph != wantGraph {
 		t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
@@ -1182,10 +1182,12 @@
 		t.Skip("pre-modules version of Go")
 	}
 
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/localmod/go.mod": `module test`,
-	})
-	defer cleanup()
+	exported := packagestest.Export(t, packagestest.Modules, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"fake.go": `package fake`,
+		}}})
+	defer exported.Cleanup()
 
 	wd, err := os.Getwd()
 	if err != nil {
@@ -1196,12 +1198,9 @@
 	// - pkg/mod/github.com/heschik/tools-testrepo/v2@v2.0.1/pkg/pkg.go
 	// - pkg/mod/github.com/heschik/tools-testrepo@v1.0.0/pkg/pkg.go
 	// but, inexplicably, not v2.0.0. Nobody knows why.
-	cfg := &packages.Config{
-		Mode: packages.LoadImports,
-		Dir:  filepath.Join(tmp, "src/localmod"),
-		Env:  append(os.Environ(), "GOPATH="+wd+"/testdata/TestName_ModulesDedup", "GO111MODULE=on"),
-	}
-	initial, err := packages.Load(cfg, "name=pkg")
+	exported.Config.Mode = packages.LoadImports
+	exported.Config.Env = append(exported.Config.Env, "GOPATH="+wd+"/testdata/TestName_ModulesDedup")
+	initial, err := packages.Load(exported.Config, "name=pkg")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1215,21 +1214,21 @@
 	t.Errorf("didn't find v2.0.2 of pkg in Load results: %v", initial)
 }
 
-func TestJSON(t *testing.T) {
+func TestJSON(t *testing.T) { packagestest.TestAll(t, testJSON) }
+func testJSON(t *testing.T, exporter packagestest.Exporter) {
 	//TODO: add in some errors
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go": `package a; const A = 1`,
-		"src/b/b.go": `package b; import "a"; var B = a.A`,
-		"src/c/c.go": `package c; import "b" ; var C = b.B`,
-		"src/d/d.go": `package d; import "b" ; var D = b.B`,
-	})
-	defer cleanup()
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a; const A = 1`,
+			"b/b.go": `package b; import "golang.org/fake/a"; var B = a.A`,
+			"c/c.go": `package c; import "golang.org/fake/b" ; var C = b.B`,
+			"d/d.go": `package d; import "golang.org/fake/b" ; var D = b.B`,
+		}}})
+	defer exported.Cleanup()
 
-	cfg := &packages.Config{
-		Mode: packages.LoadImports,
-		Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, "c", "d")
+	exported.Config.Mode = packages.LoadImports
+	initial, err := packages.Load(exported.Config, "golang.org/fake/c", "golang.org/fake/d")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1250,9 +1249,9 @@
 
 	wantJSON := `
 {
-	"ID": "a",
+	"ID": "golang.org/fake/a",
 	"Name": "a",
-	"PkgPath": "a",
+	"PkgPath": "golang.org/fake/a",
 	"GoFiles": [
 		"a.go"
 	],
@@ -1261,9 +1260,9 @@
 	]
 }
 {
-	"ID": "b",
+	"ID": "golang.org/fake/b",
 	"Name": "b",
-	"PkgPath": "b",
+	"PkgPath": "golang.org/fake/b",
 	"GoFiles": [
 		"b.go"
 	],
@@ -1271,13 +1270,13 @@
 		"b.go"
 	],
 	"Imports": {
-		"a": "a"
+		"golang.org/fake/a": "golang.org/fake/a"
 	}
 }
 {
-	"ID": "c",
+	"ID": "golang.org/fake/c",
 	"Name": "c",
-	"PkgPath": "c",
+	"PkgPath": "golang.org/fake/c",
 	"GoFiles": [
 		"c.go"
 	],
@@ -1285,13 +1284,13 @@
 		"c.go"
 	],
 	"Imports": {
-		"b": "b"
+		"golang.org/fake/b": "golang.org/fake/b"
 	}
 }
 {
-	"ID": "d",
+	"ID": "golang.org/fake/d",
 	"Name": "d",
-	"PkgPath": "d",
+	"PkgPath": "golang.org/fake/d",
 	"GoFiles": [
 		"d.go"
 	],
@@ -1299,7 +1298,7 @@
 		"d.go"
 	],
 	"Imports": {
-		"b": "b"
+		"golang.org/fake/b": "golang.org/fake/b"
 	}
 }
 `[1:]
@@ -1321,25 +1320,25 @@
 		t.Fatalf("got %d packages, want 4", len(decoded))
 	}
 	for i, want := range []*packages.Package{{
-		ID:   "a",
+		ID:   "golang.org/fake/a",
 		Name: "a",
 	}, {
-		ID:   "b",
+		ID:   "golang.org/fake/b",
 		Name: "b",
 		Imports: map[string]*packages.Package{
-			"a": &packages.Package{ID: "a"},
+			"golang.org/fake/a": &packages.Package{ID: "golang.org/fake/a"},
 		},
 	}, {
-		ID:   "c",
+		ID:   "golang.org/fake/c",
 		Name: "c",
 		Imports: map[string]*packages.Package{
-			"b": &packages.Package{ID: "b"},
+			"golang.org/fake/b": &packages.Package{ID: "golang.org/fake/b"},
 		},
 	}, {
-		ID:   "d",
+		ID:   "golang.org/fake/d",
 		Name: "d",
 		Imports: map[string]*packages.Package{
-			"b": &packages.Package{ID: "b"},
+			"golang.org/fake/b": &packages.Package{ID: "golang.org/fake/b"},
 		},
 	}} {
 		got := decoded[i]
@@ -1380,17 +1379,16 @@
 	}
 }
 
-func TestPatternPassthrough(t *testing.T) {
-	tmp, cleanup := makeTree(t, map[string]string{
-		"src/a/a.go": `package a;`,
-	})
-	defer cleanup()
+func TestPatternPassthrough(t *testing.T) { packagestest.TestAll(t, testPatternPassthrough) }
+func testPatternPassthrough(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a;`,
+		}}})
+	defer exported.Cleanup()
 
-	cfg := &packages.Config{
-		Mode: packages.LoadImports,
-		Env:  append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
-	}
-	initial, err := packages.Load(cfg, "pattern=a")
+	initial, err := packages.Load(exported.Config, "pattern=a")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1405,14 +1403,17 @@
 
 }
 
-func TestConfigDefaultEnv(t *testing.T) {
+func TestConfigDefaultEnv(t *testing.T) { packagestest.TestAll(t, testConfigDefaultEnv) }
+func testConfigDefaultEnv(t *testing.T, exporter packagestest.Exporter) {
 	if runtime.GOOS == "windows" {
 		// TODO(jayconrod): write an equivalent batch script for windows.
 		// Hint: "type" can be used to read a file to stdout.
 		t.Skip("test requires sh")
 	}
-	tmp, cleanup := makeTree(t, map[string]string{
-		"bin/gopackagesdriver": `#!/bin/sh
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"bin/gopackagesdriver": packagestest.Script(`#!/bin/sh
 
 cat - <<'EOF'
 {
@@ -1420,58 +1421,53 @@
   "Packages": [{"ID": "gopackagesdriver", "Name": "gopackagesdriver"}]
 }
 EOF
-`,
-		"src/golist/golist.go": "package golist",
-	})
-	defer cleanup()
-	if err := os.Chmod(filepath.Join(tmp, "bin", "gopackagesdriver"), 0755); err != nil {
+`),
+			"golist/golist.go": "package golist",
+		}}})
+	defer exported.Cleanup()
+	driver := exported.File("golang.org/fake", "bin/gopackagesdriver")
+	binDir := filepath.Dir(driver)
+	if err := os.Chmod(driver, 0755); err != nil {
 		t.Fatal(err)
 	}
 
 	path, ok := os.LookupEnv("PATH")
 	var pathWithDriver string
 	if ok {
-		pathWithDriver = filepath.Join(tmp, "bin") + string(os.PathListSeparator) + path
+		pathWithDriver = binDir + string(os.PathListSeparator) + path
 	} else {
-		pathWithDriver = filepath.Join(tmp, "bin")
+		pathWithDriver = binDir
 	}
-
+	coreEnv := exported.Config.Env
 	for _, test := range []struct {
 		desc    string
-		env     []string
+		path    string
+		driver  string
 		wantIDs string
 	}{
 		{
 			desc:    "driver_off",
-			env:     []string{"PATH", pathWithDriver, "GOPATH", tmp, "GOPACKAGESDRIVER", "off"},
+			path:    pathWithDriver,
+			driver:  "off",
 			wantIDs: "[golist]",
 		}, {
 			desc:    "driver_unset",
-			env:     []string{"PATH", pathWithDriver, "GOPATH", "", "GOPACKAGESDRIVER", ""},
+			path:    pathWithDriver,
+			driver:  "",
 			wantIDs: "[gopackagesdriver]",
 		}, {
 			desc:    "driver_set",
-			env:     []string{"GOPACKAGESDRIVER", filepath.Join(tmp, "bin", "gopackagesdriver")},
+			path:    "",
+			driver:  driver,
 			wantIDs: "[gopackagesdriver]",
 		},
 	} {
 		t.Run(test.desc, func(t *testing.T) {
-			for i := 0; i < len(test.env); i += 2 {
-				key, value := test.env[i], test.env[i+1]
-				old, ok := os.LookupEnv(key)
-				if value == "" {
-					os.Unsetenv(key)
-				} else {
-					os.Setenv(key, value)
-				}
-				if ok {
-					defer os.Setenv(key, old)
-				} else {
-					defer os.Unsetenv(key)
-				}
-			}
-
-			pkgs, err := packages.Load(nil, "golist")
+			oldPath := os.Getenv("PATH")
+			os.Setenv("PATH", test.path)
+			defer os.Setenv("PATH", oldPath)
+			exported.Config.Env = append(coreEnv, "GOPACKAGESDRIVER="+test.driver)
+			pkgs, err := packages.Load(exported.Config, "golist")
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -1580,40 +1576,10 @@
 	return out.String(), res
 }
 
-const skipCleanup = false // for debugging; don't commit 'true'!
-
-// makeTree creates a new temporary directory containing the specified
-// file tree, and chdirs to it. Call the cleanup function to restore the
-// cwd and delete the tree.
-func makeTree(t *testing.T, tree map[string]string) (dir string, cleanup func()) {
-	dir, err := ioutil.TempDir("", "")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	cleanup = func() {
-		if skipCleanup {
-			t.Logf("Skipping cleanup of temp dir: %s", dir)
-			return
-		}
-		os.RemoveAll(dir) // ignore errors
-	}
-
-	for name, content := range tree {
-		name := filepath.Join(dir, name)
-		if err := os.MkdirAll(filepath.Dir(name), 0777); err != nil {
-			cleanup()
-			t.Fatal(err)
-		}
-		if err := ioutil.WriteFile(name, []byte(content), 0666); err != nil {
-			cleanup()
-			t.Fatal(err)
-		}
-	}
-	return dir, cleanup
-}
-
 func constant(p *packages.Package, name string) *types.Const {
+	if p == nil || p.Types == nil {
+		return nil
+	}
 	c := p.Types.Scope().Lookup(name)
 	if c == nil {
 		return nil
diff --git a/go/packages/packagestest/export.go b/go/packages/packagestest/export.go
new file mode 100644
index 0000000..1181ec2
--- /dev/null
+++ b/go/packages/packagestest/export.go
@@ -0,0 +1,238 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package packagestest creates temporary projects on disk for testing go tools on.
+
+By changing the exporter used, you can create projects for multiple build
+systems from the same description, and run the same tests on them in many
+cases.
+*/
+package packagestest
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"golang.org/x/tools/go/packages"
+)
+
+const (
+	// gorootModule is a special module name that indicates it contains source files
+	// that should replace the normal GOROOT
+	// in general you should not use this, it only exists for some very specialized
+	// tests.
+	gorootModule = "GOROOT"
+)
+
+var (
+	skipCleanup = flag.Bool("skip-cleanup", false, "Do not delete the temporary export folders") // for debugging
+)
+
+// Module is a representation of a go module.
+type Module struct {
+	// Name is the base name of the module as it would be in the go.mod file.
+	Name string
+	// Files is the set of source files for all packages that make up the module.
+	// The keys are the file fragment that follows the module name, the value can
+	// be a string or byte slice, in which case it is the contents of the
+	// file, otherwise it must be a Writer function.
+	Files map[string]interface{}
+}
+
+// A Writer is a function that writes out a test file.
+// It is provided the name of the file to write, and may return an error if it
+// cannot write the file.
+// These are used as the content of the Files map in a Module.
+type Writer func(filename string) error
+
+// Exported is returned by the Export function to report the structure that was produced on disk.
+type Exported struct {
+	// Config is a correctly configured packages.Config ready to be passed to packages.Load.
+	// Exactly what it will contain varies depending on the Exporter being used.
+	Config *packages.Config
+
+	temp    string
+	primary string
+	written map[string]map[string]string
+}
+
+// Exporter implementations are responsible for converting from the generic description of some
+// test data to a driver specific file layout.
+type Exporter interface {
+	// Name reports the name of the exporter, used in logging and sub-test generation.
+	Name() string
+	// Filename reports the system filename for test data source file.
+	// It is given the base directory, the module the file is part of and the filename fragment to
+	// work from.
+	Filename(exported *Exported, module, fragment string) string
+	// Finalize is called once all files have been written to write any extra data needed and modify
+	// the Config to match. It is handed the full list of modules that were encountered while writing
+	// files.
+	Finalize(exported *Exported) error
+}
+
+// All is the list of known exporters.
+// This is used by TestAll to run tests with all the exporters.
+var All []Exporter
+
+// TestAll invokes the testing function once for each exporter registered in
+// the All global.
+// Each exporter will be run as a sub-test named after the exporter being used.
+func TestAll(t *testing.T, f func(*testing.T, Exporter)) {
+	for _, e := range All {
+		t.Run(e.Name(), func(t *testing.T) { f(t, e) })
+	}
+}
+
+// Export is called to write out a test directory from within a test function.
+// It takes the exporter and the build system agnostic module descriptions, and
+// uses them to build a temporary directory.
+// It returns an Exported with the results of the export.
+// The Exported.Config is prepared for loading from the exported data.
+// You must invoke Exported.Cleanup on the returned value to clean up.
+// The file deletion in the cleanup can be skipped by setting the skip-cleanup
+// flag when invoking the test, allowing the temporary directory to be left for
+// debugging tests.
+func Export(t *testing.T, exporter Exporter, modules []Module) *Exported {
+	temp, err := ioutil.TempDir("", strings.Replace(t.Name(), "/", "_", -1))
+	if err != nil {
+		t.Fatal(err)
+	}
+	exported := &Exported{
+		Config: &packages.Config{
+			Dir: temp,
+			Env: append(os.Environ(), "GOPACKAGESDRIVER=off"),
+		},
+		temp:    temp,
+		written: map[string]map[string]string{},
+	}
+	defer func() {
+		if t.Failed() || t.Skipped() {
+			exported.Cleanup()
+		}
+	}()
+	for _, module := range modules {
+		if exported.primary == "" && module.Name != gorootModule {
+			exported.primary = module.Name
+		}
+		for fragment, value := range module.Files {
+			fullpath := exporter.Filename(exported, module.Name, filepath.FromSlash(fragment))
+			written, ok := exported.written[module.Name]
+			if !ok {
+				written = map[string]string{}
+				exported.written[module.Name] = written
+			}
+			written[fragment] = fullpath
+			if err := os.MkdirAll(filepath.Dir(fullpath), 0755); err != nil {
+				t.Fatal(err)
+			}
+			switch value := value.(type) {
+			case Writer:
+				if err := value(fullpath); err != nil {
+					t.Fatal(err)
+				}
+			case string:
+				if err := ioutil.WriteFile(fullpath, []byte(value), 0644); err != nil {
+					t.Fatal(err)
+				}
+			default:
+				t.Fatalf("Invalid type %T in files, must be string or Writer", value)
+			}
+		}
+	}
+	if err := exporter.Finalize(exported); err != nil {
+		t.Fatal(err)
+	}
+	return exported
+}
+
+// Script returns a Writer that writes out contents to the file and sets the
+// executable bit on the created file.
+// It is intended for source files that are shell scripts.
+func Script(contents string) Writer {
+	return func(filename string) error {
+		return ioutil.WriteFile(filename, []byte(contents), 0755)
+	}
+}
+
+// Link returns a Writer that creates a hard link from the specified source to
+// the required file.
+// This is used to link testdata files into the generated testing tree.
+func Link(source string) Writer {
+	return func(filename string) error {
+		return os.Link(source, filename)
+	}
+}
+
+// Symlink returns a Writer that creates a symlink from the specified source to the
+// required file.
+// This is used to link testdata files into the generated testing tree.
+func Symlink(source string) Writer {
+	if !strings.HasPrefix(source, ".") {
+		if abspath, err := filepath.Abs(source); err == nil {
+			if _, err := os.Stat(source); !os.IsNotExist(err) {
+				source = abspath
+			}
+		}
+	}
+	return func(filename string) error {
+		return os.Symlink(source, filename)
+	}
+}
+
+// Copy returns a Writer that copies a file from the specified source to the
+// required file.
+// This is used to copy testdata files into the generated testing tree.
+func Copy(source string) Writer {
+	return func(filename string) error {
+		stat, err := os.Stat(source)
+		if err != nil {
+			return err
+		}
+		if !stat.Mode().IsRegular() {
+			// cannot copy non-regular files (e.g., directories,
+			// symlinks, devices, etc.)
+			return fmt.Errorf("Cannot copy non regular file %s", source)
+		}
+		contents, err := ioutil.ReadFile(source)
+		if err != nil {
+			return err
+		}
+		return ioutil.WriteFile(filename, contents, stat.Mode())
+	}
+}
+
+// Cleanup removes the temporary directory (unless the --skip-cleanup flag was set)
+// It is safe to call cleanup multiple times.
+func (e *Exported) Cleanup() {
+	if e.temp == "" {
+		return
+	}
+	if *skipCleanup {
+		log.Printf("Skipping cleanup of temp dir: %s", e.temp)
+		return
+	}
+	os.RemoveAll(e.temp) // ignore errors
+	e.temp = ""
+}
+
+// Temp returns the temporary directory that was generated.
+func (e *Exported) Temp() string {
+	return e.temp
+}
+
+// File returns the full path for the given module and file fragment.
+func (e *Exported) File(module, fragment string) string {
+	if m := e.written[module]; m != nil {
+		return m[fragment]
+	}
+	return ""
+}
diff --git a/go/packages/packagestest/export_test.go b/go/packages/packagestest/export_test.go
new file mode 100644
index 0000000..0e0b53a
--- /dev/null
+++ b/go/packages/packagestest/export_test.go
@@ -0,0 +1,68 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packagestest_test
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"golang.org/x/tools/go/packages/packagestest"
+)
+
+var testdata = []packagestest.Module{{
+	Name: "golang.org/fake1",
+	Files: map[string]interface{}{
+		"a.go": packagestest.Symlink("testdata/a.go"),
+		"b.go": "package fake1",
+	},
+}, {
+	Name: "golang.org/fake2",
+	Files: map[string]interface{}{
+		"other/a.go": "package fake2",
+	},
+}}
+
+type fileTest struct {
+	module, fragment, expect string
+	check                    func(t *testing.T, filename string)
+}
+
+func checkFiles(t *testing.T, exported *packagestest.Exported, tests []fileTest) {
+	for _, test := range tests {
+		expect := filepath.Join(exported.Temp(), filepath.FromSlash(test.expect))
+		got := exported.File(test.module, test.fragment)
+		if got == "" {
+			t.Errorf("File %v missing from the output", expect)
+		} else if got != expect {
+			t.Errorf("Got file %v, expected %v", got, expect)
+		}
+		if test.check != nil {
+			test.check(t, got)
+		}
+	}
+}
+
+func checkLink(expect string) func(t *testing.T, filename string) {
+	expect = filepath.FromSlash(expect)
+	return func(t *testing.T, filename string) {
+		if target, err := os.Readlink(filename); err != nil {
+			t.Errorf("Error checking link %v: %v", filename, err)
+		} else if target != expect {
+			t.Errorf("Link %v does not match, got %v expected %v", filename, target, expect)
+		}
+	}
+}
+
+func checkContent(expect string) func(t *testing.T, filename string) {
+	return func(t *testing.T, filename string) {
+		if content, err := ioutil.ReadFile(filename); err != nil {
+			t.Errorf("Error reading %v: %v", filename, err)
+		} else if string(content) != expect {
+			t.Errorf("Content of %v does not match, got %v expected %v", filename, string(content), expect)
+		}
+	}
+}
diff --git a/go/packages/packagestest/gopath.go b/go/packages/packagestest/gopath.go
new file mode 100644
index 0000000..149d73e
--- /dev/null
+++ b/go/packages/packagestest/gopath.go
@@ -0,0 +1,74 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packagestest
+
+import (
+	"path"
+	"path/filepath"
+)
+
+// GOPATH is the exporter that produces GOPATH layouts.
+// Each "module" is put in it's own GOPATH entry to help test complex cases.
+// Given the two files
+//     golang.org/repoa#a/a.go
+//     golang.org/repob#b/b.go
+// You would get the directory layout
+//     /sometemporarydirectory
+//     ├── repoa
+//     │   └── src
+//     │       └── golang.org
+//     │           └── repoa
+//     │               └── a
+//     │                   └── a.go
+//     └── repob
+//         └── src
+//             └── golang.org
+//                 └── repob
+//                     └── b
+//     										└── b.go
+// GOPATH would be set to
+//     /sometemporarydirectory/repoa;/sometemporarydirectory/repob
+// and the working directory would be
+//     /sometemporarydirectory/repoa/src
+var GOPATH = gopath{}
+
+func init() {
+	All = append(All, GOPATH)
+}
+
+type gopath struct{}
+
+func (gopath) Name() string {
+	return "GOPATH"
+}
+
+func (gopath) Filename(exported *Exported, module, fragment string) string {
+	return filepath.Join(gopathDir(exported, module), "src", module, fragment)
+}
+
+func (gopath) Finalize(exported *Exported) error {
+	exported.Config.Env = append(exported.Config.Env, "GO111MODULE=off")
+	gopath := ""
+	for module := range exported.written {
+		if gopath != "" {
+			gopath += string(filepath.ListSeparator)
+		}
+		dir := gopathDir(exported, module)
+		if module == gorootModule {
+			exported.Config.Env = append(exported.Config.Env, "GOROOT="+dir)
+			continue
+		}
+		gopath += dir
+		if module == exported.primary {
+			exported.Config.Dir = filepath.Join(dir, "src")
+		}
+	}
+	exported.Config.Env = append(exported.Config.Env, "GOPATH="+gopath)
+	return nil
+}
+
+func gopathDir(exported *Exported, module string) string {
+	return filepath.Join(exported.temp, path.Base(module))
+}
diff --git a/go/packages/packagestest/gopath_test.go b/go/packages/packagestest/gopath_test.go
new file mode 100644
index 0000000..bc6f62c
--- /dev/null
+++ b/go/packages/packagestest/gopath_test.go
@@ -0,0 +1,27 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packagestest_test
+
+import (
+	"path/filepath"
+	"testing"
+
+	"golang.org/x/tools/go/packages/packagestest"
+)
+
+func TestGOPATHExport(t *testing.T) {
+	exported := packagestest.Export(t, packagestest.GOPATH, testdata)
+	defer exported.Cleanup()
+	// Check that the cfg contains all the right bits
+	var expectDir = filepath.Join(exported.Temp(), "fake1", "src")
+	if exported.Config.Dir != expectDir {
+		t.Errorf("Got working directory %v expected %v", exported.Config.Dir, expectDir)
+	}
+	checkFiles(t, exported, []fileTest{
+		{"golang.org/fake1", "a.go", "fake1/src/golang.org/fake1/a.go", checkLink("testdata/a.go")},
+		{"golang.org/fake1", "b.go", "fake1/src/golang.org/fake1/b.go", checkContent("package fake1")},
+		{"golang.org/fake2", "other/a.go", "fake2/src/golang.org/fake2/other/a.go", checkContent("package fake2")},
+	})
+}
diff --git a/go/packages/packagestest/modules.go b/go/packages/packagestest/modules.go
new file mode 100644
index 0000000..039b2d4
--- /dev/null
+++ b/go/packages/packagestest/modules.go
@@ -0,0 +1,82 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.11
+
+package packagestest
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"path"
+	"path/filepath"
+)
+
+// Modules is the exporter that produces module layouts.
+// Each "repository" is put in it's own module, and the module file generated
+// will have replace directives for all other modules.
+// Given the two files
+//     golang.org/repoa#a/a.go
+//     golang.org/repob#b/b.go
+// You would get the directory layout
+//     /sometemporarydirectory
+//     ├── repoa
+//     │   ├── a
+//     │   │   └── a.go
+//     │   └── go.mod
+//     └── repob
+//         ├── b
+//         │   └── b.go
+//         └── go.mod
+// and the working directory would be
+//     /sometemporarydirectory/repoa
+var Modules = modules{}
+
+func init() {
+	All = append(All, Modules)
+}
+
+type modules struct{}
+
+func (modules) Name() string {
+	return "Modules"
+}
+
+func (modules) Filename(exported *Exported, module, fragment string) string {
+	return filepath.Join(moduleDir(exported, module), fragment)
+}
+
+func (modules) Finalize(exported *Exported) error {
+	exported.Config.Env = append(exported.Config.Env, "GO111MODULE=on")
+	for module, files := range exported.written {
+		dir := gopathDir(exported, module)
+		if module == gorootModule {
+			exported.Config.Env = append(exported.Config.Env, "GOROOT="+dir)
+			continue
+		}
+		buf := &bytes.Buffer{}
+		fmt.Fprintf(buf, "module %v\n", module)
+		// add replace directives to the paths of all other modules written
+		for other := range exported.written {
+			if other == gorootModule || other == module {
+				continue
+			}
+			fmt.Fprintf(buf, "replace %v => %v\n", other, moduleDir(exported, other))
+		}
+		modfile := filepath.Join(dir, "go.mod")
+		if err := ioutil.WriteFile(modfile, buf.Bytes(), 0644); err != nil {
+			return err
+		}
+		files["go.mod"] = modfile
+		if module == exported.primary {
+			exported.Config.Dir = dir
+		}
+	}
+	return nil
+}
+
+func moduleDir(exported *Exported, module string) string {
+	return filepath.Join(exported.temp, path.Base(module))
+}
diff --git a/go/packages/packagestest/modules_test.go b/go/packages/packagestest/modules_test.go
new file mode 100644
index 0000000..3530f07
--- /dev/null
+++ b/go/packages/packagestest/modules_test.go
@@ -0,0 +1,31 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.11
+
+package packagestest_test
+
+import (
+	"path/filepath"
+	"testing"
+
+	"golang.org/x/tools/go/packages/packagestest"
+)
+
+func TestModulesExport(t *testing.T) {
+	exported := packagestest.Export(t, packagestest.Modules, testdata)
+	defer exported.Cleanup()
+	// Check that the cfg contains all the right bits
+	var expectDir = filepath.Join(exported.Temp(), "fake1")
+	if exported.Config.Dir != expectDir {
+		t.Errorf("Got working directory %v expected %v", exported.Config.Dir, expectDir)
+	}
+	checkFiles(t, exported, []fileTest{
+		{"golang.org/fake1", "go.mod", "fake1/go.mod", nil},
+		{"golang.org/fake1", "a.go", "fake1/a.go", checkLink("testdata/a.go")},
+		{"golang.org/fake1", "b.go", "fake1/b.go", checkContent("package fake1")},
+		{"golang.org/fake2", "go.mod", "fake2/go.mod", nil},
+		{"golang.org/fake2", "other/a.go", "fake2/other/a.go", checkContent("package fake2")},
+	})
+}