| // Copyright 2010 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // This file implements a simple typechecker test harness. Packages found |
| // in the testDir directory are typechecked. Error messages reported by |
| // the typechecker are compared against the error messages expected for |
| // the test files. |
| // |
| // Expected errors are indicated in the test files by putting a comment |
| // of the form /* ERROR "rx" */ immediately following an offending token. |
| // The harness will verify that an error matching the regular expression |
| // rx is reported at that source position. Consecutive comments may be |
| // used to indicate multiple errors for the same token position. |
| // |
| // For instance, the following test file indicates that a "not declared" |
| // error should be reported for the undeclared variable x: |
| // |
| // package P0 |
| // func f() { |
| // _ = x /* ERROR "not declared" */ + 1 |
| // } |
| // |
| // If the -pkg flag is set, only packages with package names matching |
| // the regular expression provided via the flag value are tested. |
| |
| package typechecker |
| |
| import ( |
| "flag" |
| "fmt" |
| "go/ast" |
| "go/parser" |
| "go/scanner" |
| "go/token" |
| "io/ioutil" |
| "os" |
| "regexp" |
| "sort" |
| "strings" |
| "testing" |
| ) |
| |
| |
| const testDir = "./testdata" // location of test packages |
| |
| var fset = token.NewFileSet() |
| |
| var ( |
| pkgPat = flag.String("pkg", ".*", "regular expression to select test packages by package name") |
| trace = flag.Bool("trace", false, "print package names") |
| ) |
| |
| |
| // ERROR comments must be of the form /* ERROR "rx" */ and rx is |
| // a regular expression that matches the expected error message. |
| var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`) |
| |
| // expectedErrors collects the regular expressions of ERROR comments |
| // found in the package files of pkg and returns them in sorted order |
| // (by filename and position). |
| func expectedErrors(t *testing.T, pkg *ast.Package) (list scanner.ErrorList) { |
| // scan all package files |
| for filename := range pkg.Files { |
| src, err := ioutil.ReadFile(filename) |
| if err != nil { |
| t.Fatalf("expectedErrors(%s): %v", pkg.Name, err) |
| } |
| |
| var s scanner.Scanner |
| file := fset.AddFile(filename, fset.Base(), len(src)) |
| s.Init(file, src, nil, scanner.ScanComments) |
| var prev token.Pos // position of last non-comment token |
| loop: |
| for { |
| pos, tok, lit := s.Scan() |
| switch tok { |
| case token.EOF: |
| break loop |
| case token.COMMENT: |
| s := errRx.FindStringSubmatch(lit) |
| if len(s) == 2 { |
| list = append(list, &scanner.Error{fset.Position(prev), string(s[1])}) |
| } |
| default: |
| prev = pos |
| } |
| } |
| } |
| sort.Sort(list) // multiple files may not be sorted |
| return |
| } |
| |
| |
| func testFilter(f *os.FileInfo) bool { |
| return strings.HasSuffix(f.Name, ".src") && f.Name[0] != '.' |
| } |
| |
| |
| func checkError(t *testing.T, expected, found *scanner.Error) { |
| rx, err := regexp.Compile(expected.Msg) |
| if err != nil { |
| t.Errorf("%s: %v", expected.Pos, err) |
| return |
| } |
| |
| match := rx.MatchString(found.Msg) |
| |
| if expected.Pos.Offset != found.Pos.Offset { |
| if match { |
| t.Errorf("%s: expected error should have been at %s", expected.Pos, found.Pos) |
| } else { |
| t.Errorf("%s: error matching %q expected", expected.Pos, expected.Msg) |
| return |
| } |
| } |
| |
| if !match { |
| t.Errorf("%s: %q does not match %q", expected.Pos, expected.Msg, found.Msg) |
| } |
| } |
| |
| |
| func TestTypeCheck(t *testing.T) { |
| flag.Parse() |
| pkgRx, err := regexp.Compile(*pkgPat) |
| if err != nil { |
| t.Fatalf("illegal flag value %q: %s", *pkgPat, err) |
| } |
| |
| pkgs, err := parser.ParseDir(fset, testDir, testFilter, 0) |
| if err != nil { |
| scanner.PrintError(os.Stderr, err) |
| t.Fatalf("packages in %s contain syntax errors", testDir) |
| } |
| |
| for _, pkg := range pkgs { |
| if !pkgRx.MatchString(pkg.Name) { |
| continue // only test selected packages |
| } |
| |
| if *trace { |
| fmt.Println(pkg.Name) |
| } |
| |
| xlist := expectedErrors(t, pkg) |
| err := CheckPackage(fset, pkg, nil) |
| if err != nil { |
| if elist, ok := err.(scanner.ErrorList); ok { |
| // verify that errors match |
| for i := 0; i < len(xlist) && i < len(elist); i++ { |
| checkError(t, xlist[i], elist[i]) |
| } |
| // the correct number or errors must have been found |
| if len(xlist) != len(elist) { |
| fmt.Fprintf(os.Stderr, "%s\n", pkg.Name) |
| scanner.PrintError(os.Stderr, elist) |
| fmt.Fprintln(os.Stderr) |
| t.Errorf("TypeCheck(%s): %d errors expected but %d reported", pkg.Name, len(xlist), len(elist)) |
| } |
| } else { |
| t.Errorf("TypeCheck(%s): %v", pkg.Name, err) |
| } |
| } else if len(xlist) > 0 { |
| t.Errorf("TypeCheck(%s): %d errors expected but 0 reported", pkg.Name, len(xlist)) |
| } |
| } |
| } |