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 | "testing" |
14 | |
15 | "golang.org/x/sync/errgroup" |
16 | "golang.org/x/tools/go/packages" |
17 | "golang.org/x/tools/internal/gcimporter" |
18 | "golang.org/x/tools/internal/testenv" |
19 | ) |
20 | |
21 | // TestStd type-checks the standard library using shallow export data. |
22 | func TestShallowStd(t *testing.T) { |
23 | if testing.Short() { |
24 | t.Skip("skipping in short mode; too slow (https://golang.org/issue/14113)") |
25 | } |
26 | testenv.NeedsTool(t, "go") |
27 | |
28 | // Load import graph of the standard library. |
29 | // (No parsing or type-checking.) |
30 | cfg := &packages.Config{ |
31 | Mode: packages.NeedImports | |
32 | packages.NeedName | |
33 | packages.NeedFiles | // see https://github.com/golang/go/issues/56632 |
34 | packages.NeedCompiledGoFiles, |
35 | Tests: false, |
36 | } |
37 | pkgs, err := packages.Load(cfg, "std") |
38 | if err != nil { |
39 | t.Fatalf("load: %v", err) |
40 | } |
41 | if len(pkgs) < 200 { |
42 | t.Fatalf("too few packages: %d", len(pkgs)) |
43 | } |
44 | |
45 | // Type check the packages in parallel postorder. |
46 | done := make(map[*packages.Package]chan struct{}) |
47 | packages.Visit(pkgs, nil, func(p *packages.Package) { |
48 | done[p] = make(chan struct{}) |
49 | }) |
50 | packages.Visit(pkgs, nil, |
51 | func(pkg *packages.Package) { |
52 | go func() { |
53 | // Wait for all deps to be done. |
54 | for _, imp := range pkg.Imports { |
55 | <-done[imp] |
56 | } |
57 | typecheck(t, pkg) |
58 | close(done[pkg]) |
59 | }() |
60 | }) |
61 | for _, root := range pkgs { |
62 | <-done[root] |
63 | } |
64 | } |
65 | |
66 | // typecheck reads, parses, and type-checks a package. |
67 | // It squirrels the export data in the the ppkg.ExportFile field. |
68 | func typecheck(t *testing.T, ppkg *packages.Package) { |
69 | if ppkg.PkgPath == "unsafe" { |
70 | return // unsafe is special |
71 | } |
72 | |
73 | // Create a local FileSet just for this package. |
74 | fset := token.NewFileSet() |
75 | |
76 | // Parse files in parallel. |
77 | syntax := make([]*ast.File, len(ppkg.CompiledGoFiles)) |
78 | var group errgroup.Group |
79 | for i, filename := range ppkg.CompiledGoFiles { |
80 | i, filename := i, filename |
81 | group.Go(func() error { |
82 | f, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution) |
83 | if err != nil { |
84 | return err // e.g. missing file |
85 | } |
86 | syntax[i] = f |
87 | return nil |
88 | }) |
89 | } |
90 | if err := group.Wait(); err != nil { |
91 | t.Fatal(err) |
92 | } |
93 | // Inv: all files were successfully parsed. |
94 | |
95 | // Build map of dependencies by package path. |
96 | // (We don't compute this mapping for the entire |
97 | // packages graph because it is not globally consistent.) |
98 | depsByPkgPath := make(map[string]*packages.Package) |
99 | { |
100 | var visit func(*packages.Package) |
101 | visit = func(pkg *packages.Package) { |
102 | if depsByPkgPath[pkg.PkgPath] == nil { |
103 | depsByPkgPath[pkg.PkgPath] = pkg |
104 | for path := range pkg.Imports { |
105 | visit(pkg.Imports[path]) |
106 | } |
107 | } |
108 | } |
109 | visit(ppkg) |
110 | } |
111 | |
112 | // importer state |
113 | var ( |
114 | insert func(p *types.Package, name string) |
115 | importMap = make(map[string]*types.Package) // keys are PackagePaths |
116 | ) |
117 | loadFromExportData := func(imp *packages.Package) (*types.Package, error) { |
118 | data := []byte(imp.ExportFile) |
119 | return gcimporter.IImportShallow(fset, importMap, data, imp.PkgPath, insert) |
120 | } |
121 | insert = func(p *types.Package, name string) { |
122 | imp, ok := depsByPkgPath[p.Path()] |
123 | if !ok { |
124 | t.Fatalf("can't find dependency: %q", p.Path()) |
125 | } |
126 | imported, err := loadFromExportData(imp) |
127 | if err != nil { |
128 | t.Fatalf("unmarshal: %v", err) |
129 | } |
130 | if imported != p { |
131 | t.Fatalf("internal error: inconsistent packages") |
132 | } |
133 | if obj := imported.Scope().Lookup(name); obj == nil { |
134 | t.Fatalf("lookup %q.%s failed", imported.Path(), name) |
135 | } |
136 | } |
137 | |
138 | cfg := &types.Config{ |
139 | Error: func(e error) { |
140 | t.Error(e) |
141 | }, |
142 | Importer: importerFunc(func(importPath string) (*types.Package, error) { |
143 | if importPath == "unsafe" { |
144 | return types.Unsafe, nil // unsafe has no exportdata |
145 | } |
146 | imp, ok := ppkg.Imports[importPath] |
147 | if !ok { |
148 | return nil, fmt.Errorf("missing import %q", importPath) |
149 | } |
150 | return loadFromExportData(imp) |
151 | }), |
152 | } |
153 | |
154 | // Type-check the syntax trees. |
155 | tpkg, _ := cfg.Check(ppkg.PkgPath, fset, syntax, nil) |
156 | |
157 | // Save the export data. |
158 | data, err := gcimporter.IExportShallow(fset, tpkg) |
159 | if err != nil { |
160 | t.Fatalf("internal error marshalling export data: %v", err) |
161 | } |
162 | ppkg.ExportFile = string(data) |
163 | } |
164 |
Members