]> Cypherpunks repositories - gostls13.git/commitdiff
exp/types/staging: test drivers
authorRobert Griesemer <gri@golang.org>
Mon, 8 Oct 2012 01:00:56 +0000 (18:00 -0700)
committerRobert Griesemer <gri@golang.org>
Mon, 8 Oct 2012 01:00:56 +0000 (18:00 -0700)
This code has been reviewed before. The most significant
change is to check_test which now can handle more than
one error at the same error position (due to spurious
errors - should not happen in praxis once error handling
has been fine-tuned). This change makes check_test easier
to use during development.

R=rsc
CC=golang-dev
https://golang.org/cl/6584057

src/pkg/exp/types/staging/check_test.go [new file with mode: 0644]
src/pkg/exp/types/staging/resolver_test.go [new file with mode: 0644]
src/pkg/exp/types/staging/types_test.go [new file with mode: 0644]

diff --git a/src/pkg/exp/types/staging/check_test.go b/src/pkg/exp/types/staging/check_test.go
new file mode 100644 (file)
index 0000000..abcfcfb
--- /dev/null
@@ -0,0 +1,257 @@
+// Copyright 2011 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 typechecker test harness. The packages specified
+// in tests are typechecked. Error messages reported by the typechecker are
+// compared against the error messages expected in 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 p
+//     func f() {
+//             _ = x /* ERROR "not declared" */ + 1
+//     }
+
+package types
+
+import (
+       "flag"
+       "fmt"
+       "go/ast"
+       "go/parser"
+       "go/scanner"
+       "go/token"
+       "io/ioutil"
+       "os"
+       "regexp"
+       "testing"
+)
+
+var listErrors = flag.Bool("list", false, "list errors")
+
+func init() {
+       // declare builtins for testing
+       def(ast.Fun, "assert").Type = &builtin{aType, _Assert, "assert", 1, false, true}
+       def(ast.Fun, "trace").Type = &builtin{aType, _Trace, "trace", 0, true, true}
+}
+
+// The test filenames do not end in .go so that they are invisible
+// to gofmt since they contain comments that must not change their
+// positions relative to surrounding tokens.
+
+var tests = []struct {
+       name  string
+       files []string
+}{
+       {"decls0", []string{"testdata/decls0.src"}},
+       {"decls1", []string{"testdata/decls1.src"}},
+       {"decls2", []string{"testdata/decls2a.src", "testdata/decls2b.src"}},
+       {"const0", []string{"testdata/const0.src"}},
+       {"expr0", []string{"testdata/expr0.src"}},
+       {"expr1", []string{"testdata/expr1.src"}},
+       {"expr2", []string{"testdata/expr2.src"}},
+       {"expr3", []string{"testdata/expr3.src"}},
+       {"builtins", []string{"testdata/builtins.src"}},
+       {"conversions", []string{"testdata/conversions.src"}},
+       {"stmt0", []string{"testdata/stmt0.src"}},
+}
+
+var fset = token.NewFileSet()
+
+func getFile(filename string) (file *token.File) {
+       fset.Iterate(func(f *token.File) bool {
+               if f.Name() == filename {
+                       file = f
+                       return false // end iteration
+               }
+               return true
+       })
+       return file
+}
+
+func getPos(filename string, offset int) token.Pos {
+       if f := getFile(filename); f != nil {
+               return f.Pos(offset)
+       }
+       return token.NoPos
+}
+
+func parseFiles(t *testing.T, testname string, filenames []string) (map[string]*ast.File, error) {
+       files := make(map[string]*ast.File)
+       var errors scanner.ErrorList
+       for _, filename := range filenames {
+               if _, exists := files[filename]; exists {
+                       t.Fatalf("%s: duplicate file %s", testname, filename)
+               }
+               file, err := parser.ParseFile(fset, filename, nil, parser.DeclarationErrors)
+               if file == nil {
+                       t.Fatalf("%s: could not parse file %s", testname, filename)
+               }
+               files[filename] = file
+               if err != nil {
+                       // if the parser returns a non-scanner.ErrorList error
+                       // the file couldn't be read in the first place and
+                       // file == nil; in that case we shouldn't reach here
+                       errors = append(errors, err.(scanner.ErrorList)...)
+               }
+
+       }
+       return files, errors
+}
+
+// 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 files and returns them as a map of error positions to error messages.
+//
+func expectedErrors(t *testing.T, testname string, files map[string]*ast.File) map[token.Pos][]string {
+       errors := make(map[token.Pos][]string)
+
+       for filename := range files {
+               src, err := ioutil.ReadFile(filename)
+               if err != nil {
+                       t.Fatalf("%s: could not read %s", testname, filename)
+               }
+
+               var s scanner.Scanner
+               // file was parsed already - do not add it again to the file
+               // set otherwise the position information returned here will
+               // not match the position information collected by the parser
+               s.Init(getFile(filename), src, nil, scanner.ScanComments)
+               var prev token.Pos // position of last non-comment, non-semicolon token
+
+       scanFile:
+               for {
+                       pos, tok, lit := s.Scan()
+                       switch tok {
+                       case token.EOF:
+                               break scanFile
+                       case token.COMMENT:
+                               s := errRx.FindStringSubmatch(lit)
+                               if len(s) == 2 {
+                                       list := errors[prev]
+                                       errors[prev] = append(list, string(s[1]))
+                               }
+                       case token.SEMICOLON:
+                               // ignore automatically inserted semicolon
+                               if lit == "\n" {
+                                       break
+                               }
+                               fallthrough
+                       default:
+                               prev = pos
+                       }
+               }
+       }
+
+       return errors
+}
+
+func eliminate(t *testing.T, expected map[token.Pos][]string, errors error) {
+       if *listErrors || errors == nil {
+               return
+       }
+       for _, error := range errors.(scanner.ErrorList) {
+               // error.Pos is a token.Position, but we want
+               // a token.Pos so we can do a map lookup
+               pos := getPos(error.Pos.Filename, error.Pos.Offset)
+               list := expected[pos]
+               index := -1 // list index of matching message, if any
+               // we expect one of the messages in list to match the error at pos
+               for i, msg := range list {
+                       rx, err := regexp.Compile(msg)
+                       if err != nil {
+                               t.Errorf("%s: %v", error.Pos, err)
+                               continue
+                       }
+                       if match := rx.MatchString(error.Msg); match {
+                               index = i
+                               break
+                       }
+               }
+               if index >= 0 {
+                       // eliminate from list
+                       n := len(list) - 1
+                       if n > 0 {
+                               // not the last entry - swap in last element and shorten list by 1
+                               list[index] = list[n]
+                               expected[pos] = list[:n]
+                       } else {
+                               // last entry - remove list from map
+                               delete(expected, pos)
+                       }
+               } else {
+                       t.Errorf("%s: no error expected: %q", error.Pos, error.Msg)
+                       continue
+               }
+       }
+}
+
+func checkFiles(t *testing.T, testname string, testfiles []string) {
+       // TODO(gri) Eventually all these different phases should be
+       //           subsumed into a single function call that takes
+       //           a set of files and creates a fully resolved and
+       //           type-checked AST.
+
+       files, err := parseFiles(t, testname, testfiles)
+
+       // we are expecting the following errors
+       // (collect these after parsing the files so that
+       // they are found in the file set)
+       errors := expectedErrors(t, testname, files)
+
+       // verify errors returned by the parser
+       eliminate(t, errors, err)
+
+       // verify errors returned after resolving identifiers
+       pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
+       eliminate(t, errors, err)
+
+       // verify errors returned by the typechecker
+       var list scanner.ErrorList
+       errh := func(pos token.Pos, msg string) {
+               list.Add(fset.Position(pos), msg)
+       }
+       err = Check(fset, pkg, errh, nil)
+       eliminate(t, errors, list)
+
+       if *listErrors {
+               scanner.PrintError(os.Stdout, err)
+               return
+       }
+
+       // there should be no expected errors left
+       if len(errors) > 0 {
+               t.Errorf("%s: %d errors not reported:", testname, len(errors))
+               for pos, msg := range errors {
+                       t.Errorf("%s: %s\n", fset.Position(pos), msg)
+               }
+       }
+}
+
+func TestCheck(t *testing.T) {
+       // For easy debugging w/o changing the testing code,
+       // if there is a local test file, only test that file.
+       const testfile = "testdata/test.go"
+       if fi, err := os.Stat(testfile); err == nil && !fi.IsDir() {
+               fmt.Printf("WARNING: Testing only %s (remove it to run all tests)\n", testfile)
+               checkFiles(t, testfile, []string{testfile})
+               return
+       }
+
+       // Otherwise, run all the tests.
+       for _, test := range tests {
+               checkFiles(t, test.name, test.files)
+       }
+}
diff --git a/src/pkg/exp/types/staging/resolver_test.go b/src/pkg/exp/types/staging/resolver_test.go
new file mode 100644 (file)
index 0000000..4e9aa09
--- /dev/null
@@ -0,0 +1,130 @@
+// Copyright 2011 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.
+
+package types
+
+import (
+       "fmt"
+       "go/ast"
+       "go/parser"
+       "go/scanner"
+       "go/token"
+       "testing"
+)
+
+var sources = []string{
+       `package p
+       import "fmt"
+       import "math"
+       const pi = math.Pi
+       func sin(x float64) float64 {
+               return math.Sin(x)
+       }
+       var Println = fmt.Println
+       `,
+       `package p
+       import "fmt"
+       func f() string {
+               return fmt.Sprintf("%d", g())
+       }
+       `,
+       `package p
+       import . "go/parser"
+       func g() Mode { return ImportsOnly }`,
+}
+
+var pkgnames = []string{
+       "fmt",
+       "go/parser",
+       "math",
+}
+
+// ResolveQualifiedIdents resolves the selectors of qualified
+// identifiers by associating the correct ast.Object with them.
+// TODO(gri): Eventually, this functionality should be subsumed
+//            by Check.
+//
+func ResolveQualifiedIdents(fset *token.FileSet, pkg *ast.Package) error {
+       var errors scanner.ErrorList
+
+       findObj := func(pkg *ast.Object, name *ast.Ident) *ast.Object {
+               scope := pkg.Data.(*ast.Scope)
+               obj := scope.Lookup(name.Name)
+               if obj == nil {
+                       errors.Add(fset.Position(name.Pos()), fmt.Sprintf("no %s in package %s", name.Name, pkg.Name))
+               }
+               return obj
+       }
+
+       ast.Inspect(pkg, func(n ast.Node) bool {
+               if s, ok := n.(*ast.SelectorExpr); ok {
+                       if x, ok := s.X.(*ast.Ident); ok && x.Obj != nil && x.Obj.Kind == ast.Pkg {
+                               // find selector in respective package
+                               s.Sel.Obj = findObj(x.Obj, s.Sel)
+                       }
+                       return false
+               }
+               return true
+       })
+
+       return errors.Err()
+}
+
+func TestResolveQualifiedIdents(t *testing.T) {
+       // parse package files
+       fset := token.NewFileSet()
+       files := make(map[string]*ast.File)
+       for i, src := range sources {
+               filename := fmt.Sprintf("file%d", i)
+               f, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
+               if err != nil {
+                       t.Fatal(err)
+               }
+               files[filename] = f
+       }
+
+       // resolve package AST
+       pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       // check that all packages were imported
+       for _, name := range pkgnames {
+               if pkg.Imports[name] == nil {
+                       t.Errorf("package %s not imported", name)
+               }
+       }
+
+       // check that there are no top-level unresolved identifiers
+       for _, f := range pkg.Files {
+               for _, x := range f.Unresolved {
+                       t.Errorf("%s: unresolved global identifier %s", fset.Position(x.Pos()), x.Name)
+               }
+       }
+
+       // resolve qualified identifiers
+       if err := ResolveQualifiedIdents(fset, pkg); err != nil {
+               t.Error(err)
+       }
+
+       // check that qualified identifiers are resolved
+       ast.Inspect(pkg, func(n ast.Node) bool {
+               if s, ok := n.(*ast.SelectorExpr); ok {
+                       if x, ok := s.X.(*ast.Ident); ok {
+                               if x.Obj == nil {
+                                       t.Errorf("%s: unresolved qualified identifier %s", fset.Position(x.Pos()), x.Name)
+                                       return false
+                               }
+                               if x.Obj.Kind == ast.Pkg && s.Sel != nil && s.Sel.Obj == nil {
+                                       t.Errorf("%s: unresolved selector %s", fset.Position(s.Sel.Pos()), s.Sel.Name)
+                                       return false
+                               }
+                               return false
+                       }
+                       return false
+               }
+               return true
+       })
+}
diff --git a/src/pkg/exp/types/staging/types_test.go b/src/pkg/exp/types/staging/types_test.go
new file mode 100644 (file)
index 0000000..d6ddfab
--- /dev/null
@@ -0,0 +1,178 @@
+// Copyright 2012 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 contains tests verifying the types associated with an AST after
+// type checking.
+
+package types
+
+import (
+       "go/ast"
+       "go/parser"
+       "testing"
+)
+
+const filename = "<src>"
+
+func makePkg(t *testing.T, src string) (*ast.Package, error) {
+       file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
+       if err != nil {
+               return nil, err
+       }
+       files := map[string]*ast.File{filename: file}
+       pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
+       if err != nil {
+               return nil, err
+       }
+       if err := Check(fset, pkg, nil, nil); err != nil {
+               return nil, err
+       }
+       return pkg, nil
+}
+
+type testEntry struct {
+       src, str string
+}
+
+// dup returns a testEntry where both src and str are the same.
+func dup(s string) testEntry {
+       return testEntry{s, s}
+}
+
+var testTypes = []testEntry{
+       // basic types
+       dup("int"),
+       dup("float32"),
+       dup("string"),
+
+       // arrays
+       dup("[10]int"),
+
+       // slices
+       dup("[]int"),
+       dup("[][]int"),
+
+       // structs
+       dup("struct{}"),
+       dup("struct{x int}"),
+       {`struct {
+               x, y int
+               z float32 "foo"
+       }`, `struct{x int; y int; z float32 "foo"}`},
+       {`struct {
+               string
+               elems []T
+       }`, `struct{string; elems []T}`},
+
+       // pointers
+       dup("*int"),
+       dup("***struct{}"),
+       dup("*struct{a int; b float32}"),
+
+       // functions
+       dup("func()"),
+       dup("func(x int)"),
+       {"func(x, y int)", "func(x int, y int)"},
+       {"func(x, y int, z string)", "func(x int, y int, z string)"},
+       dup("func(int)"),
+       {"func(int, string, byte)", "func(int, string, byte)"},
+
+       dup("func() int"),
+       {"func() (string)", "func() string"},
+       dup("func() (u int)"),
+       {"func() (u, v int, w string)", "func() (u int, v int, w string)"},
+
+       dup("func(int) string"),
+       dup("func(x int) string"),
+       dup("func(x int) (u string)"),
+       {"func(x, y int) (u string)", "func(x int, y int) (u string)"},
+
+       dup("func(...int) string"),
+       dup("func(x ...int) string"),
+       dup("func(x ...int) (u string)"),
+       {"func(x, y ...int) (u string)", "func(x int, y ...int) (u string)"},
+
+       // interfaces
+       dup("interface{}"),
+       dup("interface{m()}"),
+       {`interface{
+               m(int) float32
+               String() string
+       }`, `interface{String() string; m(int) float32}`}, // methods are sorted
+       // TODO(gri) add test for interface w/ anonymous field
+
+       // maps
+       dup("map[string]int"),
+       {"map[struct{x, y int}][]byte", "map[struct{x int; y int}][]byte"},
+
+       // channels
+       dup("chan int"),
+       dup("chan<- func()"),
+       dup("<-chan []func() int"),
+}
+
+func TestTypes(t *testing.T) {
+       for _, test := range testTypes {
+               src := "package p; type T " + test.src
+               pkg, err := makePkg(t, src)
+               if err != nil {
+                       t.Errorf("%s: %s", src, err)
+                       continue
+               }
+               typ := underlying(pkg.Scope.Lookup("T").Type.(Type))
+               str := typeString(typ)
+               if str != test.str {
+                       t.Errorf("%s: got %s, want %s", test.src, str, test.str)
+               }
+       }
+}
+
+var testExprs = []testEntry{
+       // basic type literals
+       dup("x"),
+       dup("true"),
+       dup("42"),
+       dup("3.1415"),
+       dup("2.71828i"),
+       dup(`'a'`),
+       dup(`"foo"`),
+       dup("`bar`"),
+
+       // arbitrary expressions
+       dup("&x"),
+       dup("*x"),
+       dup("(x)"),
+       dup("x + y"),
+       dup("x + y * 10"),
+       dup("s.foo"),
+       dup("s[0]"),
+       dup("s[x:y]"),
+       dup("s[:y]"),
+       dup("s[x:]"),
+       dup("s[:]"),
+       dup("f(1, 2.3)"),
+       dup("-f(10, 20)"),
+       dup("f(x + y, +3.1415)"),
+       {"func(a, b int) {}", "(func literal)"},
+       {"func(a, b int) []int {}()[x]", "(func literal)()[x]"},
+       {"[]int{1, 2, 3}", "(composite literal)"},
+       {"[]int{1, 2, 3}[x:]", "(composite literal)[x:]"},
+       {"x.([]string)", "x.(...)"},
+}
+
+func TestExprs(t *testing.T) {
+       for _, test := range testExprs {
+               src := "package p; var _ = " + test.src + "; var (x, y int; s []string; f func(int, float32))"
+               pkg, err := makePkg(t, src)
+               if err != nil {
+                       t.Errorf("%s: %s", src, err)
+                       continue
+               }
+               expr := pkg.Files[filename].Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Values[0]
+               str := exprString(expr)
+               if str != test.str {
+                       t.Errorf("%s: got %s, want %s", test.src, str, test.str)
+               }
+       }
+}