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