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.
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.
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.
15 // For instance, the following test file indicates that a "not declared"
16 // error should be reported for the undeclared variable x:
20 // _ = x /* ERROR "not declared" */ + 1
23 // TODO(gri) Also collect strict mode errors of the form /* STRICT ... */
24 // and test against strict mode.
45 listErrors
= flag
.Bool("list", false, "list errors")
46 testFiles
= flag
.String("files", "", "space-separated list of test files")
49 // The test filenames do not end in .go so that they are invisible
50 // to gofmt since they contain comments that must not change their
51 // positions relative to surrounding tokens.
53 // Each tests entry is list of files belonging to the same package.
54 var tests
= [][]string{
55 {"testdata/errors.src"},
56 {"testdata/importdecl0a.src", "testdata/importdecl0b.src"},
57 {"testdata/importdecl1a.src", "testdata/importdecl1b.src"},
58 {"testdata/importC.src"}, // special handling in checkFiles
59 {"testdata/cycles.src"},
60 {"testdata/cycles1.src"},
61 {"testdata/cycles2.src"},
62 {"testdata/cycles3.src"},
63 {"testdata/cycles4.src"},
64 {"testdata/init0.src"},
65 {"testdata/init1.src"},
66 {"testdata/init2.src"},
67 {"testdata/decls0.src"},
68 {"testdata/decls1.src"},
69 {"testdata/decls2a.src", "testdata/decls2b.src"},
70 {"testdata/decls3.src"},
71 {"testdata/decls4.src"},
72 {"testdata/const0.src"},
73 {"testdata/const1.src"},
74 {"testdata/constdecl.src"},
75 {"testdata/vardecl.src"},
76 {"testdata/expr0.src"},
77 {"testdata/expr1.src"},
78 {"testdata/expr2.src"},
79 {"testdata/expr3.src"},
80 {"testdata/methodsets.src"},
81 {"testdata/shifts.src"},
82 {"testdata/builtins.src"},
83 {"testdata/conversions.src"},
84 {"testdata/conversions2.src"},
85 {"testdata/stmt0.src"},
86 {"testdata/stmt1.src"},
87 {"testdata/gotos.src"},
88 {"testdata/labels.src"},
89 {"testdata/issues.src"},
90 {"testdata/blank.src"},
93 var fset
= token
.NewFileSet()
95 // Positioned errors are of the form filename:line:column: message .
96 var posMsgRx
= regexp
.MustCompile(`^(.*:[0-9]+:[0-9]+): *(.*)`)
98 // splitError splits an error's error message into a position string
99 // and the actual error message. If there's no position information,
100 // pos is the empty string, and msg is the entire error message.
102 func splitError(err error
) (pos
, msg
string) {
104 if m
:= posMsgRx
.FindStringSubmatch(msg
); len(m
) == 3 {
111 func parseFiles(t
*testing
.T
, filenames
[]string) ([]*ast
.File
, []error
) {
112 var files
[]*ast
.File
114 for _
, filename
:= range filenames
{
115 file
, err
:= parser
.ParseFile(fset
, filename
, nil, parser
.AllErrors
)
117 t
.Fatalf("%s: %s", filename
, err
)
119 files
= append(files
, file
)
121 if list
, _
:= err
.(scanner
.ErrorList
); len(list
) > 0 {
122 for _
, err
:= range list
{
123 errlist
= append(errlist
, err
)
126 errlist
= append(errlist
, err
)
130 return files
, errlist
133 // ERROR comments must start with text `ERROR "rx"` or `ERROR rx` where
134 // rx is a regular expression that matches the expected error message.
135 // Space around "rx" or rx is ignored. Use the form `ERROR HERE "rx"`
136 // for error messages that are located immediately after rather than
137 // at a token's position.
139 var errRx
= regexp
.MustCompile(`^ *ERROR *(HERE)? *"?([^"]*)"?`)
141 // errMap collects the regular expressions of ERROR comments found
142 // in files and returns them as a map of error positions to error messages.
144 func errMap(t
*testing
.T
, testname
string, files
[]*ast
.File
) map[string][]string {
145 // map of position strings to lists of error message patterns
146 errmap
:= make(map[string][]string)
148 for _
, file
:= range files
{
149 filename
:= fset
.Position(file
.Package
).Filename
150 src
, err
:= ioutil
.ReadFile(filename
)
152 t
.Fatalf("%s: could not read %s", testname
, filename
)
155 var s scanner
.Scanner
156 s
.Init(fset
.AddFile(filename
, -1, len(src
)), src
, nil, scanner
.ScanComments
)
157 var prev token
.Pos
// position of last non-comment, non-semicolon token
158 var here token
.Pos
// position immediately after the token at position prev
162 pos
, tok
, lit
:= s
.Scan()
168 lit
= lit
[:len(lit
)-2] // strip trailing */
170 if s
:= errRx
.FindStringSubmatch(lit
[2:]); len(s
) == 3 {
175 p
:= fset
.Position(pos
).String()
176 errmap
[p
] = append(errmap
[p
], strings
.TrimSpace(s
[2]))
178 case token
.SEMICOLON
:
179 // ignore automatically inserted semicolon
186 var l
int // token length
190 l
= len(tok
.String())
192 here
= prev
+ token
.Pos(l
)
200 func eliminate(t
*testing
.T
, errmap
map[string][]string, errlist
[]error
) {
201 for _
, err
:= range errlist
{
202 pos
, gotMsg
:= splitError(err
)
204 index
:= -1 // list index of matching message, if any
205 // we expect one of the messages in list to match the error at pos
206 for i
, wantRx
:= range list
{
207 rx
, err
:= regexp
.Compile(wantRx
)
209 t
.Errorf("%s: %v", pos
, err
)
212 if rx
.MatchString(gotMsg
) {
218 // eliminate from list
219 if n
:= len(list
) - 1; n
> 0 {
220 // not the last entry - swap in last element and shorten list by 1
221 list
[index
] = list
[n
]
222 errmap
[pos
] = list
[:n
]
224 // last entry - remove list from map
228 t
.Errorf("%s: no error expected: %q", pos
, gotMsg
)
233 func checkFiles(t
*testing
.T
, testfiles
[]string) {
234 // parse files and collect parser errors
235 files
, errlist
:= parseFiles(t
, testfiles
)
237 pkgName
:= "<no package>"
239 pkgName
= files
[0].Name
.Name
242 if *listErrors
&& len(errlist
) > 0 {
243 t
.Errorf("--- %s:", pkgName
)
244 for _
, err
:= range errlist
{
249 // typecheck and collect typechecker errors
251 // special case for importC.src
252 if len(testfiles
) == 1 && testfiles
[0] == "testdata/importC.src" {
253 conf
.FakeImportC
= true
255 conf
.Importer
= importer
.Default()
256 conf
.Error
= func(err error
) {
261 // Ignore secondary error messages starting with "\t";
262 // they are clarifying messages for a primary error.
263 if !strings
.Contains(err
.Error(), ": \t") {
264 errlist
= append(errlist
, err
)
267 conf
.Check(pkgName
, fset
, files
, nil)
273 // match and eliminate errors;
274 // we are expecting the following errors
275 errmap
:= errMap(t
, pkgName
, files
)
276 eliminate(t
, errmap
, errlist
)
278 // there should be no expected errors left
280 t
.Errorf("--- %s: %d source positions with expected (but not reported) errors:", pkgName
, len(errmap
))
281 for pos
, list
:= range errmap
{
282 for _
, rx
:= range list
{
283 t
.Errorf("%s: %q", pos
, rx
)
289 func TestCheck(t
*testing
.T
) {
290 testenv
.MustHaveGoBuild(t
)
292 // Declare builtins for testing.
293 DefPredeclaredTestFuncs()
295 // If explicit test files are specified, only check those.
296 if files
:= *testFiles
; files
!= "" {
297 checkFiles(t
, strings
.Split(files
, " "))
301 // Otherwise, run all the tests.
302 for _
, files
:= range tests
{