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).