go/callgraph/rta: add rta analysis test case for multiple go packages

* use go/packages to load packages

Change-Id: I6e9f81b282cddc186b4905a23ff635cd98245ac8
GitHub-Last-Rev: a859e27d81476736c82a9d9c7c40ec218c2a193b
GitHub-Pull-Request: golang/tools#513
Reviewed-on: https://go-review.googlesource.com/c/tools/+/609576
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Tim King <taking@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/go/callgraph/rta/rta_test.go b/go/callgraph/rta/rta_test.go
index 8552dc7..1fc32c6 100644
--- a/go/callgraph/rta/rta_test.go
+++ b/go/callgraph/rta/rta_test.go
@@ -12,7 +12,6 @@
 import (
 	"fmt"
 	"go/ast"
-	"go/parser"
 	"go/types"
 	"sort"
 	"strings"
@@ -20,39 +19,59 @@
 
 	"golang.org/x/tools/go/callgraph"
 	"golang.org/x/tools/go/callgraph/rta"
-	"golang.org/x/tools/go/loader"
+	"golang.org/x/tools/go/packages"
 	"golang.org/x/tools/go/ssa"
 	"golang.org/x/tools/go/ssa/ssautil"
 	"golang.org/x/tools/internal/aliases"
+	"golang.org/x/tools/internal/testfiles"
+	"golang.org/x/tools/txtar"
 )
 
