github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/callgraph/rta/rta_test.go (about) 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 rta_test 11 12 import ( 13 "bytes" 14 "fmt" 15 "go/ast" 16 "go/parser" 17 "go/token" 18 "go/types" 19 "os" 20 "sort" 21 "strings" 22 "testing" 23 24 "golang.org/x/tools/go/callgraph" 25 "golang.org/x/tools/go/callgraph/rta" 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/rtype.go", 35 "testdata/iface.go", 36 } 37 38 func expectation(f *ast.File) (string, token.Pos) { 39 for _, c := range f.Comments { 40 text := strings.TrimSpace(c.Text()) 41 if t := strings.TrimPrefix(text, "WANT:\n"); t != text { 42 return t, c.Pos() 43 } 44 } 45 return "", token.NoPos 46 } 47 48 // TestRTA runs RTA on each file in inputs, prints the results, and 49 // compares it with the golden results embedded in the WANT comment at 50 // the end of the file. 51 // 52 // The results string consists of two parts: the set of dynamic call 53 // edges, "f --> g", one per line, and the set of reachable functions, 54 // one per line. Each set is sorted. 55 func TestRTA(t *testing.T) { 56 for _, filename := range inputs { 57 prog, f, mainPkg, err := loadProgInfo(filename, ssa.BuilderMode(0)) 58 if err != nil { 59 t.Error(err) 60 continue 61 } 62 63 want, pos := expectation(f) 64 if pos == token.NoPos { 65 t.Errorf("No WANT: comment in %s", filename) 66 continue 67 } 68 69 res := rta.Analyze([]*ssa.Function{ 70 mainPkg.Func("main"), 71 mainPkg.Func("init"), 72 }, true) 73 74 if got := printResult(res, mainPkg.Pkg, "dynamic", "Dynamic calls"); got != want { 75 t.Errorf("%s: got:\n%s\nwant:\n%s", 76 prog.Fset.Position(pos), got, want) 77 } 78 } 79 } 80 81 // TestRTAGenerics is TestRTA specialized for testing generics. 82 func TestRTAGenerics(t *testing.T) { 83 if !typeparams.Enabled { 84 t.Skip("TestRTAGenerics requires type parameters") 85 } 86 87 filename := "testdata/generics.go" 88 prog, f, mainPkg, err := loadProgInfo(filename, ssa.InstantiateGenerics) 89 if err != nil { 90 t.Fatal(err) 91 } 92 93 want, pos := expectation(f) 94 if pos == token.NoPos { 95 t.Fatalf("No WANT: comment in %s", filename) 96 } 97 98 res := rta.Analyze([]*ssa.Function{ 99 mainPkg.Func("main"), 100 mainPkg.Func("init"), 101 }, true) 102 103 if got := printResult(res, mainPkg.Pkg, "", "All calls"); got != want { 104 t.Errorf("%s: got:\n%s\nwant:\n%s", 105 prog.Fset.Position(pos), got, want) 106 } 107 } 108 109 func loadProgInfo(filename string, mode ssa.BuilderMode) (*ssa.Program, *ast.File, *ssa.Package, error) { 110 content, err := os.ReadFile(filename) 111 if err != nil { 112 return nil, nil, nil, fmt.Errorf("couldn't read file '%s': %s", filename, err) 113 } 114 115 conf := loader.Config{ 116 ParserMode: parser.ParseComments, 117 } 118 f, err := conf.ParseFile(filename, content) 119 if err != nil { 120 return nil, nil, nil, err 121 } 122 123 conf.CreateFromFiles("main", f) 124 iprog, err := conf.Load() 125 if err != nil { 126 return nil, nil, nil, err 127 } 128 129 prog := ssautil.CreateProgram(iprog, mode) 130 prog.Build() 131 132 return prog, f, prog.Package(iprog.Created[0].Pkg), nil 133 } 134 135 // printResult returns a string representation of res, i.e., call graph, 136 // reachable functions, and reflect types. For call graph, only edges 137 // whose description contains edgeMatch are returned and their string 138 // representation is prefixed with a desc line. 139 func printResult(res *rta.Result, from *types.Package, edgeMatch, desc string) string { 140 var buf bytes.Buffer 141 142 writeSorted := func(ss []string) { 143 sort.Strings(ss) 144 for _, s := range ss { 145 fmt.Fprintf(&buf, " %s\n", s) 146 } 147 } 148 149 buf.WriteString(desc + "\n") 150 var edges []string 151 callgraph.GraphVisitEdges(res.CallGraph, func(e *callgraph.Edge) error { 152 if strings.Contains(e.Description(), edgeMatch) { 153 edges = append(edges, fmt.Sprintf("%s --> %s", 154 e.Caller.Func.RelString(from), 155 e.Callee.Func.RelString(from))) 156 } 157 return nil 158 }) 159 writeSorted(edges) 160 161 buf.WriteString("Reachable functions\n") 162 var reachable []string 163 for f := range res.Reachable { 164 reachable = append(reachable, f.RelString(from)) 165 } 166 writeSorted(reachable) 167 168 buf.WriteString("Reflect types\n") 169 var rtypes []string 170 res.RuntimeTypes.Iterate(func(key types.Type, value interface{}) { 171 if value == false { // accessible to reflection 172 rtypes = append(rtypes, types.TypeString(key, types.RelativeTo(from))) 173 } 174 }) 175 writeSorted(rtypes) 176 177 return strings.TrimSpace(buf.String()) 178 }