| 1 | // Copyright 2014 The Go Authors. All rights reserved. |
|---|---|
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | // No testdata on Android. |
| 6 | |
| 7 | //go:build !android |
| 8 | // +build !android |
| 9 | |
| 10 | package cha_test |
| 11 | |
| 12 | import ( |
| 13 | "bytes" |
| 14 | "fmt" |
| 15 | "go/ast" |
| 16 | "go/parser" |
| 17 | "go/token" |
| 18 | "go/types" |
| 19 | "io/ioutil" |
| 20 | "sort" |
| 21 | "strings" |
| 22 | "testing" |
| 23 | |
| 24 | "golang.org/x/tools/go/callgraph" |
| 25 | "golang.org/x/tools/go/callgraph/cha" |
| 26 | "golang.org/x/tools/go/loader" |
| 27 | "golang.org/x/tools/go/ssa" |
| 28 | "golang.org/x/tools/go/ssa/ssautil" |
| 29 | "golang.org/x/tools/internal/typeparams" |
| 30 | ) |
| 31 | |
| 32 | var inputs = []string{ |
| 33 | "testdata/func.go", |
| 34 | "testdata/iface.go", |
| 35 | "testdata/recv.go", |
| 36 | "testdata/issue23925.go", |
| 37 | } |
| 38 | |
| 39 | func expectation(f *ast.File) (string, token.Pos) { |
| 40 | for _, c := range f.Comments { |
| 41 | text := strings.TrimSpace(c.Text()) |
| 42 | if t := strings.TrimPrefix(text, "WANT:\n"); t != text { |
| 43 | return t, c.Pos() |
| 44 | } |
| 45 | } |
| 46 | return "", token.NoPos |
| 47 | } |
| 48 | |
| 49 | // TestCHA runs CHA on each file in inputs, prints the dynamic edges of |
| 50 | // the call graph, and compares it with the golden results embedded in |
| 51 | // the WANT comment at the end of the file. |
| 52 | func TestCHA(t *testing.T) { |
| 53 | for _, filename := range inputs { |
| 54 | prog, f, mainPkg, err := loadProgInfo(filename, ssa.InstantiateGenerics) |
| 55 | if err != nil { |
| 56 | t.Error(err) |
| 57 | continue |
| 58 | } |
| 59 | |
| 60 | want, pos := expectation(f) |
| 61 | if pos == token.NoPos { |
| 62 | t.Error(fmt.Errorf("No WANT: comment in %s", filename)) |
| 63 | continue |
| 64 | } |
| 65 | |
| 66 | cg := cha.CallGraph(prog) |
| 67 | |
| 68 | if got := printGraph(cg, mainPkg.Pkg, "dynamic", "Dynamic calls"); got != want { |
| 69 | t.Errorf("%s: got:\n%s\nwant:\n%s", |
| 70 | prog.Fset.Position(pos), got, want) |
| 71 | } |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | // TestCHAGenerics is TestCHA tailored for testing generics, |
| 76 | func TestCHAGenerics(t *testing.T) { |
| 77 | if !typeparams.Enabled { |
| 78 | t.Skip("TestCHAGenerics requires type parameters") |
| 79 | } |
| 80 | |
| 81 | filename := "testdata/generics.go" |
| 82 | prog, f, mainPkg, err := loadProgInfo(filename, ssa.InstantiateGenerics) |
| 83 | if err != nil { |
| 84 | t.Fatal(err) |
| 85 | } |
| 86 | |
| 87 | want, pos := expectation(f) |
| 88 | if pos == token.NoPos { |
| 89 | t.Fatal(fmt.Errorf("No WANT: comment in %s", filename)) |
| 90 | } |
| 91 | |
| 92 | cg := cha.CallGraph(prog) |
| 93 | |
| 94 | if got := printGraph(cg, mainPkg.Pkg, "", "All calls"); got != want { |
| 95 | t.Errorf("%s: got:\n%s\nwant:\n%s", |
| 96 | prog.Fset.Position(pos), got, want) |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | func loadProgInfo(filename string, mode ssa.BuilderMode) (*ssa.Program, *ast.File, *ssa.Package, error) { |
| 101 | content, err := ioutil.ReadFile(filename) |
| 102 | if err != nil { |
| 103 | return nil, nil, nil, fmt.Errorf("couldn't read file '%s': %s", filename, err) |
| 104 | } |
| 105 | |
| 106 | conf := loader.Config{ |
| 107 | ParserMode: parser.ParseComments, |
| 108 | } |
| 109 | f, err := conf.ParseFile(filename, content) |
| 110 | if err != nil { |
| 111 | return nil, nil, nil, err |
| 112 | } |
| 113 | |
| 114 | conf.CreateFromFiles("main", f) |
| 115 | iprog, err := conf.Load() |
| 116 | if err != nil { |
| 117 | return nil, nil, nil, err |
| 118 | } |
| 119 | |
| 120 | prog := ssautil.CreateProgram(iprog, mode) |
| 121 | prog.Build() |
| 122 | |
| 123 | return prog, f, prog.Package(iprog.Created[0].Pkg), nil |
| 124 | } |
| 125 | |
| 126 | // printGraph returns a string representation of cg involving only edges |
| 127 | // whose description contains edgeMatch. The string representation is |
| 128 | // prefixed with a desc line. |
| 129 | func printGraph(cg *callgraph.Graph, from *types.Package, edgeMatch string, desc string) string { |
| 130 | var edges []string |
| 131 | callgraph.GraphVisitEdges(cg, func(e *callgraph.Edge) error { |
| 132 | if strings.Contains(e.Description(), edgeMatch) { |
| 133 | edges = append(edges, fmt.Sprintf("%s --> %s", |
| 134 | e.Caller.Func.RelString(from), |
| 135 | e.Callee.Func.RelString(from))) |
| 136 | } |
| 137 | return nil |
| 138 | }) |
| 139 | sort.Strings(edges) |
| 140 | |
| 141 | var buf bytes.Buffer |
| 142 | buf.WriteString(desc + "\n") |
| 143 | for _, edge := range edges { |
| 144 | fmt.Fprintf(&buf, " %s\n", edge) |
| 145 | } |
| 146 | return strings.TrimSpace(buf.String()) |
| 147 | } |
| 148 |
Members