Source file src/go/types/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 types.Check by using it to
     6  // typecheck the standard library and tests.
     7  
     8  package types_test
     9  
    10  import (
    11  	"fmt"
    12  	"go/ast"
    13  	"go/build"
    14  	"go/importer"
    15  	"go/parser"
    16  	"go/scanner"
    17  	"go/token"
    18  	"internal/testenv"
    19  	"os"
    20  	"path/filepath"
    21  	"runtime"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	. "go/types"
    27  )
    28  
    29  // The cmd/*/internal packages may have been deleted as part of a binary
    30  // release. Import from source instead.
    31  //
    32  // (See https://golang.org/issue/43232 and
    33  // https://github.com/golang/build/blob/df58bbac082bc87c4a3cdfe336d1ffe60bbaa916/cmd/release/release.go#L533-L545.)
    34  //
    35  // Use the same importer for all std lib tests to
    36  // avoid repeated importing of the same packages.
    37  var stdLibImporter = importer.ForCompiler(token.NewFileSet(), "source", nil)
    38  
    39  func TestStdlib(t *testing.T) {
    40  	testenv.MustHaveGoBuild(t)
    41  
    42  	pkgCount := 0
    43  	duration := walkPkgDirs(filepath.Join(runtime.GOROOT(), "src"), func(dir string, filenames []string) {
    44  		typecheck(t, dir, filenames)
    45  		pkgCount++
    46  	}, t.Error)
    47  
    48  	if testing.Verbose() {
    49  		fmt.Println(pkgCount, "packages typechecked in", duration)
    50  	}
    51  }
    52  
    53  // firstComment returns the contents of the first non-empty comment in
    54  // the given file, "skip", or the empty string. No matter the present
    55  // comments, if any of them contains a build tag, the result is always
    56  // "skip". Only comments before the "package" token and within the first
    57  // 4K of the file are considered.
    58  func firstComment(filename string) string {
    59  	f, err := os.Open(filename)
    60  	if err != nil {
    61  		return ""
    62  	}
    63  	defer f.Close()
    64  
    65  	var src [4 << 10]byte // read at most 4KB
    66  	n, _ := f.Read(src[:])
    67  
    68  	var first string
    69  	var s scanner.Scanner
    70  	s.Init(fset.AddFile("", fset.Base(), n), src[:n], nil /* ignore errors */, scanner.ScanComments)
    71  	for {
    72  		_, tok, lit := s.Scan()
    73  		switch tok {
    74  		case token.COMMENT:
    75  			// remove trailing */ of multi-line comment
    76  			if lit[1] == '*' {
    77  				lit = lit[:len(lit)-2]
    78  			}
    79  			contents := strings.TrimSpace(lit[2:])
    80  			if strings.HasPrefix(contents, "+build ") {
    81  				return "skip"
    82  			}
    83  			if first == "" {
    84  				first = contents // contents may be "" but that's ok
    85  			}
    86  			// continue as we may still see build tags
    87  
    88  		case token.PACKAGE, token.EOF:
    89  			return first
    90  		}
    91  	}
    92  }
    93  
    94  func testTestDir(t *testing.T, path string, ignore ...string) {
    95  	files, err := os.ReadDir(path)
    96  	if err != nil {
    97  		t.Fatal(err)
    98  	}
    99  
   100  	excluded := make(map[string]bool)
   101  	for _, filename := range ignore {
   102  		excluded[filename] = true
   103  	}
   104  
   105  	fset := token.NewFileSet()
   106  	for _, f := range files {
   107  		// filter directory contents
   108  		if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] {
   109  			continue
   110  		}
   111  
   112  		// get per-file instructions
   113  		expectErrors := false
   114  		filename := filepath.Join(path, f.Name())
   115  		goVersion := ""
   116  		if comment := firstComment(filename); comment != "" {
   117  			fields := strings.Fields(comment)
   118  			switch fields[0] {
   119  			case "skip", "compiledir":
   120  				continue // ignore this file
   121  			case "errorcheck":
   122  				expectErrors = true
   123  				for _, arg := range fields[1:] {
   124  					if arg == "-0" || arg == "-+" || arg == "-std" {
   125  						// Marked explicitly as not expecting errors (-0),
   126  						// or marked as compiling runtime/stdlib, which is only done
   127  						// to trigger runtime/stdlib-only error output.
   128  						// In both cases, the code should typecheck.
   129  						expectErrors = false
   130  						break
   131  					}
   132  					const prefix = "-lang="
   133  					if strings.HasPrefix(arg, prefix) {
   134  						goVersion = arg[len(prefix):]
   135  					}
   136  				}
   137  			}
   138  		}
   139  
   140  		// parse and type-check file
   141  		file, err := parser.ParseFile(fset, filename, nil, 0)
   142  		if err == nil {
   143  			conf := Config{GoVersion: goVersion, Importer: stdLibImporter}
   144  			_, err = conf.Check(filename, fset, []*ast.File{file}, nil)
   145  		}
   146  
   147  		if expectErrors {
   148  			if err == nil {
   149  				t.Errorf("expected errors but found none in %s", filename)
   150  			}
   151  		} else {
   152  			if err != nil {
   153  				t.Error(err)
   154  			}
   155  		}
   156  	}
   157  }
   158  
   159  func TestStdTest(t *testing.T) {
   160  	testenv.MustHaveGoBuild(t)
   161  
   162  	if testing.Short() && testenv.Builder() == "" {
   163  		t.Skip("skipping in short mode")
   164  	}
   165  
   166  	testTestDir(t, filepath.Join(runtime.GOROOT(), "test"),
   167  		"cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
   168  		"directive.go",   // tests compiler rejection of bad directive placement - ignore
   169  		"directive2.go",  // tests compiler rejection of bad directive placement - ignore
   170  		"embedfunc.go",   // tests //go:embed
   171  		"embedvers.go",   // tests //go:embed
   172  		"linkname2.go",   // go/types doesn't check validity of //go:xxx directives
   173  		"linkname3.go",   // go/types doesn't check validity of //go:xxx directives
   174  	)
   175  }
   176  
   177  func TestStdFixed(t *testing.T) {
   178  	testenv.MustHaveGoBuild(t)
   179  
   180  	if testing.Short() && testenv.Builder() == "" {
   181  		t.Skip("skipping in short mode")
   182  	}
   183  
   184  	testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "fixedbugs"),
   185  		"bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore
   186  		"issue6889.go",   // gc-specific test
   187  		"issue11362.go",  // canonical import path check
   188  		"issue16369.go",  // go/types handles this correctly - not an issue
   189  		"issue18459.go",  // go/types doesn't check validity of //go:xxx directives
   190  		"issue18882.go",  // go/types doesn't check validity of //go:xxx directives
   191  		"issue20529.go",  // go/types does not have constraints on stack size
   192  		"issue22200.go",  // go/types does not have constraints on stack size
   193  		"issue22200b.go", // go/types does not have constraints on stack size
   194  		"issue25507.go",  // go/types does not have constraints on stack size
   195  		"issue20780.go",  // go/types does not have constraints on stack size
   196  		"bug251.go",      // issue #34333 which was exposed with fix for #34151
   197  		"issue42058a.go", // go/types does not have constraints on channel element size
   198  		"issue42058b.go", // go/types does not have constraints on channel element size
   199  		"issue48097.go",  // go/types doesn't check validity of //go:xxx directives, and non-init bodyless function
   200  		"issue48230.go",  // go/types doesn't check validity of //go:xxx directives
   201  		"issue49767.go",  // go/types does not have constraints on channel element size
   202  		"issue49814.go",  // go/types does not have constraints on array size
   203  	)
   204  }
   205  
   206  func TestStdKen(t *testing.T) {
   207  	testenv.MustHaveGoBuild(t)
   208  
   209  	testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "ken"))
   210  }
   211  
   212  // Package paths of excluded packages.
   213  var excluded = map[string]bool{
   214  	"builtin": true,
   215  
   216  	// See #46027: some imports are missing for this submodule.
   217  	"crypto/ed25519/internal/edwards25519/field/_asm": true,
   218  }
   219  
   220  // typecheck typechecks the given package files.
   221  func typecheck(t *testing.T, path string, filenames []string) {
   222  	fset := token.NewFileSet()
   223  
   224  	// parse package files
   225  	var files []*ast.File
   226  	for _, filename := range filenames {
   227  		file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
   228  		if err != nil {
   229  			// the parser error may be a list of individual errors; report them all
   230  			if list, ok := err.(scanner.ErrorList); ok {
   231  				for _, err := range list {
   232  					t.Error(err)
   233  				}
   234  				return
   235  			}
   236  			t.Error(err)
   237  			return
   238  		}
   239  
   240  		if testing.Verbose() {
   241  			if len(files) == 0 {
   242  				fmt.Println("package", file.Name.Name)
   243  			}
   244  			fmt.Println("\t", filename)
   245  		}
   246  
   247  		files = append(files, file)
   248  	}
   249  
   250  	// typecheck package files
   251  	conf := Config{
   252  		Error:    func(err error) { t.Error(err) },
   253  		Importer: stdLibImporter,
   254  	}
   255  	info := Info{Uses: make(map[*ast.Ident]Object)}
   256  	conf.Check(path, fset, files, &info)
   257  
   258  	// Perform checks of API invariants.
   259  
   260  	// All Objects have a package, except predeclared ones.
   261  	errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error
   262  	for id, obj := range info.Uses {
   263  		predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
   264  		if predeclared == (obj.Pkg() != nil) {
   265  			posn := fset.Position(id.Pos())
   266  			if predeclared {
   267  				t.Errorf("%s: predeclared object with package: %s", posn, obj)
   268  			} else {
   269  				t.Errorf("%s: user-defined object without package: %s", posn, obj)
   270  			}
   271  		}
   272  	}
   273  }
   274  
   275  // pkgFilenames returns the list of package filenames for the given directory.
   276  func pkgFilenames(dir string) ([]string, error) {
   277  	ctxt := build.Default
   278  	ctxt.CgoEnabled = false
   279  	pkg, err := ctxt.ImportDir(dir, 0)
   280  	if err != nil {
   281  		if _, nogo := err.(*build.NoGoError); nogo {
   282  			return nil, nil // no *.go files, not an error
   283  		}
   284  		return nil, err
   285  	}
   286  	if excluded[pkg.ImportPath] {
   287  		return nil, nil
   288  	}
   289  	var filenames []string
   290  	for _, name := range pkg.GoFiles {
   291  		filenames = append(filenames, filepath.Join(pkg.Dir, name))
   292  	}
   293  	for _, name := range pkg.TestGoFiles {
   294  		filenames = append(filenames, filepath.Join(pkg.Dir, name))
   295  	}
   296  	return filenames, nil
   297  }
   298  
   299  func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...any)) time.Duration {
   300  	w := walker{time.Now(), 10 * time.Millisecond, pkgh, errh}
   301  	w.walk(dir)
   302  	return time.Since(w.start)
   303  }
   304  
   305  type walker struct {
   306  	start time.Time
   307  	dmax  time.Duration
   308  	pkgh  func(dir string, filenames []string)
   309  	errh  func(args ...any)
   310  }
   311  
   312  func (w *walker) walk(dir string) {
   313  	// limit run time for short tests
   314  	if testing.Short() && time.Since(w.start) >= w.dmax {
   315  		return
   316  	}
   317  
   318  	files, err := os.ReadDir(dir)
   319  	if err != nil {
   320  		w.errh(err)
   321  		return
   322  	}
   323  
   324  	// apply pkgh to the files in directory dir
   325  	// but ignore files directly under $GOROOT/src (might be temporary test files).
   326  	if dir != filepath.Join(runtime.GOROOT(), "src") {
   327  		files, err := pkgFilenames(dir)
   328  		if err != nil {
   329  			w.errh(err)
   330  			return
   331  		}
   332  		if files != nil {
   333  			w.pkgh(dir, files)
   334  		}
   335  	}
   336  
   337  	// traverse subdirectories, but don't walk into testdata
   338  	for _, f := range files {
   339  		if f.IsDir() && f.Name() != "testdata" {
   340  			w.walk(filepath.Join(dir, f.Name()))
   341  		}
   342  	}
   343  }
   344  

View as plain text