golang.org/x/tools@v0.21.0/go/callgraph/vta/vta_test.go (about)

     1  // Copyright 2021 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  package vta
     6  
     7  import (
     8  	"testing"
     9  
    10  	"golang.org/x/tools/go/analysis"
    11  	"golang.org/x/tools/go/analysis/analysistest"
    12  	"golang.org/x/tools/go/analysis/passes/buildssa"
    13  	"golang.org/x/tools/go/callgraph/cha"
    14  	"golang.org/x/tools/go/ssa"
    15  	"golang.org/x/tools/go/ssa/ssautil"
    16  )
    17  
    18  func TestVTACallGraph(t *testing.T) {
    19  	for _, file := range []string{
    20  		"testdata/src/callgraph_static.go",
    21  		"testdata/src/callgraph_ho.go",
    22  		"testdata/src/callgraph_interfaces.go",
    23  		"testdata/src/callgraph_pointers.go",
    24  		"testdata/src/callgraph_collections.go",
    25  		"testdata/src/callgraph_fields.go",
    26  		"testdata/src/callgraph_field_funcs.go",
    27  		"testdata/src/callgraph_recursive_types.go",
    28  		"testdata/src/callgraph_issue_57756.go",
    29  		"testdata/src/callgraph_comma_maps.go",
    30  	} {
    31  		t.Run(file, func(t *testing.T) {
    32  			prog, want, err := testProg(file, ssa.BuilderMode(0))
    33  			if err != nil {
    34  				t.Fatalf("couldn't load test file '%s': %s", file, err)
    35  			}
    36  			if len(want) == 0 {
    37  				t.Fatalf("couldn't find want in `%s`", file)
    38  			}
    39  
    40  			g := CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
    41  			got := callGraphStr(g)
    42  			if diff := setdiff(want, got); len(diff) > 0 {
    43  				t.Errorf("computed callgraph %v should contain %v (diff: %v)", got, want, diff)
    44  			}
    45  		})
    46  	}
    47  }
    48  
    49  // TestVTAProgVsFuncSet exemplifies and tests different possibilities
    50  // enabled by having an arbitrary function set as input to CallGraph
    51  // instead of the whole program (i.e., ssautil.AllFunctions(prog)).
    52  func TestVTAProgVsFuncSet(t *testing.T) {
    53  	prog, want, err := testProg("testdata/src/callgraph_nested_ptr.go", ssa.BuilderMode(0))
    54  	if err != nil {
    55  		t.Fatalf("couldn't load test `testdata/src/callgraph_nested_ptr.go`: %s", err)
    56  	}
    57  	if len(want) == 0 {
    58  		t.Fatal("couldn't find want in `testdata/src/callgraph_nested_ptr.go`")
    59  	}
    60  
    61  	allFuncs := ssautil.AllFunctions(prog)
    62  	g := CallGraph(allFuncs, cha.CallGraph(prog))
    63  	// VTA over the whole program will produce a call graph that
    64  	// includes Baz:(**i).Foo -> A.Foo, B.Foo.
    65  	got := callGraphStr(g)
    66  	if diff := setdiff(want, got); len(diff) > 0 {
    67  		t.Errorf("computed callgraph %v should contain %v (diff: %v)", got, want, diff)
    68  	}
    69  
    70  	// Prune the set of program functions to exclude Bar(). This should
    71  	// yield a call graph that includes different set of callees for Baz
    72  	// Baz:(**i).Foo -> A.Foo
    73  	//
    74  	// Note that the exclusion of Bar can happen, for instance, if Baz is
    75  	// considered an entry point of some data flow analysis and Bar is
    76  	// provably (e.g., using CHA forward reachability) unreachable from Baz.
    77  	noBarFuncs := make(map[*ssa.Function]bool)
    78  	for f, in := range allFuncs {
    79  		noBarFuncs[f] = in && (funcName(f) != "Bar")
    80  	}
    81  	want = []string{"Baz: Do(i) -> Do; invoke t2.Foo() -> A.Foo"}
    82  	g = CallGraph(noBarFuncs, cha.CallGraph(prog))
    83  	got = callGraphStr(g)
    84  	if diff := setdiff(want, got); len(diff) > 0 {
    85  		t.Errorf("pruned callgraph %v should contain %v (diff: %v)", got, want, diff)
    86  	}
    87  }
    88  
    89  // TestVTAPanicMissingDefinitions tests if VTA gracefully handles the case
    90  // where VTA panics when a definition of a function or method is not
    91  // available, which can happen when using analysis package. A successful
    92  // test simply does not panic.
    93  func TestVTAPanicMissingDefinitions(t *testing.T) {
    94  	run := func(pass *analysis.Pass) (interface{}, error) {
    95  		s := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
    96  		CallGraph(ssautil.AllFunctions(s.Pkg.Prog), cha.CallGraph(s.Pkg.Prog))
    97  		return nil, nil
    98  	}
    99  
   100  	analyzer := &analysis.Analyzer{
   101  		Name: "test",
   102  		Doc:  "test",
   103  		Run:  run,
   104  		Requires: []*analysis.Analyzer{
   105  			buildssa.Analyzer,
   106  		},
   107  	}
   108  
   109  	testdata := analysistest.TestData()
   110  	res := analysistest.Run(t, testdata, analyzer, "t", "d")
   111  	if len(res) != 2 {
   112  		t.Errorf("want analysis results for 2 packages; got %v", len(res))
   113  	}
   114  	for _, r := range res {
   115  		if r.Err != nil {
   116  			t.Errorf("want no error for package %v; got %v", r.Pass.Pkg.Path(), r.Err)
   117  		}
   118  	}
   119  }
   120  
   121  func TestVTACallGraphGenerics(t *testing.T) {
   122  	// TODO(zpavlinovic): add more tests
   123  	files := []string{
   124  		"testdata/src/arrays_generics.go",
   125  		"testdata/src/callgraph_generics.go",
   126  		"testdata/src/issue63146.go",
   127  	}
   128  	for _, file := range files {
   129  		t.Run(file, func(t *testing.T) {
   130  			prog, want, err := testProg(file, ssa.InstantiateGenerics)
   131  			if err != nil {
   132  				t.Fatalf("couldn't load test file '%s': %s", file, err)
   133  			}
   134  			if len(want) == 0 {
   135  				t.Fatalf("couldn't find want in `%s`", file)
   136  			}
   137  
   138  			g := CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
   139  			got := callGraphStr(g)
   140  			if diff := setdiff(want, got); len(diff) != 0 {
   141  				t.Errorf("computed callgraph %v should contain %v (diff: %v)", got, want, diff)
   142  				logFns(t, prog)
   143  			}
   144  		})
   145  	}
   146  }
   147  
   148  func TestVTACallGraphGo117(t *testing.T) {
   149  	file := "testdata/src/go117.go"
   150  	prog, want, err := testProg(file, ssa.BuilderMode(0))
   151  	if err != nil {
   152  		t.Fatalf("couldn't load test file '%s': %s", file, err)
   153  	}
   154  	if len(want) == 0 {
   155  		t.Fatalf("couldn't find want in `%s`", file)
   156  	}
   157  
   158  	g, _ := typePropGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
   159  	got := vtaGraphStr(g)
   160  	if diff := setdiff(want, got); len(diff) != 0 {
   161  		t.Errorf("`%s`: want superset of %v;\n got %v", file, want, got)
   162  	}
   163  }