github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/go/types/check_test.go (about) 1 // Copyright 2011 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 typechecker test harness. The packages specified 6 // in tests are typechecked. Error messages reported by the typechecker are 7 // compared against the error messages expected in the test files. 8 // 9 // Expected errors are indicated in the test files by putting a comment 10 // of the form /* ERROR "rx" */ immediately following an offending token. 11 // The harness will verify that an error matching the regular expression 12 // rx is reported at that source position. Consecutive comments may be 13 // used to indicate multiple errors for the same token position. 14 // 15 // For instance, the following test file indicates that a "not declared" 16 // error should be reported for the undeclared variable x: 17 // 18 // package p 19 // func f() { 20 // _ = x /* ERROR "not declared" */ + 1 21 // } 22 23 // TODO(gri) Also collect strict mode errors of the form /* STRICT ... */ 24 // and test against strict mode. 25 26 package types_test 27 28 import ( 29 "flag" 30 "go/ast" 31 "go/importer" 32 "go/parser" 33 "go/scanner" 34 "go/token" 35 "internal/testenv" 36 "io/ioutil" 37 "regexp" 38 "strings" 39 "testing" 40 41 . "go/types" 42 ) 43 44 var ( 45 listErrors = flag.Bool("errlist", false, "list errors") 46 testFiles = flag.String("files", "", "space-separated list of test files") 47 ) 48 49 // The test filenames do not end in .go so that they are invisible 50 // to gofmt since they contain comments that must not change their 51 // positions relative to surrounding tokens. 52 53 // Each tests entry is list of files belonging to the same package. 54 var tests = [][]string{ 55 {"testdata/errors.src"}, 56 {"testdata/importdecl0a.src", "testdata/importdecl0b.src"}, 57 {"testdata/importdecl1a.src", "testdata/importdecl1b.src"}, 58 {"testdata/importC.src"}, // special handling in checkFiles 59 {"testdata/cycles.src"}, 60 {"testdata/cycles1.src"}, 61 {"testdata/cycles2.src"}, 62 {"testdata/cycles3.src"}, 63 {"testdata/cycles4.src"}, 64 {"testdata/cycles5.src"}, 65 {"testdata/init0.src"}, 66 {"testdata/init1.src"}, 67 {"testdata/init2.src"}, 68 {"testdata/decls0.src"}, 69 {"testdata/decls1.src"}, 70 {"testdata/decls2a.src", "testdata/decls2b.src"}, 71 {"testdata/decls3.src"}, 72 {"testdata/decls4.src"}, 73 {"testdata/decls5.src"}, 74 {"testdata/const0.src"}, 75 {"testdata/const1.src"}, 76 {"testdata/constdecl.src"}, 77 {"testdata/vardecl.src"}, 78 {"testdata/expr0.src"}, 79 {"testdata/expr1.src"}, 80 {"testdata/expr2.src"}, 81 {"testdata/expr3.src"}, 82 {"testdata/methodsets.src"}, 83 {"testdata/shifts.src"}, 84 {"testdata/builtins.src"}, 85 {"testdata/conversions.src"}, 86 {"testdata/conversions2.src"}, 87 {"testdata/stmt0.src"}, 88 {"testdata/stmt1.src"}, 89 {"testdata/gotos.src"}, 90 {"testdata/labels.src"}, 91 {"testdata/issues.src"}, 92 {"testdata/blank.src"}, 93 {"testdata/issue25008b.src", "testdata/issue25008a.src"}, // order (b before a) is crucial! 94 {"testdata/issue26390.src"}, // stand-alone test to ensure case is triggered 95 {"testdata/issue23203a.src"}, 96 {"testdata/issue23203b.src"}, 97 {"testdata/issue28251.src"}, 98 } 99 100 var fset = token.NewFileSet() 101 102 // Positioned errors are of the form filename:line:column: message . 103 var posMsgRx = regexp.MustCompile(`^(.*:[0-9]+:[0-9]+): *(.*)`) 104 105 // splitError splits an error's error message into a position string 106 // and the actual error message. If there's no position information, 107 // pos is the empty string, and msg is the entire error message. 108 // 109 func splitError(err error) (pos, msg string) { 110 msg = err.Error() 111 if m := posMsgRx.FindStringSubmatch(msg); len(m) == 3 { 112 pos = m[1] 113 msg = m[2] 114 } 115 return 116 } 117 118 func parseFiles(t *testing.T, filenames []string) ([]*ast.File, []error) { 119 var files []*ast.File 120 var errlist []error 121 for _, filename := range filenames { 122 file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) 123 if file == nil { 124 t.Fatalf("%s: %s", filename, err) 125 } 126 files = append(files, file) 127 if err != nil { 128 if list, _ := err.(scanner.ErrorList); len(list) > 0 { 129 for _, err := range list { 130 errlist = append(errlist, err) 131 } 132 } else { 133 errlist = append(errlist, err) 134 } 135 } 136 } 137 return files, errlist 138 } 139 140 // ERROR comments must start with text `ERROR "rx"` or `ERROR rx` where 141 // rx is a regular expression that matches the expected error message. 142 // Space around "rx" or rx is ignored. Use the form `ERROR HERE "rx"` 143 // for error messages that are located immediately after rather than 144 // at a token's position. 145 // 146 var errRx = regexp.MustCompile(`^ *ERROR *(HERE)? *"?([^"]*)"?`) 147 148 // errMap collects the regular expressions of ERROR comments found 149 // in files and returns them as a map of error positions to error messages. 150 // 151 func errMap(t *testing.T, testname string, files []*ast.File) map[string][]string { 152 // map of position strings to lists of error message patterns 153 errmap := make(map[string][]string) 154 155 for _, file := range files { 156 filename := fset.Position(file.Package).Filename 157 src, err := ioutil.ReadFile(filename) 158 if err != nil { 159 t.Fatalf("%s: could not read %s", testname, filename) 160 } 161 162 var s scanner.Scanner 163 s.Init(fset.AddFile(filename, -1, len(src)), src, nil, scanner.ScanComments) 164 var prev token.Pos // position of last non-comment, non-semicolon token 165 var here token.Pos // position immediately after the token at position prev 166 167 scanFile: 168 for { 169 pos, tok, lit := s.Scan() 170 switch tok { 171 case token.EOF: 172 break scanFile 173 case token.COMMENT: 174 if lit[1] == '*' { 175 lit = lit[:len(lit)-2] // strip trailing */ 176 } 177 if s := errRx.FindStringSubmatch(lit[2:]); len(s) == 3 { 178 pos := prev 179 if s[1] == "HERE" { 180 pos = here 181 } 182 p := fset.Position(pos).String() 183 errmap[p] = append(errmap[p], strings.TrimSpace(s[2])) 184 } 185 case token.SEMICOLON: 186 // ignore automatically inserted semicolon 187 if lit == "\n" { 188 continue scanFile 189 } 190 fallthrough 191 default: 192 prev = pos 193 var l int // token length 194 if tok.IsLiteral() { 195 l = len(lit) 196 } else { 197 l = len(tok.String()) 198 } 199 here = prev + token.Pos(l) 200 } 201 } 202 } 203 204 return errmap 205 } 206 207 func eliminate(t *testing.T, errmap map[string][]string, errlist []error) { 208 for _, err := range errlist { 209 pos, gotMsg := splitError(err) 210 list := errmap[pos] 211 index := -1 // list index of matching message, if any 212 // we expect one of the messages in list to match the error at pos 213 for i, wantRx := range list { 214 rx, err := regexp.Compile(wantRx) 215 if err != nil { 216 t.Errorf("%s: %v", pos, err) 217 continue 218 } 219 if rx.MatchString(gotMsg) { 220 index = i 221 break 222 } 223 } 224 if index >= 0 { 225 // eliminate from list 226 if n := len(list) - 1; n > 0 { 227 // not the last entry - swap in last element and shorten list by 1 228 list[index] = list[n] 229 errmap[pos] = list[:n] 230 } else { 231 // last entry - remove list from map 232 delete(errmap, pos) 233 } 234 } else { 235 t.Errorf("%s: no error expected: %q", pos, gotMsg) 236 } 237 } 238 } 239 240 func checkFiles(t *testing.T, testfiles []string) { 241 // parse files and collect parser errors 242 files, errlist := parseFiles(t, testfiles) 243 244 pkgName := "<no package>" 245 if len(files) > 0 { 246 pkgName = files[0].Name.Name 247 } 248 249 if *listErrors && len(errlist) > 0 { 250 t.Errorf("--- %s:", pkgName) 251 for _, err := range errlist { 252 t.Error(err) 253 } 254 } 255 256 // typecheck and collect typechecker errors 257 var conf Config 258 // special case for importC.src 259 if len(testfiles) == 1 && testfiles[0] == "testdata/importC.src" { 260 conf.FakeImportC = true 261 } 262 conf.Importer = importer.Default() 263 conf.Error = func(err error) { 264 if *listErrors { 265 t.Error(err) 266 return 267 } 268 // Ignore secondary error messages starting with "\t"; 269 // they are clarifying messages for a primary error. 270 if !strings.Contains(err.Error(), ": \t") { 271 errlist = append(errlist, err) 272 } 273 } 274 conf.Check(pkgName, fset, files, nil) 275 276 if *listErrors { 277 return 278 } 279 280 // match and eliminate errors; 281 // we are expecting the following errors 282 errmap := errMap(t, pkgName, files) 283 eliminate(t, errmap, errlist) 284 285 // there should be no expected errors left 286 if len(errmap) > 0 { 287 t.Errorf("--- %s: %d source positions with expected (but not reported) errors:", pkgName, len(errmap)) 288 for pos, list := range errmap { 289 for _, rx := range list { 290 t.Errorf("%s: %q", pos, rx) 291 } 292 } 293 } 294 } 295 296 func TestCheck(t *testing.T) { 297 testenv.MustHaveGoBuild(t) 298 299 // Declare builtins for testing. 300 DefPredeclaredTestFuncs() 301 302 // If explicit test files are specified, only check those. 303 if files := *testFiles; files != "" { 304 checkFiles(t, strings.Split(files, " ")) 305 return 306 } 307 308 // Otherwise, run all the tests. 309 for _, files := range tests { 310 checkFiles(t, files) 311 } 312 }