Skip to content

Commit 9e69d8b

Browse files
adonovangopherbot
authored andcommitted
go/analysis/checker: make test hermetic
Before, it made assumptions about the set of packages in the standard library, which is about to change, burdening the person making that change with a spurious test failure. (Every time I do this we get burned. When will I learn?) Now, it uses a fake test data tree. Change-Id: Ib15f08cd28b38cf47fbb997291998959a679eaa2 Reviewed-on: https://go-review.googlesource.com/c/tools/+/709955 Reviewed-by: Michael Pratt <mpratt@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Alan Donovan <adonovan@google.com>
1 parent 4ee8992 commit 9e69d8b

File tree

1 file changed

+106
-52
lines changed

1 file changed

+106
-52
lines changed

go/analysis/checker/example_test.go

Lines changed: 106 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -11,94 +11,148 @@ package checker_test
1111
import (
1212
"fmt"
1313
"log"
14+
"maps"
15+
"os"
1416

1517
"golang.org/x/tools/go/analysis"
1618
"golang.org/x/tools/go/analysis/checker"
1719
"golang.org/x/tools/go/packages"
20+
"golang.org/x/tools/txtar"
1821
)
1922

23+
const testdata = `
24+
-- go.mod --
25+
module example.com
26+
go 1.21
27+
28+
-- a/a.go --
29+
package a
30+
31+
import _ "example.com/b"
32+
import _ "example.com/c"
33+
34+
func A1()
35+
func A2()
36+
func A3()
37+
38+
-- b/b.go --
39+
package b
40+
41+
func B1()
42+
func B2()
43+
44+
-- c/c.go --
45+
package c
46+
47+
import _ "example.com/d"
48+
49+
func C1()
50+
51+
-- d/d.go --
52+
package d
53+
54+
func D1()
55+
func D2()
56+
func D3()
57+
func D4()
58+
`
59+
2060
func Example() {
21-
// Load packages: just this one.
61+
// Extract a tree of Go source files.
62+
// (Avoid the standard library as it is always evolving.)
63+
dir, err := os.MkdirTemp("", "")
64+
if err != nil {
65+
log.Fatal(err)
66+
}
67+
fs, err := txtar.FS(txtar.Parse([]byte(testdata)))
68+
if err != nil {
69+
log.Fatal(err)
70+
}
71+
if err := os.CopyFS(dir, fs); err != nil {
72+
log.Fatal(err)
73+
}
74+
75+
// Load packages: example.com/a + dependencies
2276
//
77+
cfg := &packages.Config{Mode: packages.LoadAllSyntax, Dir: dir}
78+
initial, err := packages.Load(cfg, "example.com/a")
79+
if err != nil {
80+
log.Fatal(err) // failure to enumerate packages
81+
}
82+
2383
// There may be parse or type errors among the
2484
// initial packages or their dependencies,
2585
// but the analysis driver can handle faulty inputs,
2686
// as can some analyzers.
27-
cfg := &packages.Config{Mode: packages.LoadAllSyntax}
28-
initial, err := packages.Load(cfg, ".")
29-
if err != nil {
30-
log.Fatal(err) // failure to enumerate packages
31-
}
87+
packages.PrintErrors(initial)
88+
3289
if len(initial) == 0 {
3390
log.Fatalf("no initial packages")
3491
}
3592

36-
// Run analyzers (just one) on packages.
37-
analyzers := []*analysis.Analyzer{minmaxpkg}
93+
// Run analyzers (just one) on example.com packages.
94+
analyzers := []*analysis.Analyzer{pkgdecls}
3895
graph, err := checker.Analyze(analyzers, initial, nil)
3996
if err != nil {
4097
log.Fatal(err)
4198
}
4299

43-
// Print information about the results of each
44-
// analysis action, including all dependencies.
100+
// Inspect the result of each analysis action,
101+
// including those for all dependencies.
45102
//
46-
// Clients using Go 1.23 can say:
47-
// for act := range graph.All() { ... }
48-
graph.All()(func(act *checker.Action) bool {
49-
// Print information about the Action, e.g.
50-
//
51-
// act.String()
52-
// act.Result
53-
// act.Err
54-
// act.Diagnostics
55-
//
56-
// (We don't actually print anything here
57-
// as the output would vary over time,
58-
// which is unsuitable for a test.)
59-
return true
60-
})
61-
62-
// Print the minmaxpkg package fact computed for this package.
103+
// A realistic client would use Result, Err, Diagnostics,
104+
// but for test stability, we just print the action string
105+
// ("analyzer@package").
106+
for act := range graph.All() {
107+
fmt.Println("printing", act)
108+
}
109+
110+
// Print the package fact for the sole initial package.
63111
root := graph.Roots[0]
64-
fact := new(minmaxpkgFact)
112+
fact := new(pkgdeclsFact)
65113
if root.PackageFact(root.Package.Types, fact) {
66-
fmt.Printf("min=%s max=%s", fact.min, fact.max)
114+
for k, v := range fact.numdecls {
115+
fmt.Printf("%s:\t%d decls\n", k, v)
116+
}
67117
}
68-
// Output:
69-
// min=bufio max=unsafe
118+
119+
// Unordered Output:
120+
// printing pkgdecls@example.com/a
121+
// printing pkgdecls@example.com/b
122+
// printing pkgdecls@example.com/c
123+
// printing pkgdecls@example.com/d
124+
// example.com/a: 3 decls
125+
// example.com/b: 2 decls
126+
// example.com/c: 1 decls
127+
// example.com/d: 4 decls
70128
}
71129

