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