Source file
src/go/types/stdlib_test.go
1
2
3
4
5
6
7
8 package types_test
9
10 import (
11 "errors"
12 "fmt"
13 "go/ast"
14 "go/build"
15 "go/importer"
16 "go/parser"
17 "go/scanner"
18 "go/token"
19 "internal/testenv"
20 "os"
21 "path/filepath"
22 "runtime"
23 "strings"
24 "sync"
25 "testing"
26 "time"
27
28 . "go/types"
29 )
30
31
32
33
34
35
36
37
38
39 var stdLibImporter = importer.ForCompiler(token.NewFileSet(), "source", nil)
40
41 func TestStdlib(t *testing.T) {
42 if testing.Short() {
43 t.Skip("skipping in short mode")
44 }
45
46 testenv.MustHaveGoBuild(t)
47
48
49 dirFiles := make(map[string][]string)
50 root := filepath.Join(testenv.GOROOT(t), "src")
51 walkPkgDirs(root, func(dir string, filenames []string) {
52 dirFiles[dir] = filenames
53 }, t.Error)
54
55 c := &stdlibChecker{
56 dirFiles: dirFiles,
57 pkgs: make(map[string]*futurePackage),
58 }
59
60 start := time.Now()
61
62
63
64
65
66
67
68 cpulimit := make(chan struct{}, runtime.GOMAXPROCS(0))
69 var wg sync.WaitGroup
70
71 for dir := range dirFiles {
72 dir := dir
73
74 cpulimit <- struct{}{}
75 wg.Add(1)
76 go func() {
77 defer func() {
78 wg.Done()
79 <-cpulimit
80 }()
81
82 _, err := c.getDirPackage(dir)
83 if err != nil {
84 t.Errorf("error checking %s: %v", dir, err)
85 }
86 }()
87 }
88
89 wg.Wait()
90
91 if testing.Verbose() {
92 fmt.Println(len(dirFiles), "packages typechecked in", time.Since(start))
93 }
94 }
95
96
97
98 type stdlibChecker struct {
99 dirFiles map[string][]string
100
101 mu sync.Mutex
102 pkgs map[string]*futurePackage
103 }
104
105
106 type futurePackage struct {
107 done chan struct{}
108 pkg *Package
109 err error
110 }
111
112 func (c *stdlibChecker) Import(path string) (*Package, error) {
113 panic("unimplemented: use ImportFrom")
114 }
115
116 func (c *stdlibChecker) ImportFrom(path, dir string, _ ImportMode) (*Package, error) {
117 if path == "unsafe" {
118
119 return Unsafe, nil
120 }
121
122 p, err := build.Default.Import(path, dir, build.FindOnly)
123 if err != nil {
124 return nil, err
125 }
126
127 pkg, err := c.getDirPackage(p.Dir)
128 if pkg != nil {
129
130
131 return pkg, nil
132 }
133 return nil, err
134 }
135
136
137
138
139
140 func (c *stdlibChecker) getDirPackage(dir string) (*Package, error) {
141 c.mu.Lock()
142 fut, ok := c.pkgs[dir]
143 if !ok {
144
145 fut = &futurePackage{
146 done: make(chan struct{}),
147 }
148 c.pkgs[dir] = fut
149 files, ok := c.dirFiles[dir]
150 c.mu.Unlock()
151 if !ok {
152 fut.err = fmt.Errorf("no files for %s", dir)
153 } else {
154
155
156
157 fut.pkg, fut.err = typecheckFiles(dir, files, c)
158 }
159 close(fut.done)
160 } else {
161
162 c.mu.Unlock()
163 <-fut.done
164 }
165 return fut.pkg, fut.err
166 }
167
168
169
170
171
172
173 func firstComment(filename string) string {
174 f, err := os.Open(filename)
175 if err != nil {
176 return ""
177 }
178 defer f.Close()
179
180 var src [4 << 10]byte
181 n, _ := f.Read(src[:])
182
183 var first string
184 var s scanner.Scanner
185 s.Init(fset.AddFile("", fset.Base(), n), src[:n], nil , scanner.ScanComments)
186 for {
187 _, tok, lit := s.Scan()
188 switch tok {
189 case token.COMMENT:
190
191 if lit[1] == '*' {
192 lit = lit[:len(lit)-2]
193 }
194 contents := strings.TrimSpace(lit[2:])
195 if strings.HasPrefix(contents, "go:build ") {
196 return "skip"
197 }
198 if first == "" {
199 first = contents
200 }
201
202
203 case token.PACKAGE, token.EOF:
204 return first
205 }
206 }
207 }
208
209 func testTestDir(t *testing.T, path string, ignore ...string) {
210 files, err := os.ReadDir(path)
211 if err != nil {
212
213
214
215 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "test")); os.IsNotExist(err) {
216 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil {
217 t.Skipf("skipping: GOROOT/test not present")
218 }
219 }
220 t.Fatal(err)
221 }
222
223 excluded := make(map[string]bool)
224 for _, filename := range ignore {
225 excluded[filename] = true
226 }
227
228 fset := token.NewFileSet()
229 for _, f := range files {
230
231 if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] {
232 continue
233 }
234
235
236 expectErrors := false
237 filename := filepath.Join(path, f.Name())
238 goVersion := ""
239 if comment := firstComment(filename); comment != "" {
240 if strings.Contains(comment, "-goexperiment") {
241 continue
242 }
243 fields := strings.Fields(comment)
244 switch fields[0] {
245 case "skip", "compiledir":
246 continue
247 case "errorcheck":
248 expectErrors = true
249 for _, arg := range fields[1:] {
250 if arg == "-0" || arg == "-+" || arg == "-std" {
251
252
253
254
255 expectErrors = false
256 break
257 }
258 const prefix = "-lang="
259 if strings.HasPrefix(arg, prefix) {
260 goVersion = arg[len(prefix):]
261 }
262 }
263 }
264 }
265
266
267 file, err := parser.ParseFile(fset, filename, nil, 0)
268 if err == nil {
269 conf := Config{
270 GoVersion: goVersion,
271 Importer: stdLibImporter,
272 }
273 _, err = conf.Check(filename, fset, []*ast.File{file}, nil)
274 }
275
276 if expectErrors {
277 if err == nil {
278 t.Errorf("expected errors but found none in %s", filename)
279 }
280 } else {
281 if err != nil {
282 t.Error(err)
283 }
284 }
285 }
286 }
287
288 func TestStdTest(t *testing.T) {
289 testenv.MustHaveGoBuild(t)
290
291 if testing.Short() && testenv.Builder() == "" {
292 t.Skip("skipping in short mode")
293 }
294
295 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test"),
296 "cmplxdivide.go",
297 "directive.go",
298 "directive2.go",
299 "embedfunc.go",
300 "embedvers.go",
301 "linkname2.go",
302 "linkname3.go",
303 )
304 }
305
306 func TestStdFixed(t *testing.T) {
307 testenv.MustHaveGoBuild(t)
308
309 if testing.Short() && testenv.Builder() == "" {
310 t.Skip("skipping in short mode")
311 }
312
313 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "fixedbugs"),
314 "bug248.go", "bug302.go", "bug369.go",
315 "bug398.go",
316 "issue6889.go",
317 "issue11362.go",
318 "issue16369.go",
319 "issue18459.go",
320 "issue18882.go",
321 "issue20529.go",
322 "issue22200.go",
323 "issue22200b.go",
324 "issue25507.go",
325 "issue20780.go",
326 "bug251.go",
327 "issue42058a.go",
328 "issue42058b.go",
329 "issue48097.go",
330 "issue48230.go",
331 "issue49767.go",
332 "issue49814.go",
333 "issue56103.go",
334 "issue52697.go",
335
336
337
338 "bug514.go",
339 "issue40954.go",
340 "issue42032.go",
341 "issue42076.go",
342 "issue46903.go",
343 "issue51733.go",
344 "notinheap2.go",
345 "notinheap3.go",
346 )
347 }
348
349 func TestStdKen(t *testing.T) {
350 testenv.MustHaveGoBuild(t)
351
352 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "ken"))
353 }
354
355
356 var excluded = map[string]bool{
357 "builtin": true,
358
359
360 "crypto/internal/edwards25519/field/_asm": true,
361 "crypto/internal/bigmod/_asm": true,
362 }
363
364
365
366
367
368
369 var printPackageMu sync.Mutex
370
371
372 func typecheckFiles(path string, filenames []string, importer Importer) (*Package, error) {
373 fset := token.NewFileSet()
374
375
376 var files []*ast.File
377 for _, filename := range filenames {
378 file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
379 if err != nil {
380 return nil, err
381 }
382
383 files = append(files, file)
384 }
385
386 if testing.Verbose() {
387 printPackageMu.Lock()
388 fmt.Println("package", files[0].Name.Name)
389 for _, filename := range filenames {
390 fmt.Println("\t", filename)
391 }
392 printPackageMu.Unlock()
393 }
394
395
396 var errs []error
397 conf := Config{
398 Error: func(err error) {
399 errs = append(errs, err)
400 },
401 Importer: importer,
402 }
403 info := Info{Uses: make(map[*ast.Ident]Object)}
404 pkg, _ := conf.Check(path, fset, files, &info)
405 err := errors.Join(errs...)
406 if err != nil {
407 return pkg, err
408 }
409
410
411
412
413 errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0)
414 for id, obj := range info.Uses {
415 predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
416 if predeclared == (obj.Pkg() != nil) {
417 posn := fset.Position(id.Pos())
418 if predeclared {
419 return nil, fmt.Errorf("%s: predeclared object with package: %s", posn, obj)
420 } else {
421 return nil, fmt.Errorf("%s: user-defined object without package: %s", posn, obj)
422 }
423 }
424 }
425
426 return pkg, nil
427 }
428
429
430 func pkgFilenames(dir string, includeTest bool) ([]string, error) {
431 ctxt := build.Default
432 ctxt.CgoEnabled = false
433 pkg, err := ctxt.ImportDir(dir, 0)
434 if err != nil {
435 if _, nogo := err.(*build.NoGoError); nogo {
436 return nil, nil
437 }
438 return nil, err
439 }
440 if excluded[pkg.ImportPath] {
441 return nil, nil
442 }
443 var filenames []string
444 for _, name := range pkg.GoFiles {
445 filenames = append(filenames, filepath.Join(pkg.Dir, name))
446 }
447 if includeTest {
448 for _, name := range pkg.TestGoFiles {
449 filenames = append(filenames, filepath.Join(pkg.Dir, name))
450 }
451 }
452 return filenames, nil
453 }
454
455 func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...any)) {
456 w := walker{pkgh, errh}
457 w.walk(dir)
458 }
459
460 type walker struct {
461 pkgh func(dir string, filenames []string)
462 errh func(args ...any)
463 }
464
465 func (w *walker) walk(dir string) {
466 files, err := os.ReadDir(dir)
467 if err != nil {
468 w.errh(err)
469 return
470 }
471
472
473
474
475 pkgFiles, err := pkgFilenames(dir, false)
476 if err != nil {
477 w.errh(err)
478 return
479 }
480 if pkgFiles != nil {
481 w.pkgh(dir, pkgFiles)
482 }
483
484
485 for _, f := range files {
486 if f.IsDir() && f.Name() != "testdata" {
487 w.walk(filepath.Join(dir, f.Name()))
488 }
489 }
490 }
491
View as plain text