]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/types2/stdlib_test.go
test: migrate remaining files to go:build syntax
[gostls13.git] / src / cmd / compile / internal / types2 / stdlib_test.go
1 // Copyright 2013 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 // This file tests types2.Check by using it to
6 // typecheck the standard library and tests.
7
8 package types2_test
9
10 import (
11         "bytes"
12         "cmd/compile/internal/syntax"
13         "errors"
14         "fmt"
15         "go/build"
16         "internal/testenv"
17         "os"
18         "path/filepath"
19         "runtime"
20         "strings"
21         "sync"
22         "testing"
23         "time"
24
25         . "cmd/compile/internal/types2"
26 )
27
28 var stdLibImporter = defaultImporter()
29
30 func TestStdlib(t *testing.T) {
31         if testing.Short() {
32                 t.Skip("skipping in short mode")
33         }
34
35         testenv.MustHaveGoBuild(t)
36
37         // Collect non-test files.
38         dirFiles := make(map[string][]string)
39         root := filepath.Join(testenv.GOROOT(t), "src")
40         walkPkgDirs(root, func(dir string, filenames []string) {
41                 dirFiles[dir] = filenames
42         }, t.Error)
43
44         c := &stdlibChecker{
45                 dirFiles: dirFiles,
46                 pkgs:     make(map[string]*futurePackage),
47         }
48
49         start := time.Now()
50
51         // Though we read files while parsing, type-checking is otherwise CPU bound.
52         //
53         // This doesn't achieve great CPU utilization as many packages may block
54         // waiting for a common import, but in combination with the non-deterministic
55         // map iteration below this should provide decent coverage of concurrent
56         // type-checking (see golang/go#47729).
57         cpulimit := make(chan struct{}, runtime.GOMAXPROCS(0))
58         var wg sync.WaitGroup
59
60         for dir := range dirFiles {
61                 dir := dir
62
63                 cpulimit <- struct{}{}
64                 wg.Add(1)
65                 go func() {
66                         defer func() {
67                                 wg.Done()
68                                 <-cpulimit
69                         }()
70
71                         _, err := c.getDirPackage(dir)
72                         if err != nil {
73                                 t.Errorf("error checking %s: %v", dir, err)
74                         }
75                 }()
76         }
77
78         wg.Wait()
79
80         if testing.Verbose() {
81                 fmt.Println(len(dirFiles), "packages typechecked in", time.Since(start))
82         }
83 }
84
85 // stdlibChecker implements concurrent type-checking of the packages defined by
86 // dirFiles, which must define a closed set of packages (such as GOROOT/src).
87 type stdlibChecker struct {
88         dirFiles map[string][]string // non-test files per directory; must be pre-populated
89
90         mu   sync.Mutex
91         pkgs map[string]*futurePackage // future cache of type-checking results
92 }
93
94 // A futurePackage is a future result of type-checking.
95 type futurePackage struct {
96         done chan struct{} // guards pkg and err
97         pkg  *Package
98         err  error
99 }
100
101 func (c *stdlibChecker) Import(path string) (*Package, error) {
102         panic("unimplemented: use ImportFrom")
103 }
104
105 func (c *stdlibChecker) ImportFrom(path, dir string, _ ImportMode) (*Package, error) {
106         if path == "unsafe" {
107                 // unsafe cannot be type checked normally.
108                 return Unsafe, nil
109         }
110
111         p, err := build.Default.Import(path, dir, build.FindOnly)
112         if err != nil {
113                 return nil, err
114         }
115
116         pkg, err := c.getDirPackage(p.Dir)
117         if pkg != nil {
118                 // As long as pkg is non-nil, avoid redundant errors related to failed
119                 // imports. TestStdlib will collect errors once for each package.
120                 return pkg, nil
121         }
122         return nil, err
123 }
124
125 // getDirPackage gets the package defined in dir from the future cache.
126 //
127 // If this is the first goroutine requesting the package, getDirPackage
128 // type-checks.
129 func (c *stdlibChecker) getDirPackage(dir string) (*Package, error) {
130         c.mu.Lock()
131         fut, ok := c.pkgs[dir]
132         if !ok {
133                 // First request for this package dir; type check.
134                 fut = &futurePackage{
135                         done: make(chan struct{}),
136                 }
137                 c.pkgs[dir] = fut
138                 files, ok := c.dirFiles[dir]
139                 c.mu.Unlock()
140                 if !ok {
141                         fut.err = fmt.Errorf("no files for %s", dir)
142                 } else {
143                         // Using dir as the package path here may be inconsistent with the behavior
144                         // of a normal importer, but is sufficient as dir is by construction unique
145                         // to this package.
146                         fut.pkg, fut.err = typecheckFiles(dir, files, c)
147                 }
148                 close(fut.done)
149         } else {
150                 // Otherwise, await the result.
151                 c.mu.Unlock()
152                 <-fut.done
153         }
154         return fut.pkg, fut.err
155 }
156
157 // firstComment returns the contents of the first non-empty comment in
158 // the given file, "skip", or the empty string. No matter the present
159 // comments, if any of them contains a build tag, the result is always
160 // "skip". Only comments within the first 4K of the file are considered.
161 // TODO(gri) should only read until we see "package" token.
162 func firstComment(filename string) (first string) {
163         f, err := os.Open(filename)
164         if err != nil {
165                 return ""
166         }
167         defer f.Close()
168
169         // read at most 4KB
170         var buf [4 << 10]byte
171         n, _ := f.Read(buf[:])
172         src := bytes.NewBuffer(buf[:n])
173
174         // TODO(gri) we need a better way to terminate CommentsDo
175         defer func() {
176                 if p := recover(); p != nil {
177                         if s, ok := p.(string); ok {
178                                 first = s
179                         }
180                 }
181         }()
182
183         syntax.CommentsDo(src, func(_, _ uint, text string) {
184                 if text[0] != '/' {
185                         return // not a comment
186                 }
187
188                 // extract comment text
189                 if text[1] == '*' {
190                         text = text[:len(text)-2]
191                 }
192                 text = strings.TrimSpace(text[2:])
193
194                 if strings.HasPrefix(text, "go:build ") {
195                         panic("skip")
196                 }
197                 if first == "" {
198                         first = text // text may be "" but that's ok
199                 }
200                 // continue as we may still see build tags
201         })
202
203         return
204 }
205
206 func testTestDir(t *testing.T, path string, ignore ...string) {
207         files, err := os.ReadDir(path)
208         if err != nil {
209                 // cmd/distpack deletes GOROOT/test, so skip the test if it isn't present.
210                 // cmd/distpack also requires GOROOT/VERSION to exist, so use that to
211                 // suppress false-positive skips.
212                 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "test")); os.IsNotExist(err) {
213                         if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil {
214                                 t.Skipf("skipping: GOROOT/test not present")
215                         }
216                 }
217                 t.Fatal(err)
218         }
219
220         excluded := make(map[string]bool)
221         for _, filename := range ignore {
222                 excluded[filename] = true
223         }
224
225         for _, f := range files {
226                 // filter directory contents
227                 if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] {
228                         continue
229                 }
230
231                 // get per-file instructions
232                 expectErrors := false
233                 filename := filepath.Join(path, f.Name())
234                 goVersion := ""
235                 if comment := firstComment(filename); comment != "" {
236                         if strings.Contains(comment, "-goexperiment") {
237                                 continue // ignore this file
238                         }
239                         fields := strings.Fields(comment)
240                         switch fields[0] {
241                         case "skip", "compiledir":
242                                 continue // ignore this file
243                         case "errorcheck":
244                                 expectErrors = true
245                                 for _, arg := range fields[1:] {
246                                         if arg == "-0" || arg == "-+" || arg == "-std" {
247                                                 // Marked explicitly as not expecting errors (-0),
248                                                 // or marked as compiling runtime/stdlib, which is only done
249                                                 // to trigger runtime/stdlib-only error output.
250                                                 // In both cases, the code should typecheck.
251                                                 expectErrors = false
252                                                 break
253                                         }
254                                         const prefix = "-lang="
255                                         if strings.HasPrefix(arg, prefix) {
256                                                 goVersion = arg[len(prefix):]
257                                         }
258                                 }
259                         }
260                 }
261
262                 // parse and type-check file
263                 if testing.Verbose() {
264                         fmt.Println("\t", filename)
265                 }
266                 file, err := syntax.ParseFile(filename, nil, nil, 0)
267                 if err == nil {
268                         conf := Config{
269                                 GoVersion: goVersion,
270                                 Importer:  stdLibImporter,
271                         }
272                         _, err = conf.Check(filename, []*syntax.File{file}, nil)
273                 }
274
275                 if expectErrors {
276                         if err == nil {
277                                 t.Errorf("expected errors but found none in %s", filename)
278                         }
279                 } else {
280                         if err != nil {
281                                 t.Error(err)
282                         }
283                 }
284         }
285 }
286
287 func TestStdTest(t *testing.T) {
288         testenv.MustHaveGoBuild(t)
289
290         if testing.Short() && testenv.Builder() == "" {
291                 t.Skip("skipping in short mode")
292         }
293
294         testTestDir(t, filepath.Join(testenv.GOROOT(t), "test"),
295                 "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
296                 "directive.go",   // tests compiler rejection of bad directive placement - ignore
297                 "directive2.go",  // tests compiler rejection of bad directive placement - ignore
298                 "embedfunc.go",   // tests //go:embed
299                 "embedvers.go",   // tests //go:embed
300                 "linkname2.go",   // types2 doesn't check validity of //go:xxx directives
301                 "linkname3.go",   // types2 doesn't check validity of //go:xxx directives
302         )
303 }
304
305 func TestStdFixed(t *testing.T) {
306         testenv.MustHaveGoBuild(t)
307
308         if testing.Short() && testenv.Builder() == "" {
309                 t.Skip("skipping in short mode")
310         }
311
312         testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "fixedbugs"),
313                 "bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore
314                 "issue6889.go",   // gc-specific test
315                 "issue11362.go",  // canonical import path check
316                 "issue16369.go",  // types2 handles this correctly - not an issue
317                 "issue18459.go",  // types2 doesn't check validity of //go:xxx directives
318                 "issue18882.go",  // types2 doesn't check validity of //go:xxx directives
319                 "issue20529.go",  // types2 does not have constraints on stack size
320                 "issue22200.go",  // types2 does not have constraints on stack size
321                 "issue22200b.go", // types2 does not have constraints on stack size
322                 "issue25507.go",  // types2 does not have constraints on stack size
323                 "issue20780.go",  // types2 does not have constraints on stack size
324                 "issue42058a.go", // types2 does not have constraints on channel element size
325                 "issue42058b.go", // types2 does not have constraints on channel element size
326                 "issue48097.go",  // go/types doesn't check validity of //go:xxx directives, and non-init bodyless function
327                 "issue48230.go",  // go/types doesn't check validity of //go:xxx directives
328                 "issue49767.go",  // go/types does not have constraints on channel element size
329                 "issue49814.go",  // go/types does not have constraints on array size
330                 "issue56103.go",  // anonymous interface cycles; will be a type checker error in 1.22
331                 "issue52697.go",  // types2 does not have constraints on stack size
332
333                 // These tests requires runtime/cgo.Incomplete, which is only available on some platforms.
334                 // However, types2 does not know about build constraints.
335                 "bug514.go",
336                 "issue40954.go",
337                 "issue42032.go",
338                 "issue42076.go",
339                 "issue46903.go",
340                 "issue51733.go",
341                 "notinheap2.go",
342                 "notinheap3.go",
343         )
344 }
345
346 func TestStdKen(t *testing.T) {
347         testenv.MustHaveGoBuild(t)
348
349         testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "ken"))
350 }
351
352 // Package paths of excluded packages.
353 var excluded = map[string]bool{
354         "builtin": true,
355
356         // go.dev/issue/46027: some imports are missing for this submodule.
357         "crypto/internal/edwards25519/field/_asm": true,
358         "crypto/internal/bigmod/_asm":             true,
359 }
360
361 // printPackageMu synchronizes the printing of type-checked package files in
362 // the typecheckFiles function.
363 //
364 // Without synchronization, package files may be interleaved during concurrent
365 // type-checking.
366 var printPackageMu sync.Mutex
367
368 // typecheckFiles typechecks the given package files.
369 func typecheckFiles(path string, filenames []string, importer Importer) (*Package, error) {
370         // Parse package files.
371         var files []*syntax.File
372         for _, filename := range filenames {
373                 var errs []error
374                 errh := func(err error) { errs = append(errs, err) }
375                 file, err := syntax.ParseFile(filename, errh, nil, 0)
376                 if err != nil {
377                         return nil, errors.Join(errs...)
378                 }
379
380                 files = append(files, file)
381         }
382
383         if testing.Verbose() {
384                 printPackageMu.Lock()
385                 fmt.Println("package", files[0].PkgName.Value)
386                 for _, filename := range filenames {
387                         fmt.Println("\t", filename)
388                 }
389                 printPackageMu.Unlock()
390         }
391
392         // Typecheck package files.
393         var errs []error
394         conf := Config{
395                 Error: func(err error) {
396                         errs = append(errs, err)
397                 },
398                 Importer: importer,
399         }
400         info := Info{Uses: make(map[*syntax.Name]Object)}
401         pkg, _ := conf.Check(path, files, &info)
402         err := errors.Join(errs...)
403         if err != nil {
404                 return pkg, err
405         }
406
407         // Perform checks of API invariants.
408
409         // All Objects have a package, except predeclared ones.
410         errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error
411         for id, obj := range info.Uses {
412                 predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
413                 if predeclared == (obj.Pkg() != nil) {
414                         posn := id.Pos()
415                         if predeclared {
416                                 return nil, fmt.Errorf("%s: predeclared object with package: %s", posn, obj)
417                         } else {
418                                 return nil, fmt.Errorf("%s: user-defined object without package: %s", posn, obj)
419                         }
420                 }
421         }
422
423         return pkg, nil
424 }
425
426 // pkgFilenames returns the list of package filenames for the given directory.
427 func pkgFilenames(dir string, includeTest bool) ([]string, error) {
428         ctxt := build.Default
429         ctxt.CgoEnabled = false
430         pkg, err := ctxt.ImportDir(dir, 0)
431         if err != nil {
432                 if _, nogo := err.(*build.NoGoError); nogo {
433                         return nil, nil // no *.go files, not an error
434                 }
435                 return nil, err
436         }
437         if excluded[pkg.ImportPath] {
438                 return nil, nil
439         }
440         var filenames []string
441         for _, name := range pkg.GoFiles {
442                 filenames = append(filenames, filepath.Join(pkg.Dir, name))
443         }
444         if includeTest {
445                 for _, name := range pkg.TestGoFiles {
446                         filenames = append(filenames, filepath.Join(pkg.Dir, name))
447                 }
448         }
449         return filenames, nil
450 }
451
452 func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...interface{})) {
453         w := walker{pkgh, errh}
454         w.walk(dir)
455 }
456
457 type walker struct {
458         pkgh func(dir string, filenames []string)
459         errh func(args ...any)
460 }
461
462 func (w *walker) walk(dir string) {
463         files, err := os.ReadDir(dir)
464         if err != nil {
465                 w.errh(err)
466                 return
467         }
468
469         // apply pkgh to the files in directory dir
470
471         // Don't get test files as these packages are imported.
472         pkgFiles, err := pkgFilenames(dir, false)
473         if err != nil {
474                 w.errh(err)
475                 return
476         }
477         if pkgFiles != nil {
478                 w.pkgh(dir, pkgFiles)
479         }
480
481         // traverse subdirectories, but don't walk into testdata
482         for _, f := range files {
483                 if f.IsDir() && f.Name() != "testdata" {
484                         w.walk(filepath.Join(dir, f.Name()))
485                 }
486         }
487 }