Skip to content

Commit

Permalink
go/callgraph: rewrite tests to use go/packages not go/loader
Browse files Browse the repository at this point in the history
The cha_test.loadPackages function has moved to testfiles.LoadPackages.

Updates golang/go#69556

Change-Id: I5b1657388cb4d6ca435d1ab3a7cbf9cd264a0e7b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/614679
Reviewed-by: Tim King <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Auto-Submit: Alan Donovan <[email protected]>
  • Loading branch information
adonovan authored and gopherbot committed Sep 22, 2024
1 parent f8ce005 commit 01bd772
Show file tree
Hide file tree
Showing 14 changed files with 232 additions and 221 deletions.
46 changes: 20 additions & 26 deletions go/callgraph/callgraph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package callgraph_test

import (
"log"
"sync"
"testing"

Expand All @@ -13,9 +12,10 @@ import (
"golang.org/x/tools/go/callgraph/rta"
"golang.org/x/tools/go/callgraph/static"
"golang.org/x/tools/go/callgraph/vta"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)

// Benchmarks comparing different callgraph algorithms implemented in
Expand Down Expand Up @@ -47,7 +47,12 @@ import (
// CHA callgraph.
// * All algorithms are unsound w.r.t. reflection.

const httpEx = `package main
const httpEx = `
-- go.mod --
module x.io
-- main.go --
package main
import (
"fmt"
Expand All @@ -70,23 +75,12 @@ var (
main *ssa.Function
)

func example() (*ssa.Program, *ssa.Function) {
func example(t testing.TB) (*ssa.Program, *ssa.Function) {
once.Do(func() {
var conf loader.Config
f, err := conf.ParseFile("<input>", httpEx)
if err != nil {
log.Fatal(err)
}
conf.CreateFromFiles(f.Name.Name, f)

lprog, err := conf.Load()
if err != nil {
log.Fatalf("test 'package %s': Load: %s", f.Name.Name, err)
}
prog = ssautil.CreateProgram(lprog, ssa.InstantiateGenerics)
pkgs := testfiles.LoadPackages(t, txtar.Parse([]byte(httpEx)), ".")
prog, ssapkgs := ssautil.Packages(pkgs, ssa.InstantiateGenerics)
prog.Build()

main = prog.Package(lprog.Created[0].Pkg).Members["main"].(*ssa.Function)
main = ssapkgs[0].Members["main"].(*ssa.Function)
})
return prog, main
}
Expand All @@ -106,7 +100,7 @@ func logStats(b *testing.B, cnd bool, name string, cg *callgraph.Graph, main *ss

func BenchmarkStatic(b *testing.B) {
b.StopTimer()
prog, main := example()
prog, main := example(b)
b.StartTimer()

for i := 0; i < b.N; i++ {
Expand All @@ -117,7 +111,7 @@ func BenchmarkStatic(b *testing.B) {

func BenchmarkCHA(b *testing.B) {
b.StopTimer()
prog, main := example()
prog, main := example(b)
b.StartTimer()

for i := 0; i < b.N; i++ {
Expand All @@ -128,7 +122,7 @@ func BenchmarkCHA(b *testing.B) {

func BenchmarkRTA(b *testing.B) {
b.StopTimer()
_, main := example()
_, main := example(b)
b.StartTimer()

for i := 0; i < b.N; i++ {
Expand All @@ -140,7 +134,7 @@ func BenchmarkRTA(b *testing.B) {

func BenchmarkVTA(b *testing.B) {
b.StopTimer()
prog, main := example()
prog, main := example(b)
b.StartTimer()

for i := 0; i < b.N; i++ {
Expand All @@ -151,7 +145,7 @@ func BenchmarkVTA(b *testing.B) {

func BenchmarkVTA2(b *testing.B) {
b.StopTimer()
prog, main := example()
prog, main := example(b)
b.StartTimer()

for i := 0; i < b.N; i++ {
Expand All @@ -163,7 +157,7 @@ func BenchmarkVTA2(b *testing.B) {

func BenchmarkVTA3(b *testing.B) {
b.StopTimer()
prog, main := example()
prog, main := example(b)
b.StartTimer()

for i := 0; i < b.N; i++ {
Expand All @@ -176,7 +170,7 @@ func BenchmarkVTA3(b *testing.B) {

func BenchmarkVTAAlt(b *testing.B) {
b.StopTimer()
prog, main := example()
prog, main := example(b)
b.StartTimer()

for i := 0; i < b.N; i++ {
Expand All @@ -188,7 +182,7 @@ func BenchmarkVTAAlt(b *testing.B) {

func BenchmarkVTAAlt2(b *testing.B) {
b.StopTimer()
prog, main := example()
prog, main := example(b)
b.StartTimer()

for i := 0; i < b.N; i++ {
Expand Down
146 changes: 70 additions & 76 deletions go/callgraph/cha/cha_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,22 @@ import (
"bytes"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"go/types"
"os"
"path/filepath"
"sort"
"strings"
"testing"

"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/callgraph/cha"
"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/testenv"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)

var inputs = []string{
Expand All @@ -52,45 +53,38 @@ func expectation(f *ast.File) (string, token.Pos) {
// the WANT comment at the end of the file.
func TestCHA(t *testing.T) {
for _, filename := range inputs {
prog, f, mainPkg, err := loadProgInfo(filename, ssa.InstantiateGenerics)
if err != nil {
t.Error(err)
continue
}
pkg, ssapkg := loadFile(t, filename, ssa.InstantiateGenerics)

want, pos := expectation(f)
want, pos := expectation(pkg.Syntax[0])
if pos == token.NoPos {
t.Error(fmt.Errorf("No WANT: comment in %s", filename))
continue
}

cg := cha.CallGraph(prog)
cg := cha.CallGraph(ssapkg.Prog)

if got := printGraph(cg, mainPkg.Pkg, "dynamic", "Dynamic calls"); got != want {
if got := printGraph(cg, pkg.Types, "dynamic", "Dynamic calls"); got != want {
t.Errorf("%s: got:\n%s\nwant:\n%s",
prog.Fset.Position(pos), got, want)
ssapkg.Prog.Fset.Position(pos), got, want)
}
}
}

// TestCHAGenerics is TestCHA tailored for testing generics,
func TestCHAGenerics(t *testing.T) {
filename := "testdata/generics.go"
prog, f, mainPkg, err := loadProgInfo(filename, ssa.InstantiateGenerics)
if err != nil {
t.Fatal(err)
}
pkg, ssapkg := loadFile(t, filename, ssa.InstantiateGenerics)

want, pos := expectation(f)
want, pos := expectation(pkg.Syntax[0])
if pos == token.NoPos {
t.Fatal(fmt.Errorf("No WANT: comment in %s", filename))
}

cg := cha.CallGraph(prog)
cg := cha.CallGraph(ssapkg.Prog)

if got := printGraph(cg, mainPkg.Pkg, "", "All calls"); got != want {
if got := printGraph(cg, pkg.Types, "", "All calls"); got != want {
t.Errorf("%s: got:\n%s\nwant:\n%s",
prog.Fset.Position(pos), got, want)
ssapkg.Prog.Fset.Position(pos), got, want)
}
}

Expand All @@ -109,39 +103,43 @@ func TestCHAUnexported(t *testing.T) {
// We use CHA to build a callgraph, then check that it has the
// appropriate set of edges.

main := `package main
import "p2"
type I1 interface { m() }
type S1 struct { p2.I2 }
func (s S1) m() { }
func main() {
var s S1
var o I1 = s
o.m()
p2.Foo(s)
}`

p2 := `package p2
type I2 interface { m() }
type S2 struct { }
func (s S2) m() { }
func Foo(i I2) { i.m() }`
const src = `
-- go.mod --
module x.io
go 1.18
-- main/main.go --
package main
import "x.io/p2"
type I1 interface { m() }
type S1 struct { p2.I2 }
func (s S1) m() { }
func main() {
var s S1
var o I1 = s
o.m()
p2.Foo(s)
}
-- p2/p2.go --
package p2
type I2 interface { m() }
type S2 struct { }
func (s S2) m() { }
func Foo(i I2) { i.m() }
`

want := `All calls
main.init --> p2.init
main.main --> (main.S1).m
main.main --> p2.Foo
p2.Foo --> (p2.S2).m`
x.io/main.init --> x.io/p2.init
x.io/main.main --> (x.io/main.S1).m
x.io/main.main --> x.io/p2.Foo
x.io/p2.Foo --> (x.io/p2.S2).m`

conf := loader.Config{
Build: fakeContext(map[string]string{"main": main, "p2": p2}),
}
conf.Import("main")
iprog, err := conf.Load()
if err != nil {
t.Fatalf("Load failed: %v", err)
}
prog := ssautil.CreateProgram(iprog, ssa.InstantiateGenerics)
pkgs := testfiles.LoadPackages(t, txtar.Parse([]byte(src)), "./...")
prog, _ := ssautil.Packages(pkgs, ssa.InstantiateGenerics)
prog.Build()

cg := cha.CallGraph(prog)
Expand All @@ -154,39 +152,35 @@ func TestCHAUnexported(t *testing.T) {
}
}

// Simplifying wrapper around buildutil.FakeContext for single-file packages.
func fakeContext(pkgs map[string]string) *build.Context {
pkgs2 := make(map[string]map[string]string)
for path, content := range pkgs {
pkgs2[path] = map[string]string{"x.go": content}
}
return buildutil.FakeContext(pkgs2)
}
// loadFile loads a built SSA package for a single-file "x.io/main" package.
// (Ideally all uses would be converted over to txtar files with explicit go.mod files.)
func loadFile(t testing.TB, filename string, mode ssa.BuilderMode) (*packages.Package, *ssa.Package) {
testenv.NeedsGoPackages(t)

func loadProgInfo(filename string, mode ssa.BuilderMode) (*ssa.Program, *ast.File, *ssa.Package, error) {
content, err := os.ReadFile(filename)
data, err := os.ReadFile(filename)
if err != nil {
return nil, nil, nil, fmt.Errorf("couldn't read file '%s': %s", filename, err)
t.Fatal(err)
}

conf := loader.Config{
ParserMode: parser.ParseComments,
dir := t.TempDir()
cfg := &packages.Config{
Mode: packages.LoadAllSyntax,
Dir: dir,
Overlay: map[string][]byte{
filepath.Join(dir, "go.mod"): []byte("module x.io\ngo 1.22"),
filepath.Join(dir, "main/main.go"): data,
},
Env: append(os.Environ(), "GO111MODULES=on", "GOPATH=", "GOWORK=off", "GOPROXY=off"),
}
f, err := conf.ParseFile(filename, content)
pkgs, err := packages.Load(cfg, "./main")
if err != nil {
return nil, nil, nil, err
t.Fatal(err)
}

conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
return nil, nil, nil, err
if num := packages.PrintErrors(pkgs); num > 0 {
t.Fatalf("packages contained %d errors", num)
}

prog := ssautil.CreateProgram(iprog, mode)
prog, ssapkgs := ssautil.Packages(pkgs, mode)
prog.Build()

return prog, f, prog.Package(iprog.Created[0].Pkg), nil
return pkgs[0], ssapkgs[0]
}

// printGraph returns a string representation of cg involving only edges
Expand Down
2 changes: 0 additions & 2 deletions go/callgraph/cha/testdata/func.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// +build ignore

package main

// Test of dynamic function calls; no interfaces.
Expand Down
13 changes: 5 additions & 8 deletions go/callgraph/cha/testdata/generics.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build ignore
// +build ignore

package main

// Test of generic function calls.
Expand Down Expand Up @@ -38,12 +35,12 @@ func f(h func(), g func(I), k func(A), a A, b B) {
// (*A).Foo --> (A).Foo
// (*B).Foo --> (B).Foo
// f --> Bar
// f --> instantiated[main.A]
// f --> instantiated[main.A]
// f --> instantiated[main.B]
// f --> instantiated[x.io/main.A]
// f --> instantiated[x.io/main.A]
// f --> instantiated[x.io/main.B]
// instantiated --> (*A).Foo
// instantiated --> (*B).Foo
// instantiated --> (A).Foo
// instantiated --> (B).Foo
// instantiated[main.A] --> (A).Foo
// instantiated[main.B] --> (B).Foo
// instantiated[x.io/main.A] --> (A).Foo
// instantiated[x.io/main.B] --> (B).Foo
2 changes: 0 additions & 2 deletions go/callgraph/cha/testdata/iface.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// +build ignore

package main

// Test of interface calls. None of the concrete types are ever
Expand Down
2 changes: 0 additions & 2 deletions go/callgraph/cha/testdata/recv.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// +build ignore

package main

type I interface {
Expand Down
Loading

0 comments on commit 01bd772

Please sign in to comment.