github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/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  // +build !android
     8  
     9  package rta_test
    10  
    11  import (
    12  	"bytes"
    13  	"fmt"
    14  	"go/ast"
    15  	"go/parser"
    16  	"go/token"
    17  	"go/types"
    18  	"io/ioutil"
    19  	"sort"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/golangci/go-tools/callgraph"
    24  	"github.com/golangci/go-tools/callgraph/rta"
    25  	"golang.org/x/tools/go/loader"
    26  	"github.com/golangci/go-tools/ssa"
    27  	"github.com/golangci/go-tools/ssa/ssautil"
    28  )
    29  
    30  var inputs = []string{
    31  	"testdata/func.go",
    32  	"testdata/rtype.go",
    33  	"testdata/iface.go",
    34  }
    35  
    36  func expectation(f *ast.File) (string, token.Pos) {
    37  	for _, c := range f.Comments {
    38  		text := strings.TrimSpace(c.Text())
    39  		if t := strings.TrimPrefix(text, "WANT:\n"); t != text {
    40  			return t, c.Pos()
    41  		}
    42  	}
    43  	return "", token.NoPos
    44  }
    45  
    46  // TestRTA runs RTA on each file in inputs, prints the results, and
    47  // compares it with the golden results embedded in the WANT comment at
    48  // the end of the file.
    49  //
    50  // The results string consists of two parts: the set of dynamic call
    51  // edges, "f --> g", one per line, and the set of reachable functions,
    52  // one per line.  Each set is sorted.
    53  //
    54  func TestRTA(t *testing.T) {
    55  	for _, filename := range inputs {
    56  		content, err := ioutil.ReadFile(filename)
    57  		if err != nil {
    58  			t.Errorf("couldn't read file '%s': %s", filename, err)
    59  			continue
    60  		}
    61  
    62  		conf := loader.Config{
    63  			ParserMode: parser.ParseComments,
    64  		}
    65  		f, err := conf.ParseFile(filename, content)
    66  		if err != nil {
    67  			t.Error(err)
    68  			continue
    69  		}
    70  
    71  		want, pos := expectation(f)
    72  		if pos == token.NoPos {
    73  			t.Errorf("No WANT: comment in %s", filename)
    74  			continue
    75  		}
    76  
    77  		conf.CreateFromFiles("main", f)
    78  		iprog, err := conf.Load()
    79  		if err != nil {
    80  			t.Error(err)
    81  			continue
    82  		}
    83  
    84  		prog := ssautil.CreateProgram(iprog, 0)
    85  		mainPkg := prog.Package(iprog.Created[0].Pkg)
    86  		prog.Build()
    87  
    88  		res := rta.Analyze([]*ssa.Function{
    89  			mainPkg.Func("main"),
    90  			mainPkg.Func("init"),
    91  		}, true)
    92  
    93  		if got := printResult(res, mainPkg.Pkg); got != want {
    94  			t.Errorf("%s: got:\n%s\nwant:\n%s",
    95  				prog.Fset.Position(pos), got, want)
    96  		}
    97  	}
    98  }
    99  
   100  func printResult(res *rta.Result, from *types.Package) string {
   101  	var buf bytes.Buffer
   102  
   103  	writeSorted := func(ss []string) {
   104  		sort.Strings(ss)
   105  		for _, s := range ss {
   106  			fmt.Fprintf(&buf, "  %s\n", s)
   107  		}
   108  	}
   109  
   110  	buf.WriteString("Dynamic calls\n")
   111  	var edges []string
   112  	callgraph.GraphVisitEdges(res.CallGraph, func(e *callgraph.Edge) error {
   113  		if strings.Contains(e.Description(), "dynamic") {
   114  			edges = append(edges, fmt.Sprintf("%s --> %s",
   115  				e.Caller.Func.RelString(from),
   116  				e.Callee.Func.RelString(from)))
   117  		}
   118  		return nil
   119  	})
   120  	writeSorted(edges)
   121  
   122  	buf.WriteString("Reachable functions\n")
   123  	var reachable []string
   124  	for f := range res.Reachable {
   125  		reachable = append(reachable, f.RelString(from))
   126  	}
   127  	writeSorted(reachable)
   128  
   129  	buf.WriteString("Reflect types\n")
   130  	var rtypes []string
   131  	res.RuntimeTypes.Iterate(func(key types.Type, value interface{}) {
   132  		if value == false { // accessible to reflection
   133  			rtypes = append(rtypes, types.TypeString(key, types.RelativeTo(from)))
   134  		}
   135  	})
   136  	writeSorted(rtypes)
   137  
   138  	return strings.TrimSpace(buf.String())
   139  }