github.com/rsc/go@v0.0.0-20150416155037-e040fd465409/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  	"io/ioutil"
    19  	"os"
    20  	"path/filepath"
    21  	"runtime"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	. "go/types"
    27  )
    28  
    29  var (
    30  	pkgCount int // number of packages processed
    31  	start    time.Time
    32  
    33  	// Use the same importer for all std lib tests to
    34  	// avoid repeated importing of the same packages.
    35  	stdLibImporter = importer.Default()
    36  )
    37  
    38  func TestStdlib(t *testing.T) {
    39  	skipSpecialPlatforms(t)
    40  
    41  	start = time.Now()
    42  	walkDirs(t, filepath.Join(runtime.GOROOT(), "src"))
    43  	if testing.Verbose() {
    44  		fmt.Println(pkgCount, "packages typechecked in", time.Since(start))
    45  	}
    46  }
    47  
    48  // firstComment returns the contents of the first comment in
    49  // the given file, assuming there's one within the first KB.
    50  func firstComment(filename string) string {
    51  	f, err := os.Open(filename)
    52  	if err != nil {
    53  		return ""
    54  	}
    55  	defer f.Close()
    56  
    57  	var src [1 << 10]byte // read at most 1KB
    58  	n, _ := f.Read(src[:])
    59  
    60  	var s scanner.Scanner
    61  	s.Init(fset.AddFile("", fset.Base(), n), src[:n], nil, scanner.ScanComments)
    62  	for {
    63  		_, tok, lit := s.Scan()
    64  		switch tok {
    65  		case token.COMMENT:
    66  			// remove trailing */ of multi-line comment
    67  			if lit[1] == '*' {
    68  				lit = lit[:len(lit)-2]
    69  			}
    70  			return strings.TrimSpace(lit[2:])
    71  		case token.EOF:
    72  			return ""
    73  		}
    74  	}
    75  }
    76  
    77  func testTestDir(t *testing.T, path string, ignore ...string) {
    78  	files, err := ioutil.ReadDir(path)
    79  	if err != nil {
    80  		t.Fatal(err)
    81  	}
    82  
    83  	excluded := make(map[string]bool)
    84  	for _, filename := range ignore {
    85  		excluded[filename] = true
    86  	}
    87  
    88  	fset := token.NewFileSet()
    89  	for _, f := range files {
    90  		// filter directory contents
    91  		if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] {
    92  			continue
    93  		}
    94  
    95  		// get per-file instructions
    96  		expectErrors := false
    97  		filename := filepath.Join(path, f.Name())
    98  		if cmd := firstComment(filename); cmd != "" {
    99  			switch cmd {
   100  			case "skip", "compiledir":
   101  				continue // ignore this file
   102  			case "errorcheck":
   103  				expectErrors = true
   104  			}
   105  		}
   106  
   107  		// parse and type-check file
   108  		file, err := parser.ParseFile(fset, filename, nil, 0)
   109  		if err == nil {
   110  			conf := Config{Importer: stdLibImporter}
   111  			_, err = conf.Check(filename, fset, []*ast.File{file}, nil)
   112  		}
   113  
   114  		if expectErrors {
   115  			if err == nil {
   116  				t.Errorf("expected errors but found none in %s", filename)
   117  			}
   118  		} else {
   119  			if err != nil {
   120  				t.Error(err)
   121  			}
   122  		}
   123  	}
   124  }
   125  
   126  func TestStdTest(t *testing.T) {
   127  	skipSpecialPlatforms(t)
   128  
   129  	// test/recover4.go is only built for Linux and Darwin.
   130  	// TODO(gri) Remove once tests consider +build tags (issue 10370).
   131  	if runtime.GOOS != "linux" || runtime.GOOS != "darwin" {
   132  		return
   133  	}
   134  
   135  	testTestDir(t, filepath.Join(runtime.GOROOT(), "test"),
   136  		"cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
   137  		"sigchld.go",     // don't work on Windows; testTestDir should consult build tags
   138  	)
   139  }
   140  
   141  func TestStdFixed(t *testing.T) {
   142  	skipSpecialPlatforms(t)
   143  
   144  	testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "fixedbugs"),
   145  		"bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore
   146  		"bug459.go",    // possibly incorrect test - see issue 6703 (pending spec clarification)
   147  		"issue3924.go", // possibly incorrect test - see issue 6671 (pending spec clarification)
   148  		"issue6889.go", // gc-specific test
   149  	)
   150  }
   151  
   152  func TestStdKen(t *testing.T) {
   153  	skipSpecialPlatforms(t)
   154  
   155  	testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "ken"))
   156  }
   157  
   158  // Package paths of excluded packages.
   159  var excluded = map[string]bool{
   160  	"builtin": true,
   161  }
   162  
   163  // typecheck typechecks the given package files.
   164  func typecheck(t *testing.T, path string, filenames []string) {
   165  	fset := token.NewFileSet()
   166  
   167  	// parse package files
   168  	var files []*ast.File
   169  	for _, filename := range filenames {
   170  		file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
   171  		if err != nil {
   172  			// the parser error may be a list of individual errors; report them all
   173  			if list, ok := err.(scanner.ErrorList); ok {
   174  				for _, err := range list {
   175  					t.Error(err)
   176  				}
   177  				return
   178  			}
   179  			t.Error(err)
   180  			return
   181  		}
   182  
   183  		if testing.Verbose() {
   184  			if len(files) == 0 {
   185  				fmt.Println("package", file.Name.Name)
   186  			}
   187  			fmt.Println("\t", filename)
   188  		}
   189  
   190  		files = append(files, file)
   191  	}
   192  
   193  	// typecheck package files
   194  	conf := Config{
   195  		Error:    func(err error) { t.Error(err) },
   196  		Importer: stdLibImporter,
   197  	}
   198  	info := Info{Uses: make(map[*ast.Ident]Object)}
   199  	conf.Check(path, fset, files, &info)
   200  	pkgCount++
   201  
   202  	// Perform checks of API invariants.
   203  
   204  	// All Objects have a package, except predeclared ones.
   205  	errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error
   206  	for id, obj := range info.Uses {
   207  		predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
   208  		if predeclared == (obj.Pkg() != nil) {
   209  			posn := fset.Position(id.Pos())
   210  			if predeclared {
   211  				t.Errorf("%s: predeclared object with package: %s", posn, obj)
   212  			} else {
   213  				t.Errorf("%s: user-defined object without package: %s", posn, obj)
   214  			}
   215  		}
   216  	}
   217  }
   218  
   219  // pkgFilenames returns the list of package filenames for the given directory.
   220  func pkgFilenames(dir string) ([]string, error) {
   221  	ctxt := build.Default
   222  	ctxt.CgoEnabled = false
   223  	pkg, err := ctxt.ImportDir(dir, 0)
   224  	if err != nil {
   225  		if _, nogo := err.(*build.NoGoError); nogo {
   226  			return nil, nil // no *.go files, not an error
   227  		}
   228  		return nil, err
   229  	}
   230  	if excluded[pkg.ImportPath] {
   231  		return nil, nil
   232  	}
   233  	var filenames []string
   234  	for _, name := range pkg.GoFiles {
   235  		filenames = append(filenames, filepath.Join(pkg.Dir, name))
   236  	}
   237  	for _, name := range pkg.TestGoFiles {
   238  		filenames = append(filenames, filepath.Join(pkg.Dir, name))
   239  	}
   240  	return filenames, nil
   241  }
   242  
   243  // Note: Could use filepath.Walk instead of walkDirs but that wouldn't
   244  //       necessarily be shorter or clearer after adding the code to
   245  //       terminate early for -short tests.
   246  
   247  func walkDirs(t *testing.T, dir string) {
   248  	// limit run time for short tests
   249  	if testing.Short() && time.Since(start) >= 750*time.Millisecond {
   250  		return
   251  	}
   252  
   253  	fis, err := ioutil.ReadDir(dir)
   254  	if err != nil {
   255  		t.Error(err)
   256  		return
   257  	}
   258  
   259  	// typecheck package in directory
   260  	files, err := pkgFilenames(dir)
   261  	if err != nil {
   262  		t.Error(err)
   263  		return
   264  	}
   265  	if files != nil {
   266  		typecheck(t, dir, files)
   267  	}
   268  
   269  	// traverse subdirectories, but don't walk into testdata
   270  	for _, fi := range fis {
   271  		if fi.IsDir() && fi.Name() != "testdata" {
   272  			walkDirs(t, filepath.Join(dir, fi.Name()))
   273  		}
   274  	}
   275  }