apidiff: add module comparison support

Change-Id: I11fa1aac30c8e2131bd0dd948fdcaf15332f8deb
Reviewed-on: https://go-review.googlesource.com/c/exp/+/495635
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/apidiff/README.md b/apidiff/README.md
index 3d9576c..4f3b22f 100644
--- a/apidiff/README.md
+++ b/apidiff/README.md
@@ -1,8 +1,9 @@
-# Checking Go Package API Compatibility
+# Checking Go API Compatibility
 
-The `apidiff` tool in this directory determines whether two versions of the same
-package are compatible. The goal is to help the developer make an informed
-choice of semantic version after they have changed the code of their module.
+The `apidiff` tool in this directory determines whether two examples of a
+package or module are compatible. The goal is to help the developer make an
+informed choice of semantic version after they have changed the code of their
+module.
 
 `apidiff` reports two kinds of changes: incompatible ones, which require
 incrementing the major part of the semantic version, and compatible ones, which
@@ -10,12 +11,12 @@
 code changes that could affect client code, then the patch version should
 be incremented.
 
-Because `apidiff` ignores package import paths, it may be used to display API
-differences between any two packages, not just different versions of the same
-package.
-
-The current version of `apidiff` compares only packages, not modules.
-
+`apidiff` may be used to display API differences between any two packages or
+modules, not just different versions of the same thing. It does this by ignoring
+the package import paths when directly comparing two packages, and
+by ignoring module paths when comparing two modules. That is to say, when
+comparing two modules, the package import paths **do** matter, but are compared
+_relative_ to their respective module root.
 
 ## Compatibility Desiderata
 
@@ -222,6 +223,111 @@
 
 We can now present the definition of compatibility used by `apidiff`.
 
+### Module Compatibility
+
+> A new module is compatible with an old one if:
+>1. Each package present in the old module also appears in the new module,
+> with matching import paths relative to their respective module root, and
+>2. Each package present in both modules fulfills Package Compatibility as
+> defined below.
+>
+>Otherwise the modules are incompatible.
+
+If a package is converted into a nested module of the original module then
+comparing two versions of the module, before and after nested module creation,
+will produce an incompatible package removal message. This removal message does
+not necessarily mean that client code will need to change. If the package API
+retains Package Compatibility after nested module creation, then only the
+`go.mod` of the client code will need to change. Take the following example:
+
+```
+./
+  go.mod
+  go.sum
+  foo.go
+  bar/bar.go
+```
+
+Where `go.mod` is:
+
+```
+module example.com/foo
+
+go 1.20
+```
+
+Where `bar/bar.go` is:
+
+```
+package bar
+
+var V int
+```
+
+And `foo.go` is:
+
+```
+package foo
+
+import "example.com/foo/bar"
+
+_ = bar.V
+```
+
+Creating a nested module with the package `bar` while retaining Package
+Compatibility is _code_ compatible, because the import path of the package does
+not change:
+
+```
+./
+  go.mod
+  go.sum
+  foo.go
+  bar/
+    bar.go
+    go.mod
+    go.sum
+```
+
+Where `bar/go.mod` is:
+```
+module example.com/foo/bar
+
+go 1.20
+```
+
+And the top-level `go.mod` becomes:
+```
+module example.com/foo
+
+go 1.20
+
+// New dependency on nested module.
+require example.com/foo/bar v1.0.0
+```
+
+If during nested module creation either Package Compatibility is broken, like so
+in `bar/bar.go`:
+
+```
+package bar
+
+// Changed from V to T.
+var T int
+```
+
+Or the nested module uses a name other than the original package's import path,
+like so in `bar/go.mod`:
+
+```
+// Completely different module name
+module example.com/qux
+
+go 1.20
+```
+
+Then the move is backwards incompatible for client code.
+
 ### Package Compatibility
 
 > A new package is compatible with an old one if:
diff --git a/apidiff/apidiff.go b/apidiff/apidiff.go
index 76669d8..5132a58 100644
--- a/apidiff/apidiff.go
+++ b/apidiff/apidiff.go
@@ -15,12 +15,12 @@
 	"go/constant"
 	"go/token"
 	"go/types"
