github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/syntax/parser_test.go (about) 1 // Copyright 2016 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 package syntax 6 7 import ( 8 "bytes" 9 "flag" 10 "fmt" 11 "os" 12 "path/filepath" 13 "regexp" 14 "runtime" 15 "strings" 16 "sync" 17 "testing" 18 "time" 19 20 "github.com/go-asm/go/testenv" 21 ) 22 23 var ( 24 fast = flag.Bool("fast", false, "parse package files in parallel") 25 verify = flag.Bool("verify", false, "verify idempotent printing") 26 src_ = flag.String("src", "parser.go", "source file to parse") 27 skip = flag.String("skip", "", "files matching this regular expression are skipped by TestStdLib") 28 ) 29 30 func TestParse(t *testing.T) { 31 ParseFile(*src_, func(err error) { t.Error(err) }, nil, 0) 32 } 33 34 func TestVerify(t *testing.T) { 35 ast, err := ParseFile(*src_, func(err error) { t.Error(err) }, nil, 0) 36 if err != nil { 37 return // error already reported 38 } 39 verifyPrint(t, *src_, ast) 40 } 41 42 func TestStdLib(t *testing.T) { 43 if testing.Short() { 44 t.Skip("skipping test in short mode") 45 } 46 47 var skipRx *regexp.Regexp 48 if *skip != "" { 49 var err error 50 skipRx, err = regexp.Compile(*skip) 51 if err != nil { 52 t.Fatalf("invalid argument for -skip (%v)", err) 53 } 54 } 55 56 var m1 runtime.MemStats 57 runtime.ReadMemStats(&m1) 58 start := time.Now() 59 60 type parseResult struct { 61 filename string 62 lines uint 63 } 64 65 goroot := testenv.GOROOT(t) 66 67 results := make(chan parseResult) 68 go func() { 69 defer close(results) 70 for _, dir := range []string{ 71 filepath.Join(goroot, "src"), 72 filepath.Join(goroot, "misc"), 73 } { 74 if filepath.Base(dir) == "misc" { 75 // cmd/distpack deletes GOROOT/misc, so skip that directory if it isn't present. 76 // cmd/distpack also requires GOROOT/VERSION to exist, so use that to 77 // suppress false-positive skips. 78 if _, err := os.Stat(dir); os.IsNotExist(err) { 79 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil { 80 fmt.Printf("%s not present; skipping\n", dir) 81 continue 82 } 83 } 84 } 85 86 walkDirs(t, dir, func(filename string) { 87 if skipRx != nil && skipRx.MatchString(filename) { 88 // Always report skipped files since regexp 89 // typos can lead to surprising results. 90 fmt.Printf("skipping %s\n", filename) 91 return 92 } 93 if debug { 94 fmt.Printf("parsing %s\n", filename) 95 } 96 ast, err := ParseFile(filename, nil, nil, 0) 97 if err != nil { 98 t.Error(err) 99 return 100 } 101 if *verify { 102 verifyPrint(t, filename, ast) 103 } 104 results <- parseResult{filename, ast.EOF.Line()} 105 }) 106 } 107 }() 108 109 var count, lines uint 110 for res := range results { 111 count++ 112 lines += res.lines 113 if testing.Verbose() { 114 fmt.Printf("%5d %s (%d lines)\n", count, res.filename, res.lines) 115 } 116 } 117 118 dt := time.Since(start) 119 var m2 runtime.MemStats 120 runtime.ReadMemStats(&m2) 121 dm := float64(m2.TotalAlloc-m1.TotalAlloc) / 1e6 122 123 fmt.Printf("parsed %d lines (%d files) in %v (%d lines/s)\n", lines, count, dt, int64(float64(lines)/dt.Seconds())) 124 fmt.Printf("allocated %.3fMb (%.3fMb/s)\n", dm, dm/dt.Seconds()) 125 } 126 127 func walkDirs(t *testing.T, dir string, action func(string)) { 128 entries, err := os.ReadDir(dir) 129 if err != nil { 130 t.Error(err) 131 return 132 } 133 134 var files, dirs []string 135 for _, entry := range entries { 136 if entry.Type().IsRegular() { 137 if strings.HasSuffix(entry.Name(), ".go") { 138 path := filepath.Join(dir, entry.Name()) 139 files = append(files, path) 140 } 141 } else if entry.IsDir() && entry.Name() != "testdata" { 142 path := filepath.Join(dir, entry.Name()) 143 if !strings.HasSuffix(path, string(filepath.Separator)+"test") { 144 dirs = append(dirs, path) 145 } 146 } 147 } 148 149 if *fast { 150 var wg sync.WaitGroup 151 wg.Add(len(files)) 152 for _, filename := range files { 153 go func(filename string) { 154 defer wg.Done() 155 action(filename) 156 }(filename) 157 } 158 wg.Wait() 159 } else { 160 for _, filename := range files { 161 action(filename) 162 } 163 } 164 165 for _, dir := range dirs { 166 walkDirs(t, dir, action) 167 } 168 } 169 170 func verifyPrint(t *testing.T, filename string, ast1 *File) { 171 var buf1 bytes.Buffer 172 _, err := Fprint(&buf1, ast1, LineForm) 173 if err != nil { 174 panic(err) 175 } 176 bytes1 := buf1.Bytes() 177 178 ast2, err := Parse(NewFileBase(filename), &buf1, nil, nil, 0) 179 if err != nil { 180 panic(err) 181 } 182 183 var buf2 bytes.Buffer 184 _, err = Fprint(&buf2, ast2, LineForm) 185 if err != nil { 186 panic(err) 187 } 188 bytes2 := buf2.Bytes() 189 190 if bytes.Compare(bytes1, bytes2) != 0 { 191 fmt.Printf("--- %s ---\n", filename) 192 fmt.Printf("%s\n", bytes1) 193 fmt.Println() 194 195 fmt.Printf("--- %s ---\n", filename) 196 fmt.Printf("%s\n", bytes2) 197 fmt.Println() 198 199 t.Error("printed syntax trees do not match") 200 } 201 } 202 203 func TestIssue17697(t *testing.T) { 204 _, err := Parse(nil, bytes.NewReader(nil), nil, nil, 0) // return with parser error, don't panic 205 if err == nil { 206 t.Errorf("no error reported") 207 } 208 } 209 210 func TestParseFile(t *testing.T) { 211 _, err := ParseFile("", nil, nil, 0) 212 if err == nil { 213 t.Error("missing io error") 214 } 215 216 var first error 217 _, err = ParseFile("", func(err error) { 218 if first == nil { 219 first = err 220 } 221 }, nil, 0) 222 if err == nil || first == nil { 223 t.Error("missing io error") 224 } 225 if err != first { 226 t.Errorf("got %v; want first error %v", err, first) 227 } 228 } 229 230 // Make sure (PosMax + 1) doesn't overflow when converted to default 231 // type int (when passed as argument to fmt.Sprintf) on 32bit platforms 232 // (see test cases below). 233 var tooLarge int = PosMax + 1 234 235 func TestLineDirectives(t *testing.T) { 236 // valid line directives lead to a syntax error after them 237 const valid = "syntax error: package statement must be first" 238 const filename = "directives.go" 239 240 for _, test := range []struct { 241 src, msg string 242 filename string 243 line, col uint // 1-based; 0 means unknown 244 }{ 245 // ignored //line directives 246 {"//\n", valid, filename, 2, 1}, // no directive 247 {"//line\n", valid, filename, 2, 1}, // missing colon 248 {"//line foo\n", valid, filename, 2, 1}, // missing colon 249 {" //line foo:\n", valid, filename, 2, 1}, // not a line start 250 {"// line foo:\n", valid, filename, 2, 1}, // space between // and line 251 252 // invalid //line directives with one colon 253 {"//line :\n", "invalid line number: ", filename, 1, 9}, 254 {"//line :x\n", "invalid line number: x", filename, 1, 9}, 255 {"//line foo :\n", "invalid line number: ", filename, 1, 13}, 256 {"//line foo:x\n", "invalid line number: x", filename, 1, 12}, 257 {"//line foo:0\n", "invalid line number: 0", filename, 1, 12}, 258 {"//line foo:1 \n", "invalid line number: 1 ", filename, 1, 12}, 259 {"//line foo:-12\n", "invalid line number: -12", filename, 1, 12}, 260 {"//line C:foo:0\n", "invalid line number: 0", filename, 1, 14}, 261 {fmt.Sprintf("//line foo:%d\n", tooLarge), fmt.Sprintf("invalid line number: %d", tooLarge), filename, 1, 12}, 262 263 // invalid //line directives with two colons 264 {"//line ::\n", "invalid line number: ", filename, 1, 10}, 265 {"//line ::x\n", "invalid line number: x", filename, 1, 10}, 266 {"//line foo::123abc\n", "invalid line number: 123abc", filename, 1, 13}, 267 {"//line foo::0\n", "invalid line number: 0", filename, 1, 13}, 268 {"//line foo:0:1\n", "invalid line number: 0", filename, 1, 12}, 269 270 {"//line :123:0\n", "invalid column number: 0", filename, 1, 13}, 271 {"//line foo:123:0\n", "invalid column number: 0", filename, 1, 16}, 272 {fmt.Sprintf("//line foo:10:%d\n", tooLarge), fmt.Sprintf("invalid column number: %d", tooLarge), filename, 1, 15}, 273 274 // effect of valid //line directives on lines 275 {"//line foo:123\n foo", valid, "foo", 123, 0}, 276 {"//line foo:123\n foo", valid, " foo", 123, 0}, 277 {"//line foo:123\n//line bar:345\nfoo", valid, "bar", 345, 0}, 278 {"//line C:foo:123\n", valid, "C:foo", 123, 0}, 279 {"//line /src/a/a.go:123\n foo", valid, "/src/a/a.go", 123, 0}, 280 {"//line :x:1\n", valid, ":x", 1, 0}, 281 {"//line foo ::1\n", valid, "foo :", 1, 0}, 282 {"//line foo:123abc:1\n", valid, "foo:123abc", 1, 0}, 283 {"//line foo :123:1\n", valid, "foo ", 123, 1}, 284 {"//line ::123\n", valid, ":", 123, 0}, 285 286 // effect of valid //line directives on columns 287 {"//line :x:1:10\n", valid, ":x", 1, 10}, 288 {"//line foo ::1:2\n", valid, "foo :", 1, 2}, 289 {"//line foo:123abc:1:1000\n", valid, "foo:123abc", 1, 1000}, 290 {"//line foo :123:1000\n\n", valid, "foo ", 124, 1}, 291 {"//line ::123:1234\n", valid, ":", 123, 1234}, 292 293 // //line directives with omitted filenames lead to empty filenames 294 {"//line :10\n", valid, "", 10, 0}, 295 {"//line :10:20\n", valid, filename, 10, 20}, 296 {"//line bar:1\n//line :10\n", valid, "", 10, 0}, 297 {"//line bar:1\n//line :10:20\n", valid, "bar", 10, 20}, 298 299 // ignored /*line directives 300 {"/**/", valid, filename, 1, 5}, // no directive 301 {"/*line*/", valid, filename, 1, 9}, // missing colon 302 {"/*line foo*/", valid, filename, 1, 13}, // missing colon 303 {" //line foo:*/", valid, filename, 1, 16}, // not a line start 304 {"/* line foo:*/", valid, filename, 1, 16}, // space between // and line 305 306 // invalid /*line directives with one colon 307 {"/*line :*/", "invalid line number: ", filename, 1, 9}, 308 {"/*line :x*/", "invalid line number: x", filename, 1, 9}, 309 {"/*line foo :*/", "invalid line number: ", filename, 1, 13}, 310 {"/*line foo:x*/", "invalid line number: x", filename, 1, 12}, 311 {"/*line foo:0*/", "invalid line number: 0", filename, 1, 12}, 312 {"/*line foo:1 */", "invalid line number: 1 ", filename, 1, 12}, 313 {"/*line C:foo:0*/", "invalid line number: 0", filename, 1, 14}, 314 {fmt.Sprintf("/*line foo:%d*/", tooLarge), fmt.Sprintf("invalid line number: %d", tooLarge), filename, 1, 12}, 315 316 // invalid /*line directives with two colons 317 {"/*line ::*/", "invalid line number: ", filename, 1, 10}, 318 {"/*line ::x*/", "invalid line number: x", filename, 1, 10}, 319 {"/*line foo::123abc*/", "invalid line number: 123abc", filename, 1, 13}, 320 {"/*line foo::0*/", "invalid line number: 0", filename, 1, 13}, 321 {"/*line foo:0:1*/", "invalid line number: 0", filename, 1, 12}, 322 323 {"/*line :123:0*/", "invalid column number: 0", filename, 1, 13}, 324 {"/*line foo:123:0*/", "invalid column number: 0", filename, 1, 16}, 325 {fmt.Sprintf("/*line foo:10:%d*/", tooLarge), fmt.Sprintf("invalid column number: %d", tooLarge), filename, 1, 15}, 326 327 // effect of valid /*line directives on lines 328 {"/*line foo:123*/ foo", valid, "foo", 123, 0}, 329 {"/*line foo:123*/\n//line bar:345\nfoo", valid, "bar", 345, 0}, 330 {"/*line C:foo:123*/", valid, "C:foo", 123, 0}, 331 {"/*line /src/a/a.go:123*/ foo", valid, "/src/a/a.go", 123, 0}, 332 {"/*line :x:1*/", valid, ":x", 1, 0}, 333 {"/*line foo ::1*/", valid, "foo :", 1, 0}, 334 {"/*line foo:123abc:1*/", valid, "foo:123abc", 1, 0}, 335 {"/*line foo :123:10*/", valid, "foo ", 123, 10}, 336 {"/*line ::123*/", valid, ":", 123, 0}, 337 338 // effect of valid /*line directives on columns 339 {"/*line :x:1:10*/", valid, ":x", 1, 10}, 340 {"/*line foo ::1:2*/", valid, "foo :", 1, 2}, 341 {"/*line foo:123abc:1:1000*/", valid, "foo:123abc", 1, 1000}, 342 {"/*line foo :123:1000*/\n", valid, "foo ", 124, 1}, 343 {"/*line ::123:1234*/", valid, ":", 123, 1234}, 344 345 // /*line directives with omitted filenames lead to the previously used filenames 346 {"/*line :10*/", valid, "", 10, 0}, 347 {"/*line :10:20*/", valid, filename, 10, 20}, 348 {"//line bar:1\n/*line :10*/", valid, "", 10, 0}, 349 {"//line bar:1\n/*line :10:20*/", valid, "bar", 10, 20}, 350 } { 351 base := NewFileBase(filename) 352 _, err := Parse(base, strings.NewReader(test.src), nil, nil, 0) 353 if err == nil { 354 t.Errorf("%s: no error reported", test.src) 355 continue 356 } 357 perr, ok := err.(Error) 358 if !ok { 359 t.Errorf("%s: got %v; want parser error", test.src, err) 360 continue 361 } 362 if msg := perr.Msg; msg != test.msg { 363 t.Errorf("%s: got msg = %q; want %q", test.src, msg, test.msg) 364 } 365 366 pos := perr.Pos 367 if filename := pos.RelFilename(); filename != test.filename { 368 t.Errorf("%s: got filename = %q; want %q", test.src, filename, test.filename) 369 } 370 if line := pos.RelLine(); line != test.line { 371 t.Errorf("%s: got line = %d; want %d", test.src, line, test.line) 372 } 373 if col := pos.RelCol(); col != test.col { 374 t.Errorf("%s: got col = %d; want %d", test.src, col, test.col) 375 } 376 } 377 } 378 379 // Test that typical uses of UnpackListExpr don't allocate. 380 func TestUnpackListExprAllocs(t *testing.T) { 381 var x Expr = NewName(Pos{}, "x") 382 allocs := testing.AllocsPerRun(1000, func() { 383 list := UnpackListExpr(x) 384 if len(list) != 1 || list[0] != x { 385 t.Fatalf("unexpected result") 386 } 387 }) 388 389 if allocs > 0 { 390 errorf := t.Errorf 391 if testenv.OptimizationOff() { 392 errorf = t.Logf // noopt builder disables inlining 393 } 394 errorf("UnpackListExpr allocated %v times", allocs) 395 } 396 }