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