mirror of
https://github.com/golang/go
synced 2024-11-18 12:54:44 -07:00
cmd/callgraph: use go/packages
Because go/packages presents each synthesized test main package as a first-class package, the tests now analyze all of the dependencies of "testing", which they previously avoided. This makes the tests slower and the resulting call graph much larger, so they now look for a subset match, not an exact match, on the set of graph edges. Change-Id: I9d7acf420e41cbffc03ca8423f5afb3ef671d775 Reviewed-on: https://go-review.googlesource.com/128695 Reviewed-by: Ian Cottrell <iancottrell@google.com> Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
2fad9c5652
commit
1bd72987c2
@ -37,7 +37,7 @@ import (
|
||||
"golang.org/x/tools/go/callgraph/cha"
|
||||
"golang.org/x/tools/go/callgraph/rta"
|
||||
"golang.org/x/tools/go/callgraph/static"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/go/pointer"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
@ -67,7 +67,7 @@ const Usage = `callgraph: display the the call graph of a Go program.
|
||||
|
||||
Usage:
|
||||
|
||||
callgraph [-algo=static|cha|rta|pta] [-test] [-format=...] <args>...
|
||||
callgraph [-algo=static|cha|rta|pta] [-test] [-format=...] package...
|
||||
|
||||
Flags:
|
||||
|
||||
@ -118,8 +118,6 @@ Flags:
|
||||
import path of the enclosing package. Consult the go/ssa
|
||||
API documentation for details.
|
||||
|
||||
` + loader.FromArgsUsage + `
|
||||
|
||||
Examples:
|
||||
|
||||
Show the call graph of the trivial web server application:
|
||||
@ -158,7 +156,7 @@ func init() {
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if err := doCallgraph(&build.Default, *algoFlag, *formatFlag, *testFlag, flag.Args()); err != nil {
|
||||
if err := doCallgraph("", "", *algoFlag, *formatFlag, *testFlag, flag.Args()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "callgraph: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@ -166,28 +164,27 @@ func main() {
|
||||
|
||||
var stdout io.Writer = os.Stdout
|
||||
|
||||
func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []string) error {
|
||||
conf := loader.Config{Build: ctxt}
|
||||
|
||||
func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) error {
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintln(os.Stderr, Usage)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use the initial packages from the command line.
|
||||
_, err := conf.FromArgs(args, tests)
|
||||
if err != nil {
|
||||
return err
|
||||
cfg := &packages.Config{
|
||||
Mode: packages.LoadAllSyntax,
|
||||
Tests: tests,
|
||||
Dir: dir,
|
||||
}
|
||||
|
||||
// Load, parse and type-check the whole program.
|
||||
iprog, err := conf.Load()
|
||||
if gopath != "" {
|
||||
cfg.Env = append(os.Environ(), "GOPATH="+gopath) // to enable testing
|
||||
}
|
||||
initial, err := packages.Load(cfg, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create and build SSA-form program representation.
|
||||
prog := ssautil.CreateProgram(iprog, 0)
|
||||
prog, pkgs := ssautil.Packages(initial, 0)
|
||||
prog.Build()
|
||||
|
||||
// -- call graph construction ------------------------------------------
|
||||
@ -221,7 +218,7 @@ func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []st
|
||||
}
|
||||
}
|
||||
|
||||
mains, err := mainPackages(prog, tests)
|
||||
mains, err := mainPackages(pkgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -237,7 +234,7 @@ func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []st
|
||||
cg = ptares.CallGraph
|
||||
|
||||
case "rta":
|
||||
mains, err := mainPackages(prog, tests)
|
||||
mains, err := mainPackages(pkgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -305,25 +302,13 @@ func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []st
|
||||
|
||||
// mainPackages returns the main packages to analyze.
|
||||
// Each resulting package is named "main" and has a main function.
|
||||
func mainPackages(prog *ssa.Program, tests bool) ([]*ssa.Package, error) {
|
||||
pkgs := prog.AllPackages() // TODO(adonovan): use only initial packages
|
||||
|
||||
// If tests, create a "testmain" package for each test.
|
||||
func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) {
|
||||
var mains []*ssa.Package
|
||||
if tests {
|
||||
for _, pkg := range pkgs {
|
||||
if main := prog.CreateTestMainPackage(pkg); main != nil {
|
||||
mains = append(mains, main)
|
||||
}
|
||||
for _, p := range pkgs {
|
||||
if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil {
|
||||
mains = append(mains, p)
|
||||
}
|
||||
if mains == nil {
|
||||
return nil, fmt.Errorf("no tests")
|
||||
}
|
||||
return mains, nil
|
||||
}
|
||||
|
||||
// Otherwise, use the main packages.
|
||||
mains = append(mains, ssautil.MainPackages(pkgs)...)
|
||||
if len(mains) == 0 {
|
||||
return nil, fmt.Errorf("no main packages")
|
||||
}
|
||||
|
@ -11,25 +11,23 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"reflect"
|
||||
"sort"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCallgraph(t *testing.T) {
|
||||
ctxt := build.Default // copy
|
||||
ctxt.GOPATH = "testdata"
|
||||
|
||||
const format = "{{.Caller}} --> {{.Callee}}"
|
||||
gopath, err := filepath.Abs("testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
algo, format string
|
||||
tests bool
|
||||
want []string
|
||||
algo string
|
||||
tests bool
|
||||
want []string
|
||||
}{
|
||||
{"rta", format, false, []string{
|
||||
{"rta", false, []string{
|
||||
// rta imprecisely shows cross product of {main,main2} x {C,D}
|
||||
`pkg.main --> (pkg.C).f`,
|
||||
`pkg.main --> (pkg.D).f`,
|
||||
@ -37,7 +35,7 @@ func TestCallgraph(t *testing.T) {
|
||||
`pkg.main2 --> (pkg.C).f`,
|
||||
`pkg.main2 --> (pkg.D).f`,
|
||||
}},
|
||||
{"pta", format, false, []string{
|
||||
{"pta", false, []string{
|
||||
// pta distinguishes main->C, main2->D. Also has a root node.
|
||||
`<root> --> pkg.init`,
|
||||
`<root> --> pkg.main`,
|
||||
@ -45,37 +43,42 @@ func TestCallgraph(t *testing.T) {
|
||||
`pkg.main --> pkg.main2`,
|
||||
`pkg.main2 --> (pkg.D).f`,
|
||||
}},
|
||||
// tests: main is not called.
|
||||
{"rta", format, true, []string{
|
||||
`pkg$testmain.init --> pkg.init`,
|
||||
// tests: both the package's main and the test's main are called.
|
||||
// The callgraph includes all the guts of the "testing" package.
|
||||
{"rta", true, []string{
|
||||
`pkg.test.main --> testing.MainStart`,
|
||||
`testing.runExample --> pkg.Example`,
|
||||
`pkg.Example --> (pkg.C).f`,
|
||||
`pkg.main --> (pkg.C).f`,
|
||||
}},
|
||||
{"pta", format, true, []string{
|
||||
`<root> --> pkg$testmain.init`,
|
||||
`<root> --> pkg.Example`,
|
||||
`pkg$testmain.init --> pkg.init`,
|
||||
{"pta", true, []string{
|
||||
`<root> --> pkg.test.main`,
|
||||
`<root> --> pkg.main`,
|
||||
`pkg.test.main --> testing.MainStart`,
|
||||
`testing.runExample --> pkg.Example`,
|
||||
`pkg.Example --> (pkg.C).f`,
|
||||
`pkg.main --> (pkg.C).f`,
|
||||
}},
|
||||
} {
|
||||
const format = "{{.Caller}} --> {{.Callee}}"
|
||||
stdout = new(bytes.Buffer)
|
||||
if err := doCallgraph(&ctxt, test.algo, test.format, test.tests, []string{"pkg"}); err != nil {
|
||||
if err := doCallgraph("testdata/src", gopath, test.algo, format, test.tests, []string{"pkg"}); err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
got := sortedLines(fmt.Sprint(stdout))
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Errorf("callgraph(%q, %q, %t):\ngot:\n%s\nwant:\n%s",
|
||||
test.algo, test.format, test.tests,
|
||||
strings.Join(got, "\n"),
|
||||
strings.Join(test.want, "\n"))
|
||||
edges := make(map[string]bool)
|
||||
for _, line := range strings.Split(fmt.Sprint(stdout), "\n") {
|
||||
edges[line] = true
|
||||
}
|
||||
for _, edge := range test.want {
|
||||
if !edges[edge] {
|
||||
t.Errorf("callgraph(%q, %t): missing edge: %s",
|
||||
test.algo, test.tests, edge)
|
||||
}
|
||||
}
|
||||
if t.Failed() {
|
||||
t.Log("got:\n", stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sortedLines(s string) []string {
|
||||
s = strings.TrimSpace(s)
|
||||
lines := strings.Split(s, "\n")
|
||||
sort.Strings(lines)
|
||||
return lines
|
||||
}
|
||||
|
5
cmd/callgraph/testdata/src/pkg/pkg_test.go
vendored
5
cmd/callgraph/testdata/src/pkg/pkg_test.go
vendored
@ -1,7 +1,10 @@
|
||||
package main
|
||||
|
||||
// Don't import "testing", it adds a lot of callgraph edges.
|
||||
// An Example function must have an "Output:" comment for the go build
|
||||
// system to generate a call to it from the test main package.
|
||||
|
||||
func Example() {
|
||||
C(0).f()
|
||||
|
||||
// Output:
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user