go/ssa: speed up TestTypeparamTest by loading tests once

Previously, TestTypeparamTest constructs a go module for each file
under test/typeparam and uses packages.Load to load it. However, the
packages.Load utilizes the 'go list' underlying and it costs a lot.
As a result, this made the test 50x slower.

This change tries to put each file under a separated package
in the same go module so 'go list' will be used only once.

It supports to skip the test if the test/typeparam under GOROOT doesn't
exist.

Fixes golang/go#70078

Change-Id: I2953709236602f1f6d0b53457ef21bb83561e656
Reviewed-on: https://go-review.googlesource.com/c/tools/+/623135
Reviewed-by: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Richard Miller <millerresearch@gmail.com>
diff --git a/go/ssa/builder_test.go b/go/ssa/builder_test.go
index a38186d..f001fff 100644
--- a/go/ssa/builder_test.go
+++ b/go/ssa/builder_test.go
@@ -6,6 +6,7 @@
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"go/ast"
 	"go/importer"
@@ -686,8 +687,10 @@
 		t.Fatalf("Failed to load errors package from std: %s", err)
 	}
 	goroot := filepath.Dir(filepath.Dir(filepath.Dir(stdPkgs[0].GoFiles[0])))
-
 	dir := filepath.Join(goroot, "test", "typeparam")
+	if _, err = os.Stat(dir); errors.Is(err, os.ErrNotExist) {
+		t.Skipf("test/typeparam doesn't exist under GOROOT %s", goroot)
+	}
 
 	// Collect all of the .go files in
 	fsys := os.DirFS(dir)
@@ -696,24 +699,39 @@
 		t.Fatal(err)
 	}
 
+	// Each call to buildPackage calls package.Load, which invokes "go list",
+	// and with over 300 subtests this can be very slow (minutes, or tens
+	// on some platforms). So, we use an overlay to map each test file to a
+	// distinct single-file package and load them all at once.
+	overlay := map[string][]byte{
+		"go.mod": goMod("example.com", -1),
+	}
 	for _, entry := range entries {
 		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
 			continue // Consider standalone go files.
 		}
-		t.Run(entry.Name(), func(t *testing.T) {
-			src, err := fs.ReadFile(fsys, entry.Name())
-			if err != nil {
-				t.Fatal(err)
-			}
+		src, err := fs.ReadFile(fsys, entry.Name())
+		if err != nil {
+			t.Fatal(err)
+		}
+		// Only build test files that can be compiled, or compiled and run.
+		if !bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// compile")) {
+			t.Logf("%s: not detected as a run test", entry.Name())
+			continue
+		}
 
-			// Only build test files that can be compiled, or compiled and run.
-			if !bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// compile")) {
-				t.Skipf("not detected as a run test")
-			}
+		filename := fmt.Sprintf("%s/main.go", entry.Name())
+		overlay[filename] = src
+	}
 
-			t.Logf("Input: %s\n", entry.Name())
-
-			_, _ = buildPackage(t, string(src), ssa.SanityCheckFunctions|ssa.InstantiateGenerics)
+	// load all packages inside the overlay so 'go list' will be triggered only once.
+	pkgs := loadPackages(t, overlayFS(overlay), "./...")
+	for _, p := range pkgs {
+		originFilename := filepath.Base(filepath.Dir(p.GoFiles[0]))
+		t.Run(originFilename, func(t *testing.T) {
+			t.Parallel()
+			prog, _ := ssautil.Packages([]*packages.Package{p}, ssa.SanityCheckFunctions|ssa.InstantiateGenerics)
+			prog.Package(p.Types).Build()
 		})
 	}
 }