]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile/internal/syntax: implement regression test harness for syntax errors
authorRobert Griesemer <gri@golang.org>
Thu, 18 Jan 2018 23:03:38 +0000 (15:03 -0800)
committerRobert Griesemer <gri@golang.org>
Mon, 12 Feb 2018 22:58:06 +0000 (22:58 +0000)
R=go1.11

Fixes #20800.

Change-Id: Ifea273521d42a543a43da2f655ace7c295650e30
Reviewed-on: https://go-review.googlesource.com/88335
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
src/cmd/compile/fmt_test.go
src/cmd/compile/internal/syntax/error_test.go [new file with mode: 0644]
src/cmd/compile/internal/syntax/testdata/sample.src [new file with mode: 0644]

index 7342b5492bf59c79f2ad62dedf7bf140620d6f39..ca6e6d0b691168916c765e286c8c7f87b9ec41d9 100644 (file)
@@ -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 (file)
index 0000000..72b1ad6
--- /dev/null
@@ -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 (file)
index 0000000..5a2b4bf
--- /dev/null
@@ -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