-// TestRTA runs RTA on each testdata/*.go file and compares the
-// results with the expectations expressed in the WANT comment.
+// TestRTA runs RTA on each testdata/*.txtar file containing a single
+// go file in a single package or multiple files in different packages,
+// and compares the results with the expectations expressed in the WANT
+// comment.
 func TestRTA(t *testing.T) {
-	filenames := []string{
-		"testdata/func.go",
-		"testdata/generics.go",
-		"testdata/iface.go",
-		"testdata/reflectcall.go",
-		"testdata/rtype.go",
+	archivePaths := []string{
+		"testdata/func.txtar",
+		"testdata/generics.txtar",
+		"testdata/iface.txtar",
+		"testdata/reflectcall.txtar",
+		"testdata/rtype.txtar",
+		"testdata/multipkgs.txtar",
 	}
-	for _, filename := range filenames {
-		t.Run(filename, func(t *testing.T) {
-			// Load main program and build SSA.
-			// TODO(adonovan): use go/packages instead.
-			conf := loader.Config{ParserMode: parser.ParseComments}
-			f, err := conf.ParseFile(filename, nil)
-			if err != nil {
-				t.Fatal(err)
+	for _, archive := range archivePaths {
+		t.Run(archive, func(t *testing.T) {
+			pkgs := loadPackages(t, archive)
+
+			// find the file which contains the expected result
+			var f *ast.File
+			for _, p := range pkgs {
+				// We assume the packages have a single file or
+				// the wanted result is in the first file of the main package.
+				if p.Name == "main" {
+					f = p.Syntax[0]
+				}
 			}
-			conf.CreateFromFiles("main", f)
-			lprog, err := conf.Load()
-			if err != nil {
-				t.Fatal(err)
+			if f == nil {
+				t.Fatalf("failed to find the file with expected result within main package %s", archive)
 			}
-			prog := ssautil.CreateProgram(lprog, ssa.InstantiateGenerics)
+
+			prog, spkgs := ssautil.Packages(pkgs, ssa.SanityCheckFunctions|ssa.InstantiateGenerics)
+
+			// find the main package to get functions for rta analysis
+			var mainPkg *ssa.Package
+			for _, sp := range spkgs {
+				if sp.Pkg.Name() == "main" {
+					mainPkg = sp
+					break
+				}
+			}
+			if mainPkg == nil {
+				t.Fatalf("failed to find main ssa package %s", archive)
+			}
+
 			prog.Build()
-			mainPkg := prog.Package(lprog.Created[0].Pkg)
 
 			res := rta.Analyze([]*ssa.Function{
 				mainPkg.Func("main"),
@@ -64,6 +83,40 @@
 	}
 }
 
+// loadPackages unpacks the archive to a temporary directory and loads all packages within it.
+func loadPackages(t *testing.T, archive string) []*packages.Package {
+	ar, err := txtar.ParseFile(archive)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fs, err := txtar.FS(ar)
+	if err != nil {
+		t.Fatal(err)
+	}
+	dir := testfiles.CopyToTmp(t, fs)
+
+	var baseConfig = &packages.Config{
+		Mode: packages.NeedSyntax |
+			packages.NeedTypesInfo |
+			packages.NeedDeps |
+			packages.NeedName |
+			packages.NeedFiles |
+			packages.NeedImports |
+			packages.NeedCompiledGoFiles |
+			packages.NeedTypes,
+		Dir: dir,
+	}
+	pkgs, err := packages.Load(baseConfig, "./...")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if num := packages.PrintErrors(pkgs); num > 0 {
+		t.Fatalf("packages contained %d errors", num)
+	}
+	return pkgs
+}
+
 // check tests the RTA analysis results against the test expectations
 // defined by a comment starting with a line "WANT:".
 //
diff --git a/go/callgraph/rta/testdata/func.go b/go/callgraph/rta/testdata/func.txtar
similarity index 90%
rename from go/callgraph/rta/testdata/func.go
rename to go/callgraph/rta/testdata/func.txtar
index bcdcb6e..57930a4 100644
--- a/go/callgraph/rta/testdata/func.go
+++ b/go/callgraph/rta/testdata/func.txtar
@@ -1,6 +1,8 @@
-//go:build ignore
-// +build ignore
+-- go.mod --
+module example.com
+go 1.18
 
+-- func.go --
 package main
 
 // Test of dynamic function calls.
@@ -36,4 +38,4 @@
 //  reachable init$1
 //  reachable init$2
 // !reachable B
-//  reachable main
+//  reachable main
\ No newline at end of file
diff --git a/go/callgraph/rta/testdata/generics.go b/go/callgraph/rta/testdata/generics.go
deleted file mode 100644
index 17ed6b5..0000000
--- a/go/callgraph/rta/testdata/generics.go
+++ /dev/null
@@ -1,79 +0,0 @@
-//go:build ignore
-// +build ignore
-
-package main
-
-// Test of generic function calls.
-
-type I interface {
-	Foo()
-}
-
-type A struct{}
-
-func (a A) Foo() {}
-
-type B struct{}
-
-func (b B) Foo() {}
-
-func instantiated[X I](x X) {
-	x.Foo()
-}
-
-var a A
-var b B
-
-func main() {
-	instantiated[A](a) // static call
-	instantiated[B](b) // static call
-
-	local[C]().Foo()
-
-	lambda[A]()()()
-}
-
-func local[X I]() I {
-	var x X
-	return x
-}
-
-type C struct{}
-
-func (c C) Foo() {}
-
-func lambda[X I]() func() func() {
-	return func() func() {
-		var x X
-		return x.Foo
-	}
-}
-
-// WANT:
-//
-//  edge (*C).Foo --static method call--> (C).Foo
-//  edge (A).Foo$bound --static method call--> (A).Foo
-//  edge instantiated[main.A] --static method call--> (A).Foo
-//  edge instantiated[main.B] --static method call--> (B).Foo
-//  edge main --dynamic method call--> (*C).Foo
-//  edge main --dynamic function call--> (A).Foo$bound
-//  edge main --dynamic method call--> (C).Foo
-//  edge main --static function call--> instantiated[main.A]
-//  edge main --static function call--> instantiated[main.B]
-//  edge main --static function call--> lambda[main.A]
-//  edge main --dynamic function call--> lambda[main.A]$1
-//  edge main --static function call--> local[main.C]
-//
-//  reachable (*C).Foo
-//  reachable (A).Foo
-//  reachable (A).Foo$bound
-//  reachable (B).Foo
-//  reachable (C).Foo
-//  reachable instantiated[main.A]
-//  reachable instantiated[main.B]
-//  reachable lambda[main.A]
-//  reachable lambda[main.A]$1
-//  reachable local[main.C]
-//
-//  rtype *C
-//  rtype C
diff --git a/go/callgraph/rta/testdata/generics.txtar b/go/callgraph/rta/testdata/generics.txtar
new file mode 100644
index 0000000..b803974
--- /dev/null
+++ b/go/callgraph/rta/testdata/generics.txtar
@@ -0,0 +1,81 @@
+-- go.mod --
+module example.com
+go 1.18
+
+-- generics.go --
+package main
+
+// Test of generic function calls.
+
+type I interface {
+	Foo()
+}
+
+type A struct{}
+
+func (a A) Foo() {}
+
+type B struct{}
+
+func (b B) Foo() {}
+
+func instantiated[X I](x X) {
+	x.Foo()
+}
+
+var a A
+var b B
+
+func main() {
+	instantiated[A](a) // static call
+	instantiated[B](b) // static call
+
+	local[C]().Foo()
+
+	lambda[A]()()()
+}
+
+func local[X I]() I {
+	var x X
+	return x
+}
+
+type C struct{}
+
+func (c C) Foo() {}
+
+func lambda[X I]() func() func() {
+	return func() func() {
+		var x X
+		return x.Foo
+	}
+}
+
+// WANT:
+//
+//  edge (*C).Foo --static method call--> (C).Foo
+//  edge (A).Foo$bound --static method call--> (A).Foo
+//  edge instantiated[example.com.A] --static method call--> (A).Foo
+//  edge instantiated[example.com.B] --static method call--> (B).Foo
+//  edge main --dynamic method call--> (*C).Foo
+//  edge main --dynamic function call--> (A).Foo$bound
+//  edge main --dynamic method call--> (C).Foo
+//  edge main --static function call--> instantiated[example.com.A]
+//  edge main --static function call--> instantiated[example.com.B]
+//  edge main --static function call--> lambda[example.com.A]
+//  edge main --dynamic function call--> lambda[example.com.A]$1
+//  edge main --static function call--> local[example.com.C]
+//
+//  reachable (*C).Foo
+//  reachable (A).Foo
+//  reachable (A).Foo$bound
+//  reachable (B).Foo
+//  reachable (C).Foo
+//  reachable instantiated[example.com.A]
+//  reachable instantiated[example.com.B]
+//  reachable lambda[example.com.A]
+//  reachable lambda[example.com.A]$1
+//  reachable local[example.com.C]
+//
+//  rtype *C
+//  rtype C
diff --git a/go/callgraph/rta/testdata/iface.go b/go/callgraph/rta/testdata/iface.txtar
similarity index 96%
rename from go/callgraph/rta/testdata/iface.go
rename to go/callgraph/rta/testdata/iface.txtar
index c559204..ceb0140 100644
--- a/go/callgraph/rta/testdata/iface.go
+++ b/go/callgraph/rta/testdata/iface.txtar
@@ -1,6 +1,8 @@
-//go:build ignore
-// +build ignore
+-- go.mod --
+module example.com
+go 1.18
 
+-- iface.go --
 package main
 
 // Test of interface calls.
diff --git a/go/callgraph/rta/testdata/multipkgs.txtar b/go/callgraph/rta/testdata/multipkgs.txtar
new file mode 100644
index 0000000..908fea0
--- /dev/null
+++ b/go/callgraph/rta/testdata/multipkgs.txtar
@@ -0,0 +1,106 @@
+-- go.mod --
+module example.com
+go 1.18
+
+-- iface.go --
+package main
+
+import (
+	"example.com/subpkg"
+)
+
+func use(interface{})
+
+// Test of interface calls.
+
+func main() {
+	use(subpkg.A(0))
+	use(new(subpkg.B))
+	use(subpkg.B2(0))
+
+	var i interface {
+		F()
+	}
+
+	// assign an interface type with a function return interface value
+	i = subpkg.NewInterfaceF()
+
+	i.F()
+}
+
+func dead() {
+	use(subpkg.D(0))
+}
+
+// WANT:
+//
+// edge (*example.com/subpkg.A).F --static method call--> (example.com/subpkg.A).F
+// edge (*example.com/subpkg.B2).F --static method call--> (example.com/subpkg.B2).F
+// edge (*example.com/subpkg.C).F --static method call--> (example.com/subpkg.C).F
+// edge init --static function call--> example.com/subpkg.init
+// edge main --dynamic method call--> (*example.com/subpkg.A).F
+// edge main --dynamic method call--> (*example.com/subpkg.B).F
+// edge main --dynamic method call--> (*example.com/subpkg.B2).F
+// edge main --dynamic method call--> (*example.com/subpkg.C).F
+// edge main --dynamic method call--> (example.com/subpkg.A).F
+// edge main --dynamic method call--> (example.com/subpkg.B2).F
+// edge main --dynamic method call--> (example.com/subpkg.C).F
+// edge main --static function call--> example.com/subpkg.NewInterfaceF
+// edge main --static function call--> use
+//
+// reachable (*example.com/subpkg.A).F
+// reachable (*example.com/subpkg.B).F
+// reachable (*example.com/subpkg.B2).F
+// reachable (*example.com/subpkg.C).F
+// reachable (example.com/subpkg.A).F
+// !reachable (example.com/subpkg.B).F
+// reachable (example.com/subpkg.B2).F
+// reachable (example.com/subpkg.C).F
+// reachable example.com/subpkg.NewInterfaceF
+// reachable example.com/subpkg.init
+// !reachable (*example.com/subpkg.D).F
+// !reachable (example.com/subpkg.D).F
+// reachable init
+// reachable main
+// reachable use
+//
+// rtype *example.com/subpkg.A
+// rtype *example.com/subpkg.B
+// rtype *example.com/subpkg.B2
+// rtype *example.com/subpkg.C
+// rtype example.com/subpkg.B
+// rtype example.com/subpkg.A
+// rtype example.com/subpkg.B2
+// rtype example.com/subpkg.C
+// !rtype example.com/subpkg.D
+
+-- subpkg/impl.go --
+package subpkg
+
+type InterfaceF interface {
+	F()
+}
+
+type A byte // instantiated but not a reflect type
+
+func (A) F() {} // reachable: exported method of reflect type
+
+type B int // a reflect type
+
+func (*B) F() {} // reachable: exported method of reflect type
+
+type B2 int // a reflect type, and *B2 also
+
+func (B2) F() {} // reachable: exported method of reflect type
+
+type C string
+
+func (C) F() {} // reachable: exported by NewInterfaceF
+
+func NewInterfaceF() InterfaceF {
+	return C("")
+}
+
+type D uint // instantiated only in dead code
+
+func (*D) F() {} // unreachable
\ No newline at end of file
diff --git a/go/callgraph/rta/testdata/reflectcall.go b/go/callgraph/rta/testdata/reflectcall.txtar
similarity index 94%
rename from go/callgraph/rta/testdata/reflectcall.go
rename to go/callgraph/rta/testdata/reflectcall.txtar
index 8f71fb5..67cd290 100644
--- a/go/callgraph/rta/testdata/reflectcall.go
+++ b/go/callgraph/rta/testdata/reflectcall.txtar
@@ -1,6 +1,8 @@
-//go:build ignore
-// +build ignore
+-- go.mod --
+module example.com
+go 1.18
 
+-- reflectcall.go --
 // Test of a reflective call to an address-taken function.
 //
 // Dynamically, this program executes both print statements.
diff --git a/go/callgraph/rta/testdata/rtype.go b/go/callgraph/rta/testdata/rtype.txtar
similarity index 91%
rename from go/callgraph/rta/testdata/rtype.go
rename to go/callgraph/rta/testdata/rtype.txtar
index 6d84e03..377bc1f 100644
--- a/go/callgraph/rta/testdata/rtype.go
+++ b/go/callgraph/rta/testdata/rtype.txtar
@@ -1,6 +1,8 @@
-//go:build ignore
-// +build ignore
+-- go.mod --
+module example.com
+go 1.18
 
+-- rtype.go --
 package main
 
 // Test of runtime types (types for which descriptors are needed).