1
0
mirror of https://github.com/golang/go synced 2024-11-19 23:34:40 -07:00

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>
This commit is contained in:
Robert Griesemer 2018-01-18 15:03:38 -08:00
parent b890688986
commit 52fcac3b7c
3 changed files with 225 additions and 0 deletions

View File

@ -656,6 +656,7 @@ var knownFormats = map[string]string{
"cmd/compile/internal/syntax.Node %T": "", "cmd/compile/internal/syntax.Node %T": "",
"cmd/compile/internal/syntax.Operator %d": "", "cmd/compile/internal/syntax.Operator %d": "",
"cmd/compile/internal/syntax.Operator %s": "", "cmd/compile/internal/syntax.Operator %s": "",
"cmd/compile/internal/syntax.position %s": "",
"cmd/compile/internal/syntax.token %d": "", "cmd/compile/internal/syntax.token %d": "",
"cmd/compile/internal/syntax.token %q": "", "cmd/compile/internal/syntax.token %q": "",
"cmd/compile/internal/syntax.token %s": "", "cmd/compile/internal/syntax.token %s": "",

View File

@ -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))
}
}
}

View File

@ -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