+	"strings"
 )
 
 // Changes reports on the differences between the APIs of the old and new packages.
 // It classifies each difference as either compatible or incompatible (breaking.) For
-// a detailed discussion of what constitutes an incompatible change, see the package
-// documentation.
+// a detailed discussion of what constitutes an incompatible change, see the README.
 func Changes(old, new *types.Package) Report {
 	d := newDiffer(old, new)
 	d.checkPackage()
@@ -34,6 +34,63 @@
 	return r
 }
 
+// ModuleChanges reports on the differences between the APIs of the old and new
+// modules. It classifies each difference as either compatible or incompatible
+// (breaking). This includes the addition and removal of entire packages. For a
+// detailed discussion of what constitutes an incompatible change, see the README.
+func ModuleChanges(old, new *Module) Report {
+	var r Report
+
+	oldPkgs := make(map[string]*types.Package)
+	for _, p := range old.Packages {
+		oldPkgs[old.relativePath(p)] = p
+	}
+
+	newPkgs := make(map[string]*types.Package)
+	for _, p := range new.Packages {
+		newPkgs[new.relativePath(p)] = p
+	}
+
+	for n, op := range oldPkgs {
+		if np, ok := newPkgs[n]; ok {
+			// shared package, compare surfaces
+			rr := Changes(op, np)
+			r.Changes = append(r.Changes, rr.Changes...)
+		} else {
+			// old package was removed
+			r.Changes = append(r.Changes, packageChange(op, "removed", false))
+		}
+	}
+
+	for n, np := range newPkgs {
+		if _, ok := oldPkgs[n]; !ok {
+			// new package was added
+			r.Changes = append(r.Changes, packageChange(np, "added", true))
+		}
+	}
+
+	return r
+}
+
+func packageChange(p *types.Package, change string, compatible bool) Change {
+	return Change{
+		Message:    fmt.Sprintf("package %s: %s", p.Path(), change),
+		Compatible: compatible,
+	}
+}
+
+// Module is a convenience type for representing a Go module with a path and a
+// slice of Packages contained within.
+type Module struct {
+	Path     string
+	Packages []*types.Package
+}
+
+// relativePath computes the module-relative package path of the given Package.
+func (m *Module) relativePath(p *types.Package) string {
+	return strings.TrimPrefix(p.Path(), m.Path)
+}
+
 type differ struct {
 	old, new *types.Package
 	// Correspondences between named types.
diff --git a/apidiff/apidiff_test.go b/apidiff/apidiff_test.go
index 40cd463..86e7955 100644
--- a/apidiff/apidiff_test.go
+++ b/apidiff/apidiff_test.go
@@ -14,8 +14,71 @@
 
 	"github.com/google/go-cmp/cmp"
 	"golang.org/x/tools/go/packages"
+	"golang.org/x/tools/go/packages/packagestest"
 )
 
