mirror of
https://github.com/golang/go
synced 2024-10-02 14:38:33 -06: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:
parent
b890688986
commit
52fcac3b7c
@ -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": "",
|
||||
|
191
src/cmd/compile/internal/syntax/error_test.go
Normal file
191
src/cmd/compile/internal/syntax/error_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
33
src/cmd/compile/internal/syntax/testdata/sample.src
vendored
Normal file
33
src/cmd/compile/internal/syntax/testdata/sample.src
vendored
Normal 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
|
Loading…
Reference in New Issue
Block a user