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

View as plain text