github.com/AndrienkoAleksandr/go@v0.0.19/src/go/types/stdlib_test.go (about) 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 types.Check by using it to 6 // typecheck the standard library and tests. 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 // The cmd/*/internal packages may have been deleted as part of a binary 32 // release. Import from source instead. 33 // 34 // (See https://golang.org/issue/43232 and 35 // https://github.com/golang/build/blob/df58bbac082bc87c4a3cdfe336d1ffe60bbaa916/cmd/release/release.go#L533-L545.) 36 // 37 // Use the same importer for all std lib tests to 38 // avoid repeated importing of the same packages. 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 // Collect non-test files. 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 // Though we read files while parsing, type-checking is otherwise CPU bound. 63 // 64 // This doesn't achieve great CPU utilization as many packages may block 65 // waiting for a common import, but in combination with the non-deterministic 66 // map iteration below this should provide decent coverage of concurrent 67 // type-checking (see golang/go#47729). 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 // stdlibChecker implements concurrent type-checking of the packages defined by 97 // dirFiles, which must define a closed set of packages (such as GOROOT/src). 98 type stdlibChecker struct { 99 dirFiles map[string][]string // non-test files per directory; must be pre-populated 100 101 mu sync.Mutex 102 pkgs map[string]*futurePackage // future cache of type-checking results 103 } 104 105 // A futurePackage is a future result of type-checking. 106 type futurePackage struct { 107 done chan struct{} // guards pkg and err 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 // unsafe cannot be type checked normally. 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 // As long as pkg is non-nil, avoid redundant errors related to failed 130 // imports. TestStdlib will collect errors once for each package. 131 return pkg, nil 132 } 133 return nil, err 134 } 135 136 // getDirPackage gets the package defined in dir from the future cache. 137 // 138 // If this is the first goroutine requesting the package, getDirPackage 139 // type-checks. 140 func (c *stdlibChecker) getDirPackage(dir string) (*Package, error) { 141 c.mu.Lock() 142 fut, ok := c.pkgs[dir] 143 if !ok { 144 // First request for this package dir; type check. 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 // Using dir as the package path here may be inconsistent with the behavior 155 // of a normal importer, but is sufficient as dir is by construction unique 156 // to this package. 157 fut.pkg, fut.err = typecheckFiles(dir, files, c) 158 } 159 close(fut.done) 160 } else { 161 // Otherwise, await the result. 162 c.mu.Unlock() 163 <-fut.done 164 } 165 return fut.pkg, fut.err 166 } 167 168 // firstComment returns the contents of the first non-empty comment in 169 // the given file, "skip", or the empty string. No matter the present 170 // comments, if any of them contains a build tag, the result is always 171 // "skip". Only comments before the "package" token and within the first 172 // 4K of the file are considered. 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 // read at most 4KB 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 /* ignore errors */, scanner.ScanComments) 186 for { 187 _, tok, lit := s.Scan() 188 switch tok { 189 case token.COMMENT: 190 // remove trailing */ of multi-line comment 191 if lit[1] == '*' { 192 lit = lit[:len(lit)-2] 193 } 194 contents := strings.TrimSpace(lit[2:]) 195 if strings.HasPrefix(contents, "+build ") { 196 return "skip" 197 } 198 if first == "" { 199 first = contents // contents may be "" but that's ok 200 } 201 // continue as we may still see build tags 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 // cmd/distpack deletes GOROOT/test, so skip the test if it isn't present. 213 // cmd/distpack also requires GOROOT/VERSION to exist, so use that to 214 // suppress false-positive skips. 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 // filter directory contents 231 if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] { 232 continue 233 } 234 235 // get per-file instructions 236 expectErrors := false 237 filename := filepath.Join(path, f.Name()) 238 goVersion := "" 239 if comment := firstComment(filename); comment != "" { 240 fields := strings.Fields(comment) 241 switch fields[0] { 242 case "skip", "compiledir": 243 continue // ignore this file 244 case "errorcheck": 245 expectErrors = true 246 for _, arg := range fields[1:] { 247 if arg == "-0" || arg == "-+" || arg == "-std" { 248 // Marked explicitly as not expecting errors (-0), 249 // or marked as compiling runtime/stdlib, which is only done 250 // to trigger runtime/stdlib-only error output. 251 // In both cases, the code should typecheck. 252 expectErrors = false 253 break 254 } 255 const prefix = "-lang=" 256 if strings.HasPrefix(arg, prefix) { 257 goVersion = arg[len(prefix):] 258 } 259 } 260 } 261 } 262 263 // parse and type-check file 264 file, err := parser.ParseFile(fset, filename, nil, 0) 265 if err == nil { 266 conf := Config{ 267 GoVersion: goVersion, 268 Importer: stdLibImporter, 269 } 270 _, err = conf.Check(filename, fset, []*ast.File{file}, nil) 271 } 272 273 if expectErrors { 274 if err == nil { 275 t.Errorf("expected errors but found none in %s", filename) 276 } 277 } else { 278 if err != nil { 279 t.Error(err) 280 } 281 } 282 } 283 } 284 285 func TestStdTest(t *testing.T) { 286 testenv.MustHaveGoBuild(t) 287 288 if testing.Short() && testenv.Builder() == "" { 289 t.Skip("skipping in short mode") 290 } 291 292 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test"), 293 "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore 294 "directive.go", // tests compiler rejection of bad directive placement - ignore 295 "directive2.go", // tests compiler rejection of bad directive placement - ignore 296 "embedfunc.go", // tests //go:embed 297 "embedvers.go", // tests //go:embed 298 "linkname2.go", // go/types doesn't check validity of //go:xxx directives 299 "linkname3.go", // go/types doesn't check validity of //go:xxx directives 300 ) 301 } 302 303 func TestStdFixed(t *testing.T) { 304 testenv.MustHaveGoBuild(t) 305 306 if testing.Short() && testenv.Builder() == "" { 307 t.Skip("skipping in short mode") 308 } 309 310 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "fixedbugs"), 311 "bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore 312 "issue6889.go", // gc-specific test 313 "issue11362.go", // canonical import path check 314 "issue16369.go", // go/types handles this correctly - not an issue 315 "issue18459.go", // go/types doesn't check validity of //go:xxx directives 316 "issue18882.go", // go/types doesn't check validity of //go:xxx directives 317 "issue20529.go", // go/types does not have constraints on stack size 318 "issue22200.go", // go/types does not have constraints on stack size 319 "issue22200b.go", // go/types does not have constraints on stack size 320 "issue25507.go", // go/types does not have constraints on stack size 321 "issue20780.go", // go/types does not have constraints on stack size 322 "bug251.go", // go.dev/issue/34333 which was exposed with fix for go.dev/issue/34151 323 "issue42058a.go", // go/types does not have constraints on channel element size 324 "issue42058b.go", // go/types does not have constraints on channel element size 325 "issue48097.go", // go/types doesn't check validity of //go:xxx directives, and non-init bodyless function 326 "issue48230.go", // go/types doesn't check validity of //go:xxx directives 327 "issue49767.go", // go/types does not have constraints on channel element size 328 "issue49814.go", // go/types does not have constraints on array size 329 "issue56103.go", // anonymous interface cycles; will be a type checker error in 1.22 330 331 // These tests requires runtime/cgo.Incomplete, which is only available on some platforms. 332 // However, go/types does not know about build constraints. 333 "bug514.go", 334 "issue40954.go", 335 "issue42032.go", 336 "issue42076.go", 337 "issue46903.go", 338 "issue51733.go", 339 "notinheap2.go", 340 "notinheap3.go", 341 ) 342 } 343 344 func TestStdKen(t *testing.T) { 345 testenv.MustHaveGoBuild(t) 346 347 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "ken")) 348 } 349 350 // Package paths of excluded packages. 351 var excluded = map[string]bool{ 352 "builtin": true, 353 354 // See go.dev/issue/46027: some imports are missing for this submodule. 355 "crypto/internal/edwards25519/field/_asm": true, 356 "crypto/internal/bigmod/_asm": true, 357 } 358 359 // printPackageMu synchronizes the printing of type-checked package files in 360 // the typecheckFiles function. 361 // 362 // Without synchronization, package files may be interleaved during concurrent 363 // type-checking. 364 var printPackageMu sync.Mutex 365 366 // typecheckFiles typechecks the given package files. 367 func typecheckFiles(path string, filenames []string, importer Importer) (*Package, error) { 368 fset := token.NewFileSet() 369 370 // Parse package files. 371 var files []*ast.File 372 for _, filename := range filenames { 373 file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) 374 if err != nil { 375 return nil, err 376 } 377 378 files = append(files, file) 379 } 380 381 if testing.Verbose() { 382 printPackageMu.Lock() 383 fmt.Println("package", files[0].Name.Name) 384 for _, filename := range filenames { 385 fmt.Println("\t", filename) 386 } 387 printPackageMu.Unlock() 388 } 389 390 // Typecheck package files. 391 var errs []error 392 conf := Config{ 393 Error: func(err error) { 394 errs = append(errs, err) 395 }, 396 Importer: importer, 397 } 398 info := Info{Uses: make(map[*ast.Ident]Object)} 399 pkg, _ := conf.Check(path, fset, files, &info) 400 err := errors.Join(errs...) 401 if err != nil { 402 return pkg, err 403 } 404 405 // Perform checks of API invariants. 406 407 // All Objects have a package, except predeclared ones. 408 errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error 409 for id, obj := range info.Uses { 410 predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError 411 if predeclared == (obj.Pkg() != nil) { 412 posn := fset.Position(id.Pos()) 413 if predeclared { 414 return nil, fmt.Errorf("%s: predeclared object with package: %s", posn, obj) 415 } else { 416 return nil, fmt.Errorf("%s: user-defined object without package: %s", posn, obj) 417 } 418 } 419 } 420 421 return pkg, nil 422 } 423 424 // pkgFilenames returns the list of package filenames for the given directory. 425 func pkgFilenames(dir string, includeTest bool) ([]string, error) { 426 ctxt := build.Default 427 ctxt.CgoEnabled = false 428 pkg, err := ctxt.ImportDir(dir, 0) 429 if err != nil { 430 if _, nogo := err.(*build.NoGoError); nogo { 431 return nil, nil // no *.go files, not an error 432 } 433 return nil, err 434 } 435 if excluded[pkg.ImportPath] { 436 return nil, nil 437 } 438 var filenames []string 439 for _, name := range pkg.GoFiles { 440 filenames = append(filenames, filepath.Join(pkg.Dir, name)) 441 } 442 if includeTest { 443 for _, name := range pkg.TestGoFiles { 444 filenames = append(filenames, filepath.Join(pkg.Dir, name)) 445 } 446 } 447 return filenames, nil 448 } 449 450 func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...any)) { 451 w := walker{pkgh, errh} 452 w.walk(dir) 453 } 454 455 type walker struct { 456 pkgh func(dir string, filenames []string) 457 errh func(args ...any) 458 } 459 460 func (w *walker) walk(dir string) { 461 files, err := os.ReadDir(dir) 462 if err != nil { 463 w.errh(err) 464 return 465 } 466 467 // apply pkgh to the files in directory dir 468 469 // Don't get test files as these packages are imported. 470 pkgFiles, err := pkgFilenames(dir, false) 471 if err != nil { 472 w.errh(err) 473 return 474 } 475 if pkgFiles != nil { 476 w.pkgh(dir, pkgFiles) 477 } 478 479 // traverse subdirectories, but don't walk into testdata 480 for _, f := range files { 481 if f.IsDir() && f.Name() != "testdata" { 482 w.walk(filepath.Join(dir, f.Name())) 483 } 484 } 485 }