blob: d6c467a2226eeca77e5696566e2a97dccf39647b [file]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"go/token"
"os"
"os/exec"
"path/filepath"
"slices"
"testing"
"golang.org/x/exp/apidiff"
)
// TestModuleExportDataRoundTrip checks that writing a module's export
// data and reading it back yields the same set of packages with the
// same API. In particular, dependencies of the module's packages must
// not appear as packages of the module, and main packages must not be
// dropped; either would be reported as a spurious change when the file
// is compared against the module loaded from source.
func TestModuleExportDataRoundTrip(t *testing.T) {
if _, err := exec.LookPath("go"); err != nil {
t.Skipf("go command not available: %v", err)
}
dir := t.TempDir()
for name, content := range map[string]string{
"go.mod": "module example.com/m\n\ngo 1.24\n",
"a/a.go": `package a
import "fmt"
type T struct{ S fmt.Stringer }
type G[E any] struct{ Elem E }
type Alias = G[int]
`,
"b/b.go": `package b
import (
"strings"
"example.com/m/a"
)
func F(t a.T) a.Alias { return a.Alias{} }
var R *strings.Reader
`,
"cmd/c/main.go": `package main
import "example.com/m/b"
var _ = b.F
func main() {}
`,
} {
filename := filepath.Join(dir, filepath.FromSlash(name))
if err := os.MkdirAll(filepath.Dir(filename), 0o777); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filename, []byte(content), 0o666); err != nil {
t.Fatal(err)
}
}
t.Chdir(dir)
fset := token.NewFileSet()
loaded, err := loadModule(fset, ".")
if err != nil {
t.Fatal(err)
}
file := filepath.Join(t.TempDir(), "export")
if err := writeModuleExportData(fset, loaded, file); err != nil {
t.Fatal(err)
}
read, err := readModuleExportData(token.NewFileSet(), file)
if err != nil {
t.Fatal(err)
}
if read.Path != loaded.Path {
t.Errorf("module path: got %q, want %q", read.Path, loaded.Path)
}
paths := func(m *apidiff.Module) []string {
var paths []string
for _, pkg := range m.Packages {
paths = append(paths, pkg.Path())
}
slices.Sort(paths)
return paths
}
if got, want := paths(read), paths(loaded); !slices.Equal(got, want) {
t.Errorf("packages: got %q, want %q", got, want)
}
// Compare in both directions.
for _, report := range []apidiff.Report{
apidiff.ModuleChanges(loaded, read),
apidiff.ModuleChanges(read, loaded),
} {
for _, c := range report.Changes {
t.Errorf("unexpected change: %s", c.Message)
}
}
}