72-
// minmaxpkg is a trivial example analyzer that uses package facts to
130+
// pkgdecls is a trivial example analyzer that uses package facts to
73131
// compute information from the entire dependency graph.
74-
var minmaxpkg = &analysis.Analyzer{
75-
Name: "minmaxpkg",
76-
Doc: "Finds the min- and max-named packages among our dependencies.",
132+
var pkgdecls = &analysis.Analyzer{
133+
Name: "pkgdecls",
134+
Doc: "Computes a package fact mapping each package to its number of declarations.",
77135
Run: run,
78-
FactTypes: []analysis.Fact{(*minmaxpkgFact)(nil)},
136+
FactTypes: []analysis.Fact{(*pkgdeclsFact)(nil)},
79137
}
80138

81-
// A package fact that records the alphabetically min and max-named
82-
// packages among the dependencies of this package.
83-
// (This property was chosen because it is relatively stable
84-
// as the codebase evolves, avoiding frequent test breakage.)
85-
type minmaxpkgFact struct{ min, max string }
139+
type pkgdeclsFact struct{ numdecls map[string]int }
86140

87-
func (*minmaxpkgFact) AFact() {}
141+
func (*pkgdeclsFact) AFact() {}
88142

89143
func run(pass *analysis.Pass) (any, error) {
90-
// Compute the min and max of the facts from our direct imports.
91-
f := &minmaxpkgFact{min: pass.Pkg.Path(), max: pass.Pkg.Path()}
144+
numdecls := map[string]int{
145+
pass.Pkg.Path(): pass.Pkg.Scope().Len(),
146+
}
147+
148+
// Compute the union across all dependencies.
92149
for _, imp := range pass.Pkg.Imports() {
93-
if f2 := new(minmaxpkgFact); pass.ImportPackageFact(imp, f2) {
94-
if f2.min < f.min {
95-
f.min = f2.min
96-
}
97-
if f2.max > f.max {
98-
f.max = f2.max
99-
}
150+
if depFact := new(pkgdeclsFact); pass.ImportPackageFact(imp, depFact) {
151+
maps.Copy(numdecls, depFact.numdecls)
100152
}
101153
}
102-
pass.ExportPackageFact(f)
154+
155+
pass.ExportPackageFact(&pkgdeclsFact{numdecls})
156+
103157
return nil, nil
104158
}

0 commit comments

Comments
 (0)