mirror of https://github.com/golang/go.git
go/callgraph/cha: adds tests for (instantiated) generics
Updates golang/go#48525 Change-Id: Id568e0b188dd045356f556cb9d759a775c8c5a04 Reviewed-on: https://go-review.googlesource.com/c/tools/+/402474 Reviewed-by: Tim King <taking@google.com>
This commit is contained in:
parent
dcaea06afc
commit
a37ba1418f
|
|
@ -24,7 +24,9 @@ import (
|
|||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/callgraph/cha"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
var inputs = []string{
|
||||
|
|
@ -49,16 +51,7 @@ 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 {
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Errorf("couldn't read file '%s': %s", filename, err)
|
||||
continue
|
||||
}
|
||||
|
||||
conf := loader.Config{
|
||||
ParserMode: parser.ParseComments,
|
||||
}
|
||||
f, err := conf.ParseFile(filename, content)
|
||||
prog, f, mainPkg, err := loadProgInfo(filename, ssa.InstantiateGenerics)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
|
|
@ -66,34 +59,77 @@ func TestCHA(t *testing.T) {
|
|||
|
||||
want, pos := expectation(f)
|
||||
if pos == token.NoPos {
|
||||
t.Errorf("No WANT: comment in %s", filename)
|
||||
t.Error(fmt.Errorf("No WANT: comment in %s", filename))
|
||||
continue
|
||||
}
|
||||
|
||||
conf.CreateFromFiles("main", f)
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(iprog, 0)
|
||||
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||
prog.Build()
|
||||
|
||||
cg := cha.CallGraph(prog)
|
||||
|
||||
if got := printGraph(cg, mainPkg.Pkg); got != want {
|
||||
if got := printGraph(cg, mainPkg.Pkg, "dynamic", "Dynamic calls"); got != want {
|
||||
t.Errorf("%s: got:\n%s\nwant:\n%s",
|
||||
prog.Fset.Position(pos), got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printGraph(cg *callgraph.Graph, from *types.Package) string {
|
||||
// TestCHAGenerics is TestCHA tailored for testing generics,
|
||||
func TestCHAGenerics(t *testing.T) {
|
||||
if !typeparams.Enabled {
|
||||
t.Skip("TestCHAGenerics requires type parameters")
|
||||
}
|
||||
|
||||
filename := "testdata/generics.go"
|
||||
prog, f, mainPkg, err := loadProgInfo(filename, ssa.InstantiateGenerics)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want, pos := expectation(f)
|
||||
if pos == token.NoPos {
|
||||
t.Fatal(fmt.Errorf("No WANT: comment in %s", filename))
|
||||
}
|
||||
|
||||
cg := cha.CallGraph(prog)
|
||||
|
||||
if got := printGraph(cg, mainPkg.Pkg, "", "All calls"); got != want {
|
||||
t.Errorf("%s: got:\n%s\nwant:\n%s",
|
||||
prog.Fset.Position(pos), got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func loadProgInfo(filename string, mode ssa.BuilderMode) (*ssa.Program, *ast.File, *ssa.Package, error) {
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("couldn't read file '%s': %s", filename, err)
|
||||
}
|
||||
|
||||
conf := loader.Config{
|
||||
ParserMode: parser.ParseComments,
|
||||
}
|
||||
f, err := conf.ParseFile(filename, content)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
conf.CreateFromFiles("main", f)
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(iprog, mode)
|
||||
prog.Build()
|
||||
|
||||
return prog, f, prog.Package(iprog.Created[0].Pkg), nil
|
||||
}
|
||||
|
||||
// printGraph returns a string representation of cg involving only edges
|
||||
// whose description contains edgeMatch. The string representation is
|
||||
// prefixed with a desc line.
|
||||
func printGraph(cg *callgraph.Graph, from *types.Package, edgeMatch string, desc string) string {
|
||||
var edges []string
|
||||
callgraph.GraphVisitEdges(cg, func(e *callgraph.Edge) error {
|
||||
if strings.Contains(e.Description(), "dynamic") {
|
||||
if strings.Contains(e.Description(), edgeMatch) {
|
||||
edges = append(edges, fmt.Sprintf("%s --> %s",
|
||||
e.Caller.Func.RelString(from),
|
||||
e.Callee.Func.RelString(from)))
|
||||
|
|
@ -103,7 +139,7 @@ func printGraph(cg *callgraph.Graph, from *types.Package) string {
|
|||
sort.Strings(edges)
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("Dynamic calls\n")
|
||||
buf.WriteString(desc + "\n")
|
||||
for _, edge := range edges {
|
||||
fmt.Fprintf(&buf, " %s\n", edge)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
//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()
|
||||
}
|
||||
|
||||
func Bar() {}
|
||||
|
||||
func f(h func(), g func(I), k func(A), a A, b B) {
|
||||
h()
|
||||
|
||||
k(a)
|
||||
g(b) // g:func(I) is not matched by instantiated[[B]]:func(B)
|
||||
|
||||
instantiated[A](a) // static call
|
||||
instantiated[B](b) // static call
|
||||
}
|
||||
|
||||
// WANT:
|
||||
// All calls
|
||||
// (*A).Foo --> (A).Foo
|
||||
// (*B).Foo --> (B).Foo
|
||||
// f --> Bar
|
||||
// f --> instantiated[[main.A]]
|
||||
// f --> instantiated[[main.A]]
|
||||
// f --> instantiated[[main.B]]
|
||||
// instantiated[[main.A]] --> (A).Foo
|
||||
// instantiated[[main.B]] --> (B).Foo
|
||||
Loading…
Reference in New Issue