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  }