aboutsummaryrefslogtreecommitdiff
path: root/internal/gcimporter/shallow_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/gcimporter/shallow_test.go')
-rw-r--r--internal/gcimporter/shallow_test.go226
1 files changed, 226 insertions, 0 deletions
diff --git a/internal/gcimporter/shallow_test.go b/internal/gcimporter/shallow_test.go
new file mode 100644
index 000000000..429c34b3d
--- /dev/null
+++ b/internal/gcimporter/shallow_test.go
@@ -0,0 +1,226 @@
+// Copyright 2022 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 gcimporter_test
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "os"
+ "strings"
+ "testing"
+
+ "golang.org/x/sync/errgroup"
+ "golang.org/x/tools/go/packages"
+ "golang.org/x/tools/internal/gcimporter"
+ "golang.org/x/tools/internal/testenv"
+)
+
+// TestStd type-checks the standard library using shallow export data.
+func TestShallowStd(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping in short mode; too slow (https://golang.org/issue/14113)")
+ }
+ testenv.NeedsTool(t, "go")
+
+ // Load import graph of the standard library.
+ // (No parsing or type-checking.)
+ cfg := &packages.Config{
+ Mode: packages.NeedImports |
+ packages.NeedName |
+ packages.NeedFiles | // see https://github.com/golang/go/issues/56632
+ packages.NeedCompiledGoFiles,
+ Tests: false,
+ }
+ pkgs, err := packages.Load(cfg, "std")
+ if err != nil {
+ t.Fatalf("load: %v", err)
+ }
+ if len(pkgs) < 200 {
+ t.Fatalf("too few packages: %d", len(pkgs))
+ }
+
+ // Type check the packages in parallel postorder.
+ done := make(map[*packages.Package]chan struct{})
+ packages.Visit(pkgs, nil, func(p *packages.Package) {
+ done[p] = make(chan struct{})
+ })
+ packages.Visit(pkgs, nil,
+ func(pkg *packages.Package) {
+ go func() {
+ // Wait for all deps to be done.
+ for _, imp := range pkg.Imports {
+ <-done[imp]
+ }
+ typecheck(t, pkg)
+ close(done[pkg])
+ }()
+ })
+ for _, root := range pkgs {
+ <-done[root]
+ }
+}
+
+// typecheck reads, parses, and type-checks a package.
+// It squirrels the export data in the the ppkg.ExportFile field.
+func typecheck(t *testing.T, ppkg *packages.Package) {
+ if ppkg.PkgPath == "unsafe" {
+ return // unsafe is special
+ }
+
+ // Create a local FileSet just for this package.
+ fset := token.NewFileSet()
+
+ // Parse files in parallel.
+ syntax := make([]*ast.File, len(ppkg.CompiledGoFiles))
+ var group errgroup.Group
+ for i, filename := range ppkg.CompiledGoFiles {
+ i, filename := i, filename
+ group.Go(func() error {
+ f, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution)
+ if err != nil {
+ return err // e.g. missing file
+ }
+ syntax[i] = f
+ return nil
+ })
+ }
+ if err := group.Wait(); err != nil {
+ t.Fatal(err)
+ }
+ // Inv: all files were successfully parsed.
+
+ // Build map of dependencies by package path.
+ // (We don't compute this mapping for the entire
+ // packages graph because it is not globally consistent.)
+ depsByPkgPath := make(map[string]*packages.Package)
+ {
+ var visit func(*packages.Package)
+ visit = func(pkg *packages.Package) {
+ if depsByPkgPath[pkg.PkgPath] == nil {
+ depsByPkgPath[pkg.PkgPath] = pkg
+ for path := range pkg.Imports {
+ visit(pkg.Imports[path])
+ }
+ }
+ }
+ visit(ppkg)
+ }
+
+ // importer state
+ var (
+ insert func(p *types.Package, name string)
+ importMap = make(map[string]*types.Package) // keys are PackagePaths
+ )
+ loadFromExportData := func(imp *packages.Package) (*types.Package, error) {
+ data := []byte(imp.ExportFile)
+ return gcimporter.IImportShallow(fset, importMap, data, imp.PkgPath, insert)
+ }
+ insert = func(p *types.Package, name string) {
+ imp, ok := depsByPkgPath[p.Path()]
+ if !ok {
+ t.Fatalf("can't find dependency: %q", p.Path())
+ }
+ imported, err := loadFromExportData(imp)
+ if err != nil {
+ t.Fatalf("unmarshal: %v", err)
+ }
+ if imported != p {
+ t.Fatalf("internal error: inconsistent packages")
+ }
+ if obj := imported.Scope().Lookup(name); obj == nil {
+ t.Fatalf("lookup %q.%s failed", imported.Path(), name)
+ }
+ }
+
+ cfg := &types.Config{
+ Error: func(e error) {
+ t.Error(e)
+ },
+ Importer: importerFunc(func(importPath string) (*types.Package, error) {
+ if importPath == "unsafe" {
+ return types.Unsafe, nil // unsafe has no exportdata
+ }
+ imp, ok := ppkg.Imports[importPath]
+ if !ok {
+ return nil, fmt.Errorf("missing import %q", importPath)
+ }
+ return loadFromExportData(imp)
+ }),
+ }
+
+ // Type-check the syntax trees.
+ tpkg, _ := cfg.Check(ppkg.PkgPath, fset, syntax, nil)
+ postTypeCheck(t, fset, tpkg)
+
+ // Save the export data.
+ data, err := gcimporter.IExportShallow(fset, tpkg)
+ if err != nil {
+ t.Fatalf("internal error marshalling export data: %v", err)
+ }
+ ppkg.ExportFile = string(data)
+}
+
+// postTypeCheck is called after a package is type checked.
+// We use it to assert additional correctness properties,
+// for example, that the apparent location of "fmt.Println"
+// corresponds to its source location: in other words,
+// export+import preserves high-fidelity positions.
+func postTypeCheck(t *testing.T, fset *token.FileSet, pkg *types.Package) {
+ // We hard-code a few interesting test-case objects.
+ var obj types.Object
+ switch pkg.Path() {
+ case "fmt":
+ // func fmt.Println
+ obj = pkg.Scope().Lookup("Println")
+ case "net/http":
+ // method (*http.Request).ParseForm
+ req := pkg.Scope().Lookup("Request")
+ obj, _, _ = types.LookupFieldOrMethod(req.Type(), true, pkg, "ParseForm")
+ default:
+ return
+ }
+ if obj == nil {
+ t.Errorf("object not found in package %s", pkg.Path())
+ return
+ }
+
+ // Now check the source fidelity of the object's position.
+ posn := fset.Position(obj.Pos())
+ data, err := os.ReadFile(posn.Filename)
+ if err != nil {
+ t.Errorf("can't read source file declaring %v: %v", obj, err)
+ return
+ }
+
+ // Check line and column denote a source interval containing the object's identifier.
+ line := strings.Split(string(data), "\n")[posn.Line-1]
+
+ if id := line[posn.Column-1 : posn.Column-1+len(obj.Name())]; id != obj.Name() {
+ t.Errorf("%+v: expected declaration of %v at this line, column; got %q", posn, obj, line)
+ }
+
+ // Check offset.
+ if id := string(data[posn.Offset : posn.Offset+len(obj.Name())]); id != obj.Name() {
+ t.Errorf("%+v: expected declaration of %v at this offset; got %q", posn, obj, id)
+ }
+
+ // Check commutativity of Position() and start+len(name) operations:
+ // Position(startPos+len(name)) == Position(startPos) + len(name).
+ // This important property is a consequence of the way in which the
+ // decoder fills the gaps in the sparse line-start offset table.
+ endPosn := fset.Position(obj.Pos() + token.Pos(len(obj.Name())))
+ wantEndPosn := token.Position{
+ Filename: posn.Filename,
+ Offset: posn.Offset + len(obj.Name()),
+ Line: posn.Line,
+ Column: posn.Column + len(obj.Name()),
+ }
+ if endPosn != wantEndPosn {
+ t.Errorf("%+v: expected end Position of %v here; was at %+v", wantEndPosn, obj, endPosn)
+ }
+}