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.
16 "golang.org/x/tools/go/analysis/analysistest"
17 "golang.org/x/tools/go/analysis/internal/facts"
18 "golang.org/x/tools/go/packages"
19 "golang.org/x/tools/internal/testenv"
26 func (f *myFact) String() string { return fmt.Sprintf("myFact(%s)", f.S) }
27 func (f *myFact) AFact() {}
29 func TestEncodeDecode(t *testing.T) {
30 gob.Register(new(myFact))
33 // c does not directly depend on a, but it indirectly uses a.T.
35 // Package a2 is never loaded directly so it is incomplete.
37 // We use only types in this example because we rely on
38 // types.Eval to resolve the lookup expressions, and it only
39 // works for types. This is a definite gap in the typechecker API.
40 files := map[string]string{
41 "a/a.go": `package a; type A int; type T int`,
42 "a2/a.go": `package a2; type A2 int; type Unneeded int`,
43 "b/b.go": `package b; import ("a"; "a2"); type B chan a2.A2; type F func() a.T`,
44 "c/c.go": `package c; import "b"; type C []b.B`,
46 dir, cleanup, err := analysistest.WriteFiles(files)
52 // factmap represents the passing of encoded facts from one
53 // package to another. In practice one would use the file system.
54 factmap := make(map[string][]byte)
55 read := func(path string) ([]byte, error) { return factmap[path], nil }
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.
62 // Note: Loop iterations are not independent test cases;
63 // order matters, as we populate factmap.
64 type lookups []struct {
68 for _, test := range []struct {
76 {"a.A", "myFact(a.A)"},
77 {"a.T", "myFact(a.T)"},
80 {"F(nil)()", "myFact(a.T)"}, // (result type of b.F)
83 {"b.B", "myFact(b.B)"},
84 {"b.F", "myFact(b.F)"},
85 //{"b.F(nil)()", "myFact(a.T)"}, // no fact; TODO(adonovan): investigate
87 {"C{}[0]", "myFact(b.B)"},
88 {"<-(C{}[0])", "no fact"}, // object but no fact (we never "analyze" a2)
92 pkg, err := load(t, dir, test.path)
98 facts, err := facts.Decode(pkg, read)
100 t.Fatalf("Decode failed: %v", err)
103 t.Logf("decode %s facts = %v", pkg.Path(), facts) // show all facts
107 // (one fact for each package-level object)
109 for _, name := range scope.Names() {
110 obj := scope.Lookup(name)
111 fact := &myFact{obj.Pkg().Name() + "." + obj.Name()}
112 facts.ExportObjectFact(obj, fact)
116 // (after export, because an analyzer may import its own facts)
117 for _, lookup := range test.lookups {
120 if obj := find(pkg, lookup.objexpr); obj == nil {
122 } else if facts.ImportObjectFact(obj, fact) {
127 if got != lookup.want {
128 t.Errorf("in %s, ImportObjectFact(%s, %T) = %s, want %s",
129 pkg.Path(), lookup.objexpr, fact, got, lookup.want)
134 factmap[pkg.Path()] = facts.Encode()
138 func find(p *types.Package, expr string) types.Object {
139 // types.Eval only allows us to compute a TypeName object for an expression.
140 // TODO(adonovan): support other expressions that denote an object:
141 // - an identifier (or qualified ident) for a func, const, or var
142 // - new(T).f for a field or method
143 // I've added CheckExpr in https://go-review.googlesource.com/c/go/+/144677.
144 // If that becomes available, use it.
146 // Choose an arbitrary position within the (single-file) package
147 // so that we are within the scope of its import declarations.
148 somepos := p.Scope().Lookup(p.Scope().Names()[0]).Pos()
149 tv, err := types.Eval(token.NewFileSet(), p, somepos, expr)
153 if n, ok := tv.Type.(*types.Named); ok {
159 func load(t *testing.T, dir string, path string) (*types.Package, error) {
160 cfg := &packages.Config{
161 Mode: packages.LoadSyntax,
163 Env: append(os.Environ(), "GOPATH="+dir, "GO111MODULE=off", "GOPROXY=off"),
165 testenv.NeedsGoPackagesEnv(t, cfg.Env)
166 pkgs, err := packages.Load(cfg, path)
170 if packages.PrintErrors(pkgs) > 0 {
171 return nil, fmt.Errorf("packages had errors")
174 return nil, fmt.Errorf("no package matched %s", path)
176 return pkgs[0].Types, nil
179 type otherFact struct {
183 func (f *otherFact) String() string { return fmt.Sprintf("otherFact(%s)", f.S) }
184 func (f *otherFact) AFact() {}
186 func TestFactFilter(t *testing.T) {
187 files := map[string]string{
188 "a/a.go": `package a; type A int`,
190 dir, cleanup, err := analysistest.WriteFiles(files)
196 pkg, err := load(t, dir, "a")
201 obj := pkg.Scope().Lookup("A")
202 s, err := facts.Decode(pkg, func(string) ([]byte, error) { return nil, nil })
206 s.ExportObjectFact(obj, &myFact{"good object fact"})
207 s.ExportPackageFact(&myFact{"good package fact"})
208 s.ExportObjectFact(obj, &otherFact{"bad object fact"})
209 s.ExportPackageFact(&otherFact{"bad package fact"})
211 filter := map[reflect.Type]bool{
212 reflect.TypeOf(&myFact{}): true,
215 pkgFacts := s.AllPackageFacts(filter)
216 wantPkgFacts := `[{package a ("a") myFact(good package fact)}]`
217 if got := fmt.Sprintf("%v", pkgFacts); got != wantPkgFacts {
218 t.Errorf("AllPackageFacts: got %v, want %v", got, wantPkgFacts)
221 objFacts := s.AllObjectFacts(filter)
222 wantObjFacts := "[{type a.A int myFact(good object fact)}]"
223 if got := fmt.Sprintf("%v", objFacts); got != wantObjFacts {
224 t.Errorf("AllObjectFacts: got %v, want %v", got, wantObjFacts)