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 }