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 }