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