GoPLS Viewer

Home|gopls/internal/facts/facts_test.go
1// Copyright 2018 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
5package facts_test
6
7import (
8    "encoding/gob"
9    "fmt"
10    "go/token"
11    "go/types"
12    "os"
13    "reflect"
14    "testing"
15
16    "golang.org/x/tools/go/analysis/analysistest"
17    "golang.org/x/tools/go/packages"
18    "golang.org/x/tools/internal/facts"
19    "golang.org/x/tools/internal/testenv"
20    "golang.org/x/tools/internal/typeparams"
21)
22
23type myFact struct {
24    S string
25}
26
27func (f *myFactString() string { return fmt.Sprintf("myFact(%s)"f.S) }
28func (f *myFactAFact()         {}
29
30func init() {
31    gob.Register(new(myFact))
32}
33
34func TestEncodeDecode(t *testing.T) {
35    tests := []struct {
36        name       string
37        typeparams bool // requires typeparams to be enabled
38        files      map[string]string
39        plookups   []pkgLookups // see testEncodeDecode for details
40    }{
41        {
42            name"loading-order",
43            // c -> b -> a, a2
44            // c does not directly depend on a, but it indirectly uses a.T.
45            //
46            // Package a2 is never loaded directly so it is incomplete.
47            //
48            // We use only types in this example because we rely on
49            // types.Eval to resolve the lookup expressions, and it only
50            // works for types. This is a definite gap in the typechecker API.
51            files: map[string]string{
52                "a/a.go":  `package a; type A int; type T int`,
53                "a2/a.go"`package a2; type A2 int; type Unneeded int`,
54                "b/b.go":  `package b; import ("a"; "a2"); type B chan a2.A2; type F func() a.T`,
55                "c/c.go":  `package c; import "b"; type C []b.B`,
56            },
57            // In the following table, we analyze packages (a, b, c) in order,
58            // look up various objects accessible within each package,
59            // and see if they have a fact.  The "analysis" exports a fact
60            // for every object at package level.
61            //
62            // Note: Loop iterations are not independent test cases;
63            // order matters, as we populate factmap.
64            plookups: []pkgLookups{
65                {"a", []lookup{
66                    {"A""myFact(a.A)"},
67                }},
68                {"b", []lookup{
69                    {"a.A""myFact(a.A)"},
70                    {"a.T""myFact(a.T)"},
71                    {"B""myFact(b.B)"},
72                    {"F""myFact(b.F)"},
73                    {"F(nil)()""myFact(a.T)"}, // (result type of b.F)
74                }},
75                {"c", []lookup{
76                    {"b.B""myFact(b.B)"},
77                    {"b.F""myFact(b.F)"},
78                    //{"b.F(nil)()", "myFact(a.T)"}, // no fact; TODO(adonovan): investigate
79                    {"C""myFact(c.C)"},
80                    {"C{}[0]""myFact(b.B)"},
81                    {"<-(C{}[0])""no fact"}, // object but no fact (we never "analyze" a2)
82                }},
83            },
84        },
85        {
86            name"globals",
87            files: map[string]string{
88                "a/a.go"`package a;
89                type T1 int
90                type T2 int
91                type T3 int
92                type T4 int
93                type T5 int
94                type K int; type V string
95                `,
96                "b/b.go"`package b
97                import "a"
98                var (
99                    G1 []a.T1
100                    G2 [7]a.T2
101                    G3 chan a.T3
102                    G4 *a.T4
103                    G5 struct{ F a.T5 }
104                    G6 map[a.K]a.V
105                )
106                `,
107                "c/c.go"`package c; import "b";
108                var (
109                    v1 = b.G1
110                    v2 = b.G2
111                    v3 = b.G3
112                    v4 = b.G4
113                    v5 = b.G5
114                    v6 = b.G6
115                )
116                `,
117            },
118            plookups: []pkgLookups{
119                {"a", []lookup{}},
120                {"b", []lookup{}},
121                {"c", []lookup{
122                    {"v1[0]""myFact(a.T1)"},
123                    {"v2[0]""myFact(a.T2)"},
124                    {"<-v3""myFact(a.T3)"},
125                    {"*v4""myFact(a.T4)"},
126                    {"v5.F""myFact(a.T5)"},
127                    {"v6[0]""myFact(a.V)"},
128                }},
129            },
130        },
131        {
132            name:       "typeparams",
133            typeparamstrue,
134            files: map[string]string{
135                "a/a.go"`package a
136                  type T1 int
137                  type T2 int
138                  type T3 interface{Foo()}
139                  type T4 int
140                  type T5 int
141                  type T6 interface{Foo()}
142                `,
143                "b/b.go"`package b
144                  import "a"
145                  type N1[T a.T1|int8] func() T
146                  type N2[T any] struct{ F T }
147                  type N3[T a.T3] func() T
148                  type N4[T a.T4|int8] func() T
149                  type N5[T interface{Bar() a.T5} ] func() T
150        
151                  type t5 struct{}; func (t5) Bar() a.T5 { return 0 }
152        
153                  var G1 N1[a.T1]
154                  var G2 func() N2[a.T2]
155                  var G3 N3[a.T3]
156                  var G4 N4[a.T4]
157                  var G5 N5[t5]
158        
159                  func F6[T a.T6]() T { var x T; return x }
160                  `,
161                "c/c.go"`package c; import "b";
162                  var (
163                      v1 = b.G1
164                      v2 = b.G2
165                      v3 = b.G3
166                      v4 = b.G4
167                      v5 = b.G5
168                      v6 = b.F6[t6]
169                  )
170        
171                  type t6 struct{}; func (t6) Foo() {}
172                `,
173            },
174            plookups: []pkgLookups{
175                {"a", []lookup{}},
176                {"b", []lookup{}},
177                {"c", []lookup{
178                    {"v1""myFact(b.N1)"},
179                    {"v1()""myFact(a.T1)"},
180                    {"v2()""myFact(b.N2)"},
181                    {"v2().F""myFact(a.T2)"},
182                    {"v3""myFact(b.N3)"},
183                    {"v4""myFact(b.N4)"},
184                    {"v4()""myFact(a.T4)"},
185                    {"v5""myFact(b.N5)"},
186                    {"v5()""myFact(b.t5)"},
187                    {"v6()""myFact(c.t6)"},
188                }},
189            },
190        },
191    }
192
193    for i := range tests {
194        test := tests[i]
195        t.Run(test.name, func(t *testing.T) {
196            t.Parallel()
197            if test.typeparams && !typeparams.Enabled {
198                t.Skip("type parameters are not enabled")
199            }
200            testEncodeDecode(ttest.filestest.plookups)
201        })
202    }
203}
204
205type lookup struct {
206    objexpr string
207    want    string
208}
209
210type pkgLookups struct {
211    path    string
212    lookups []lookup
213}
214
215// testEncodeDecode tests fact encoding and decoding and simulates how package facts
216// are passed during analysis. It operates on a group of Go file contents. Then
217// for each <package, []lookup> in tests it does the following:
218//  1. loads and type checks the package,
219//  2. calls (*facts.Decoder).Decode to load the facts exported by its imports,
220//  3. exports a myFact Fact for all of package level objects,
221//  4. For each lookup for the current package:
222//     4.a) lookup the types.Object for an Go source expression in the curent package
223//     (or confirms one is not expected want=="no object"),
224//     4.b) finds a Fact for the object (or confirms one is not expected want=="no fact"),
225//     4.c) compares the content of the Fact to want.
226//  5. encodes the Facts of the package.
227//
228// Note: tests are not independent test cases; order matters (as does a package being
229// skipped). It changes what Facts can be imported.
230//
231// Failures are reported on t.
232func testEncodeDecode(t *testing.Tfiles map[string]stringtests []pkgLookups) {
233    dircleanuperr := analysistest.WriteFiles(files)
234    if err != nil {
235        t.Fatal(err)
236    }
237    defer cleanup()
238
239    // factmap represents the passing of encoded facts from one
240    // package to another. In practice one would use the file system.
241    factmap := make(map[string][]byte)
242    read := func(imp *types.Package) ([]byteerror) { return factmap[imp.Path()], nil }
243
244    // Analyze packages in order, look up various objects accessible within
245    // each package, and see if they have a fact.  The "analysis" exports a
246    // fact for every object at package level.
247    //
248    // Note: Loop iterations are not independent test cases;
249    // order matters, as we populate factmap.
250    for _test := range tests {
251        // load package
252        pkgerr := load(tdirtest.path)
253        if err != nil {
254            t.Fatal(err)
255        }
256
257        // decode
258        factserr := facts.NewDecoder(pkg).Decode(read)
259        if err != nil {
260            t.Fatalf("Decode failed: %v"err)
261        }
262        t.Logf("decode %s facts = %v"pkg.Path(), facts// show all facts
263
264        // export
265        // (one fact for each package-level object)
266        for _name := range pkg.Scope().Names() {
267            obj := pkg.Scope().Lookup(name)
268            fact := &myFact{obj.Pkg().Name() + "." + obj.Name()}
269            facts.ExportObjectFact(objfact)
270        }
271        t.Logf("exported %s facts = %v"pkg.Path(), facts// show all facts
272
273        // import
274        // (after export, because an analyzer may import its own facts)
275        for _lookup := range test.lookups {
276            fact := new(myFact)
277            var got string
278            if obj := find(pkglookup.objexpr); obj == nil {
279                got = "no object"
280            } else if facts.ImportObjectFact(objfact) {
281                got = fact.String()
282            } else {
283                got = "no fact"
284            }
285            if got != lookup.want {
286                t.Errorf("in %s, ImportObjectFact(%s, %T) = %s, want %s",
287                    pkg.Path(), lookup.objexprfactgotlookup.want)
288            }
289        }
290
291        // encode
292        factmap[pkg.Path()] = facts.Encode()
293    }
294}
295
296func find(p *types.Packageexpr stringtypes.Object {
297    // types.Eval only allows us to compute a TypeName object for an expression.
298    // TODO(adonovan): support other expressions that denote an object:
299    // - an identifier (or qualified ident) for a func, const, or var
300    // - new(T).f for a field or method
301    // I've added CheckExpr in https://go-review.googlesource.com/c/go/+/144677.
302    // If that becomes available, use it.
303
304    // Choose an arbitrary position within the (single-file) package
305    // so that we are within the scope of its import declarations.
306    somepos := p.Scope().Lookup(p.Scope().Names()[0]).Pos()
307    tverr := types.Eval(token.NewFileSet(), psomeposexpr)
308    if err != nil {
309        return nil
310    }
311    if nok := tv.Type.(*types.Named); ok {
312        return n.Obj()
313    }
314    return nil
315}
316
317func load(t *testing.Tdir stringpath string) (*types.Packageerror) {
318    cfg := &packages.Config{
319        Modepackages.LoadSyntax,
320        Dir:  dir,
321        Env:  append(os.Environ(), "GOPATH="+dir"GO111MODULE=off""GOPROXY=off"),
322    }
323    testenv.NeedsGoPackagesEnv(tcfg.Env)
324    pkgserr := packages.Load(cfgpath)
325    if err != nil {
326        return nilerr
327    }
328    if packages.PrintErrors(pkgs) > 0 {
329        return nilfmt.Errorf("packages had errors")
330    }
331    if len(pkgs) == 0 {
332        return nilfmt.Errorf("no package matched %s"path)
333    }
334    return pkgs[0].Typesnil
335}
336
337type otherFact struct {
338    S string
339}
340
341func (f *otherFactString() string { return fmt.Sprintf("otherFact(%s)"f.S) }
342func (f *otherFactAFact()         {}
343
344func TestFactFilter(t *testing.T) {
345    files := map[string]string{
346        "a/a.go"`package a; type A int`,
347    }
348    dircleanuperr := analysistest.WriteFiles(files)
349    if err != nil {
350        t.Fatal(err)
351    }
352    defer cleanup()
353
354    pkgerr := load(tdir"a")
355    if err != nil {
356        t.Fatal(err)
357    }
358
359    obj := pkg.Scope().Lookup("A")
360    serr := facts.NewDecoder(pkg).Decode(func(*types.Package) ([]byteerror) { return nilnil })
361    if err != nil {
362        t.Fatal(err)
363    }
364    s.ExportObjectFact(obj, &myFact{"good object fact"})
365    s.ExportPackageFact(&myFact{"good package fact"})
366    s.ExportObjectFact(obj, &otherFact{"bad object fact"})
367    s.ExportPackageFact(&otherFact{"bad package fact"})
368
369    filter := map[reflect.Type]bool{
370        reflect.TypeOf(&myFact{}): true,
371    }
372
373    pkgFacts := s.AllPackageFacts(filter)
374    wantPkgFacts := `[{package a ("a") myFact(good package fact)}]`
375    if got := fmt.Sprintf("%v"pkgFacts); got != wantPkgFacts {
376        t.Errorf("AllPackageFacts: got %v, want %v"gotwantPkgFacts)
377    }
378
379    objFacts := s.AllObjectFacts(filter)
380    wantObjFacts := "[{type a.A int myFact(good object fact)}]"
381    if got := fmt.Sprintf("%v"objFacts); got != wantObjFacts {
382        t.Errorf("AllObjectFacts: got %v, want %v"gotwantObjFacts)
383    }
384}
385
MembersX
load.t
load.err
TestFactFilter.objFacts
myFact.S
testEncodeDecode.t
testEncodeDecode.cleanup
TestFactFilter.filter
TestEncodeDecode.RangeStmt_4690.i
testEncodeDecode
find.expr
TestFactFilter.t
TestFactFilter.pkg
myFact.String.f
testEncodeDecode.RangeStmt_6770.BlockStmt.facts
init
testEncodeDecode.RangeStmt_6770.test
testEncodeDecode.RangeStmt_6770.BlockStmt.RangeStmt_7476.lookup
find.err
TestFactFilter.err
analysistest
myFact.String
testEncodeDecode.dir
testEncodeDecode.err
otherFact.S
TestFactFilter
TestFactFilter.files
testing
myFact.AFact
lookup
lookup.objexpr
testEncodeDecode.factmap
testEncodeDecode.RangeStmt_6770.BlockStmt.pkg
find.p
load.pkgs
myFact
myFact.AFact.f
TestFactFilter.obj
TestFactFilter.s
otherFact.String.f
otherFact.String
TestEncodeDecode.tests
otherFact
otherFact.AFact
pkgLookups.path
testEncodeDecode.RangeStmt_6770.BlockStmt.RangeStmt_7476.BlockStmt.got
testEncodeDecode.RangeStmt_6770.BlockStmt.RangeStmt_7148.BlockStmt.fact
load
load.dir
TestFactFilter.got
lookup.want
testEncodeDecode.files
load.path
typeparams
find
testEncodeDecode.RangeStmt_6770.BlockStmt.RangeStmt_7148.name
otherFact.AFact.f
token
packages
testEncodeDecode.tests
find.somepos
TestFactFilter.pkgFacts
TestFactFilter.wantPkgFacts
TestEncodeDecode
pkgLookups
TestEncodeDecode.t
testEncodeDecode.RangeStmt_6770.BlockStmt.RangeStmt_7476.BlockStmt.obj
find.tv
TestFactFilter.dir
TestFactFilter.cleanup
os
testenv
testEncodeDecode.RangeStmt_6770.BlockStmt.err
testEncodeDecode.RangeStmt_6770.BlockStmt.RangeStmt_7148.BlockStmt.obj
load.cfg
facts
testEncodeDecode.RangeStmt_6770.BlockStmt.RangeStmt_7476.BlockStmt.fact
pkgLookups.lookups
TestFactFilter.wantObjFacts
Members
X