1
2
3
4
5 package syntax
6
7 import (
8 "bytes"
9 "flag"
10 "fmt"
11 "io/ioutil"
12 "path/filepath"
13 "regexp"
14 "runtime"
15 "strings"
16 "sync"
17 "testing"
18 "time"
19 )
20
21 var (
22 fast = flag.Bool("fast", false, "parse package files in parallel")
23 verify = flag.Bool("verify", false, "verify idempotent printing")
24 src_ = flag.String("src", "parser.go", "source file to parse")
25 skip = flag.String("skip", "", "files matching this regular expression are skipped by TestStdLib")
26 )
27
28 func TestParse(t *testing.T) {
29 ParseFile(*src_, func(err error) { t.Error(err) }, nil, AllowGenerics)
30 }
31
32 func TestVerify(t *testing.T) {
33 ast, err := ParseFile(*src_, func(err error) { t.Error(err) }, nil, AllowGenerics)
34 if err != nil {
35 return
36 }
37 verifyPrint(t, *src_, ast)
38 }
39
40 func TestParseGo2(t *testing.T) {
41 dir := filepath.Join(testdata, "go2")
42 list, err := ioutil.ReadDir(dir)
43 if err != nil {
44 t.Fatal(err)
45 }
46 for _, fi := range list {
47 name := fi.Name()
48 if !fi.IsDir() && !strings.HasPrefix(name, ".") {
49 ParseFile(filepath.Join(dir, name), func(err error) { t.Error(err) }, nil, AllowGenerics)
50 }
51 }
52 }
53
54 func TestStdLib(t *testing.T) { testStdLib(t, 0) }
55 func TestStdLibGeneric(t *testing.T) { testStdLib(t, AllowGenerics) }
56
57 func testStdLib(t *testing.T, mode Mode) {
58 if testing.Short() {
59 t.Skip("skipping test in short mode")
60 }
61
62 var skipRx *regexp.Regexp
63 if *skip != "" {
64 var err error
65 skipRx, err = regexp.Compile(*skip)
66 if err != nil {
67 t.Fatalf("invalid argument for -skip (%v)", err)
68 }
69 }
70
71 var m1 runtime.MemStats
72 runtime.ReadMemStats(&m1)
73 start := time.Now()
74
75 type parseResult struct {
76 filename string
77 lines uint
78 }
79
80 results := make(chan parseResult)
81 go func() {
82 defer close(results)
83 for _, dir := range []string{
84 runtime.GOROOT(),
85 } {
86 walkDirs(t, dir, func(filename string) {
87 if skipRx != nil && skipRx.MatchString(filename) {
88
89
90 fmt.Printf("skipping %s\n", filename)
91 return
92 }
93 if debug {
94 fmt.Printf("parsing %s\n", filename)
95 }
96 ast, err := ParseFile(filename, nil, nil, mode)
97 if err != nil {
98 t.Error(err)
99 return
100 }
101 if *verify {
102 verifyPrint(t, filename, ast)
103 }
104 results <- parseResult{filename, ast.EOF.Line()}
105 })
106 }
107 }()
108
109 var count, lines uint
110 for res := range results {
111 count++
112 lines += res.lines
113 if testing.Verbose() {
114 fmt.Printf("%5d %s (%d lines)\n", count, res.filename, res.lines)
115 }
116 }
117
118 dt := time.Since(start)
119 var m2 runtime.MemStats
120 runtime.ReadMemStats(&m2)
121 dm := float64(m2.TotalAlloc-m1.TotalAlloc) / 1e6
122
123 fmt.Printf("parsed %d lines (%d files) in %v (%d lines/s)\n", lines, count, dt, int64(float64(lines)/dt.Seconds()))
124 fmt.Printf("allocated %.3fMb (%.3fMb/s)\n", dm, dm/dt.Seconds())
125 }
126
127 func walkDirs(t *testing.T, dir string, action func(string)) {
128 fis, err := ioutil.ReadDir(dir)
129 if err != nil {
130 t.Error(err)
131 return
132 }
133
134 var files, dirs []string
135 for _, fi := range fis {
136 if fi.Mode().IsRegular() {
137 if strings.HasSuffix(fi.Name(), ".go") {
138 path := filepath.Join(dir, fi.Name())
139 files = append(files, path)
140 }
141 } else if fi.IsDir() && fi.Name() != "testdata" {
142 path := filepath.Join(dir, fi.Name())
143 if !strings.HasSuffix(path, string(filepath.Separator)+"test") {
144 dirs = append(dirs, path)
145 }
146 }
147 }
148
149 if *fast {
150 var wg sync.WaitGroup
151 wg.Add(len(files))
152 for _, filename := range files {
153 go func(filename string) {
154 defer wg.Done()
155 action(filename)
156 }(filename)
157 }
158 wg.Wait()
159 } else {
160 for _, filename := range files {
161 action(filename)
162 }
163 }
164
165 for _, dir := range dirs {
166 walkDirs(t, dir, action)
167 }
168 }
169
170 func verifyPrint(t *testing.T, filename string, ast1 *File) {
171 var buf1 bytes.Buffer
172 _, err := Fprint(&buf1, ast1, LineForm)
173 if err != nil {
174 panic(err)
175 }
176 bytes1 := buf1.Bytes()
177
178 ast2, err := Parse(NewFileBase(filename), &buf1, nil, nil, 0)
179 if err != nil {
180 panic(err)
181 }
182
183 var buf2 bytes.Buffer
184 _, err = Fprint(&buf2, ast2, LineForm)
185 if err != nil {
186 panic(err)
187 }
188 bytes2 := buf2.Bytes()
189
190 if bytes.Compare(bytes1, bytes2) != 0 {
191 fmt.Printf("--- %s ---\n", filename)
192 fmt.Printf("%s\n", bytes1)
193 fmt.Println()
194
195 fmt.Printf("--- %s ---\n", filename)
196 fmt.Printf("%s\n", bytes2)
197 fmt.Println()
198
199 t.Error("printed syntax trees do not match")
200 }
201 }
202
203 func TestIssue17697(t *testing.T) {
204 _, err := Parse(nil, bytes.NewReader(nil), nil, nil, 0)
205 if err == nil {
206 t.Errorf("no error reported")
207 }
208 }
209
210 func TestParseFile(t *testing.T) {
211 _, err := ParseFile("", nil, nil, 0)
212 if err == nil {
213 t.Error("missing io error")
214 }
215
216 var first error
217 _, err = ParseFile("", func(err error) {
218 if first == nil {
219 first = err
220 }
221 }, nil, 0)
222 if err == nil || first == nil {
223 t.Error("missing io error")
224 }
225 if err != first {
226 t.Errorf("got %v; want first error %v", err, first)
227 }
228 }
229
230
231
232
233 var tooLarge int = PosMax + 1
234
235 func TestLineDirectives(t *testing.T) {
236
237 const valid = "syntax error: package statement must be first"
238 const filename = "directives.go"
239
240 for _, test := range []struct {
241 src, msg string
242 filename string
243 line, col uint
244 }{
245
246 {"//\n", valid, filename, 2, 1},
247 {"//line\n", valid, filename, 2, 1},
248 {"//line foo\n", valid, filename, 2, 1},
249 {" //line foo:\n", valid, filename, 2, 1},
250 {"// line foo:\n", valid, filename, 2, 1},
251
252
253 {"//line :\n", "invalid line number: ", filename, 1, 9},
254 {"//line :x\n", "invalid line number: x", filename, 1, 9},
255 {"//line foo :\n", "invalid line number: ", filename, 1, 13},
256 {"//line foo:x\n", "invalid line number: x", filename, 1, 12},
257 {"//line foo:0\n", "invalid line number: 0", filename, 1, 12},
258 {"//line foo:1 \n", "invalid line number: 1 ", filename, 1, 12},
259 {"//line foo:-12\n", "invalid line number: -12", filename, 1, 12},
260 {"//line C:foo:0\n", "invalid line number: 0", filename, 1, 14},
261 {fmt.Sprintf("//line foo:%d\n", tooLarge), fmt.Sprintf("invalid line number: %d", tooLarge), filename, 1, 12},
262
263
264 {"//line ::\n", "invalid line number: ", filename, 1, 10},
265 {"//line ::x\n", "invalid line number: x", filename, 1, 10},
266 {"//line foo::123abc\n", "invalid line number: 123abc", filename, 1, 13},
267 {"//line foo::0\n", "invalid line number: 0", filename, 1, 13},
268 {"//line foo:0:1\n", "invalid line number: 0", filename, 1, 12},
269
270 {"//line :123:0\n", "invalid column number: 0", filename, 1, 13},
271 {"//line foo:123:0\n", "invalid column number: 0", filename, 1, 16},
272 {fmt.Sprintf("//line foo:10:%d\n", tooLarge), fmt.Sprintf("invalid column number: %d", tooLarge), filename, 1, 15},
273
274
275 {"//line foo:123\n foo", valid, "foo", 123, 0},
276 {"//line foo:123\n foo", valid, " foo", 123, 0},
277 {"//line foo:123\n//line bar:345\nfoo", valid, "bar", 345, 0},
278 {"//line C:foo:123\n", valid, "C:foo", 123, 0},
279 {"//line /src/a/a.go:123\n foo", valid, "/src/a/a.go", 123, 0},
280 {"//line :x:1\n", valid, ":x", 1, 0},
281 {"//line foo ::1\n", valid, "foo :", 1, 0},
282 {"//line foo:123abc:1\n", valid, "foo:123abc", 1, 0},
283 {"//line foo :123:1\n", valid, "foo ", 123, 1},
284 {"//line ::123\n", valid, ":", 123, 0},
285
286
287 {"//line :x:1:10\n", valid, ":x", 1, 10},
288 {"//line foo ::1:2\n", valid, "foo :", 1, 2},
289 {"//line foo:123abc:1:1000\n", valid, "foo:123abc", 1, 1000},
290 {"//line foo :123:1000\n\n", valid, "foo ", 124, 1},
291 {"//line ::123:1234\n", valid, ":", 123, 1234},
292
293
294 {"//line :10\n", valid, "", 10, 0},
295 {"//line :10:20\n", valid, filename, 10, 20},
296 {"//line bar:1\n//line :10\n", valid, "", 10, 0},
297 {"//line bar:1\n//line :10:20\n", valid, "bar", 10, 20},
298
299
300 {"/**/", valid, filename, 1, 5},
301 {"/*line*/", valid, filename, 1, 9},
302 {"/*line foo*/", valid, filename, 1, 13},
303 {" //line foo:*/", valid, filename, 1, 16},
304 {"/* line foo:*/", valid, filename, 1, 16},
305
306
307 {"/*line :*/", "invalid line number: ", filename, 1, 9},
308 {"/*line :x*/", "invalid line number: x", filename, 1, 9},
309 {"/*line foo :*/", "invalid line number: ", filename, 1, 13},
310 {"/*line foo:x*/", "invalid line number: x", filename, 1, 12},
311 {"/*line foo:0*/", "invalid line number: 0", filename, 1, 12},
312 {"/*line foo:1 */", "invalid line number: 1 ", filename, 1, 12},
313 {"/*line C:foo:0*/", "invalid line number: 0", filename, 1, 14},
314 {fmt.Sprintf("/*line foo:%d*/", tooLarge), fmt.Sprintf("invalid line number: %d", tooLarge), filename, 1, 12},
315
316
317 {"/*line ::*/", "invalid line number: ", filename, 1, 10},
318 {"/*line ::x*/", "invalid line number: x", filename, 1, 10},
319 {"/*line foo::123abc*/", "invalid line number: 123abc", filename, 1, 13},
320 {"/*line foo::0*/", "invalid line number: 0", filename, 1, 13},
321 {"/*line foo:0:1*/", "invalid line number: 0", filename, 1, 12},
322
323 {"/*line :123:0*/", "invalid column number: 0", filename, 1, 13},
324 {"/*line foo:123:0*/", "invalid column number: 0", filename, 1, 16},
325 {fmt.Sprintf("/*line foo:10:%d*/", tooLarge), fmt.Sprintf("invalid column number: %d", tooLarge), filename, 1, 15},
326
327
328 {"/*line foo:123*/ foo", valid, "foo", 123, 0},
329 {"/*line foo:123*/\n//line bar:345\nfoo", valid, "bar", 345, 0},
330 {"/*line C:foo:123*/", valid, "C:foo", 123, 0},
331 {"/*line /src/a/a.go:123*/ foo", valid, "/src/a/a.go", 123, 0},
332 {"/*line :x:1*/", valid, ":x", 1, 0},
333 {"/*line foo ::1*/", valid, "foo :", 1, 0},
334 {"/*line foo:123abc:1*/", valid, "foo:123abc", 1, 0},
335 {"/*line foo :123:10*/", valid, "foo ", 123, 10},
336 {"/*line ::123*/", valid, ":", 123, 0},
337
338
339 {"/*line :x:1:10*/", valid, ":x", 1, 10},
340 {"/*line foo ::1:2*/", valid, "foo :", 1, 2},
341 {"/*line foo:123abc:1:1000*/", valid, "foo:123abc", 1, 1000},
342 {"/*line foo :123:1000*/\n", valid, "foo ", 124, 1},
343 {"/*line ::123:1234*/", valid, ":", 123, 1234},
344
345
346 {"/*line :10*/", valid, "", 10, 0},
347 {"/*line :10:20*/", valid, filename, 10, 20},
348 {"//line bar:1\n/*line :10*/", valid, "", 10, 0},
349 {"//line bar:1\n/*line :10:20*/", valid, "bar", 10, 20},
350 } {
351 base := NewFileBase(filename)
352 _, err := Parse(base, strings.NewReader(test.src), nil, nil, 0)
353 if err == nil {
354 t.Errorf("%s: no error reported", test.src)
355 continue
356 }
357 perr, ok := err.(Error)
358 if !ok {
359 t.Errorf("%s: got %v; want parser error", test.src, err)
360 continue
361 }
362 if msg := perr.Msg; msg != test.msg {
363 t.Errorf("%s: got msg = %q; want %q", test.src, msg, test.msg)
364 }
365
366 pos := perr.Pos
367 if filename := pos.RelFilename(); filename != test.filename {
368 t.Errorf("%s: got filename = %q; want %q", test.src, filename, test.filename)
369 }
370 if line := pos.RelLine(); line != test.line {
371 t.Errorf("%s: got line = %d; want %d", test.src, line, test.line)
372 }
373 if col := pos.RelCol(); col != test.col {
374 t.Errorf("%s: got col = %d; want %d", test.src, col, test.col)
375 }
376 }
377 }
378
View as plain text