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>
diff --git a/go/analysis/checker/example_test.go b/go/analysis/checker/example_test.go
index 524c5b7..9f3aac8 100644
--- a/go/analysis/checker/example_test.go
+++ b/go/analysis/checker/example_test.go
@@ -11,94 +11,148 @@
 import (
 	"fmt"
 	"log"
+	"maps"
+	"os"
 
 	"golang.org/x/tools/go/analysis"
 	"golang.org/x/tools/go/analysis/checker"
 	"golang.org/x/tools/go/packages"
+	"golang.org/x/tools/txtar"
 )
 
+const testdata = `
+-- go.mod --
+module example.com
+go 1.21
+
+-- a/a.go --
+package a
+
+import _ "example.com/b"
+import _ "example.com/c"
+
+func A1()
+func A2()
+func A3()
+
+-- b/b.go --
+package b
+
+func B1()
+func B2()
+
+-- c/c.go --
+package c
+
+import _ "example.com/d"
+
+func C1()
+
+-- d/d.go --
+package d
+
+func D1()
+func D2()
+func D3()
+func D4()
+`
+
 func Example() {
-	// Load packages: just this one.
+	// Extract a tree of Go source files.
+	// (Avoid the standard library as it is always evolving.)
+	dir, err := os.MkdirTemp("", "")
+	if err != nil {
+		log.Fatal(err)
+	}
+	fs, err := txtar.FS(txtar.Parse([]byte(testdata)))
+	if err != nil {
+		log.Fatal(err)
+	}
+	if err := os.CopyFS(dir, fs); err != nil {
+		log.Fatal(err)
+	}
+
+	// Load packages: example.com/a + dependencies
 	//
+	cfg := &packages.Config{Mode: packages.LoadAllSyntax, Dir: dir}
+	initial, err := packages.Load(cfg, "example.com/a")
+	if err != nil {
+		log.Fatal(err) // failure to enumerate packages
+	}
+
 	// There may be parse or type errors among the
 	// initial packages or their dependencies,
 	// but the analysis driver can handle faulty inputs,
 	// as can some analyzers.
-	cfg := &packages.Config{Mode: packages.LoadAllSyntax}
-	initial, err := packages.Load(cfg, ".")
-	if err != nil {
-		log.Fatal(err) // failure to enumerate packages
-	}
+	packages.PrintErrors(initial)
+
 	if len(initial) == 0 {
 		log.Fatalf("no initial packages")
 	}
 
-	// Run analyzers (just one) on packages.
-	analyzers := []*analysis.Analyzer{minmaxpkg}
+	// Run analyzers (just one) on example.com packages.
+	analyzers := []*analysis.Analyzer{pkgdecls}
 	graph, err := checker.Analyze(analyzers, initial, nil)
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	// Print information about the results of each
-	// analysis action, including all dependencies.
+	// Inspect the result of each analysis action,
+	// including those for all dependencies.
 	//
-	// Clients using Go 1.23 can say:
-	//     for act := range graph.All() { ... }
-	graph.All()(func(act *checker.Action) bool {
-		// Print information about the Action, e.g.
-		//
-		//  act.String()
-		//  act.Result
-		//  act.Err
-		//  act.Diagnostics
-		//
-		// (We don't actually print anything here
-		// as the output would vary over time,
-		// which is unsuitable for a test.)
-		return true
-	})
-
-	// Print the minmaxpkg package fact computed for this package.
-	root := graph.Roots[0]
-	fact := new(minmaxpkgFact)
-	if root.PackageFact(root.Package.Types, fact) {
-		fmt.Printf("min=%s max=%s", fact.min, fact.max)
+	// A realistic client would use Result, Err, Diagnostics,
+	// but for test stability, we just print the action string
+	// ("analyzer@package").
+	for act := range graph.All() {
+		fmt.Println("printing", act)
 	}
-	// Output:
-	// min=bufio max=unsafe
-}
 
-// minmaxpkg is a trivial example analyzer that uses package facts to
-// compute information from the entire dependency graph.
-var minmaxpkg = &analysis.Analyzer{
-	Name:      "minmaxpkg",
-	Doc:       "Finds the min- and max-named packages among our dependencies.",
-	Run:       run,
-	FactTypes: []analysis.Fact{(*minmaxpkgFact)(nil)},
-}
-
-// A package fact that records the alphabetically min and max-named
-// packages among the dependencies of this package.
-// (This property was chosen because it is relatively stable
-// as the codebase evolves, avoiding frequent test breakage.)
-type minmaxpkgFact struct{ min, max string }
-
-func (*minmaxpkgFact) AFact() {}
-
-func run(pass *analysis.Pass) (any, error) {
-	// Compute the min and max of the facts from our direct imports.
-	f := &minmaxpkgFact{min: pass.Pkg.Path(), max: pass.Pkg.Path()}
-	for _, imp := range pass.Pkg.Imports() {
-		if f2 := new(minmaxpkgFact); pass.ImportPackageFact(imp, f2) {
-			if f2.min < f.min {
-				f.min = f2.min
-			}
-			if f2.max > f.max {
-				f.max = f2.max
-			}
+	// Print the package fact for the sole initial package.
+	root := graph.Roots[0]
+	fact := new(pkgdeclsFact)
+	if root.PackageFact(root.Package.Types, fact) {
+		for k, v := range fact.numdecls {
+			fmt.Printf("%s:\t%d decls\n", k, v)
 		}
 	}
-	pass.ExportPackageFact(f)
+
+	// Unordered Output:
+	// printing pkgdecls@example.com/a
+	// printing pkgdecls@example.com/b
+	// printing pkgdecls@example.com/c
+	// printing pkgdecls@example.com/d
+	// example.com/a:	3 decls
+	// example.com/b:	2 decls
+	// example.com/c:	1 decls
+	// example.com/d:	4 decls
+}
+
+// pkgdecls is a trivial example analyzer that uses package facts to
+// compute information from the entire dependency graph.
+var pkgdecls = &analysis.Analyzer{
+	Name:      "pkgdecls",
+	Doc:       "Computes a package fact mapping each package to its number of declarations.",
+	Run:       run,
+	FactTypes: []analysis.Fact{(*pkgdeclsFact)(nil)},
+}
+
+type pkgdeclsFact struct{ numdecls map[string]int }
+
+func (*pkgdeclsFact) AFact() {}
+
+func run(pass *analysis.Pass) (any, error) {
+	numdecls := map[string]int{
+		pass.Pkg.Path(): pass.Pkg.Scope().Len(),
+	}
+
+	// Compute the union across all dependencies.
+	for _, imp := range pass.Pkg.Imports() {
+		if depFact := new(pkgdeclsFact); pass.ImportPackageFact(imp, depFact) {
+			maps.Copy(numdecls, depFact.numdecls)
+		}
+	}
+
+	pass.ExportPackageFact(&pkgdeclsFact{numdecls})
+
 	return nil, nil
 }