@@ -13,21 +13,22 @@ import (
13
13
"bytes"
14
14
"fmt"
15
15
"go/ast"
16
- "go/build"
17
- "go/parser"
18
16
"go/token"
19
17
"go/types"
20
18
"os"
19
+ "path/filepath"
21
20
"sort"
22
21
"strings"
23
22
"testing"
24
23
25
- "golang.org/x/tools/go/buildutil"
26
24
"golang.org/x/tools/go/callgraph"
27
25
"golang.org/x/tools/go/callgraph/cha"
28
- "golang.org/x/tools/go/loader "
26
+ "golang.org/x/tools/go/packages "
29
27
"golang.org/x/tools/go/ssa"
30
28
"golang.org/x/tools/go/ssa/ssautil"
29
+ "golang.org/x/tools/internal/testenv"
30
+ "golang.org/x/tools/internal/testfiles"
31
+ "golang.org/x/tools/txtar"
31
32
)
32
33
33
34
var inputs = []string {
@@ -52,45 +53,38 @@ func expectation(f *ast.File) (string, token.Pos) {
52
53
// the WANT comment at the end of the file.
53
54
func TestCHA (t * testing.T ) {
54
55
for _ , filename := range inputs {
55
- prog , f , mainPkg , err := loadProgInfo (filename , ssa .InstantiateGenerics )
56
- if err != nil {
57
- t .Error (err )
58
- continue
59
- }
56
+ pkg , ssapkg := loadFile (t , filename , ssa .InstantiateGenerics )
60
57
61
- want , pos := expectation (f )
58
+ want , pos := expectation (pkg . Syntax [ 0 ] )
62
59
if pos == token .NoPos {
63
60
t .Error (fmt .Errorf ("No WANT: comment in %s" , filename ))
64
61
continue
65
62
}
66
63
67
- cg := cha .CallGraph (prog )
64
+ cg := cha .CallGraph (ssapkg . Prog )
68
65
69
- if got := printGraph (cg , mainPkg . Pkg , "dynamic" , "Dynamic calls" ); got != want {
66
+ if got := printGraph (cg , pkg . Types , "dynamic" , "Dynamic calls" ); got != want {
70
67
t .Errorf ("%s: got:\n %s\n want:\n %s" ,
71
- prog .Fset .Position (pos ), got , want )
68
+ ssapkg . Prog .Fset .Position (pos ), got , want )
72
69
}
73
70
}
74
71
}
75
72
76
73
// TestCHAGenerics is TestCHA tailored for testing generics,
77
74
func TestCHAGenerics (t * testing.T ) {
78
75
filename := "testdata/generics.go"
79
- prog , f , mainPkg , err := loadProgInfo (filename , ssa .InstantiateGenerics )
80
- if err != nil {
81
- t .Fatal (err )
82
- }
76
+ pkg , ssapkg := loadFile (t , filename , ssa .InstantiateGenerics )
83
77
84
- want , pos := expectation (f )
78
+ want , pos := expectation (pkg . Syntax [ 0 ] )
85
79
if pos == token .NoPos {
86
80
t .Fatal (fmt .Errorf ("No WANT: comment in %s" , filename ))
87
81
}
88
82
89
- cg := cha .CallGraph (prog )
83
+ cg := cha .CallGraph (ssapkg . Prog )
90
84
91
- if got := printGraph (cg , mainPkg . Pkg , "" , "All calls" ); got != want {
85
+ if got := printGraph (cg , pkg . Types , "" , "All calls" ); got != want {
92
86
t .Errorf ("%s: got:\n %s\n want:\n %s" ,
93
- prog .Fset .Position (pos ), got , want )
87
+ ssapkg . Prog .Fset .Position (pos ), got , want )
94
88
}
95
89
}
96
90
@@ -109,39 +103,43 @@ func TestCHAUnexported(t *testing.T) {
109
103
// We use CHA to build a callgraph, then check that it has the
110
104
// appropriate set of edges.
111
105
112
- main := `package main
113
- import "p2"
114
- type I1 interface { m() }
115
- type S1 struct { p2.I2 }
116
- func (s S1) m() { }
117
- func main() {
118
- var s S1
119
- var o I1 = s
120
- o.m()
121
- p2.Foo(s)
122
- }`
123
-
124
- p2 := `package p2
125
- type I2 interface { m() }
126
- type S2 struct { }
127
- func (s S2) m() { }
128
- func Foo(i I2) { i.m() }`
106
+ const src = `
107
+ -- go.mod --
108
+ module x.io
109
+ go 1.18
110
+
111
+ -- main/main.go --
112
+ package main
113
+
114
+ import "x.io/p2"
115
+
116
+ type I1 interface { m() }
117
+ type S1 struct { p2.I2 }
118
+ func (s S1) m() { }
119
+ func main() {
120
+ var s S1
121
+ var o I1 = s
122
+ o.m()
123
+ p2.Foo(s)
124
+ }
125
+
126
+ -- p2/p2.go --
127
+ package p2
128
+
129
+ type I2 interface { m() }
130
+ type S2 struct { }
131
+ func (s S2) m() { }
132
+ func Foo(i I2) { i.m() }
133
+ `
129
134
130
135
want := `All calls
131
- main.init --> p2.init
132
- main.main --> (main.S1).m
133
- main.main --> p2.Foo
134
- p2.Foo --> (p2.S2).m`
136
+ x.io/ main.init --> x.io/ p2.init
137
+ x.io/ main.main --> (x.io/ main.S1).m
138
+ x.io/ main.main --> x.io/ p2.Foo
139
+ x.io/ p2.Foo --> (x.io/ p2.S2).m`
135
140
136
- conf := loader.Config {
137
- Build : fakeContext (map [string ]string {"main" : main , "p2" : p2 }),
138
- }
139
- conf .Import ("main" )
140
- iprog , err := conf .Load ()
141
- if err != nil {
142
- t .Fatalf ("Load failed: %v" , err )
143
- }
144
- prog := ssautil .CreateProgram (iprog , ssa .InstantiateGenerics )
141
+ pkgs := testfiles .LoadPackages (t , txtar .Parse ([]byte (src )), "./..." )
142
+ prog , _ := ssautil .Packages (pkgs , ssa .InstantiateGenerics )
145
143
prog .Build ()
146
144
147
145
cg := cha .CallGraph (prog )
@@ -154,39 +152,35 @@ func TestCHAUnexported(t *testing.T) {
154
152
}
155
153
}
156
154
157
- // Simplifying wrapper around buildutil.FakeContext for single-file packages.
158
- func fakeContext (pkgs map [string ]string ) * build.Context {
159
- pkgs2 := make (map [string ]map [string ]string )
160
- for path , content := range pkgs {
161
- pkgs2 [path ] = map [string ]string {"x.go" : content }
162
- }
163
- return buildutil .FakeContext (pkgs2 )
164
- }
155
+ // loadFile loads a built SSA package for a single-file "x.io/main" package.
156
+ // (Ideally all uses would be converted over to txtar files with explicit go.mod files.)
157
+ func loadFile (t testing.TB , filename string , mode ssa.BuilderMode ) (* packages.Package , * ssa.Package ) {
158
+ testenv .NeedsGoPackages (t )
165
159
166
- func loadProgInfo (filename string , mode ssa.BuilderMode ) (* ssa.Program , * ast.File , * ssa.Package , error ) {
167
- content , err := os .ReadFile (filename )
160
+ data , err := os .ReadFile (filename )
168
161
if err != nil {
169
- return nil , nil , nil , fmt . Errorf ( "couldn't read file '%s': %s" , filename , err )
162
+ t . Fatal ( err )
170
163
}
171
-
172
- conf := loader.Config {
173
- ParserMode : parser .ParseComments ,
164
+ dir := t .TempDir ()
165
+ cfg := & packages.Config {
166
+ Mode : packages .LoadAllSyntax ,
167
+ Dir : dir ,
168
+ Overlay : map [string ][]byte {
169
+ filepath .Join (dir , "go.mod" ): []byte ("module x.io\n go 1.22" ),
170
+ filepath .Join (dir , "main/main.go" ): data ,
171
+ },
172
+ Env : append (os .Environ (), "GO111MODULES=on" , "GOPATH=" , "GOWORK=off" , "GOPROXY=off" ),
174
173
}
175
- f , err := conf . ParseFile ( filename , content )
174
+ pkgs , err := packages . Load ( cfg , "./main" )
176
175
if err != nil {
177
- return nil , nil , nil , err
176
+ t . Fatal ( err )
178
177
}
179
-
180
- conf .CreateFromFiles ("main" , f )
181
- iprog , err := conf .Load ()
182
- if err != nil {
183
- return nil , nil , nil , err
178
+ if num := packages .PrintErrors (pkgs ); num > 0 {
179
+ t .Fatalf ("packages contained %d errors" , num )
184
180
}
185
-
186
- prog := ssautil .CreateProgram (iprog , mode )
181
+ prog , ssapkgs := ssautil .Packages (pkgs , mode )
187
182
prog .Build ()
188
-
189
- return prog , f , prog .Package (iprog .Created [0 ].Pkg ), nil
183
+ return pkgs [0 ], ssapkgs [0 ]
190
184
}
191
185
192
186
// printGraph returns a string representation of cg involving only edges
0 commit comments