Source file src/go/types/stdlib_test.go

Documentation: go/types

     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  	"io/ioutil"
    20  	"os"
    21  	"path/filepath"
    22  	"runtime"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	. "go/types"
    28  )
    29  
    30  var (
    31  	pkgCount int // number of packages processed
    32  	start    time.Time
    33  
    34  	// Use the same importer for all std lib tests to
    35  	// avoid repeated importing of the same packages.
    36  	stdLibImporter = importer.Default()
    37  )
    38  
    39  func TestStdlib(t *testing.T) {
    40  	testenv.MustHaveGoBuild(t)
    41  
    42  	start = time.Now()
    43  	walkDirs(t, filepath.Join(runtime.GOROOT(), "src"))
    44  	if testing.Verbose() {
    45  		fmt.Println(pkgCount, "packages typechecked in", time.Since(start))
    46  	}
    47  }
    48  
    49  // firstComment returns the contents of the first non-empty comment in
    50  // the given file, "skip", or the empty string. No matter the present
    51  // comments, if any of them contains a build tag, the result is always
    52  // "skip". Only comments before the "package" token and within the first
    53  // 4K of the file are considered.
    54  func firstComment(filename string) string {
    55  	f, err := os.Open(filename)
    56  	if err != nil {
    57  		return ""
    58  	}
    59  	defer f.Close()
    60  
    61  	var src [4 << 10]byte // read at most 4KB
    62  	n, _ := f.Read(src[:])
    63  
    64  	var first string
    65  	var s scanner.Scanner
    66  	s.Init(fset.AddFile("", fset.Base(), n), src[:n], nil /* ignore errors */, scanner.ScanComments)
    67  	for {
    68  		_, tok, lit := s.Scan()
    69  		switch tok {
    70  		case token.COMMENT:
    71  			// remove trailing */ of multi-line comment
    72  			if lit[1] == '*' {
    73  				lit = lit[:len(lit)-2]
    74  			}
    75  			contents := strings.TrimSpace(lit[2:])
    76  			if strings.HasPrefix(contents, "+build ") {
    77  				return "skip"
    78  			}
    79  			if first == "" {
    80  				first = contents // contents may be "" but that's ok
    81  			}
    82  			// continue as we may still see build tags
    83  
    84  		case token.PACKAGE, token.EOF:
    85  			return first
    86  		}
    87  	}
    88  }
    89  
    90  func testTestDir(t *testing.T, path string, ignore ...string) {
    91  	files, err := ioutil.ReadDir(path)
    92  	if err != nil {
    93  		t.Fatal(err)
    94  	}
    95  
    96  	excluded := make(map[string]bool)
    97  	for _, filename := range ignore {
    98  		excluded[filename] = true
    99  	}
   100  
   101  	fset := token.NewFileSet()
   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  		if comment := firstComment(filename); comment != "" {
   112  			fields := strings.Fields(comment)
   113  			switch fields[0] {
   114  			case "skip", "compiledir":
   115  				continue // ignore this file
   116  			case "errorcheck":
   117  				expectErrors = true
   118  				for _, arg := range fields[1:] {
   119  					if arg == "-0" || arg == "-+" || arg == "-std" {
   120  						// Marked explicitly as not expected errors (-0),
   121  						// or marked as compiling runtime/stdlib, which is only done
   122  						// to trigger runtime/stdlib-only error output.
   123  						// In both cases, the code should typecheck.
   124  						expectErrors = false
   125  						break
   126  					}
   127  				}
   128  			}
   129  		}
   130  
   131  		// parse and type-check file
   132  		file, err := parser.ParseFile(fset, filename, nil, 0)
   133  		if err == nil {
   134  			conf := Config{Importer: stdLibImporter}
   135  			_, err = conf.Check(filename, fset, []*ast.File{file}, nil)
   136  		}
   137  
   138  		if expectErrors {
   139  			if err == nil {
   140  				t.Errorf("expected errors but found none in %s", filename)
   141  			}
   142  		} else {
   143  			if err != nil {
   144  				t.Error(err)
   145  			}
   146  		}
   147  	}
   148  }
   149  
   150  func TestStdTest(t *testing.T) {
   151  	testenv.MustHaveGoBuild(t)
   152  
   153  	if testing.Short() && testenv.Builder() == "" {
   154  		t.Skip("skipping in short mode")
   155  	}
   156  
   157  	testTestDir(t, filepath.Join(runtime.GOROOT(), "test"),
   158  		"cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
   159  	)
   160  }
   161  
   162  func TestStdFixed(t *testing.T) {
   163  	testenv.MustHaveGoBuild(t)
   164  
   165  	if testing.Short() && testenv.Builder() == "" {
   166  		t.Skip("skipping in short mode")
   167  	}
   168  
   169  	testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "fixedbugs"),
   170  		"bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore
   171  		"issue6889.go",   // gc-specific test
   172  		"issue7746.go",   // large constants - consumes too much memory
   173  		"issue11362.go",  // canonical import path check
   174  		"issue16369.go",  // go/types handles this correctly - not an issue
   175  		"issue18459.go",  // go/types doesn't check validity of //go:xxx directives
   176  		"issue18882.go",  // go/types doesn't check validity of //go:xxx directives
   177  		"issue20232.go",  // go/types handles larger constants than gc
   178  		"issue20529.go",  // go/types does not have constraints on stack size
   179  		"issue22200.go",  // go/types does not have constraints on stack size
   180  		"issue22200b.go", // go/types does not have constraints on stack size
   181  		"issue25507.go",  // go/types does not have constraints on stack size
   182  		"issue20780.go",  // go/types does not have constraints on stack size
   183  		"issue31747.go",  // go/types does not have constraints on language level (-lang=go1.12) (see #31793)
   184  	)
   185  }
   186  
   187  func TestStdKen(t *testing.T) {
   188  	testenv.MustHaveGoBuild(t)
   189  
   190  	testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "ken"))
   191  }
   192  
   193  // Package paths of excluded packages.
   194  var excluded = map[string]bool{
   195  	"builtin": true,
   196  }
   197  
   198  // typecheck typechecks the given package files.
   199  func typecheck(t *testing.T, path string, filenames []string) {
   200  	fset := token.NewFileSet()
   201  
   202  	// parse package files
   203  	var files []*ast.File
   204  	for _, filename := range filenames {
   205  		file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
   206  		if err != nil {
   207  			// the parser error may be a list of individual errors; report them all
   208  			if list, ok := err.(scanner.ErrorList); ok {
   209  				for _, err := range list {
   210  					t.Error(err)
   211  				}
   212  				return
   213  			}
   214  			t.Error(err)
   215  			return
   216  		}
   217  
   218  		if testing.Verbose() {
   219  			if len(files) == 0 {
   220  				fmt.Println("package", file.Name.Name)
   221  			}
   222  			fmt.Println("\t", filename)
   223  		}
   224  
   225  		files = append(files, file)
   226  	}
   227  
   228  	// typecheck package files
   229  	conf := Config{
   230  		Error:    func(err error) { t.Error(err) },
   231  		Importer: stdLibImporter,
   232  	}
   233  	info := Info{Uses: make(map[*ast.Ident]Object)}
   234  	conf.Check(path, fset, files, &info)
   235  	pkgCount++
   236  
   237  	// Perform checks of API invariants.
   238  
   239  	// All Objects have a package, except predeclared ones.
   240  	errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error
   241  	for id, obj := range info.Uses {
   242  		predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
   243  		if predeclared == (obj.Pkg() != nil) {
   244  			posn := fset.Position(id.Pos())
   245  			if predeclared {
   246  				t.Errorf("%s: predeclared object with package: %s", posn, obj)
   247  			} else {
   248  				t.Errorf("%s: user-defined object without package: %s", posn, obj)
   249  			}
   250  		}
   251  	}
   252  }
   253  
   254  // pkgFilenames returns the list of package filenames for the given directory.
   255  func pkgFilenames(dir string) ([]string, error) {
   256  	ctxt := build.Default
   257  	ctxt.CgoEnabled = false
   258  	pkg, err := ctxt.ImportDir(dir, 0)
   259  	if err != nil {
   260  		if _, nogo := err.(*build.NoGoError); nogo {
   261  			return nil, nil // no *.go files, not an error
   262  		}
   263  		return nil, err
   264  	}
   265  	if excluded[pkg.ImportPath] {
   266  		return nil, nil
   267  	}
   268  	var filenames []string
   269  	for _, name := range pkg.GoFiles {
   270  		filenames = append(filenames, filepath.Join(pkg.Dir, name))
   271  	}
   272  	for _, name := range pkg.TestGoFiles {
   273  		filenames = append(filenames, filepath.Join(pkg.Dir, name))
   274  	}
   275  	return filenames, nil
   276  }
   277  
   278  // Note: Could use filepath.Walk instead of walkDirs but that wouldn't
   279  //       necessarily be shorter or clearer after adding the code to
   280  //       terminate early for -short tests.
   281  
   282  func walkDirs(t *testing.T, dir string) {
   283  	// limit run time for short tests
   284  	if testing.Short() && time.Since(start) >= 10*time.Millisecond {
   285  		return
   286  	}
   287  
   288  	fis, err := ioutil.ReadDir(dir)
   289  	if err != nil {
   290  		t.Error(err)
   291  		return
   292  	}
   293  
   294  	// typecheck package in directory
   295  	// but ignore files directly under $GOROOT/src (might be temporary test files).
   296  	if dir != filepath.Join(runtime.GOROOT(), "src") {
   297  		files, err := pkgFilenames(dir)
   298  		if err != nil {
   299  			t.Error(err)
   300  			return
   301  		}
   302  		if files != nil {
   303  			typecheck(t, dir, files)
   304  		}
   305  	}
   306  
   307  	// traverse subdirectories, but don't walk into testdata
   308  	for _, fi := range fis {
   309  		if fi.IsDir() && fi.Name() != "testdata" {
   310  			walkDirs(t, filepath.Join(dir, fi.Name()))
   311  		}
   312  	}
   313  }
   314  

View as plain text