aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRobert Griesemer <gri@golang.org>2018-01-18 15:03:38 -0800
committerRobert Griesemer <gri@golang.org>2018-02-12 22:58:06 +0000
commit52fcac3b7cf8a8d660b4d73188238ac9e7974ee4 (patch)
tree57ecb90a4b8637df1116dbe339e28acf7538ff17 /src
parentb8906889861d0efaf5682a7d26417111eaba3480 (diff)
downloadgo-52fcac3b7cf8a8d660b4d73188238ac9e7974ee4.tar.xz
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 <mdempsky@google.com>
Diffstat (limited to 'src')
-rw-r--r--src/cmd/compile/fmt_test.go1
-rw-r--r--src/cmd/compile/internal/syntax/error_test.go191
-rw-r--r--src/cmd/compile/internal/syntax/testdata/sample.src33
3 files changed, 225 insertions, 0 deletions
diff --git a/src/cmd/compile/fmt_test.go b/src/cmd/compile/fmt_test.go
index 7342b5492b..ca6e6d0b69 100644
--- a/src/cmd/compile/fmt_test.go
+++ b/src/cmd/compile/fmt_test.go
@@ -656,6 +656,7 @@ var knownFormats = map[string]string{
"cmd/compile/internal/syntax.Node %T": "",
"cmd/compile/internal/syntax.Operator %d": "",
"cmd/compile/internal/syntax.Operator %s": "",
+ "cmd/compile/internal/syntax.position %s": "",
"cmd/compile/internal/syntax.token %d": "",
"cmd/compile/internal/syntax.token %q": "",
"cmd/compile/internal/syntax.token %s": "",
diff --git a/src/cmd/compile/internal/syntax/error_test.go b/src/cmd/compile/internal/syntax/error_test.go
new file mode 100644
index 0000000000..72b1ad6333
--- /dev/null
+++ b/src/cmd/compile/internal/syntax/error_test.go
@@ -0,0 +1,191 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file implements a regression test harness for syntax errors.
+// The files in the testdata directory are parsed and the reported
+// errors are compared against the errors declared in those files.
+//
+// Errors are declared in place in the form of "error comments",
+// just before (or on the same line as) the offending token.
+//
+// Error comments must be of the form // ERROR rx or /* ERROR rx */
+// where rx is a regular expression that matches the reported error
+// message. The rx text comprises the comment text after "ERROR ",
+// with any white space around it stripped.
+//
+// If the line comment form is used, the reported error's line must
+// match the line of the error comment.
+//
+// If the regular comment form is used, the reported error's position
+// must match the position of the token immediately following the
+// error comment. Thus, /* ERROR ... */ comments should appear
+// immediately before the position where the error is reported.
+//
+// Currently, the test harness only supports one error comment per
+// token. If multiple error comments appear before a token, only
+// the last one is considered.
+
+package syntax
+
+import (
+ "flag"
+ "fmt"
+ "internal/testenv"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strings"
+ "testing"
+)
+
+const testdata = "testdata" // directory containing test files
+
+var print = flag.Bool("print", false, "only print errors")
+
+// A position represents a source position in the current file.
+type position struct {
+ line, col uint
+}
+
+func (pos position) String() string {
+ return fmt.Sprintf("%d:%d", pos.line, pos.col)
+}
+
+func sortedPositions(m map[position]string) []position {
+ list := make([]position, len(m))
+ i := 0
+ for pos := range m {
+ list[i] = pos
+ i++
+ }
+ sort.Slice(list, func(i, j int) bool {
+ a, b := list[i], list[j]
+ return a.line < b.line || a.line == b.line && a.col < b.col
+ })
+ return list
+}
+
+// declaredErrors returns a map of source positions to error
+// patterns, extracted from error comments in the given file.
+// Error comments in the form of line comments use col = 0
+// in their position.
+func declaredErrors(t *testing.T, filename string) map[position]string {
+ f, err := os.Open(filename)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ declared := make(map[position]string)
+
+ var s scanner
+ var pattern string
+ s.init(f, func(line, col uint, msg string) {
+ // errors never start with '/' so they are automatically excluded here
+ switch {
+ case strings.HasPrefix(msg, "// ERROR "):
+ // we can't have another comment on the same line - just add it
+ declared[position{s.line, 0}] = strings.TrimSpace(msg[9:])
+ case strings.HasPrefix(msg, "/* ERROR "):
+ // we may have more comments before the next token - collect them
+ pattern = strings.TrimSpace(msg[9 : len(msg)-2])
+ }
+ }, comments)
+
+ // consume file
+ for {
+ s.next()
+ if pattern != "" {
+ declared[position{s.line, s.col}] = pattern
+ pattern = ""
+ }
+ if s.tok == _EOF {
+ break
+ }
+ }
+
+ return declared
+}
+
+func testSyntaxErrors(t *testing.T, filename string) {
+ declared := declaredErrors(t, filename)
+ if *print {
+ fmt.Println("Declared errors:")
+ for _, pos := range sortedPositions(declared) {
+ fmt.Printf("%s:%s: %s\n", filename, pos, declared[pos])
+ }
+
+ fmt.Println()
+ fmt.Println("Reported errors:")
+ }
+
+ f, err := os.Open(filename)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ ParseFile(filename, func(err error) {
+ e, ok := err.(Error)
+ if !ok {
+ return
+ }
+
+ if *print {
+ fmt.Println(err)
+ return
+ }
+
+ orig := position{e.Pos.Line(), e.Pos.Col()}
+ pos := orig
+ pattern, found := declared[pos]
+ if !found {
+ // try line comment (only line must match)
+ pos = position{e.Pos.Line(), 0}
+ pattern, found = declared[pos]
+ }
+ if found {
+ rx, err := regexp.Compile(pattern)
+ if err != nil {
+ t.Errorf("%s: %v", pos, err)
+ return
+ }
+ if match := rx.MatchString(e.Msg); !match {
+ t.Errorf("%s: %q does not match %q", pos, e.Msg, pattern)
+ return
+ }
+ // we have a match - eliminate this error
+ delete(declared, pos)
+ } else {
+ t.Errorf("%s: unexpected error: %s", orig, e.Msg)
+ }
+ }, nil, 0)
+
+ if *print {
+ fmt.Println()
+ return // we're done
+ }
+
+ // report expected but not reported errors
+ for pos, pattern := range declared {
+ t.Errorf("%s: missing error: %s", pos, pattern)
+ }
+}
+
+func TestSyntaxErrors(t *testing.T) {
+ testenv.MustHaveGoBuild(t) // we need access to source (testdata)
+
+ list, err := ioutil.ReadDir(testdata)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, fi := range list {
+ name := fi.Name()
+ if !fi.IsDir() && !strings.HasPrefix(name, ".") {
+ testSyntaxErrors(t, filepath.Join(testdata, name))
+ }
+ }
+}
diff --git a/src/cmd/compile/internal/syntax/testdata/sample.src b/src/cmd/compile/internal/syntax/testdata/sample.src
new file mode 100644
index 0000000000..5a2b4bf0c4
--- /dev/null
+++ b/src/cmd/compile/internal/syntax/testdata/sample.src
@@ -0,0 +1,33 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This is a sample test file illustrating the use
+// of error comments with the error test harness.
+
+package p
+
+// The following are invalid error comments; they are
+// silently ignored. The prefix must be exactly one of
+// "/* ERROR " or "// ERROR ".
+//
+/*ERROR*/
+/*ERROR foo*/
+/* ERRORfoo */
+/* ERROR foo */
+//ERROR
+// ERROR
+// ERRORfoo
+// ERROR foo
+
+// This is a valid error comment; it applies to the
+// immediately following token.
+import "math" /* ERROR unexpected comma */ ,
+
+// If there are multiple /*-style error comments before
+// the next token, only the last one is considered.
+type x = /* ERROR ignored */ /* ERROR literal 0 in type declaration */ 0
+
+// A //-style error comment matches any error position
+// on the same line.
+func () foo() // ERROR method has no receiver