+func TestModuleChanges(t *testing.T) {
+	packagestest.TestAll(t, testModuleChanges)
+}
+
+func testModuleChanges(t *testing.T, x packagestest.Exporter) {
+	e := packagestest.Export(t, x, []packagestest.Module{
+		{
+			Name: "example.com/moda",
+			Files: map[string]any{
+				"foo/foo.go":     "package foo\n\nconst Version = 1",
+				"foo/baz/baz.go": "package baz",
+			},
+		},
+		{
+			Name: "example.com/modb",
+			Files: map[string]any{
+				"foo/foo.go": "package foo\n\nconst Version = 2\nconst Other = 1",
+				"bar/bar.go": "package bar",
+			},
+		},
+	})
+	defer e.Cleanup()
+
+	a, err := loadModule(t, e.Config, "example.com/moda")
+	if err != nil {
+		t.Fatal(err)
+	}
+	b, err := loadModule(t, e.Config, "example.com/modb")
+	if err != nil {
+		t.Fatal(err)
+	}
+	report := ModuleChanges(a, b)
+	if len(report.Changes) == 0 {
+		t.Fatal("expected some changes, but got none")
+	}
+	wanti := []string{
+		"Version: value changed from 1 to 2",
+		"package example.com/moda/foo/baz: removed",
+	}
+	sort.Strings(wanti)
+
+	got := report.messages(false)
+	sort.Strings(got)
+
+	if diff := cmp.Diff(wanti, got); diff != "" {
+		t.Errorf("incompatibles: mismatch (-want, +got)\n%s", diff)
+	}
+
+	wantc := []string{
+		"Other: added",
+		"package example.com/modb/bar: added",
+	}
+	sort.Strings(wantc)
+
+	got = report.messages(true)
+	sort.Strings(got)
+
+	if diff := cmp.Diff(wantc, got); diff != "" {
+		t.Errorf("compatibles: mismatch (-want, +got)\n%s", diff)
+	}
+}
+
 func TestChanges(t *testing.T) {
 	dir, err := os.MkdirTemp("", "apidiff_test")
 	if err != nil {
@@ -27,11 +90,11 @@
 	sort.Strings(wanti)
 	sort.Strings(wantc)
 
-	oldpkg, err := load(t, "apidiff/old", dir)
+	oldpkg, err := loadPackage(t, "apidiff/old", dir)
 	if err != nil {
 		t.Fatal(err)
 	}
-	newpkg, err := load(t, "apidiff/new", dir)
+	newpkg, err := loadPackage(t, "apidiff/new", dir)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -116,7 +179,31 @@
 	return
 }
 
-func load(t *testing.T, importPath, goPath string) (*packages.Package, error) {
+// Copied from cmd/apidiff/main.go.
+func loadModule(t *testing.T, cfg *packages.Config, modulePath string) (*Module, error) {
+	needsGoPackages(t)
+
+	cfg.Mode = cfg.Mode | packages.LoadTypes
+	loaded, err := packages.Load(cfg, fmt.Sprintf("%s/...", modulePath))
+	if err != nil {
+		return nil, err
+	}
+	if len(loaded) == 0 {
+		return nil, fmt.Errorf("found no packages for module %s", modulePath)
+	}
+	var tpkgs []*types.Package
+	for _, p := range loaded {
+		if len(p.Errors) > 0 {
+			// TODO: use errors.Join once Go 1.21 is released.
+			return nil, p.Errors[0]
+		}
+		tpkgs = append(tpkgs, p.Types)
+	}
+
+	return &Module{Path: modulePath, Packages: tpkgs}, nil
+}
+
+func loadPackage(t *testing.T, importPath, goPath string) (*packages.Package, error) {
 	needsGoPackages(t)
 
 	cfg := &packages.Config{
@@ -137,7 +224,7 @@
 }
 
 func TestExportedFields(t *testing.T) {
-	pkg, err := load(t, "golang.org/x/exp/apidiff/testdata/exported_fields", "")
+	pkg, err := loadPackage(t, "golang.org/x/exp/apidiff/testdata/exported_fields", "")
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/cmd/apidiff/main.go b/cmd/apidiff/main.go
index 92b763c..67261a1 100644
--- a/cmd/apidiff/main.go
+++ b/cmd/apidiff/main.go
@@ -19,6 +19,7 @@
 	exportDataOutfile = flag.String("w", "", "file for export data")
 	incompatibleOnly  = flag.Bool("incompatible", false, "display only incompatible changes")
 	allowInternal     = flag.Bool("allow-internal", false, "allow apidiff to compare internal packages")
+	moduleMode        = flag.Bool("m", false, "compare modules instead of packages")
 )
 
 func main() {
@@ -28,6 +29,9 @@
 		fmt.Fprintf(w, "apidiff OLD NEW\n")
 		fmt.Fprintf(w, "   compares OLD and NEW package APIs\n")
 		fmt.Fprintf(w, "   where OLD and NEW are either import paths or files of export data\n")
+		fmt.Fprintf(w, "apidiff -m OLD NEW\n")
+		fmt.Fprintf(w, "   compares OLD and NEW module APIs\n")
+		fmt.Fprintf(w, "   where OLD and NEW are module paths\n")
 		fmt.Fprintf(w, "apidiff -w FILE IMPORT_PATH\n")
 		fmt.Fprintf(w, "   writes export data of the package at IMPORT_PATH to FILE\n")
 		fmt.Fprintf(w, "   NOTE: In a GOPATH-less environment, this option consults the\n")
@@ -36,6 +40,9 @@
 		fmt.Fprintf(w, "   to. In most cases users want the latter behavior, so be sure\n")
 		fmt.Fprintf(w, "   to cd to the exact directory which contains the module\n")
 		fmt.Fprintf(w, "   definition of IMPORT_PATH.\n")
+		fmt.Fprintf(w, "apidiff -m -w FILE MODULE_PATH\n")
+		fmt.Fprintf(w, "   writes export data of the module at MODULE_PATH to FILE\n")
+		fmt.Fprintf(w, "   Same NOTE for packages applies to modules.\n")
 		flag.PrintDefaults()
 	}
 
@@ -45,40 +52,61 @@
 			flag.Usage()
 			os.Exit(2)
 		}
-		pkg := mustLoadPackage(flag.Arg(0))
-		if err := writeExportData(pkg, *exportDataOutfile); err != nil {
+		if err := loadAndWrite(flag.Arg(0)); err != nil {
 			die("writing export data: %v", err)
 		}
+		os.Exit(0)
+	}
+
+	if len(flag.Args()) != 2 {
+		flag.Usage()
+		os.Exit(2)
+	}
+
+	var report apidiff.Report
+	if *moduleMode {
+		oldmod := mustLoadOrReadModule(flag.Arg(0))
+		newmod := mustLoadOrReadModule(flag.Arg(1))
+
+		report = apidiff.ModuleChanges(oldmod, newmod)
 	} else {
-		if len(flag.Args()) != 2 {
-			flag.Usage()
-			os.Exit(2)
-		}
-		oldpkg := mustLoadOrRead(flag.Arg(0))
-		newpkg := mustLoadOrRead(flag.Arg(1))
+		oldpkg := mustLoadOrReadPackage(flag.Arg(0))
+		newpkg := mustLoadOrReadPackage(flag.Arg(1))
 		if !*allowInternal {
-			if isInternalPackage(oldpkg.Path()) && isInternalPackage(newpkg.Path()) {
+			if isInternalPackage(oldpkg.Path(), "") && isInternalPackage(newpkg.Path(), "") {
 				fmt.Fprintf(os.Stderr, "Ignoring internal package %s\n", oldpkg.Path())
 				os.Exit(0)
 			}
 		}
-		report := apidiff.Changes(oldpkg, newpkg)
-		var err error
-		if *incompatibleOnly {
-			err = report.TextIncompatible(os.Stdout, false)
-		} else {
-			err = report.Text(os.Stdout)
-		}
-		if err != nil {
-			die("writing report: %v", err)
-		}
+		report = apidiff.Changes(oldpkg, newpkg)
+	}
+
+	var err error
+	if *incompatibleOnly {
+		err = report.TextIncompatible(os.Stdout, false)
+	} else {
+		err = report.Text(os.Stdout)
+	}
+	if err != nil {
+		die("writing report: %v", err)
 	}
 }
 
-func mustLoadOrRead(importPathOrFile string) *types.Package {
+func loadAndWrite(path string) error {
+	if *moduleMode {
+		module := mustLoadModule(path)
+		return writeModuleExportData(module, *exportDataOutfile)
+	}
+
+	// Loading and writing data for only a single package.
+	pkg := mustLoadPackage(path)
+	return writePackageExportData(pkg, *exportDataOutfile)
+}
+
+func mustLoadOrReadPackage(importPathOrFile string) *types.Package {
 	fileInfo, err := os.Stat(importPathOrFile)
 	if err == nil && fileInfo.Mode().IsRegular() {
-		pkg, err := readExportData(importPathOrFile)
+		pkg, err := readPackageExportData(importPathOrFile)
 		if err != nil {
 			die("reading export data from %s: %v", importPathOrFile, err)
 		}
@@ -108,12 +136,95 @@
 		return nil, fmt.Errorf("found no packages for import %s", importPath)
 	}
 	if len(pkgs[0].Errors) > 0 {
+		// TODO: use errors.Join once Go 1.21 is released.
 		return nil, pkgs[0].Errors[0]
 	}
 	return pkgs[0], nil
 }
 
-func readExportData(filename string) (*types.Package, error) {
+func mustLoadOrReadModule(modulePathOrFile string) *apidiff.Module {
+	var module *apidiff.Module
+	fileInfo, err := os.Stat(modulePathOrFile)
+	if err == nil && fileInfo.Mode().IsRegular() {
+		module, err = readModuleExportData(modulePathOrFile)
+		if err != nil {
+			die("reading export data from %s: %v", modulePathOrFile, err)
+		}
+	} else {
+		module = mustLoadModule(modulePathOrFile)
+	}
+
+	filterInternal(module, *allowInternal)
+
+	return module
+}
+
+func mustLoadModule(modulepath string) *apidiff.Module {
+	module, err := loadModule(modulepath)
+	if err != nil {
+		die("loading %s: %v", modulepath, err)
+	}
+	return module
+}
+
+func loadModule(modulepath string) (*apidiff.Module, error) {
+	cfg := &packages.Config{Mode: packages.LoadTypes |
+		packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps | packages.NeedModule,
+	}
+	loaded, err := packages.Load(cfg, fmt.Sprintf("%s/...", modulepath))
+	if err != nil {
+		return nil, err
+	}
+	if len(loaded) == 0 {
+		return nil, fmt.Errorf("found no packages for module %s", modulepath)
+	}
+	var tpkgs []*types.Package
+	for _, p := range loaded {
+		if len(p.Errors) > 0 {
+			// TODO: use errors.Join once Go 1.21 is released.
+			return nil, p.Errors[0]
+		}
+		tpkgs = append(tpkgs, p.Types)
+	}
+
+	return &apidiff.Module{Path: loaded[0].Module.Path, Packages: tpkgs}, nil
+}
+
+func readModuleExportData(filename string) (*apidiff.Module, error) {
+	f, err := os.Open(filename)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	r := bufio.NewReader(f)
+	modPath, err := r.ReadString('\n')
+	if err != nil {
+		return nil, err
+	}
+	modPath = modPath[:len(modPath)-1] // remove delimiter
+	m := map[string]*types.Package{}
+	pkgs, err := gcexportdata.ReadBundle(r, token.NewFileSet(), m)
+	if err != nil {
+		return nil, err
+	}
+
+	return &apidiff.Module{Path: modPath, Packages: pkgs}, nil
+}
+
+func writeModuleExportData(module *apidiff.Module, filename string) error {
+	f, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintln(f, module.Path)
+	// TODO: Determine if token.NewFileSet is appropriate here.
+	if err := gcexportdata.WriteBundle(f, token.NewFileSet(), module.Packages); err != nil {
+		return err
+	}
+	return f.Close()
+}
+
+func readPackageExportData(filename string) (*types.Package, error) {
 	f, err := os.Open(filename)
 	if err != nil {
 		return nil, err
@@ -129,7 +240,7 @@
 	return gcexportdata.Read(r, token.NewFileSet(), m, pkgPath)
 }
 
-func writeExportData(pkg *packages.Package, filename string) error {
+func writePackageExportData(pkg *packages.Package, filename string) error {
 	f, err := os.Create(filename)
 	if err != nil {
 		return err
@@ -150,13 +261,32 @@
 	os.Exit(1)
 }
 
-func isInternalPackage(pkgPath string) bool {
+func filterInternal(m *apidiff.Module, allow bool) {
+	if allow {
+		return
+	}
+
+	var nonInternal []*types.Package
+	for _, p := range m.Packages {
+		if !isInternalPackage(p.Path(), m.Path) {
+			nonInternal = append(nonInternal, p)
+		} else {
+			fmt.Fprintf(os.Stderr, "Ignoring internal package %s\n", p.Path())
+		}
+	}
+	m.Packages = nonInternal
+}
+
+func isInternalPackage(pkgPath, modulePath string) bool {
+	pkgPath = strings.TrimPrefix(pkgPath, modulePath)
 	switch {
 	case strings.HasSuffix(pkgPath, "/internal"):
 		return true
 	case strings.Contains(pkgPath, "/internal/"):
 		return true
-	case pkgPath == "internal", strings.HasPrefix(pkgPath, "internal/"):
+	case pkgPath == "internal":
+		return true
+	case strings.HasPrefix(pkgPath, "internal/"):
 		return true
 	}
 	return false
diff --git a/cmd/apidiff/main_test.go b/cmd/apidiff/main_test.go
new file mode 100644
index 0000000..7cf8bf3
--- /dev/null
+++ b/cmd/apidiff/main_test.go
@@ -0,0 +1,144 @@
+package main
+
+import (
+	"go/types"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"golang.org/x/exp/apidiff"
+)
+
+func TestIsInternalPackage(t *testing.T) {
+	for _, tst := range []struct {
+		name, pkg, mod string
+		want           bool
+	}{
+		{
+			name: "not internal no module",
+			pkg:  "foo",
+			want: false,
+		},
+		{
+			name: "not internal with module",
+			pkg:  "example.com/bar/foo",
+			mod:  "example.com/bar",
+			want: false,
+		},
+		{
+			name: "internal no module",
+			pkg:  "internal",
+			want: true,
+		},
+		{
+			name: "leading internal no module",
+			pkg:  "internal/foo",
+			want: true,
+		},
+		{
+			name: "middle internal no module",
+			pkg:  "foo/internal/bar",
+			want: true,
+		},
+		{
+			name: "ending internal no module",
+			pkg:  "foo/internal",
+			want: true,
+		},
+
+		{
+			name: "leading internal with module",
+			pkg:  "example.com/baz/internal/foo",
+			mod:  "example.com/baz",
+			want: true,
+		},
+		{
+			name: "middle internal with module",
+			pkg:  "example.com/baz/foo/internal/bar",
+			mod:  "example.com/baz",
+			want: true,
+		},
+		{
+			name: "ending internal with module",
+			pkg:  "example.com/baz/foo/internal",
+			mod:  "example.com/baz",
+			want: true,
+		},
+		{
+			name: "not package internal with internal module",
+			pkg:  "example.com/internal/foo",
+			mod:  "example.com/internal",
+			want: false,
+		},
+	} {
+		t.Run(tst.name, func(t *testing.T) {
+			if got := isInternalPackage(tst.pkg, tst.mod); got != tst.want {
+				t.Errorf("expected %v, got %v for %s/%s", tst.want, got, tst.mod, tst.pkg)
+			}
+		})
+	}
+}
+
+func TestFilterInternal(t *testing.T) {
+	for _, tst := range []struct {
+		name  string
+		mod   *apidiff.Module
+		allow bool
+		want  []*types.Package
+	}{
+		{
+			name: "allow internal",
+			mod: &apidiff.Module{
+				Path: "example.com/foo",
+				Packages: []*types.Package{
+					types.NewPackage("example.com/foo/bar", "bar"),
+					types.NewPackage("example.com/foo/internal", "internal"),
+					types.NewPackage("example.com/foo/internal/buz", "buz"),
+					types.NewPackage("example.com/foo/bar/internal", "internal"),
+				},
+			},
+			allow: true,
+			want: []*types.Package{
+				types.NewPackage("example.com/foo/bar", "bar"),
+				types.NewPackage("example.com/foo/internal", "internal"),
+				types.NewPackage("example.com/foo/internal/buz", "buz"),
+				types.NewPackage("example.com/foo/bar/internal", "internal"),
+			},
+		},
+		{
+			name: "filter internal",
+			mod: &apidiff.Module{
+				Path: "example.com/foo",
+				Packages: []*types.Package{
+					types.NewPackage("example.com/foo/bar", "bar"),
+					types.NewPackage("example.com/foo/internal", "internal"),
+					types.NewPackage("example.com/foo/internal/buz", "buz"),
+					types.NewPackage("example.com/foo/bar/internal", "internal"),
+				},
+			},
+			want: []*types.Package{
+				types.NewPackage("example.com/foo/bar", "bar"),
+			},
+		},
+		{
+			name: "filter internal nothing left",
+			mod: &apidiff.Module{
+				Path: "example.com/foo",
+				Packages: []*types.Package{
+					types.NewPackage("example.com/foo/internal", "internal"),
+				},
+			},
+			want: nil,
+		},
+	} {
+		t.Run(tst.name, func(t *testing.T) {
+			filterInternal(tst.mod, tst.allow)
+			if diff := cmp.Diff(tst.mod.Packages, tst.want, cmp.Comparer(comparePath)); diff != "" {
+				t.Errorf("got(-),want(+):\n%s", diff)
+			}
+		})
+	}
+}
+
+func comparePath(x, y *types.Package) bool {
+	return x.Path() == y.Path()
+}