1// Copyright 2016 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
5package syntax
6
7import (
8	"bytes"
9	"cmd/internal/src"
10	"flag"
11	"fmt"
12	"io/ioutil"
13	"path/filepath"
14	"runtime"
15	"strings"
16	"sync"
17	"testing"
18	"time"
19)
20
21var fast = flag.Bool("fast", false, "parse package files in parallel")
22var src_ = flag.String("src", "parser.go", "source file to parse")
23var verify = flag.Bool("verify", false, "verify idempotent printing")
24
25func TestParse(t *testing.T) {
26	ParseFile(*src_, func(err error) { t.Error(err) }, nil, 0)
27}
28
29func TestStdLib(t *testing.T) {
30	if testing.Short() {
31		t.Skip("skipping test in short mode")
32	}
33
34	var m1 runtime.MemStats
35	runtime.ReadMemStats(&m1)
36	start := time.Now()
37
38	type parseResult struct {
39		filename string
40		lines    uint
41	}
42
43	results := make(chan parseResult)
44	go func() {
45		defer close(results)
46		for _, dir := range []string{
47			runtime.GOROOT(),
48		} {
49			walkDirs(t, dir, func(filename string) {
50				if debug {
51					fmt.Printf("parsing %s\n", filename)
52				}
53				ast, err := ParseFile(filename, nil, nil, 0)
54				if err != nil {
55					t.Error(err)
56					return
57				}
58				if *verify {
59					verifyPrint(filename, ast)
60				}
61				results <- parseResult{filename, ast.Lines}
62			})
63		}
64	}()
65
66	var count, lines uint
67	for res := range results {
68		count++
69		lines += res.lines
70		if testing.Verbose() {
71			fmt.Printf("%5d  %s (%d lines)\n", count, res.filename, res.lines)
72		}
73	}
74
75	dt := time.Since(start)
76	var m2 runtime.MemStats
77	runtime.ReadMemStats(&m2)
78	dm := float64(m2.TotalAlloc-m1.TotalAlloc) / 1e6
79
80	fmt.Printf("parsed %d lines (%d files) in %v (%d lines/s)\n", lines, count, dt, int64(float64(lines)/dt.Seconds()))
81	fmt.Printf("allocated %.3fMb (%.3fMb/s)\n", dm, dm/dt.Seconds())
82}
83
84func walkDirs(t *testing.T, dir string, action func(string)) {
85	fis, err := ioutil.ReadDir(dir)
86	if err != nil {
87		t.Error(err)
88		return
89	}
90
91	var files, dirs []string
92	for _, fi := range fis {
93		if fi.Mode().IsRegular() {
94			if strings.HasSuffix(fi.Name(), ".go") {
95				path := filepath.Join(dir, fi.Name())
96				files = append(files, path)
97			}
98		} else if fi.IsDir() && fi.Name() != "testdata" {
99			path := filepath.Join(dir, fi.Name())
100			if !strings.HasSuffix(path, "/test") {
101				dirs = append(dirs, path)
102			}
103		}
104	}
105
106	if *fast {
107		var wg sync.WaitGroup
108		wg.Add(len(files))
109		for _, filename := range files {
110			go func(filename string) {
111				defer wg.Done()
112				action(filename)
113			}(filename)
114		}
115		wg.Wait()
116	} else {
117		for _, filename := range files {
118			action(filename)
119		}
120	}
121
122	for _, dir := range dirs {
123		walkDirs(t, dir, action)
124	}
125}
126
127func verifyPrint(filename string, ast1 *File) {
128	var buf1 bytes.Buffer
129	_, err := Fprint(&buf1, ast1, true)
130	if err != nil {
131		panic(err)
132	}
133
134	ast2, err := ParseBytes(src.NewFileBase(filename, filename), buf1.Bytes(), nil, nil, nil, 0)
135	if err != nil {
136		panic(err)
137	}
138
139	var buf2 bytes.Buffer
140	_, err = Fprint(&buf2, ast2, true)
141	if err != nil {
142		panic(err)
143	}
144
145	if bytes.Compare(buf1.Bytes(), buf2.Bytes()) != 0 {
146		fmt.Printf("--- %s ---\n", filename)
147		fmt.Printf("%s\n", buf1.Bytes())
148		fmt.Println()
149
150		fmt.Printf("--- %s ---\n", filename)
151		fmt.Printf("%s\n", buf2.Bytes())
152		fmt.Println()
153		panic("not equal")
154	}
155}
156
157func TestIssue17697(t *testing.T) {
158	_, err := ParseBytes(nil, nil, nil, nil, nil, 0) // return with parser error, don't panic
159	if err == nil {
160		t.Errorf("no error reported")
161	}
162}
163
164func TestParseFile(t *testing.T) {
165	_, err := ParseFile("", nil, nil, 0)
166	if err == nil {
167		t.Error("missing io error")
168	}
169
170	var first error
171	_, err = ParseFile("", func(err error) {
172		if first == nil {
173			first = err
174		}
175	}, nil, 0)
176	if err == nil || first == nil {
177		t.Error("missing io error")
178	}
179	if err != first {
180		t.Errorf("got %v; want first error %v", err, first)
181	}
182}
183
184func TestLineDirectives(t *testing.T) {
185	for _, test := range []struct {
186		src, msg  string
187		filename  string
188		line, col uint // 0-based
189	}{
190		// test validity of //line directive
191		{`//line :`, "invalid line number: ", "", 0, 8},
192		{`//line :x`, "invalid line number: x", "", 0, 8},
193		{`//line foo :`, "invalid line number: ", "", 0, 12},
194		{`//line foo:123abc`, "invalid line number: 123abc", "", 0, 11},
195		{`/**///line foo:x`, "syntax error: package statement must be first", "", 0, 16}, //line directive not at start of line - ignored
196		{`//line foo:0`, "invalid line number: 0", "", 0, 11},
197		{fmt.Sprintf(`//line foo:%d`, lineMax+1), fmt.Sprintf("invalid line number: %d", lineMax+1), "", 0, 11},
198
199		// test effect of //line directive on (relative) position information
200		{"//line foo:123\n   foo", "syntax error: package statement must be first", "foo", 123 - linebase, 3},
201		{"//line foo:123\n//line bar:345\nfoo", "syntax error: package statement must be first", "bar", 345 - linebase, 0},
202
203		{"//line " + runtime.GOROOT() + "/src/a/a.go:123\n   foo", "syntax error: package statement must be first", "$GOROOT/src/a/a.go", 123 - linebase, 3},
204	} {
205		fileh := func(name string) string {
206			if strings.HasPrefix(name, runtime.GOROOT()) {
207				return "$GOROOT" + name[len(runtime.GOROOT()):]
208			}
209			return name
210		}
211		_, err := ParseBytes(nil, []byte(test.src), nil, nil, fileh, 0)
212		if err == nil {
213			t.Errorf("%s: no error reported", test.src)
214			continue
215		}
216		perr, ok := err.(Error)
217		if !ok {
218			t.Errorf("%s: got %v; want parser error", test.src, err)
219			continue
220		}
221		if msg := perr.Msg; msg != test.msg {
222			t.Errorf("%s: got msg = %q; want %q", test.src, msg, test.msg)
223		}
224		if filename := perr.Pos.RelFilename(); filename != test.filename {
225			t.Errorf("%s: got filename = %q; want %q", test.src, filename, test.filename)
226		}
227		if line := perr.Pos.RelLine(); line != test.line+linebase {
228			t.Errorf("%s: got line = %d; want %d", test.src, line, test.line+linebase)
229		}
230		if col := perr.Pos.Col(); col != test.col+colbase {
231			t.Errorf("%s: got col = %d; want %d", test.src, col, test.col+colbase)
232		}
233	}
234}
235