Skip to content

Commit 52fcac3

Browse files
committed
cmd/compile/internal/syntax: implement regression test harness for syntax errors
R=go1.11 Fixes #20800. Change-Id: Ifea273521d42a543a43da2f655ace7c295650e30 Reviewed-on: https://go-review.googlesource.com/88335 Reviewed-by: Matthew Dempsky <[email protected]>
1 parent b890688 commit 52fcac3

File tree

3 files changed

+225
-0
lines changed

3 files changed

+225
-0
lines changed

src/cmd/compile/fmt_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,7 @@ var knownFormats = map[string]string{
656656
"cmd/compile/internal/syntax.Node %T": "",
657657
"cmd/compile/internal/syntax.Operator %d": "",
658658
"cmd/compile/internal/syntax.Operator %s": "",
659+
"cmd/compile/internal/syntax.position %s": "",
659660
"cmd/compile/internal/syntax.token %d": "",
660661
"cmd/compile/internal/syntax.token %q": "",
661662
"cmd/compile/internal/syntax.token %s": "",
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Copyright 2018 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+
5+
// This file implements a regression test harness for syntax errors.
6+
// The files in the testdata directory are parsed and the reported
7+
// errors are compared against the errors declared in those files.
8+
//
9+
// Errors are declared in place in the form of "error comments",
10+
// just before (or on the same line as) the offending token.
11+
//
12+
// Error comments must be of the form // ERROR rx or /* ERROR rx */
13+
// where rx is a regular expression that matches the reported error
14+
// message. The rx text comprises the comment text after "ERROR ",
15+
// with any white space around it stripped.
16+
//
17+
// If the line comment form is used, the reported error's line must
18+
// match the line of the error comment.
19+
//
20+
// If the regular comment form is used, the reported error's position
21+
// must match the position of the token immediately following the
22+
// error comment. Thus, /* ERROR ... */ comments should appear
23+
// immediately before the position where the error is reported.
24+
//
25+
// Currently, the test harness only supports one error comment per
26+
// token. If multiple error comments appear before a token, only
27+
// the last one is considered.
28+
29+
package syntax
30+
31+
import (
32+
"flag"
33+
"fmt"
34+
"internal/testenv"
35+
"io/ioutil"
36+
"os"
37+
"path/filepath"
38+
"regexp"
39+
"sort"
40+
"strings"
41+
"testing"
42+
)
43+
44+
const testdata = "testdata" // directory containing test files
45+
46+
var print = flag.Bool("print", false, "only print errors")
47+
48+
// A position represents a source position in the current file.
49+
type position struct {
50+
line, col uint
51+
}
52+
53+
func (pos position) String() string {
54+
return fmt.Sprintf("%d:%d", pos.line, pos.col)
55+
}
56+
57+
func sortedPositions(m map[position]string) []position {
58+
list := make([]position, len(m))
59+
i := 0
60+
for pos := range m {
61+
list[i] = pos
62+
i++
63+
}
64+
sort.Slice(list, func(i, j int) bool {
65+
a, b := list[i], list[j]
66+
return a.line < b.line || a.line == b.line && a.col < b.col
67+
})
68+
return list
69+
}
70+
71+
// declaredErrors returns a map of source positions to error
72+
// patterns, extracted from error comments in the given file.
73+
// Error comments in the form of line comments use col = 0
74+
// in their position.
75+
func declaredErrors(t *testing.T, filename string) map[position]string {
76+
f, err := os.Open(filename)
77+
if err != nil {
78+
t.Fatal(err)
79+
}
80+
defer f.Close()
81+
82+
declared := make(map[position]string)
83+
84+
var s scanner
85+
var pattern string
86+
s.init(f, func(line, col uint, msg string) {
87+
// errors never start with '/' so they are automatically excluded here
88+
switch {
89+
case strings.HasPrefix(msg, "// ERROR "):
90+
// we can't have another comment on the same line - just add it
91+
declared[position{s.line, 0}] = strings.TrimSpace(msg[9:])
92+
case strings.HasPrefix(msg, "/* ERROR "):
93+
// we may have more comments before the next token - collect them
94+
pattern = strings.TrimSpace(msg[9 : len(msg)-2])
95+
}
96+
}, comments)
97+
98+
// consume file
99+
for {
100+
s.next()
101+
if pattern != "" {
102+
declared[position{s.line, s.col}] = pattern
103+
pattern = ""
104+
}
105+
if s.tok == _EOF {
106+
break
107+
}
108+
}
109+
110+
return declared
111+
}
112+
113+
func testSyntaxErrors(t *testing.T, filename string) {
114+
declared := declaredErrors(t, filename)
115+
if *print {
116+
fmt.Println("Declared errors:")
117+
for _, pos := range sortedPositions(declared) {
118+
fmt.Printf("%s:%s: %s\n", filename, pos, declared[pos])
119+
}
120+
121+
fmt.Println()
122+
fmt.Println("Reported errors:")
123+
}
124+
125+
f, err := os.Open(filename)
126+
if err != nil {
127+
t.Fatal(err)
128+
}
129+
defer f.Close()
130+
131+
ParseFile(filename, func(err error) {
132+
e, ok := err.(Error)
133+
if !ok {
134+
return
135+
}
136+
137+
if *print {
138+
fmt.Println(err)
139+
return
140+
}
141+
142+
orig := position{e.Pos.Line(), e.Pos.Col()}
143+
pos := orig
144+
pattern, found := declared[pos]
145+
if !found {
146+
// try line comment (only line must match)
147+
pos = position{e.Pos.Line(), 0}
148+
pattern, found = declared[pos]
149+
}
150+
if found {
151+
rx, err := regexp.Compile(pattern)
152+
if err != nil {
153+
t.Errorf("%s: %v", pos, err)
154+
return
155+
}
156+
if match := rx.MatchString(e.Msg); !match {
157+
t.Errorf("%s: %q does not match %q", pos, e.Msg, pattern)
158+
return
159+
}
160+
// we have a match - eliminate this error
161+
delete(declared, pos)
162+
} else {
163+
t.Errorf("%s: unexpected error: %s", orig, e.Msg)
164+
}
165+
}, nil, 0)
166+
167+
if *print {
168+
fmt.Println()
169+
return // we're done
170+
}
171+
172+
// report expected but not reported errors
173+
for pos, pattern := range declared {
174+
t.Errorf("%s: missing error: %s", pos, pattern)
175+
}
176+
}
177+
178+
func TestSyntaxErrors(t *testing.T) {
179+
testenv.MustHaveGoBuild(t) // we need access to source (testdata)
180+
181+
list, err := ioutil.ReadDir(testdata)
182+
if err != nil {
183+
t.Fatal(err)
184+
}
185+
for _, fi := range list {
186+
name := fi.Name()
187+
if !fi.IsDir() && !strings.HasPrefix(name, ".") {
188+
testSyntaxErrors(t, filepath.Join(testdata, name))
189+
}
190+
}
191+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2018 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+
5+
// This is a sample test file illustrating the use
6+
// of error comments with the error test harness.
7+
8+
package p
9+
10+
// The following are invalid error comments; they are
11+
// silently ignored. The prefix must be exactly one of
12+
// "/* ERROR " or "// ERROR ".
13+
//
14+
/*ERROR*/
15+
/*ERROR foo*/
16+
/* ERRORfoo */
17+
/* ERROR foo */
18+
//ERROR
19+
// ERROR
20+
// ERRORfoo
21+
// ERROR foo
22+
23+
// This is a valid error comment; it applies to the
24+
// immediately following token.
25+
import "math" /* ERROR unexpected comma */ ,
26+
27+
// If there are multiple /*-style error comments before
28+
// the next token, only the last one is considered.
29+
type x = /* ERROR ignored */ /* ERROR literal 0 in type declaration */ 0
30+
31+
// A //-style error comment matches any error position
32+
// on the same line.
33+
func () foo() // ERROR method has no receiver

0 commit comments

Comments
 (0)