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