github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/compile/internal/syntax/error_test.go (about)

     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  	"github.com/gagliardetto/golang-go/not-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  	ParseFile(filename, func(err error) {
   132  		e, ok := err.(Error)
   133  		if !ok {
   134  			return
   135  		}
   136  
   137  		if *print {
   138  			fmt.Println(err)
   139  			return
   140  		}
   141  
   142  		orig := position{e.Pos.Line(), e.Pos.Col()}
   143  		pos := orig
   144  		pattern, found := declared[pos]
   145  		if !found {
   146  			// try line comment (only line must match)
   147  			pos = position{e.Pos.Line(), 0}
   148  			pattern, found = declared[pos]
   149  		}
   150  		if found {
   151  			rx, err := regexp.Compile(pattern)
   152  			if err != nil {
   153  				t.Errorf("%s: %v", pos, err)
   154  				return
   155  			}
   156  			if match := rx.MatchString(e.Msg); !match {
   157  				t.Errorf("%s: %q does not match %q", pos, e.Msg, pattern)
   158  				return
   159  			}
   160  			// we have a match - eliminate this error
   161  			delete(declared, pos)
   162  		} else {
   163  			t.Errorf("%s: unexpected error: %s", orig, e.Msg)
   164  		}
   165  	}, nil, 0)
   166  
   167  	if *print {
   168  		fmt.Println()
   169  		return // we're done
   170  	}
   171  
   172  	// report expected but not reported errors
   173  	for pos, pattern := range declared {
   174  		t.Errorf("%s: missing error: %s", pos, pattern)
   175  	}
   176  }
   177  
   178  func TestSyntaxErrors(t *testing.T) {
   179  	testenv.MustHaveGoBuild(t) // we need access to source (testdata)
   180  
   181  	list, err := ioutil.ReadDir(testdata)
   182  	if err != nil {
   183  		t.Fatal(err)
   184  	}
   185  	for _, fi := range list {
   186  		name := fi.Name()
   187  		if !fi.IsDir() && !strings.HasPrefix(name, ".") {
   188  			testSyntaxErrors(t, filepath.Join(testdata, name))
   189  		}
   190  	}
   191  }