...

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  	"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{Importer: stdLibImporter}
	 144  			SetGoVersion(&conf, goVersion)
	 145  			_, err = conf.Check(filename, fset, []*ast.File{file}, nil)
	 146  		}
	 147  
	 148  		if expectErrors {
	 149  			if err == nil {
	 150  				t.Errorf("expected errors but found none in %s", filename)
	 151  			}
	 152  		} else {
	 153  			if err != nil {
	 154  				t.Error(err)
	 155  			}
	 156  		}
	 157  	}
	 158  }
	 159  
	 160  func TestStdTest(t *testing.T) {
	 161  	testenv.MustHaveGoBuild(t)
	 162  
	 163  	if testing.Short() && testenv.Builder() == "" {
	 164  		t.Skip("skipping in short mode")
	 165  	}
	 166  
	 167  	testTestDir(t, filepath.Join(runtime.GOROOT(), "test"),
	 168  		"cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
	 169  		"directive.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  	)
	 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",	// go/types handles this correctly - not an issue
	 188  		"issue18459.go",	// go/types doesn't check validity of //go:xxx directives
	 189  		"issue18882.go",	// go/types doesn't check validity of //go:xxx directives
	 190  		"issue20529.go",	// go/types does not have constraints on stack size
	 191  		"issue22200.go",	// go/types does not have constraints on stack size
	 192  		"issue22200b.go", // go/types does not have constraints on stack size
	 193  		"issue25507.go",	// go/types does not have constraints on stack size
	 194  		"issue20780.go",	// go/types does not have constraints on stack size
	 195  		"bug251.go",			// issue #34333 which was exposed with fix for #34151
	 196  		"issue42058a.go", // go/types does not have constraints on channel element size
	 197  		"issue42058b.go", // go/types does not have constraints on channel element size
	 198  	)
	 199  }
	 200  
	 201  func TestStdKen(t *testing.T) {
	 202  	testenv.MustHaveGoBuild(t)
	 203  
	 204  	testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "ken"))
	 205  }
	 206  
	 207  // Package paths of excluded packages.
	 208  var excluded = map[string]bool{
	 209  	"builtin": true,
	 210  
	 211  	// See #46027: some imports are missing for this submodule.
	 212  	"crypto/ed25519/internal/edwards25519/field/_asm": true,
	 213  }
	 214  
	 215  // typecheck typechecks the given package files.
	 216  func typecheck(t *testing.T, path string, filenames []string) {
	 217  	fset := token.NewFileSet()
	 218  
	 219  	// parse package files
	 220  	var files []*ast.File
	 221  	for _, filename := range filenames {
	 222  		file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
	 223  		if err != nil {
	 224  			// the parser error may be a list of individual errors; report them all
	 225  			if list, ok := err.(scanner.ErrorList); ok {
	 226  				for _, err := range list {
	 227  					t.Error(err)
	 228  				}
	 229  				return
	 230  			}
	 231  			t.Error(err)
	 232  			return
	 233  		}
	 234  
	 235  		if testing.Verbose() {
	 236  			if len(files) == 0 {
	 237  				fmt.Println("package", file.Name.Name)
	 238  			}
	 239  			fmt.Println("\t", filename)
	 240  		}
	 241  
	 242  		files = append(files, file)
	 243  	}
	 244  
	 245  	// typecheck package files
	 246  	conf := Config{
	 247  		Error:		func(err error) { t.Error(err) },
	 248  		Importer: stdLibImporter,
	 249  	}
	 250  	info := Info{Uses: make(map[*ast.Ident]Object)}
	 251  	conf.Check(path, fset, files, &info)
	 252  
	 253  	// Perform checks of API invariants.
	 254  
	 255  	// All Objects have a package, except predeclared ones.
	 256  	errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error
	 257  	for id, obj := range info.Uses {
	 258  		predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
	 259  		if predeclared == (obj.Pkg() != nil) {
	 260  			posn := fset.Position(id.Pos())
	 261  			if predeclared {
	 262  				t.Errorf("%s: predeclared object with package: %s", posn, obj)
	 263  			} else {
	 264  				t.Errorf("%s: user-defined object without package: %s", posn, obj)
	 265  			}
	 266  		}
	 267  	}
	 268  }
	 269  
	 270  // pkgFilenames returns the list of package filenames for the given directory.
	 271  func pkgFilenames(dir string) ([]string, error) {
	 272  	ctxt := build.Default
	 273  	ctxt.CgoEnabled = false
	 274  	pkg, err := ctxt.ImportDir(dir, 0)
	 275  	if err != nil {
	 276  		if _, nogo := err.(*build.NoGoError); nogo {
	 277  			return nil, nil // no *.go files, not an error
	 278  		}
	 279  		return nil, err
	 280  	}
	 281  	if excluded[pkg.ImportPath] {
	 282  		return nil, nil
	 283  	}
	 284  	var filenames []string
	 285  	for _, name := range pkg.GoFiles {
	 286  		filenames = append(filenames, filepath.Join(pkg.Dir, name))
	 287  	}
	 288  	for _, name := range pkg.TestGoFiles {
	 289  		filenames = append(filenames, filepath.Join(pkg.Dir, name))
	 290  	}
	 291  	return filenames, nil
	 292  }
	 293  
	 294  func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...interface{})) time.Duration {
	 295  	w := walker{time.Now(), 10 * time.Millisecond, pkgh, errh}
	 296  	w.walk(dir)
	 297  	return time.Since(w.start)
	 298  }
	 299  
	 300  type walker struct {
	 301  	start time.Time
	 302  	dmax	time.Duration
	 303  	pkgh	func(dir string, filenames []string)
	 304  	errh	func(args ...interface{})
	 305  }
	 306  
	 307  func (w *walker) walk(dir string) {
	 308  	// limit run time for short tests
	 309  	if testing.Short() && time.Since(w.start) >= w.dmax {
	 310  		return
	 311  	}
	 312  
	 313  	files, err := os.ReadDir(dir)
	 314  	if err != nil {
	 315  		w.errh(err)
	 316  		return
	 317  	}
	 318  
	 319  	// apply pkgh to the files in directory dir
	 320  	// but ignore files directly under $GOROOT/src (might be temporary test files).
	 321  	if dir != filepath.Join(runtime.GOROOT(), "src") {
	 322  		files, err := pkgFilenames(dir)
	 323  		if err != nil {
	 324  			w.errh(err)
	 325  			return
	 326  		}
	 327  		if files != nil {
	 328  			w.pkgh(dir, files)
	 329  		}
	 330  	}
	 331  
	 332  	// traverse subdirectories, but don't walk into testdata
	 333  	for _, f := range files {
	 334  		if f.IsDir() && f.Name() != "testdata" {
	 335  			w.walk(filepath.Join(dir, f.Name()))
	 336  		}
	 337  	}
	 338  }
	 339  

View as plain text