Source file src/cmd/compile/internal/syntax/error_test.go

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // This file implements a regression test harness for syntax errors.
     6  // The files in the testdata directory are parsed and the reported
     7  // errors are compared against the errors declared in those files.
     8  //
     9  // Errors are declared in place in the form of "error comments",
    10  // just before (or on the same line as) the offending token.
    11  //
    12  // Error comments must be of the form // ERROR rx or /* ERROR rx */
    13  // where rx is a regular expression that matches the reported error
    14  // message. The rx text comprises the comment text after "ERROR ",
    15  // with any white space around it stripped.
    16  //
    17  // If the line comment form is used, the reported error's line must
    18  // match the line of the error comment.
    19  //
    20  // If the regular comment form is used, the reported error's position
    21  // must match the position of the token immediately following the
    22  // error comment. Thus, /* ERROR ... */ comments should appear
    23  // immediately before the position where the error is reported.
    24  //
    25  // Currently, the test harness only supports one error comment per
    26  // token. If multiple error comments appear before a token, only
    27  // the last one is considered.
    28  
    29  package syntax
    30  
    31  import (
    32  	"flag"
    33  	"fmt"
    34  	"internal/testenv"
    35  	"io/ioutil"
    36  	"os"
    37  	"path/filepath"
    38  	"regexp"
    39  	"sort"
    40  	"strings"
    41  	"testing"
    42  )
    43  
    44  const testdata = "testdata" // directory containing test files
    45  
    46  var print = flag.Bool("print", false, "only print errors")
    47  
    48  // A position represents a source position in the current file.
    49  type position struct {
    50  	line, col uint
    51  }
    52  
    53  func (pos position) String() string {
    54  	return fmt.Sprintf("%d:%d", pos.line, pos.col)
    55  }
    56  
    57  func sortedPositions(m map[position]string) []position {
    58  	list := make([]position, len(m))
    59  	i := 0
    60  	for pos := range m {
    61  		list[i] = pos
    62  		i++
    63  	}
    64  	sort.Slice(list, func(i, j int) bool {
    65  		a, b := list[i], list[j]
    66  		return a.line < b.line || a.line == b.line && a.col < b.col
    67  	})
    68  	return list
    69  }
    70  
    71  // declaredErrors returns a map of source positions to error
    72  // patterns, extracted from error comments in the given file.
    73  // Error comments in the form of line comments use col = 0
    74  // in their position.
    75  func declaredErrors(t *testing.T, filename string) map[position]string {
    76  	f, err := os.Open(filename)
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  	defer f.Close()
    81  
    82  	declared := make(map[position]string)
    83  
    84  	var s scanner
    85  	var pattern string
    86  	s.init(f, func(line, col uint, msg string) {
    87  		// errors never start with '/' so they are automatically excluded here
    88  		switch {
    89  		case strings.HasPrefix(msg, "// ERROR "):
    90  			// we can't have another comment on the same line - just add it
    91  			declared[position{s.line, 0}] = strings.TrimSpace(msg[9:])
    92  		case strings.HasPrefix(msg, "/* ERROR "):
    93  			// we may have more comments before the next token - collect them
    94  			pattern = strings.TrimSpace(msg[9 : len(msg)-2])
    95  		}
    96  	}, comments)
    97  
    98  	// consume file
    99  	for {
   100  		s.next()
   101  		if pattern != "" {
   102  			declared[position{s.line, s.col}] = pattern
   103  			pattern = ""
   104  		}
   105  		if s.tok == _EOF {
   106  			break
   107  		}
   108  	}
   109  
   110  	return declared
   111  }
   112  
   113  func testSyntaxErrors(t *testing.T, filename string) {
   114  	declared := declaredErrors(t, filename)
   115  	if *print {
   116  		fmt.Println("Declared errors:")
   117  		for _, pos := range sortedPositions(declared) {
   118  			fmt.Printf("%s:%s: %s\n", filename, pos, declared[pos])
   119  		}
   120  
   121  		fmt.Println()
   122  		fmt.Println("Reported errors:")
   123  	}
   124  
   125  	f, err := os.Open(filename)
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  	defer f.Close()
   130  
   131  	var mode Mode
   132  	if strings.HasSuffix(filename, ".go2") {
   133  		mode = AllowGenerics
   134  	}
   135  	ParseFile(filename, func(err error) {
   136  		e, ok := err.(Error)
   137  		if !ok {
   138  			return
   139  		}
   140  
   141  		if *print {
   142  			fmt.Println(err)
   143  			return
   144  		}
   145  
   146  		orig := position{e.Pos.Line(), e.Pos.Col()}
   147  		pos := orig
   148  		pattern, found := declared[pos]
   149  		if !found {
   150  			// try line comment (only line must match)
   151  			pos = position{e.Pos.Line(), 0}
   152  			pattern, found = declared[pos]
   153  		}
   154  		if found {
   155  			rx, err := regexp.Compile(pattern)
   156  			if err != nil {
   157  				t.Errorf("%s:%s: %v", filename, pos, err)
   158  				return
   159  			}
   160  			if match := rx.MatchString(e.Msg); !match {
   161  				t.Errorf("%s:%s: %q does not match %q", filename, pos, e.Msg, pattern)
   162  				return
   163  			}
   164  			// we have a match - eliminate this error
   165  			delete(declared, pos)
   166  		} else {
   167  			t.Errorf("%s:%s: unexpected error: %s", filename, orig, e.Msg)
   168  		}
   169  	}, nil, mode)
   170  
   171  	if *print {
   172  		fmt.Println()
   173  		return // we're done
   174  	}
   175  
   176  	// report expected but not reported errors
   177  	for pos, pattern := range declared {
   178  		t.Errorf("%s:%s: missing error: %s", filename, pos, pattern)
   179  	}
   180  }
   181  
   182  func TestSyntaxErrors(t *testing.T) {
   183  	testenv.MustHaveGoBuild(t) // we need access to source (testdata)
   184  
   185  	list, err := ioutil.ReadDir(testdata)
   186  	if err != nil {
   187  		t.Fatal(err)
   188  	}
   189  	for _, fi := range list {
   190  		name := fi.Name()
   191  		if !fi.IsDir() && !strings.HasPrefix(name, ".") {
   192  			testSyntaxErrors(t, filepath.Join(testdata, name))
   193  		}
   194  	}
   195  }
   196  

View as plain text