@@ -36,6 +36,7 @@ import (
36
36
"path/filepath"
37
37
"reflect"
38
38
"regexp"
39
+ "sort"
39
40
"strings"
40
41
"testing"
41
42
49
50
50
51
var fset = token .NewFileSet ()
51
52
52
- // Positioned errors are of the form filename:line:column: message .
53
- var posMsgRx = regexp .MustCompile (`^(.*:\d+:\d+): *(?s)(.*)` )
54
-
55
- // splitError splits an error's error message into a position string
56
- // and the actual error message. If there's no position information,
57
- // pos is the empty string, and msg is the entire error message.
58
- func splitError (err error ) (pos , msg string ) {
59
- msg = err .Error ()
60
- if m := posMsgRx .FindStringSubmatch (msg ); len (m ) == 3 {
61
- pos = m [1 ]
62
- msg = m [2 ]
63
- }
64
- return
65
- }
66
-
67
53
func parseFiles (t * testing.T , filenames []string , srcs [][]byte , mode parser.Mode ) ([]* ast.File , []error ) {
68
54
var files []* ast.File
69
55
var errlist []error
@@ -86,87 +72,22 @@ func parseFiles(t *testing.T, filenames []string, srcs [][]byte, mode parser.Mod
86
72
return files , errlist
87
73
}
88
74
89
- // ERROR comments must start with text `ERROR "rx"` or `ERROR rx` where
90
- // rx is a regular expression that matches the expected error message.
91
- // Space around "rx" or rx is ignored.
92
- var errRx = regexp .MustCompile (`^ *ERROR *"?([^"]*)"?` )
93
-
94
- // errMap collects the regular expressions of ERROR comments found
95
- // in files and returns them as a map of error positions to error messages.
96
- //
97
- // srcs must be a slice of the same length as files, containing the original
98
- // source for the parsed AST.
99
- func errMap (t * testing.T , files []* ast.File , srcs [][]byte ) map [string ][]string {
100
- // map of position strings to lists of error message patterns
101
- errmap := make (map [string ][]string )
102
-
103
- for i , file := range files {
104
- tok := fset .File (file .Package )
105
- src := srcs [i ]
106
- var s scanner.Scanner
107
- s .Init (tok , src , nil , scanner .ScanComments )
108
- var prev token.Pos // position of last non-comment, non-semicolon token
109
-
110
- scanFile:
111
- for {
112
- pos , tok , lit := s .Scan ()
113
- switch tok {
114
- case token .EOF :
115
- break scanFile
116
- case token .COMMENT :
117
- if lit [1 ] == '*' {
118
- lit = lit [:len (lit )- 2 ] // strip trailing */
119
- }
120
- if s := errRx .FindStringSubmatch (lit [2 :]); len (s ) == 2 {
121
- p := fset .Position (prev ).String ()
122
- errmap [p ] = append (errmap [p ], strings .TrimSpace (s [1 ]))
123
- }
124
- case token .SEMICOLON :
125
- // ignore automatically inserted semicolon
126
- if lit == "\n " {
127
- continue scanFile
128
- }
129
- fallthrough
130
- default :
131
- prev = pos
132
- }
133
- }
75
+ func unpackError (fset * token.FileSet , err error ) (token.Position , string ) {
76
+ switch err := err .(type ) {
77
+ case * scanner.Error :
78
+ return err .Pos , err .Msg
79
+ case Error :
80
+ return fset .Position (err .Pos ), err .Msg
134
81
}
135
-
136
- return errmap
82
+ panic ("unreachable" )
137
83
}
138
84
139
- func eliminate (t * testing.T , errmap map [string ][]string , errlist []error ) {
140
- for _ , err := range errlist {
141
- pos , gotMsg := splitError (err )
142
- list := errmap [pos ]
143
- index := - 1 // list index of matching message, if any
144
- // we expect one of the messages in list to match the error at pos
145
- for i , wantRx := range list {
146
- rx , err := regexp .Compile (wantRx )
147
- if err != nil {
148
- t .Errorf ("%s: %v" , pos , err )
149
- continue
150
- }
151
- if rx .MatchString (gotMsg ) {
152
- index = i
153
- break
154
- }
155
- }
156
- if index >= 0 {
157
- // eliminate from list
158
- if n := len (list ) - 1 ; n > 0 {
159
- // not the last entry - swap in last element and shorten list by 1
160
- list [index ] = list [n ]
161
- errmap [pos ] = list [:n ]
162
- } else {
163
- // last entry - remove list from map
164
- delete (errmap , pos )
165
- }
166
- } else {
167
- t .Errorf ("%s: no error expected: %q" , pos , gotMsg )
168
- }
85
+ // delta returns the absolute difference between x and y.
86
+ func delta (x , y int ) int {
87
+ if x < y {
88
+ return y - x
169
89
}
90
+ return x - y
170
91
}
171
92
172
93
// parseFlags parses flags from the first line of the given source
@@ -262,28 +183,94 @@ func testFiles(t *testing.T, sizes Sizes, filenames []string, srcs [][]byte, man
262
183
return
263
184
}
264
185
186
+ // sort errlist in source order
187
+ sort .Slice (errlist , func (i , j int ) bool {
188
+ // TODO(gri) This is not correct as scanner.Errors
189
+ // don't have a correctly set Offset. But we only
190
+ // care about sorting when multiple equal errors
191
+ // appear on the same line, which happens with some
192
+ // type checker errors.
193
+ // For now this works. Will remove need for sorting
194
+ // in a subsequent CL.
195
+ pi , _ := unpackError (fset , errlist [i ])
196
+ pj , _ := unpackError (fset , errlist [j ])
197
+ return pi .Offset < pj .Offset
198
+ })
199
+
200
+ // collect expected errors
201
+ errmap := make (map [string ]map [int ][]comment )
202
+ for i , filename := range filenames {
203
+ if m := commentMap (srcs [i ], regexp .MustCompile ("^ ERROR " )); len (m ) > 0 {
204
+ errmap [filename ] = m
205
+ }
206
+ }
207
+
208
+ // match against found errors
265
209
for _ , err := range errlist {
266
- err , ok := err .(Error )
267
- if ! ok {
210
+ gotPos , gotMsg := unpackError (fset , err )
211
+
212
+ // find list of errors for the respective error line
213
+ filename := gotPos .Filename
214
+ filemap := errmap [filename ]
215
+ line := gotPos .Line
216
+ var errList []comment
217
+ if filemap != nil {
218
+ errList = filemap [line ]
219
+ }
220
+
221
+ // one of errors in errList should match the current error
222
+ index := - 1 // errList index of matching message, if any
223
+ for i , want := range errList {
224
+ pattern := strings .TrimSpace (want .text [len (" ERROR " ):])
225
+ if n := len (pattern ); n >= 2 && pattern [0 ] == '"' && pattern [n - 1 ] == '"' {
226
+ pattern = pattern [1 : n - 1 ]
227
+ }
228
+ rx , err := regexp .Compile (pattern )
229
+ if err != nil {
230
+ t .Errorf ("%s:%d:%d: %v" , filename , line , want .col , err )
231
+ continue
232
+ }
233
+ if rx .MatchString (gotMsg ) {
234
+ index = i
235
+ break
236
+ }
237
+ }
238
+ if index < 0 {
239
+ t .Errorf ("%s: no error expected: %q" , gotPos , gotMsg )
268
240
continue
269
241
}
270
- code := readCode (err )
271
- if code == 0 {
272
- t .Errorf ("missing error code: %v" , err )
242
+
243
+ // column position must be within expected colDelta
244
+ const colDelta = 0
245
+ want := errList [index ]
246
+ if delta (gotPos .Column , want .col ) > colDelta {
247
+ t .Errorf ("%s: got col = %d; want %d" , gotPos , gotPos .Column , want .col )
273
248
}
274
- }
275
249
276
- // match and eliminate errors;
277
- // we are expecting the following errors
278
- errmap := errMap (t , files , srcs )
279
- eliminate (t , errmap , errlist )
250
+ // eliminate from errList
251
+ if n := len (errList ) - 1 ; n > 0 {
252
+ // not the last entry - slide entries down (don't reorder)
253
+ copy (errList [index :], errList [index + 1 :])
254
+ filemap [line ] = errList [:n ]
255
+ } else {
256
+ // last entry - remove errList from filemap
257
+ delete (filemap , line )
258
+ }
259
+
260
+ // if filemap is empty, eliminate from errmap
261
+ if len (filemap ) == 0 {
262
+ delete (errmap , filename )
263
+ }
264
+ }
280
265
281
266
// there should be no expected errors left
282
267
if len (errmap ) > 0 {
283
- t .Errorf ("--- %s: %d source positions with expected (but not reported) errors:" , pkgName , len (errmap ))
284
- for pos , list := range errmap {
285
- for _ , rx := range list {
286
- t .Errorf ("%s: %q" , pos , rx )
268
+ t .Errorf ("--- %s: unreported errors:" , pkgName )
269
+ for filename , filemap := range errmap {
270
+ for line , errList := range filemap {
271
+ for _ , err := range errList {
272
+ t .Errorf ("%s:%d:%d: %s" , filename , line , err .col , err .text )
273
+ }
287
274
}
288
275
}
289
276
}
0 commit comments