golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/internal/gcimporter/shallow_test.go (about)

     1  // Copyright 2022 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package gcimporter_test
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/parser"
    11  	"go/token"
    12  	"go/types"
    13  	"os"
    14  	"strings"
    15  	"testing"
    16  
    17  	"golang.org/x/sync/errgroup"
    18  	"golang.org/x/tools/go/packages"
    19  	"golang.org/x/tools/internal/gcimporter"
    20  	"golang.org/x/tools/internal/testenv"
    21  )
    22  
    23  // TestShallowStd type-checks the standard library using shallow export data.
    24  func TestShallowStd(t *testing.T) {
    25  	if testing.Short() {
    26  		t.Skip("skipping in short mode; too slow (https://golang.org/issue/14113)")
    27  	}
    28  	testenv.NeedsTool(t, "go")
    29  
    30  	// Load import graph of the standard library.
    31  	// (No parsing or type-checking.)
    32  	cfg := &packages.Config{
    33  		Mode: packages.NeedImports |
    34  			packages.NeedName |
    35  			packages.NeedFiles | // see https://github.com/golang/go/issues/56632
    36  			packages.NeedCompiledGoFiles,
    37  		Tests: false,
    38  	}
    39  	pkgs, err := packages.Load(cfg, "std")
    40  	if err != nil {
    41  		t.Fatalf("load: %v", err)
    42  	}
    43  	if len(pkgs) < 200 {
    44  		t.Fatalf("too few packages: %d", len(pkgs))
    45  	}
    46  
    47  	// Type check the packages in parallel postorder.
    48  	done := make(map[*packages.Package]chan struct{})
    49  	packages.Visit(pkgs, nil, func(p *packages.Package) {
    50  		done[p] = make(chan struct{})
    51  	})
    52  	packages.Visit(pkgs, nil,
    53  		func(pkg *packages.Package) {
    54  			go func() {
    55  				// Wait for all deps to be done.
    56  				for _, imp := range pkg.Imports {
    57  					<-done[imp]
    58  				}
    59  				typecheck(t, pkg)
    60  				close(done[pkg])
    61  			}()
    62  		})
    63  	for _, root := range pkgs {
    64  		<-done[root]
    65  	}
    66  }
    67  
    68  // typecheck reads, parses, and type-checks a package.
    69  // It squirrels the export data in the ppkg.ExportFile field.
    70  func typecheck(t *testing.T, ppkg *packages.Package) {
    71  	if ppkg.PkgPath == "unsafe" {
    72  		return // unsafe is special
    73  	}
    74  
    75  	// Create a local FileSet just for this package.
    76  	fset := token.NewFileSet()
    77  
    78  	// Parse files in parallel.
    79  	syntax := make([]*ast.File, len(ppkg.CompiledGoFiles))
    80  	var group errgroup.Group
    81  	for i, filename := range ppkg.CompiledGoFiles {
    82  		i, filename := i, filename
    83  		group.Go(func() error {
    84  			f, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution)
    85  			if err != nil {
    86  				return err // e.g. missing file
    87  			}
    88  			syntax[i] = f
    89  			return nil
    90  		})
    91  	}
    92  	if err := group.Wait(); err != nil {
    93  		t.Fatal(err)
    94  	}
    95  	// Inv: all files were successfully parsed.
    96  
    97  	// Build map of dependencies by package path.
    98  	// (We don't compute this mapping for the entire
    99  	// packages graph because it is not globally consistent.)
   100  	depsByPkgPath := make(map[string]*packages.Package)
   101  	{
   102  		var visit func(*packages.Package)
   103  		visit = func(pkg *packages.Package) {
   104  			if depsByPkgPath[pkg.PkgPath] == nil {
   105  				depsByPkgPath[pkg.PkgPath] = pkg
   106  				for path := range pkg.Imports {
   107  					visit(pkg.Imports[path])
   108  				}
   109  			}
   110  		}
   111  		visit(ppkg)
   112  	}
   113  
   114  	// importer state
   115  	var (
   116  		loadFromExportData func(*packages.Package) (*types.Package, error)
   117  		importMap          = map[string]*types.Package{ // keys are PackagePaths
   118  			ppkg.PkgPath: types.NewPackage(ppkg.PkgPath, ppkg.Name),
   119  		}
   120  	)
   121  	loadFromExportData = func(imp *packages.Package) (*types.Package, error) {
   122  		export := []byte(imp.ExportFile)
   123  		getPackages := func(items []gcimporter.GetPackagesItem) error {
   124  			for i, item := range items {
   125  				pkg, ok := importMap[item.Path]
   126  				if !ok {
   127  					dep, ok := depsByPkgPath[item.Path]
   128  					if !ok {
   129  						return fmt.Errorf("can't find dependency: %q", item.Path)
   130  					}
   131  					pkg = types.NewPackage(item.Path, dep.Name)
   132  					importMap[item.Path] = pkg
   133  					loadFromExportData(dep) // side effect: populate package scope
   134  				}
   135  				items[i].Pkg = pkg
   136  			}
   137  			return nil
   138  		}
   139  		return gcimporter.IImportShallow(fset, getPackages, export, imp.PkgPath, nil)
   140  	}
   141  
   142  	// Type-check the syntax trees.
   143  	cfg := &types.Config{
   144  		Error: func(e error) {
   145  			t.Error(e)
   146  		},
   147  		Importer: importerFunc(func(importPath string) (*types.Package, error) {
   148  			if importPath == "unsafe" {
   149  				return types.Unsafe, nil // unsafe has no exportdata
   150  			}
   151  			imp, ok := ppkg.Imports[importPath]
   152  			if !ok {
   153  				return nil, fmt.Errorf("missing import %q", importPath)
   154  			}
   155  			return loadFromExportData(imp)
   156  		}),
   157  	}
   158  
   159  	// (Use NewChecker+Files to ensure Package.Name is set explicitly.)
   160  	tpkg := types.NewPackage(ppkg.PkgPath, ppkg.Name)
   161  	_ = types.NewChecker(cfg, fset, tpkg, nil).Files(syntax) // ignore error
   162  	// Check sanity.
   163  	postTypeCheck(t, fset, tpkg)
   164  
   165  	// Save the export data.
   166  	data, err := gcimporter.IExportShallow(fset, tpkg, nil)
   167  	if err != nil {
   168  		t.Fatalf("internal error marshalling export data: %v", err)
   169  	}
   170  	ppkg.ExportFile = string(data)
   171  }
   172  
   173  // postTypeCheck is called after a package is type checked.
   174  // We use it to assert additional correctness properties,
   175  // for example, that the apparent location of "fmt.Println"
   176  // corresponds to its source location: in other words,
   177  // export+import preserves high-fidelity positions.
   178  func postTypeCheck(t *testing.T, fset *token.FileSet, pkg *types.Package) {
   179  	// We hard-code a few interesting test-case objects.
   180  	var obj types.Object
   181  	switch pkg.Path() {
   182  	case "fmt":
   183  		// func fmt.Println
   184  		obj = pkg.Scope().Lookup("Println")
   185  	case "net/http":
   186  		// method (*http.Request).ParseForm
   187  		req := pkg.Scope().Lookup("Request")
   188  		obj, _, _ = types.LookupFieldOrMethod(req.Type(), true, pkg, "ParseForm")
   189  	default:
   190  		return
   191  	}
   192  	if obj == nil {
   193  		t.Errorf("object not found in package %s", pkg.Path())
   194  		return
   195  	}
   196  
   197  	// Now check the source fidelity of the object's position.
   198  	posn := fset.Position(obj.Pos())
   199  	data, err := os.ReadFile(posn.Filename)
   200  	if err != nil {
   201  		t.Errorf("can't read source file declaring %v: %v", obj, err)
   202  		return
   203  	}
   204  
   205  	// Check line and column denote a source interval containing the object's identifier.
   206  	line := strings.Split(string(data), "\n")[posn.Line-1]
   207  
   208  	if id := line[posn.Column-1 : posn.Column-1+len(obj.Name())]; id != obj.Name() {
   209  		t.Errorf("%+v: expected declaration of %v at this line, column; got %q", posn, obj, line)
   210  	}
   211  
   212  	// Check offset.
   213  	if id := string(data[posn.Offset : posn.Offset+len(obj.Name())]); id != obj.Name() {
   214  		t.Errorf("%+v: expected declaration of %v at this offset; got %q", posn, obj, id)
   215  	}
   216  
   217  	// Check commutativity of Position() and start+len(name) operations:
   218  	// Position(startPos+len(name)) == Position(startPos) + len(name).
   219  	// This important property is a consequence of the way in which the
   220  	// decoder fills the gaps in the sparse line-start offset table.
   221  	endPosn := fset.Position(obj.Pos() + token.Pos(len(obj.Name())))
   222  	wantEndPosn := token.Position{
   223  		Filename: posn.Filename,
   224  		Offset:   posn.Offset + len(obj.Name()),
   225  		Line:     posn.Line,
   226  		Column:   posn.Column + len(obj.Name()),
   227  	}
   228  	if endPosn != wantEndPosn {
   229  		t.Errorf("%+v: expected end Position of %v here; was at %+v", wantEndPosn, obj, endPosn)
   230  	}
   231  }