OSDN Git Service

libgo: Update to weekly.2012-01-15.
[pf3gnuchains/gcc-fork.git] / libgo / go / exp / types / check_test.go
1 // Copyright 2011 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 implements a typechecker test harness. The packages specified
6 // in tests are typechecked. Error messages reported by the typechecker are
7 // compared against the error messages expected in the test files.
8 //
9 // Expected errors are indicated in the test files by putting a comment
10 // of the form /* ERROR "rx" */ immediately following an offending token.
11 // The harness will verify that an error matching the regular expression
12 // rx is reported at that source position. Consecutive comments may be
13 // used to indicate multiple errors for the same token position.
14 //
15 // For instance, the following test file indicates that a "not declared"
16 // error should be reported for the undeclared variable x:
17 //
18 //      package p
19 //      func f() {
20 //              _ = x /* ERROR "not declared" */ + 1
21 //      }
22
23 package types
24
25 import (
26         "fmt"
27         "go/ast"
28         "go/parser"
29         "go/scanner"
30         "go/token"
31         "io/ioutil"
32         "os"
33         "regexp"
34         "testing"
35 )
36
37 // The test filenames do not end in .go so that they are invisible
38 // to gofmt since they contain comments that must not change their
39 // positions relative to surrounding tokens.
40
41 var tests = []struct {
42         name  string
43         files []string
44 }{
45         {"test0", []string{"testdata/test0.src"}},
46 }
47
48 var fset = token.NewFileSet()
49
50 // TODO(gri) This functionality should be in token.Fileset.
51 func getFile(filename string) *token.File {
52         for f := range fset.Files() {
53                 if f.Name() == filename {
54                         return f
55                 }
56         }
57         return nil
58 }
59
60 // TODO(gri) This functionality should be in token.Fileset.
61 func getPos(filename string, offset int) token.Pos {
62         if f := getFile(filename); f != nil {
63                 return f.Pos(offset)
64         }
65         return token.NoPos
66 }
67
68 // TODO(gri) Need to revisit parser interface. We should be able to use parser.ParseFiles
69 //           or a similar function instead.
70 func parseFiles(t *testing.T, testname string, filenames []string) (map[string]*ast.File, error) {
71         files := make(map[string]*ast.File)
72         var errors scanner.ErrorList
73         for _, filename := range filenames {
74                 if _, exists := files[filename]; exists {
75                         t.Fatalf("%s: duplicate file %s", testname, filename)
76                 }
77                 file, err := parser.ParseFile(fset, filename, nil, parser.DeclarationErrors)
78                 if file == nil {
79                         t.Fatalf("%s: could not parse file %s", testname, filename)
80                 }
81                 files[filename] = file
82                 if err != nil {
83                         // if the parser returns a non-scanner.ErrorList error
84                         // the file couldn't be read in the first place and
85                         // file == nil; in that case we shouldn't reach here
86                         errors = append(errors, err.(scanner.ErrorList)...)
87                 }
88
89         }
90         return files, errors
91 }
92
93 // ERROR comments must be of the form /* ERROR "rx" */ and rx is
94 // a regular expression that matches the expected error message.
95 //
96 var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`)
97
98 // expectedErrors collects the regular expressions of ERROR comments found
99 // in files and returns them as a map of error positions to error messages.
100 //
101 func expectedErrors(t *testing.T, testname string, files map[string]*ast.File) map[token.Pos]string {
102         errors := make(map[token.Pos]string)
103         for filename := range files {
104                 src, err := ioutil.ReadFile(filename)
105                 if err != nil {
106                         t.Fatalf("%s: could not read %s", testname, filename)
107                 }
108
109                 var s scanner.Scanner
110                 // file was parsed already - do not add it again to the file
111                 // set otherwise the position information returned here will
112                 // not match the position information collected by the parser
113                 s.Init(getFile(filename), src, nil, scanner.ScanComments)
114                 var prev token.Pos // position of last non-comment, non-semicolon token
115
116         scanFile:
117                 for {
118                         pos, tok, lit := s.Scan()
119                         switch tok {
120                         case token.EOF:
121                                 break scanFile
122                         case token.COMMENT:
123                                 s := errRx.FindStringSubmatch(lit)
124                                 if len(s) == 2 {
125                                         errors[prev] = string(s[1])
126                                 }
127                         case token.SEMICOLON:
128                                 // ignore automatically inserted semicolon
129                                 if lit == "\n" {
130                                         break
131                                 }
132                                 fallthrough
133                         default:
134                                 prev = pos
135                         }
136                 }
137         }
138         return errors
139 }
140
141 func eliminate(t *testing.T, expected map[token.Pos]string, errors error) {
142         if errors == nil {
143                 return
144         }
145         for _, error := range errors.(scanner.ErrorList) {
146                 // error.Pos is a token.Position, but we want
147                 // a token.Pos so we can do a map lookup
148                 // TODO(gri) Need to move scanner.Errors over
149                 //           to use token.Pos and file set info.
150                 pos := getPos(error.Pos.Filename, error.Pos.Offset)
151                 if msg, found := expected[pos]; found {
152                         // we expect a message at pos; check if it matches
153                         rx, err := regexp.Compile(msg)
154                         if err != nil {
155                                 t.Errorf("%s: %v", error.Pos, err)
156                                 continue
157                         }
158                         if match := rx.MatchString(error.Msg); !match {
159                                 t.Errorf("%s: %q does not match %q", error.Pos, error.Msg, msg)
160                                 continue
161                         }
162                         // we have a match - eliminate this error
163                         delete(expected, pos)
164                 } else {
165                         // To keep in mind when analyzing failed test output:
166                         // If the same error position occurs multiple times in errors,
167                         // this message will be triggered (because the first error at
168                         // the position removes this position from the expected errors).
169                         t.Errorf("%s: no (multiple?) error expected, but found: %s", error.Pos, error.Msg)
170                 }
171         }
172 }
173
174 func check(t *testing.T, testname string, testfiles []string) {
175         // TODO(gri) Eventually all these different phases should be
176         //           subsumed into a single function call that takes
177         //           a set of files and creates a fully resolved and
178         //           type-checked AST.
179
180         files, err := parseFiles(t, testname, testfiles)
181
182         // we are expecting the following errors
183         // (collect these after parsing the files so that
184         // they are found in the file set)
185         errors := expectedErrors(t, testname, files)
186
187         // verify errors returned by the parser
188         eliminate(t, errors, err)
189
190         // verify errors returned after resolving identifiers
191         pkg, err := ast.NewPackage(fset, files, GcImporter, Universe)
192         eliminate(t, errors, err)
193
194         // verify errors returned by the typechecker
195         _, err = Check(fset, pkg)
196         eliminate(t, errors, err)
197
198         // there should be no expected errors left
199         if len(errors) > 0 {
200                 t.Errorf("%s: %d errors not reported:", testname, len(errors))
201                 for pos, msg := range errors {
202                         t.Errorf("%s: %s\n", fset.Position(pos), msg)
203                 }
204         }
205 }
206
207 func TestCheck(t *testing.T) {
208         // For easy debugging w/o changing the testing code,
209         // if there is a local test file, only test that file.
210         const testfile = "test.go"
211         if fi, err := os.Stat(testfile); err == nil && !fi.IsDir() {
212                 fmt.Printf("WARNING: Testing only %s (remove it to run all tests)\n", testfile)
213                 check(t, testfile, []string{testfile})
214                 return
215         }
216
217         // Otherwise, run all the tests.
218         for _, test := range tests {
219                 check(t, test.name, test.files)
220         }
221 }