| // Copyright 2013 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // This file tests types.Check by using it to |
| // typecheck the standard library and tests. |
| |
| package types_test |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/build" |
| "go/importer" |
| "go/parser" |
| "go/scanner" |
| "go/token" |
| "internal/testenv" |
| "os" |
| "path/filepath" |
| "runtime" |
| "strings" |
| "testing" |
| "time" |
| |
| . "go/types" |
| ) |
| |
| // The cmd/*/internal packages may have been deleted as part of a binary |
| // release. Import from source instead. |
| // |
| // (See https://golang.org/issue/43232 and |
| // https://github.com/golang/build/blob/df58bbac082bc87c4a3cdfe336d1ffe60bbaa916/cmd/release/release.go#L533-L545.) |
| // |
| // Use the same importer for all std lib tests to |
| // avoid repeated importing of the same packages. |
| var stdLibImporter = importer.ForCompiler(token.NewFileSet(), "source", nil) |
| |
| func TestStdlib(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| |
| pkgCount := 0 |
| duration := walkPkgDirs(filepath.Join(runtime.GOROOT(), "src"), func(dir string, filenames []string) { |
| typecheck(t, dir, filenames) |
| pkgCount++ |
| }, t.Error) |
| |
| if testing.Verbose() { |
| fmt.Println(pkgCount, "packages typechecked in", duration) |
| } |
| } |
| |
| // firstComment returns the contents of the first non-empty comment in |
| // the given file, "skip", or the empty string. No matter the present |
| // comments, if any of them contains a build tag, the result is always |
| // "skip". Only comments before the "package" token and within the first |
| // 4K of the file are considered. |
| func firstComment(filename string) string { |
| f, err := os.Open(filename) |
| if err != nil { |
| return "" |
| } |
| defer f.Close() |
| |
| var src [4 << 10]byte // read at most 4KB |
| n, _ := f.Read(src[:]) |
| |
| var first string |
| var s scanner.Scanner |
| s.Init(fset.AddFile("", fset.Base(), n), src[:n], nil /* ignore errors */, scanner.ScanComments) |
| for { |
| _, tok, lit := s.Scan() |
| switch tok { |
| case token.COMMENT: |
| // remove trailing */ of multi-line comment |
| if lit[1] == '*' { |
| lit = lit[:len(lit)-2] |
| } |
| contents := strings.TrimSpace(lit[2:]) |
| if strings.HasPrefix(contents, "+build ") { |
| return "skip" |
| } |
| if first == "" { |
| first = contents // contents may be "" but that's ok |
| } |
| // continue as we may still see build tags |
| |
| case token.PACKAGE, token.EOF: |
| return first |
| } |
| } |
| } |
| |
| func testTestDir(t *testing.T, path string, ignore ...string) { |
| t.Skip("skipping for gccgo") |
| |
| files, err := os.ReadDir(path) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| excluded := make(map[string]bool) |
| for _, filename := range ignore { |
| excluded[filename] = true |
| } |
| |
| fset := token.NewFileSet() |
| for _, f := range files { |
| // filter directory contents |
| if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] { |
| continue |
| } |
| |
| // get per-file instructions |
| expectErrors := false |
| filename := filepath.Join(path, f.Name()) |
| goVersion := "" |
| if comment := firstComment(filename); comment != "" { |
| fields := strings.Fields(comment) |
| switch fields[0] { |
| case "skip", "compiledir": |
| continue // ignore this file |
| case "errorcheck": |
| expectErrors = true |
| for _, arg := range fields[1:] { |
| if arg == "-0" || arg == "-+" || arg == "-std" { |
| // Marked explicitly as not expecting errors (-0), |
| // or marked as compiling runtime/stdlib, which is only done |
| // to trigger runtime/stdlib-only error output. |
| // In both cases, the code should typecheck. |
| expectErrors = false |
| break |
| } |
| const prefix = "-lang=" |
| if strings.HasPrefix(arg, prefix) { |
| goVersion = arg[len(prefix):] |
| } |
| } |
| } |
| } |
| |
| // parse and type-check file |
| file, err := parser.ParseFile(fset, filename, nil, 0) |
| if err == nil { |
| conf := Config{Importer: stdLibImporter} |
| SetGoVersion(&conf, goVersion) |
| _, err = conf.Check(filename, fset, []*ast.File{file}, nil) |
| } |
| |
| if expectErrors { |
| if err == nil { |
| t.Errorf("expected errors but found none in %s", filename) |
| } |
| } else { |
| if err != nil { |
| t.Error(err) |
| } |
| } |
| } |
| } |
| |
| func TestStdTest(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| |
| if testing.Short() && testenv.Builder() == "" { |
| t.Skip("skipping in short mode") |
| } |
| |
| testTestDir(t, filepath.Join(runtime.GOROOT(), "test"), |
| "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore |
| "directive.go", // tests compiler rejection of bad directive placement - ignore |
| "embedfunc.go", // tests //go:embed |
| "embedvers.go", // tests //go:embed |
| "linkname2.go", // go/types doesn't check validity of //go:xxx directives |
| ) |
| } |
| |
| func TestStdFixed(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| |
| if testing.Short() && testenv.Builder() == "" { |
| t.Skip("skipping in short mode") |
| } |
| |
| testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "fixedbugs"), |
| "bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore |
| "issue6889.go", // gc-specific test |
| "issue11362.go", // canonical import path check |
| "issue16369.go", // go/types handles this correctly - not an issue |
| "issue18459.go", // go/types doesn't check validity of //go:xxx directives |
| "issue18882.go", // go/types doesn't check validity of //go:xxx directives |
| "issue20529.go", // go/types does not have constraints on stack size |
| "issue22200.go", // go/types does not have constraints on stack size |
| "issue22200b.go", // go/types does not have constraints on stack size |
| "issue25507.go", // go/types does not have constraints on stack size |
| "issue20780.go", // go/types does not have constraints on stack size |
| "bug251.go", // issue #34333 which was exposed with fix for #34151 |
| "issue42058a.go", // go/types does not have constraints on channel element size |
| "issue42058b.go", // go/types does not have constraints on channel element size |
| ) |
| } |
| |
| func TestStdKen(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| |
| testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "ken")) |
| } |
| |
| // Package paths of excluded packages. |
| var excluded = map[string]bool{ |
| "builtin": true, |
| |
| // See #46027: some imports are missing for this submodule. |
| "crypto/ed25519/internal/edwards25519/field/_asm": true, |
| } |
| |
| // typecheck typechecks the given package files. |
| func typecheck(t *testing.T, path string, filenames []string) { |
| t.Skip("skipping for gccgo") |
| |
| fset := token.NewFileSet() |
| |
| // parse package files |
| var files []*ast.File |
| for _, filename := range filenames { |
| file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) |
| if err != nil { |
| // the parser error may be a list of individual errors; report them all |
| if list, ok := err.(scanner.ErrorList); ok { |
| for _, err := range list { |
| t.Error(err) |
| } |
| return |
| } |
| t.Error(err) |
| return |
| } |
| |
| if testing.Verbose() { |
| if len(files) == 0 { |
| fmt.Println("package", file.Name.Name) |
| } |
| fmt.Println("\t", filename) |
| } |
| |
| files = append(files, file) |
| } |
| |
| // typecheck package files |
| conf := Config{ |
| Error: func(err error) { t.Error(err) }, |
| Importer: stdLibImporter, |
| } |
| info := Info{Uses: make(map[*ast.Ident]Object)} |
| conf.Check(path, fset, files, &info) |
| |
| // Perform checks of API invariants. |
| |
| // All Objects have a package, except predeclared ones. |
| errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error |
| for id, obj := range info.Uses { |
| predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError |
| if predeclared == (obj.Pkg() != nil) { |
| posn := fset.Position(id.Pos()) |
| if predeclared { |
| t.Errorf("%s: predeclared object with package: %s", posn, obj) |
| } else { |
| t.Errorf("%s: user-defined object without package: %s", posn, obj) |
| } |
| } |
| } |
| } |
| |
| // pkgFilenames returns the list of package filenames for the given directory. |
| func pkgFilenames(dir string) ([]string, error) { |
| ctxt := build.Default |
| ctxt.CgoEnabled = false |
| pkg, err := ctxt.ImportDir(dir, 0) |
| if err != nil { |
| if _, nogo := err.(*build.NoGoError); nogo { |
| return nil, nil // no *.go files, not an error |
| } |
| return nil, err |
| } |
| if excluded[pkg.ImportPath] { |
| return nil, nil |
| } |
| var filenames []string |
| for _, name := range pkg.GoFiles { |
| filenames = append(filenames, filepath.Join(pkg.Dir, name)) |
| } |
| for _, name := range pkg.TestGoFiles { |
| filenames = append(filenames, filepath.Join(pkg.Dir, name)) |
| } |
| return filenames, nil |
| } |
| |
| func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...interface{})) time.Duration { |
| w := walker{time.Now(), 10 * time.Millisecond, pkgh, errh} |
| w.walk(dir) |
| return time.Since(w.start) |
| } |
| |
| type walker struct { |
| start time.Time |
| dmax time.Duration |
| pkgh func(dir string, filenames []string) |
| errh func(args ...interface{}) |
| } |
| |
| func (w *walker) walk(dir string) { |
| // limit run time for short tests |
| if testing.Short() && time.Since(w.start) >= w.dmax { |
| return |
| } |
| |
| files, err := os.ReadDir(dir) |
| if err != nil { |
| w.errh(err) |
| return |
| } |
| |
| // apply pkgh to the files in directory dir |
| // but ignore files directly under $GOROOT/src (might be temporary test files). |
| if dir != filepath.Join(runtime.GOROOT(), "src") { |
| files, err := pkgFilenames(dir) |
| if err != nil { |
| w.errh(err) |
| return |
| } |
| if files != nil { |
| w.pkgh(dir, files) |
| } |
| } |
| |
| // traverse subdirectories, but don't walk into testdata |
| for _, f := range files { |
| if f.IsDir() && f.Name() != "testdata" { |
| w.walk(filepath.Join(dir, f.Name())) |
| } |
| } |
| } |