1
2
3
4
5 package facts_test
6
7 import (
8 "encoding/gob"
9 "fmt"
10 "go/ast"
11 "go/parser"
12 "go/token"
13 "go/types"
14 "os"
15 "reflect"
16 "strings"
17 "testing"
18
19 "golang.org/x/tools/go/analysis/analysistest"
20 "golang.org/x/tools/go/packages"
21 "golang.org/x/tools/internal/aliases"
22 "golang.org/x/tools/internal/facts"
23 "golang.org/x/tools/internal/testenv"
24 )
25
26 type myFact struct {
27 S string
28 }
29
30 func (f *myFact) String() string { return fmt.Sprintf("myFact(%s)", f.S) }
31 func (f *myFact) AFact() {}
32
33 func init() {
34 gob.Register(new(myFact))
35 }
36
37 func TestEncodeDecode(t *testing.T) {
38 tests := []struct {
39 name string
40 typeparams bool
41 files map[string]string
42 plookups []pkgLookups
43 }{
44 {
45 name: "loading-order",
46
47
48
49
50
51
52
53
54 files: map[string]string{
55 "a/a.go": `package a; type A int; type T int`,
56 "a2/a.go": `package a2; type A2 int; type Unneeded int`,
57 "b/b.go": `package b; import ("a"; "a2"); type B chan a2.A2; type F func() a.T`,
58 "c/c.go": `package c; import "b"; type C []b.B`,
59 },
60
61
62
63
64
65
66
67 plookups: []pkgLookups{
68 {"a", []lookup{
69 {"A", "myFact(a.A)"},
70 }},
71 {"b", []lookup{
72 {"a.A", "myFact(a.A)"},
73 {"a.T", "myFact(a.T)"},
74 {"B", "myFact(b.B)"},
75 {"F", "myFact(b.F)"},
76 {"F(nil)()", "myFact(a.T)"},
77 }},
78 {"c", []lookup{
79 {"b.B", "myFact(b.B)"},
80 {"b.F", "myFact(b.F)"},
81 {"b.F(nil)()", "myFact(a.T)"},
82 {"C", "myFact(c.C)"},
83 {"C{}[0]", "myFact(b.B)"},
84 {"<-(C{}[0])", "no fact"},
85 }},
86 },
87 },
88 {
89 name: "underlying",
90
91
92
93
94 files: map[string]string{
95 "a/a.go": `package a; type a int; type T *a`,
96 "b/b.go": `package b; import "a"; type B a.T`,
97 "c/c.go": `package c; import "b"; type C b.B; var q = *C(nil)`,
98 },
99 plookups: []pkgLookups{
100 {"a", []lookup{
101 {"a", "myFact(a.a)"},
102 {"T", "myFact(a.T)"},
103 }},
104 {"b", []lookup{
105 {"B", "myFact(b.B)"},
106 {"B(nil)", "myFact(b.B)"},
107 {"*(B(nil))", "myFact(a.a)"},
108 }},
109 {"c", []lookup{
110 {"C", "myFact(c.C)"},
111 {"C(nil)", "myFact(c.C)"},
112 {"*C(nil)", "myFact(a.a)"},
113 {"q", "myFact(a.a)"},
114 }},
115 },
116 },
117 {
118 name: "methods",
119
120
121
122 files: map[string]string{
123 "a/a.go": `package a; type T int`,
124 "b/b.go": `package b; import "a"; type B struct{}; func (_ B) M() a.T { return 0 }`,
125 "c/c.go": `package c; import "b"; var C b.B`,
126 },
127 plookups: []pkgLookups{
128 {"a", []lookup{
129 {"T", "myFact(a.T)"},
130 }},
131 {"b", []lookup{
132 {"B{}", "myFact(b.B)"},
133 {"B{}.M()", "myFact(a.T)"},
134 }},
135 {"c", []lookup{
136 {"C", "myFact(b.B)"},
137 {"C.M()", "myFact(a.T)"},
138 }},
139 },
140 },
141 {
142 name: "globals",
143 files: map[string]string{
144 "a/a.go": `package a;
145 type T1 int
146 type T2 int
147 type T3 int
148 type T4 int
149 type T5 int
150 type K int; type V string
151 `,
152 "b/b.go": `package b
153 import "a"
154 var (
155 G1 []a.T1
156 G2 [7]a.T2
157 G3 chan a.T3
158 G4 *a.T4
159 G5 struct{ F a.T5 }
160 G6 map[a.K]a.V
161 )
162 `,
163 "c/c.go": `package c; import "b";
164 var (
165 v1 = b.G1
166 v2 = b.G2
167 v3 = b.G3
168 v4 = b.G4
169 v5 = b.G5
170 v6 = b.G6
171 )
172 `,
173 },
174 plookups: []pkgLookups{
175 {"a", []lookup{}},
176 {"b", []lookup{}},
177 {"c", []lookup{
178 {"v1[0]", "myFact(a.T1)"},
179 {"v2[0]", "myFact(a.T2)"},
180 {"<-v3", "myFact(a.T3)"},
181 {"*v4", "myFact(a.T4)"},
182 {"v5.F", "myFact(a.T5)"},
183 {"v6[0]", "myFact(a.V)"},
184 }},
185 },
186 },
187 {
188 name: "typeparams",
189 typeparams: true,
190 files: map[string]string{
191 "a/a.go": `package a
192 type T1 int
193 type T2 int
194 type T3 interface{Foo()}
195 type T4 int
196 type T5 int
197 type T6 interface{Foo()}
198 `,
199 "b/b.go": `package b
200 import "a"
201 type N1[T a.T1|int8] func() T
202 type N2[T any] struct{ F T }
203 type N3[T a.T3] func() T
204 type N4[T a.T4|int8] func() T
205 type N5[T interface{Bar() a.T5} ] func() T
206
207 type t5 struct{}; func (t5) Bar() a.T5 { return 0 }
208
209 var G1 N1[a.T1]
210 var G2 func() N2[a.T2]
211 var G3 N3[a.T3]
212 var G4 N4[a.T4]
213 var G5 N5[t5]
214
215 func F6[T a.T6]() T { var x T; return x }
216 `,
217 "c/c.go": `package c; import "b";
218 var (
219 v1 = b.G1
220 v2 = b.G2
221 v3 = b.G3
222 v4 = b.G4
223 v5 = b.G5
224 v6 = b.F6[t6]
225 )
226
227 type t6 struct{}; func (t6) Foo() {}
228 `,
229 },
230 plookups: []pkgLookups{
231 {"a", []lookup{}},
232 {"b", []lookup{}},
233 {"c", []lookup{
234 {"v1", "myFact(b.N1)"},
235 {"v1()", "myFact(a.T1)"},
236 {"v2()", "myFact(b.N2)"},
237 {"v2().F", "myFact(a.T2)"},
238 {"v3", "myFact(b.N3)"},
239 {"v4", "myFact(b.N4)"},
240 {"v4()", "myFact(a.T4)"},
241 {"v5", "myFact(b.N5)"},
242 {"v5()", "myFact(b.t5)"},
243 {"v6()", "myFact(c.t6)"},
244 }},
245 },
246 },
247 }
248
249 for i := range tests {
250 test := tests[i]
251 t.Run(test.name, func(t *testing.T) {
252 t.Parallel()
253 testEncodeDecode(t, test.files, test.plookups)
254 })
255 }
256 }
257
258 type lookup struct {
259 objexpr string
260 want string
261 }
262
263 type pkgLookups struct {
264 path string
265 lookups []lookup
266 }
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285 func testEncodeDecode(t *testing.T, files map[string]string, tests []pkgLookups) {
286 dir, cleanup, err := analysistest.WriteFiles(files)
287 if err != nil {
288 t.Fatal(err)
289 }
290 defer cleanup()
291
292
293
294 factmap := make(map[string][]byte)
295 read := func(pkgPath string) ([]byte, error) { return factmap[pkgPath], nil }
296
297
298
299
300
301
302
303 for _, test := range tests {
304
305 pkg, err := load(t, dir, test.path)
306 if err != nil {
307 t.Fatal(err)
308 }
309
310
311 facts, err := facts.NewDecoder(pkg).Decode(read)
312 if err != nil {
313 t.Fatalf("Decode failed: %v", err)
314 }
315 t.Logf("decode %s facts = %v", pkg.Path(), facts)
316
317
318
319 for _, name := range pkg.Scope().Names() {
320 obj := pkg.Scope().Lookup(name)
321 fact := &myFact{obj.Pkg().Name() + "." + obj.Name()}
322 facts.ExportObjectFact(obj, fact)
323 }
324 t.Logf("exported %s facts = %v", pkg.Path(), facts)
325
326
327
328 for _, lookup := range test.lookups {
329 fact := new(myFact)
330 var got string
331 if obj := find(pkg, lookup.objexpr); obj == nil {
332 got = "no object"
333 } else if facts.ImportObjectFact(obj, fact) {
334 got = fact.String()
335 } else {
336 got = "no fact"
337 }
338 if got != lookup.want {
339 t.Errorf("in %s, ImportObjectFact(%s, %T) = %s, want %s",
340 pkg.Path(), lookup.objexpr, fact, got, lookup.want)
341 }
342 }
343
344
345 factmap[pkg.Path()] = facts.Encode()
346 }
347 }
348
349 func find(p *types.Package, expr string) types.Object {
350
351
352
353
354
355
356
357
358
359 somepos := p.Scope().Lookup(p.Scope().Names()[0]).Pos()
360 tv, err := types.Eval(token.NewFileSet(), p, somepos, expr)
361 if err != nil {
362 return nil
363 }
364 if n, ok := aliases.Unalias(tv.Type).(*types.Named); ok {
365 return n.Obj()
366 }
367 return nil
368 }
369
370 func load(t *testing.T, dir string, path string) (*types.Package, error) {
371 cfg := &packages.Config{
372 Mode: packages.LoadSyntax,
373 Dir: dir,
374 Env: append(os.Environ(), "GOPATH="+dir, "GO111MODULE=off", "GOPROXY=off"),
375 }
376 testenv.NeedsGoPackagesEnv(t, cfg.Env)
377 pkgs, err := packages.Load(cfg, path)
378 if err != nil {
379 return nil, err
380 }
381 if packages.PrintErrors(pkgs) > 0 {
382 return nil, fmt.Errorf("packages had errors")
383 }
384 if len(pkgs) == 0 {
385 return nil, fmt.Errorf("no package matched %s", path)
386 }
387 return pkgs[0].Types, nil
388 }
389
390 type otherFact struct {
391 S string
392 }
393
394 func (f *otherFact) String() string { return fmt.Sprintf("otherFact(%s)", f.S) }
395 func (f *otherFact) AFact() {}
396
397 func TestFactFilter(t *testing.T) {
398 files := map[string]string{
399 "a/a.go": `package a; type A int`,
400 }
401 dir, cleanup, err := analysistest.WriteFiles(files)
402 if err != nil {
403 t.Fatal(err)
404 }
405 defer cleanup()
406
407 pkg, err := load(t, dir, "a")
408 if err != nil {
409 t.Fatal(err)
410 }
411
412 obj := pkg.Scope().Lookup("A")
413 s, err := facts.NewDecoder(pkg).Decode(func(pkgPath string) ([]byte, error) { return nil, nil })
414 if err != nil {
415 t.Fatal(err)
416 }
417 s.ExportObjectFact(obj, &myFact{"good object fact"})
418 s.ExportPackageFact(&myFact{"good package fact"})
419 s.ExportObjectFact(obj, &otherFact{"bad object fact"})
420 s.ExportPackageFact(&otherFact{"bad package fact"})
421
422 filter := map[reflect.Type]bool{
423 reflect.TypeOf(&myFact{}): true,
424 }
425
426 pkgFacts := s.AllPackageFacts(filter)
427 wantPkgFacts := `[{package a ("a") myFact(good package fact)}]`
428 if got := fmt.Sprintf("%v", pkgFacts); got != wantPkgFacts {
429 t.Errorf("AllPackageFacts: got %v, want %v", got, wantPkgFacts)
430 }
431
432 objFacts := s.AllObjectFacts(filter)
433 wantObjFacts := "[{type a.A int myFact(good object fact)}]"
434 if got := fmt.Sprintf("%v", objFacts); got != wantObjFacts {
435 t.Errorf("AllObjectFacts: got %v, want %v", got, wantObjFacts)
436 }
437 }
438
439
440
441
442
443 func TestMalformed(t *testing.T) {
444 var findPkg func(*types.Package, string) *types.Package
445 findPkg = func(p *types.Package, name string) *types.Package {
446 if p.Name() == name {
447 return p
448 }
449 for _, o := range p.Imports() {
450 if f := findPkg(o, name); f != nil {
451 return f
452 }
453 }
454 return nil
455 }
456
457 type pkgTest struct {
458 content string
459 err string
460 wants map[string]string
461 }
462 tests := []struct {
463 name string
464 pkgs []pkgTest
465 }{
466 {
467 name: "initialization-cycle",
468 pkgs: []pkgTest{
469
470 {
471 content: `package a; type N[T any] struct { F *N[N[T]] }`,
472 err: "instantiation cycle:",
473 wants: map[string]string{"a": "myFact(a.[N])", "b": "no package", "c": "no package"},
474 },
475 {
476 content: `package b; import "a"; type B a.N[int]`,
477 wants: map[string]string{"a": "myFact(a.[N])", "b": "myFact(b.[B])", "c": "no package"},
478 },
479 {
480 content: `package c; import "b"; var C b.B`,
481 wants: map[string]string{"a": "no fact", "b": "myFact(b.[B])", "c": "myFact(c.[C])"},
482
483 },
484 },
485 },
486 }
487
488 for i := range tests {
489 test := tests[i]
490 t.Run(test.name, func(t *testing.T) {
491 t.Parallel()
492
493
494 packages := make(map[string]*types.Package)
495 conf := types.Config{
496 Importer: closure(packages),
497 Error: func(err error) {},
498 }
499 fset := token.NewFileSet()
500 factmap := make(map[string][]byte)
501 read := func(pkgPath string) ([]byte, error) { return factmap[pkgPath], nil }
502
503
504
505
506 for i, pkgTest := range test.pkgs {
507
508 f, err := parser.ParseFile(fset, fmt.Sprintf("%d.go", i), pkgTest.content, 0)
509 if err != nil {
510 t.Fatal(err)
511 }
512
513
514 pkg, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil)
515 var got string
516 if err != nil {
517 got = err.Error()
518 }
519 if !strings.Contains(got, pkgTest.err) {
520 t.Fatalf("%s: type checking error %q did not match pattern %q", pkg.Path(), err.Error(), pkgTest.err)
521 }
522 packages[pkg.Path()] = pkg
523
524
525 facts, err := facts.NewDecoder(pkg).Decode(read)
526 if err != nil {
527 t.Fatalf("Decode failed: %v", err)
528 }
529
530
531 fact := &myFact{fmt.Sprintf("%s.%s", pkg.Name(), pkg.Scope().Names())}
532 facts.ExportPackageFact(fact)
533
534
535 for other, want := range pkgTest.wants {
536 fact := new(myFact)
537 var got string
538 if found := findPkg(pkg, other); found == nil {
539 got = "no package"
540 } else if facts.ImportPackageFact(found, fact) {
541 got = fact.String()
542 } else {
543 got = "no fact"
544 }
545 if got != want {
546 t.Errorf("in %s, ImportPackageFact(%s, %T) = %s, want %s",
547 pkg.Path(), other, fact, got, want)
548 }
549 }
550
551
552 factmap[pkg.Path()] = facts.Encode()
553 }
554 })
555 }
556 }
557
558 type closure map[string]*types.Package
559
560 func (c closure) Import(path string) (*types.Package, error) { return c[path], nil }
561
View as plain text