github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/tests/gorepo/run.go (about) 1 //go:build ignore 2 // +build ignore 3 4 // skip 5 6 // Copyright 2012 The Go Authors. All rights reserved. 7 // Use of this source code is governed by a BSD-style 8 // license that can be found in the LICENSE file. 9 10 // Run runs tests in the test directory. 11 // 12 // To run manually with summary, verbose output, and full stack traces of of known failures: 13 // 14 // go run run.go -summary -v -show_known_fails 15 // 16 // TODO(bradfitz): docs of some sort, once we figure out how we're changing 17 // headers of files 18 package main 19 20 import ( 21 "bytes" 22 "errors" 23 "flag" 24 "fmt" 25 "go/build/constraint" 26 "hash/fnv" 27 "io" 28 "log" 29 "os" 30 "os/exec" 31 "path" 32 "path/filepath" 33 "regexp" 34 "runtime" 35 "sort" 36 "strconv" 37 "strings" 38 "time" 39 "unicode" 40 41 gbuild "github.com/gopherjs/gopherjs/build" 42 ) 43 44 // ----------------------------------------------------------------------------- 45 // GOPHERJS: Known test fails for GopherJS compiler. 46 // 47 // TODO: Reduce these to zero or as close as possible. 48 var knownFails = map[string]failReason{ 49 "fixedbugs/bug114.go": {desc: "fixedbugs/bug114.go:15:27: B32 (untyped int constant 4294967295) overflows int"}, 50 "fixedbugs/bug242.go": {desc: "bad map check 13 false false Error: fail"}, 51 "fixedbugs/bug260.go": {desc: "maybe unsupportedFeature, pointer arithm"}, 52 "fixedbugs/bug262.go": {desc: "Error: fail"}, 53 "fixedbugs/bug273.go": {desc: "BUG: didn't crash: badcap1"}, 54 "fixedbugs/bug328.go": {desc: "incorrect output"}, 55 "fixedbugs/bug347.go": {desc: "BUG: bug347: cannot find caller"}, 56 "fixedbugs/bug348.go": {desc: "BUG: bug348: cannot find caller"}, 57 "fixedbugs/bug352.go": {desc: "BUG: bug352 struct{}"}, 58 "fixedbugs/bug409.go": {desc: "1 2 3 4"}, 59 "fixedbugs/bug433.go": {desc: "Error: [object Object]"}, 60 "fixedbugs/issue11656.go": {desc: "Error: Native function not implemented: runtime/debug.setPanicOnFault"}, 61 "fixedbugs/issue4085b.go": {desc: "Error: got panic JavaScript error: Invalid typed array length, want len out of range"}, 62 "fixedbugs/issue4316.go": {desc: "Error: runtime error: invalid memory address or nil pointer dereference"}, 63 "fixedbugs/issue4388.go": {desc: "Error: expected <autogenerated>:1 have anonymous function:0"}, 64 "fixedbugs/issue4562.go": {desc: "Error: cannot find issue4562.go on stack"}, 65 "fixedbugs/issue4620.go": {desc: "map[0:1 1:2], Error: m[i] != 2"}, 66 "fixedbugs/issue5856.go": {category: requiresSourceMapSupport}, 67 "fixedbugs/issue6899.go": {desc: "incorrect output -0"}, 68 "fixedbugs/issue7550.go": {category: neverTerminates, desc: "FATAL ERROR: invalid table size Allocation failed - process out of memory"}, 69 "fixedbugs/issue7690.go": {desc: "Error: runtime error: slice bounds out of range"}, 70 "fixedbugs/issue8047b.go": {desc: "Error: [object Object]"}, 71 72 // Failing due to use of os/exec.Command, which is unsupported. Now skipped via !nacl build tag. 73 /*"fixedbugs/bug248.go": {desc: "os/exec.Command unsupported"}, 74 "fixedbugs/bug302.go": {desc: "os/exec.Command unsupported"}, 75 "fixedbugs/bug345.go": {desc: "os/exec.Command unsupported"}, 76 "fixedbugs/bug369.go": {desc: "os/exec.Command unsupported"}, 77 "fixedbugs/bug429_run.go": {desc: "os/exec.Command unsupported"}, 78 "fixedbugs/issue9862_run.go": {desc: "os/exec.Command unsupported"},*/ 79 "fixedbugs/issue10607.go": {desc: "os/exec.Command unsupported"}, 80 "fixedbugs/issue13268.go": {desc: "os/exec.Command unsupported"}, 81 "fixedbugs/issue14636.go": {desc: "os/exec.Command unsupported"}, 82 83 // These are new tests in Go 1.7. 84 "fixedbugs/issue14646.go": {category: unsureIfGopherJSSupportsThisFeature, desc: "tests runtime.Caller behavior in a deferred func in SSA backend... does GopherJS even support runtime.Caller?"}, 85 "fixedbugs/issue15039.go": {desc: "valid bug but deal with after Go 1.7 support is out? it's likely not a regression"}, 86 "fixedbugs/issue15281.go": {desc: "also looks valid but deal with after Go 1.7 support is out? it's likely not a regression"}, 87 88 // These are new tests in Go 1.8. 89 "fixedbugs/issue17381.go": {category: unsureIfGopherJSSupportsThisFeature, desc: "tests runtime.{Callers,FuncForPC} behavior in a deferred func with garbage on stack... does GopherJS even support runtime.{Callers,FuncForPC}?"}, 90 "fixedbugs/issue18149.go": {desc: "//line directives with filenames are not correctly parsed, see https://github.com/gopherjs/gopherjs/issues/553."}, 91 92 // These are new tests in Go 1.9. 93 "fixedbugs/issue19182.go": {category: neverTerminates, desc: "needs GOMAXPROCS=2"}, 94 "fixedbugs/issue19040.go": {desc: `panicwrap error text: 95 "runtime error: invalid memory address or nil pointer dereference" 96 want: 97 "value method main.T.F called using nil *T pointer"`}, 98 "fixedbugs/issue19246.go": {desc: "expected nil pointer dereference panic"}, // Issue https://golang.org/issues/19246: Failed to evaluate some zero-sized values when converting them to interfaces. 99 100 // These are new tests in Go 1.10. 101 "fixedbugs/issue21879.go": {desc: "incorrect output related to runtime.Callers, runtime.CallersFrames, etc."}, 102 "fixedbugs/issue21887.go": {desc: "incorrect output (although within spec, not worth fixing) for println(^uint64(0)). got: { '$high': 4294967295, '$low': 4294967295, '$val': [Circular] } want: 18446744073709551615"}, 103 "fixedbugs/issue22660.go": {category: notApplicable, desc: "test of gc compiler, uses os/exec.Command"}, 104 "fixedbugs/issue23305.go": {desc: "GopherJS fails to compile println(0xffffffff), maybe because 32-bit arch"}, 105 106 // These are new tests in Go 1.11. 107 "fixedbugs/issue21221.go": {category: usesUnsupportedPackage, desc: "uses unsafe package and compares nil pointers"}, 108 "fixedbugs/issue22662.go": {desc: "line directives not fully working. Error: got /private/var/folders/b8/66r1c5856mqds1mrf2tjtq8w0000gn/T:1; want ??:1"}, 109 "fixedbugs/issue22662b.go": {category: usesUnsupportedPackage, desc: "os/exec.Command unsupported"}, 110 "fixedbugs/issue23188.go": {desc: "incorrect order of evaluation of index operations"}, 111 "fixedbugs/issue24547.go": {desc: "incorrect computing method sets with shadowed methods"}, 112 113 // These are new tests in Go 1.12. 114 "fixedbugs/issue23837.go": {desc: "missing panic on nil pointer-to-empty-struct dereference"}, 115 "fixedbugs/issue27201.go": {desc: "incorrect stack trace for nil dereference in inlined function"}, 116 "fixedbugs/issue27518b.go": {desc: "sigpanic can make dead pointer live again"}, 117 "fixedbugs/issue29190.go": {desc: "append does not fail when length overflows", category: neverTerminates}, 118 119 // These are new tests in Go 1.12.9. 120 "fixedbugs/issue30977.go": {category: neverTerminates, desc: "does for { runtime.GC() }"}, 121 "fixedbugs/issue32477.go": {category: notApplicable, desc: "uses runtime.SetFinalizer and runtime.GC"}, 122 123 // These are new tests in Go 1.13-1.16. 124 "fixedbugs/issue19113.go": {category: lowLevelRuntimeDifference, desc: "JavaScript bit shifts by negative amount don't cause an exception"}, 125 "fixedbugs/issue24491a.go": {category: notApplicable, desc: "tests interaction between unsafe and GC; uses runtime.SetFinalizer()"}, 126 "fixedbugs/issue24491b.go": {category: notApplicable, desc: "tests interaction between unsafe and GC; uses runtime.SetFinalizer()"}, 127 "fixedbugs/issue29504.go": {category: notApplicable, desc: "requires source map support beyond what GopherJS currently provides"}, 128 // This test incorrectly passes because main function's name is returned as "main" and not "main.main". Even number of bugs cancel each other out ¯\_(ツ)_/¯ 129 // "fixedbugs/issue29735.go": {category: usesUnsupportedPackage, desc: "GopherJS only supports runtime.FuncForPC() with position counters previously returned by runtime.Callers() or runtime.Caller()"}, 130 "fixedbugs/issue30116.go": {desc: "GopherJS doesn't specify the array/slice index selector in the out-of-bounds message"}, 131 "fixedbugs/issue30116u.go": {desc: "GopherJS doesn't specify the array/slice index selector in the out-of-bounds message"}, 132 "fixedbugs/issue34395.go": {category: neverTerminates, desc: "https://github.com/gopherjs/gopherjs/issues/1007"}, 133 "fixedbugs/issue35027.go": {category: usesUnsupportedPackage, desc: "uses unsupported conversion to reflect.SliceHeader and -gcflags=-d=checkptr"}, 134 "fixedbugs/issue35576.go": {category: lowLevelRuntimeDifference, desc: "GopherJS print/println format for floats differs from Go's"}, 135 "fixedbugs/issue40917.go": {category: notApplicable, desc: "uses pointer arithmetic and unsupported flag -gcflags=-d=checkptr"}, 136 137 // These are new tests in Go 1.17 138 "fixedbugs/issue45045.go": {category: notApplicable, desc: "GC related, not relevant to GopherJS"}, 139 "fixedbugs/issue5493.go": {category: notApplicable, desc: "GC related, not relevant to GopherJS"}, 140 "fixedbugs/issue46725.go": {category: notApplicable, desc: "GC related, not relevant to GopherJS"}, 141 "fixedbugs/issue43444.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format is different from Go's"}, 142 "fixedbugs/issue23017.go": {desc: "https://github.com/gopherjs/gopherjs/issues/1063"}, 143 144 // These are new tests in Go 1.17.8 145 "fixedbugs/issue50854.go": {category: lowLevelRuntimeDifference, desc: "negative int32 overflow behaves differently in JS"}, 146 147 // These are new tests in Go 1.18 148 "fixedbugs/issue47928.go": {category: notApplicable, desc: "//go:nointerface is a part of GOEXPERIMENT=fieldtrack and is not supported by GopherJS"}, 149 "fixedbugs/issue48536.go": {category: usesUnsupportedPackage, desc: "https://github.com/gopherjs/gopherjs/issues/1130"}, 150 "fixedbugs/issue48898.go": {category: other, desc: "https://github.com/gopherjs/gopherjs/issues/1128"}, 151 "fixedbugs/issue53600.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format is different from Go's"}, 152 "typeparam/chans.go": {category: neverTerminates, desc: "uses runtime.SetFinalizer() and runtime.GC()."}, 153 "typeparam/issue51733.go": {category: usesUnsupportedPackage, desc: "unsafe: uintptr to struct pointer conversion is unsupported"}, 154 "typeparam/typeswitch5.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format is different from Go's"}, 155 156 // Failures related to the lack of generics support. Ideally, this section 157 // should be emptied once https://github.com/gopherjs/gopherjs/issues/1013 is 158 // fixed. 159 "typeparam/nested.go": {category: usesUnsupportedGenerics, desc: "incomplete support for generic types inside generic functions"}, 160 161 // These are new tests in Go 1.19 162 "typeparam/issue51521.go": {category: lowLevelRuntimeDifference, desc: "different panic message when calling a method on nil interface"}, 163 "fixedbugs/issue50672.go": {category: other, desc: "https://github.com/gopherjs/gopherjs/issues/1271"}, 164 "fixedbugs/issue53653.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format of int64 is different from Go's"}, 165 } 166 167 type failCategory uint8 168 169 const ( 170 other failCategory = iota 171 neverTerminates // Test never terminates (so avoid starting it). 172 usesUnsupportedPackage // Test fails because it imports an unsupported package, e.g., "unsafe". 173 requiresSourceMapSupport // Test fails without source map support (as configured in CI), because it tries to check filename/line number via runtime.Caller. 174 usesUnsupportedGenerics // Test uses generics (type parameters) that are not currently supported. 175 compilerPanic 176 unsureIfGopherJSSupportsThisFeature 177 lowLevelRuntimeDifference // JavaScript runtime behaves differently from Go in ways that are difficult to work around. 178 notApplicable // Test that doesn't need to run under GopherJS; it doesn't apply to the Go language in a general way. 179 ) 180 181 type failReason struct { 182 category failCategory 183 desc string 184 } 185 186 // ----------------------------------------------------------------------------- 187 188 var ( 189 verbose = flag.Bool("v", false, "verbose. if set, parallelism is set to 1.") 190 numParallel = flag.Int("n", runtime.NumCPU(), "number of parallel tests to run") 191 summary = flag.Bool("summary", false, "show summary of results") 192 showSkips = flag.Bool("show_skips", false, "show skipped tests") 193 showKnownFails = flag.Bool("show_known_fails", false, "show full error output of known fails") 194 updateErrors = flag.Bool("update_errors", false, "update error messages in test file based on compiler output") 195 runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run") 196 197 shard = flag.Int("shard", 0, "shard index to run. Only applicable if -shards is non-zero.") 198 shards = flag.Int("shards", 0, "number of shards. If 0, all tests are run. This is used by the continuous build.") 199 ) 200 201 var ( 202 goos, goarch string 203 204 // dirs are the directories to look for *.go files in. 205 // TODO(bradfitz): just use all directories? 206 dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "typeparam"} 207 208 // ratec controls the max number of tests running at a time. 209 ratec chan bool 210 211 // toRun is the channel of tests to run. 212 // It is nil until the first test is started. 213 toRun chan *test 214 215 // rungatec controls the max number of runoutput tests 216 // executed in parallel as they can each consume a lot of memory. 217 rungatec chan bool 218 ) 219 220 // maxTests is an upper bound on the total number of tests. 221 // It is used as a channel buffer size to make sure sends don't block. 222 const maxTests = 5000 223 224 func main() { 225 flag.Parse() 226 227 // GOPHERJS. 228 err := os.Chdir(filepath.Join(gbuild.DefaultGOROOT, "test")) 229 if err != nil { 230 log.Fatalln(err) 231 } 232 233 // GOPHERJS: We're running this script natively, but the tests are executed with js architecture. 234 goos = getenv("GOOS", "js") 235 goarch = getenv("GOARCH", "ecmascript") 236 237 findExecCmd() 238 239 // Disable parallelism if using a simulator. 240 // Do not disable parallelism in verbose mode, since Go's file IO had internal 241 // r/w locking, which should make significant output garbling very unlikely. 242 // GopherJS CI setup runs these tests in verbose mode, but it can benefit from 243 // parallelism a lot. 244 if len(findExecCmd()) > 0 { 245 *numParallel = 1 246 } 247 248 if *verbose { 249 fmt.Printf("goos: %q, goarch: %q\n", goos, goarch) 250 fmt.Printf("parallel: %d\n", *numParallel) 251 } 252 253 ratec = make(chan bool, *numParallel) 254 rungatec = make(chan bool, *runoutputLimit) 255 256 var tests []*test 257 if flag.NArg() > 0 { 258 for _, arg := range flag.Args() { 259 if arg == "-" || arg == "--" { 260 // Permit running: 261 // $ go run run.go - env.go 262 // $ go run run.go -- env.go 263 // $ go run run.go - ./fixedbugs 264 // $ go run run.go -- ./fixedbugs 265 continue 266 } 267 if fi, err := os.Stat(arg); err == nil && fi.IsDir() { 268 for _, baseGoFile := range goFiles(arg) { 269 tests = append(tests, startTest(arg, baseGoFile)) 270 } 271 } else if strings.HasSuffix(arg, ".go") { 272 dir, file := filepath.Split(arg) 273 tests = append(tests, startTest(dir, file)) 274 } else { 275 log.Fatalf("can't yet deal with non-directory and non-go file %q", arg) 276 } 277 } 278 } else { 279 for _, dir := range dirs { 280 for _, baseGoFile := range goFiles(dir) { 281 tests = append(tests, startTest(dir, baseGoFile)) 282 } 283 } 284 } 285 286 failed := false 287 resCount := map[string]int{} 288 for _, test := range tests { 289 <-test.donec 290 // GOPHERJS. 291 if test.action == "skip" && !*showSkips { 292 continue 293 } 294 status := "ok " 295 errStr := "" 296 // GOPHERJS. 297 if _, ok := knownFails[filepath.ToSlash(test.goFileName())]; ok && test.err != nil { 298 errStr = test.err.Error() 299 test.err = nil 300 status = "knfl" // knfl means known failure. Expect test to fail. 301 } else if ok && test.err == nil { 302 // unok means unexpected okay. Test was expected to fail, but it unexpectedly succeeded. 303 // If this is not an accident, it should be removed from knownFails map. 304 status = "unok" 305 } 306 if _, isSkip := test.err.(skipError); isSkip { 307 test.err = nil 308 errStr = "unexpected skip for " + path.Join(test.dir, test.gofile) + ": " + errStr 309 status = "FAIL" 310 } 311 if test.err != nil { 312 status = "FAIL" 313 errStr = test.err.Error() 314 } 315 if status == "FAIL" { 316 failed = true 317 } 318 // GOPHERJS. 319 if status == "unok" { 320 failed = true 321 } 322 resCount[status]++ 323 if status == "skip" && !*verbose && !*showSkips { 324 continue 325 } 326 dt := fmt.Sprintf("%.3fs", test.dt.Seconds()) 327 if status == "FAIL" { 328 fmt.Printf("# go run run.go -- %s\n%s\nFAIL\t%s\t%s\n", 329 path.Join(test.dir, test.gofile), 330 errStr, test.goFileName(), dt) 331 continue 332 } 333 // GOPHERJS. 334 if status == "knfl" && *showKnownFails { 335 fmt.Printf("# go run run.go -show_known_fails -- %s\n%s\nknfl\t%s\t%s\n", 336 path.Join(test.dir, test.gofile), 337 errStr, test.goFileName(), dt) 338 continue 339 } 340 if !*verbose && status != "unok" { 341 continue 342 } 343 fmt.Printf("%s\t%s\t%s\n", status, test.goFileName(), dt) 344 } 345 346 if *summary { 347 for k, v := range resCount { 348 fmt.Printf("%5d %s\n", v, k) 349 } 350 } 351 352 if failed { 353 os.Exit(1) 354 } 355 } 356 357 func shardMatch(name string) bool { 358 if *shards == 0 { 359 return true 360 } 361 h := fnv.New32() 362 io.WriteString(h, name) 363 return int(h.Sum32()%uint32(*shards)) == *shard 364 } 365 366 func goFiles(dir string) []string { 367 f, err := os.Open(dir) 368 check(err) 369 dirnames, err := f.Readdirnames(-1) 370 f.Close() 371 check(err) 372 names := []string{} 373 for _, name := range dirnames { 374 if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && shardMatch(name) { 375 names = append(names, name) 376 } 377 } 378 sort.Strings(names) 379 return names 380 } 381 382 type runCmd func(...string) ([]byte, error) 383 384 func compileFile(runcmd runCmd, longname string) (out []byte, err error) { 385 return runcmd("go", "tool", "compile", "-e", longname) 386 } 387 388 func compileInDir(runcmd runCmd, dir string, names ...string) (out []byte, err error) { 389 cmd := []string{"go", "tool", "compile", "-e", "-D", ".", "-I", "."} 390 for _, name := range names { 391 cmd = append(cmd, filepath.Join(dir, name)) 392 } 393 return runcmd(cmd...) 394 } 395 396 func linkFile(runcmd runCmd, goname string) (err error) { 397 pfile := strings.Replace(goname, ".go", ".o", -1) 398 _, err = runcmd("go", "tool", "link", "-w", "-o", "a.exe", "-L", ".", pfile) 399 return 400 } 401 402 // skipError describes why a test was skipped. 403 type skipError string 404 405 func (s skipError) Error() string { return string(s) } 406 407 func check(err error) { 408 if err != nil { 409 log.Fatal(err) 410 } 411 } 412 413 // test holds the state of a test. 414 type test struct { 415 dir, gofile string 416 donec chan bool // closed when done 417 dt time.Duration 418 419 src string 420 action string // "compile", "build", etc. 421 422 tempDir string 423 err error 424 } 425 426 // startTest 427 func startTest(dir, gofile string) *test { 428 t := &test{ 429 dir: dir, 430 gofile: gofile, 431 donec: make(chan bool, 1), 432 } 433 if toRun == nil { 434 toRun = make(chan *test, maxTests) 435 go runTests() 436 } 437 select { 438 case toRun <- t: 439 default: 440 panic("toRun buffer size (maxTests) is too small") 441 } 442 return t 443 } 444 445 // runTests runs tests in parallel, but respecting the order they 446 // were enqueued on the toRun channel. 447 func runTests() { 448 for { 449 ratec <- true 450 t := <-toRun 451 go func() { 452 t.run() 453 <-ratec 454 }() 455 } 456 } 457 458 var cwd, _ = os.Getwd() 459 460 func (t *test) goFileName() string { 461 return filepath.Join(t.dir, t.gofile) 462 } 463 464 func (t *test) goDirName() string { 465 return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1)) 466 } 467 468 func goDirFiles(longdir string) (filter []os.DirEntry, err error) { 469 files, dirErr := os.ReadDir(longdir) 470 if dirErr != nil { 471 return nil, dirErr 472 } 473 for _, gofile := range files { 474 if filepath.Ext(gofile.Name()) == ".go" { 475 filter = append(filter, gofile) 476 } 477 } 478 return 479 } 480 481 var packageRE = regexp.MustCompile(`(?m)^package (\w+)`) 482 483 func goDirPackages(longdir string) ([][]string, error) { 484 files, err := goDirFiles(longdir) 485 if err != nil { 486 return nil, err 487 } 488 var pkgs [][]string 489 m := make(map[string]int) 490 for _, file := range files { 491 name := file.Name() 492 data, err := os.ReadFile(filepath.Join(longdir, name)) 493 if err != nil { 494 return nil, err 495 } 496 pkgname := packageRE.FindStringSubmatch(string(data)) 497 if pkgname == nil { 498 return nil, fmt.Errorf("cannot find package name in %s", name) 499 } 500 i, ok := m[pkgname[1]] 501 if !ok { 502 i = len(pkgs) 503 pkgs = append(pkgs, nil) 504 m[pkgname[1]] = i 505 } 506 pkgs[i] = append(pkgs[i], name) 507 } 508 return pkgs, nil 509 } 510 511 type context struct { 512 GOOS string 513 GOARCH string 514 } 515 516 // shouldTest looks for build tags in a source file and returns 517 // whether the file should be used according to the tags. 518 func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) { 519 // Custom rule, treat js as equivalent to nacl. 520 if goarch == "js" { 521 goarch = "nacl" 522 } 523 524 for _, line := range strings.Split(src, "\n") { 525 if strings.HasPrefix(line, "package ") { 526 break 527 } 528 if expr, err := constraint.Parse(line); err == nil { 529 ctxt := &context{ 530 GOOS: goos, 531 GOARCH: goarch, 532 } 533 if !expr.Eval(ctxt.match) { 534 return false, line 535 } 536 } 537 } 538 return true, "" 539 } 540 541 func (ctxt *context) match(name string) bool { 542 if name == "" { 543 return false 544 } 545 546 // Tags must be letters, digits, underscores or dots. 547 // Unlike in Go identifiers, all digits are fine (e.g., "386"). 548 for _, c := range name { 549 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { 550 return false 551 } 552 } 553 554 // GOPHERJS: Ignore "goexperiment." for now 555 // GOPHERJS: Don't match "cgo" since not supported 556 // GOPHERJS: Don't match "gc" 557 if name == ctxt.GOOS || name == ctxt.GOARCH { 558 return true 559 } 560 561 // GOPHERJS: Don't match "gcflags_noopt" 562 if name == "test_run" { 563 return true 564 } 565 566 return false 567 } 568 569 func init() { checkShouldTest() } 570 571 // run runs a test. 572 func (t *test) run() { 573 start := time.Now() 574 defer func() { 575 t.dt = time.Since(start) 576 close(t.donec) 577 }() 578 579 // GOPHERJS: Some tests may never terminate once started. Avoid starting them. 580 if kf, ok := knownFails[filepath.ToSlash(t.goFileName())]; ok && kf.category == neverTerminates { 581 t.err = skipError("skipping because it doesn't terminate") 582 return 583 } 584 585 srcBytes, err := os.ReadFile(t.goFileName()) 586 if err != nil { 587 t.err = err 588 return 589 } 590 t.src = string(srcBytes) 591 if t.src[0] == '\n' { 592 t.err = skipError("starts with newline") 593 return 594 } 595 596 // Execution recipe stops at first blank line. 597 action, _, ok := strings.Cut(t.src, "\n\n") 598 if !ok { 599 t.err = fmt.Errorf("double newline ending execution recipe not found in %s", t.goFileName()) 600 return 601 } 602 if firstLine, rest, ok := strings.Cut(action, "\n"); ok && strings.Contains(firstLine, "+build") { 603 // skip first line 604 action = rest 605 } 606 action = strings.TrimPrefix(action, "//") 607 608 // Check for build constraints only up to the actual code. 609 header, _, ok := strings.Cut(t.src, "\npackage") 610 if !ok { 611 header = action // some files are intentionally malformed 612 } 613 if ok, why := shouldTest(header, goos, goarch); !ok { 614 t.action = "skip" 615 if *showSkips { 616 fmt.Printf("%-20s %-20s: %s\n", t.action, t.goFileName(), why) 617 } 618 return 619 } 620 621 var args, flags []string 622 wantError := false 623 f, err := splitQuoted(action) 624 if err != nil { 625 t.err = fmt.Errorf("invalid test recipe: %v", err) 626 return 627 } 628 if len(f) > 0 { 629 action = f[0] 630 args = f[1:] 631 } 632 633 // GOPHERJS: For now, only run with "run", "cmpout" actions, in "fixedbugs" and "typeparam" dirs. Skip all others. 634 switch action { 635 case "run", "cmpout": 636 if d := filepath.Clean(t.dir); d != "fixedbugs" && d != "typeparam" { 637 action = "skip" 638 } 639 default: 640 action = "skip" 641 } 642 643 switch action { 644 case "rundircmpout": 645 action = "rundir" 646 t.action = "rundir" 647 case "cmpout": 648 action = "run" // the run case already looks for <dir>/<test>.out files 649 fallthrough 650 case "compile", "compiledir", "build", "run", "runoutput", "rundir": 651 t.action = action 652 case "errorcheck", "errorcheckdir", "errorcheckoutput": 653 t.action = action 654 wantError = true 655 for len(args) > 0 && strings.HasPrefix(args[0], "-") { 656 if args[0] == "-0" { 657 wantError = false 658 } else { 659 flags = append(flags, args[0]) 660 } 661 args = args[1:] 662 } 663 case "skip": 664 t.action = "skip" 665 return 666 default: 667 t.err = skipError("skipped; unknown pattern: " + action) 668 t.action = "??" 669 return 670 } 671 672 t.makeTempDir() 673 defer os.RemoveAll(t.tempDir) 674 675 err = os.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0o644) 676 check(err) 677 678 // A few tests (of things like the environment) require these to be set. 679 if os.Getenv("GOOS") == "" { 680 os.Setenv("GOOS", goos) 681 } 682 if os.Getenv("GOARCH") == "" { 683 os.Setenv("GOARCH", goarch) 684 } 685 686 { 687 // GopherJS: we don't support any of -gcflags, but for the most part they 688 // are not too relevant to the outcome of the test. 689 supportedArgs := []string{} 690 for _, a := range args { 691 if strings.HasPrefix(a, "-gcflags") { 692 continue 693 } 694 supportedArgs = append(supportedArgs, a) 695 } 696 args = supportedArgs 697 } 698 699 useTmp := true 700 runcmd := func(args ...string) ([]byte, error) { 701 cmd := exec.Command(args[0], args[1:]...) 702 var buf bytes.Buffer 703 cmd.Stdout = &buf 704 cmd.Stderr = &buf 705 if useTmp { 706 cmd.Dir = t.tempDir 707 cmd.Env = envForDir(cmd.Dir) 708 } 709 err := cmd.Run() 710 if err != nil { 711 err = fmt.Errorf("%s\n%s", err, buf.Bytes()) 712 } 713 return buf.Bytes(), err 714 } 715 716 long := filepath.Join(cwd, t.goFileName()) 717 switch action { 718 default: 719 t.err = fmt.Errorf("unimplemented action %q", action) 720 721 case "errorcheck": 722 cmdline := []string{"go", "tool", "compile", "-e", "-o", "a.o"} 723 cmdline = append(cmdline, flags...) 724 cmdline = append(cmdline, long) 725 out, err := runcmd(cmdline...) 726 if wantError { 727 if err == nil { 728 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 729 return 730 } 731 } else { 732 if err != nil { 733 t.err = err 734 return 735 } 736 } 737 if *updateErrors { 738 t.updateErrors(string(out), long) 739 } 740 t.err = t.errorCheck(string(out), long, t.gofile) 741 return 742 743 case "compile": 744 _, t.err = compileFile(runcmd, long) 745 746 case "compiledir": 747 // Compile all files in the directory in lexicographic order. 748 longdir := filepath.Join(cwd, t.goDirName()) 749 pkgs, err := goDirPackages(longdir) 750 if err != nil { 751 t.err = err 752 return 753 } 754 for _, gofiles := range pkgs { 755 _, t.err = compileInDir(runcmd, longdir, gofiles...) 756 if t.err != nil { 757 return 758 } 759 } 760 761 case "errorcheckdir": 762 // errorcheck all files in lexicographic order 763 // useful for finding importing errors 764 longdir := filepath.Join(cwd, t.goDirName()) 765 pkgs, err := goDirPackages(longdir) 766 if err != nil { 767 t.err = err 768 return 769 } 770 for i, gofiles := range pkgs { 771 out, err := compileInDir(runcmd, longdir, gofiles...) 772 if i == len(pkgs)-1 { 773 if wantError && err == nil { 774 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 775 return 776 } else if !wantError && err != nil { 777 t.err = err 778 return 779 } 780 } else if err != nil { 781 t.err = err 782 return 783 } 784 var fullshort []string 785 for _, name := range gofiles { 786 fullshort = append(fullshort, filepath.Join(longdir, name), name) 787 } 788 t.err = t.errorCheck(string(out), fullshort...) 789 if t.err != nil { 790 break 791 } 792 } 793 794 case "rundir": 795 // Compile all files in the directory in lexicographic order. 796 // then link as if the last file is the main package and run it 797 longdir := filepath.Join(cwd, t.goDirName()) 798 pkgs, err := goDirPackages(longdir) 799 if err != nil { 800 t.err = err 801 return 802 } 803 for i, gofiles := range pkgs { 804 _, err := compileInDir(runcmd, longdir, gofiles...) 805 if err != nil { 806 t.err = err 807 return 808 } 809 if i == len(pkgs)-1 { 810 err = linkFile(runcmd, gofiles[0]) 811 if err != nil { 812 t.err = err 813 return 814 } 815 var cmd []string 816 cmd = append(cmd, findExecCmd()...) 817 cmd = append(cmd, filepath.Join(t.tempDir, "a.exe")) 818 cmd = append(cmd, args...) 819 out, err := runcmd(cmd...) 820 if err != nil { 821 t.err = err 822 return 823 } 824 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 825 t.err = fmt.Errorf("incorrect output\n%s", out) 826 } 827 } 828 } 829 830 case "build": 831 _, err := runcmd("go", "build", "-o", "a.exe", long) 832 if err != nil { 833 t.err = err 834 } 835 836 case "run": 837 useTmp = false 838 // GOPHERJS. 839 out, err := runcmd(append([]string{"gopherjs", "run", t.goFileName()}, args...)...) 840 if err != nil { 841 t.err = err 842 return 843 } 844 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 845 t.err = fmt.Errorf("incorrect output\n%s", out) 846 } 847 848 case "runoutput": 849 rungatec <- true 850 defer func() { 851 <-rungatec 852 }() 853 useTmp = false 854 out, err := runcmd(append([]string{"go", "run", t.goFileName()}, args...)...) 855 if err != nil { 856 t.err = err 857 return 858 } 859 tfile := filepath.Join(t.tempDir, "tmp__.go") 860 if err := os.WriteFile(tfile, out, 0o666); err != nil { 861 t.err = fmt.Errorf("write tempfile:%s", err) 862 return 863 } 864 out, err = runcmd("go", "run", tfile) 865 if err != nil { 866 t.err = err 867 return 868 } 869 if string(out) != t.expectedOutput() { 870 t.err = fmt.Errorf("incorrect output\n%s", out) 871 } 872 873 case "errorcheckoutput": 874 useTmp = false 875 out, err := runcmd(append([]string{"go", "run", t.goFileName()}, args...)...) 876 if err != nil { 877 t.err = err 878 return 879 } 880 tfile := filepath.Join(t.tempDir, "tmp__.go") 881 err = os.WriteFile(tfile, out, 0o666) 882 if err != nil { 883 t.err = fmt.Errorf("write tempfile:%s", err) 884 return 885 } 886 cmdline := []string{"go", "tool", "compile", "-e", "-o", "a.o"} 887 cmdline = append(cmdline, flags...) 888 cmdline = append(cmdline, tfile) 889 out, err = runcmd(cmdline...) 890 if wantError { 891 if err == nil { 892 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 893 return 894 } 895 } else { 896 if err != nil { 897 t.err = err 898 return 899 } 900 } 901 t.err = t.errorCheck(string(out), tfile, "tmp__.go") 902 return 903 } 904 } 905 906 var execCmd []string 907 908 func findExecCmd() []string { 909 if execCmd != nil { 910 return execCmd 911 } 912 execCmd = []string{} // avoid work the second time 913 if goos == runtime.GOOS && goarch == runtime.GOARCH { 914 return execCmd 915 } 916 path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch)) 917 if err == nil { 918 execCmd = []string{path} 919 } 920 return execCmd 921 } 922 923 func (t *test) String() string { 924 return filepath.Join(t.dir, t.gofile) 925 } 926 927 func (t *test) makeTempDir() { 928 var err error 929 t.tempDir, err = os.MkdirTemp("", "") 930 check(err) 931 } 932 933 func (t *test) expectedOutput() string { 934 filename := filepath.Join(t.dir, t.gofile) 935 filename = filename[:len(filename)-len(".go")] 936 filename += ".out" 937 b, _ := os.ReadFile(filename) 938 return string(b) 939 } 940 941 func splitOutput(out string) []string { 942 // gc error messages continue onto additional lines with leading tabs. 943 // Split the output at the beginning of each line that doesn't begin with a tab. 944 // <autogenerated> lines are impossible to match so those are filtered out. 945 var res []string 946 for _, line := range strings.Split(out, "\n") { 947 if strings.HasSuffix(line, "\r") { // remove '\r', output by compiler on windows 948 line = line[:len(line)-1] 949 } 950 if strings.HasPrefix(line, "\t") { 951 res[len(res)-1] += "\n" + line 952 } else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "<autogenerated>") { 953 continue 954 } else if strings.TrimSpace(line) != "" { 955 res = append(res, line) 956 } 957 } 958 return res 959 } 960 961 func (t *test) errorCheck(outStr string, fullshort ...string) (err error) { 962 defer func() { 963 if *verbose && err != nil { 964 log.Printf("%s gc output:\n%s", t, outStr) 965 } 966 }() 967 var errs []error 968 out := splitOutput(outStr) 969 970 // Cut directory name. 971 for i := range out { 972 for j := 0; j < len(fullshort); j += 2 { 973 full, short := fullshort[j], fullshort[j+1] 974 out[i] = strings.Replace(out[i], full, short, -1) 975 } 976 } 977 978 var want []wantedError 979 for j := 0; j < len(fullshort); j += 2 { 980 full, short := fullshort[j], fullshort[j+1] 981 want = append(want, t.wantedErrors(full, short)...) 982 } 983 984 for _, we := range want { 985 var errmsgs []string 986 errmsgs, out = partitionStrings(we.prefix, out) 987 if len(errmsgs) == 0 { 988 errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr)) 989 continue 990 } 991 matched := false 992 n := len(out) 993 for _, errmsg := range errmsgs { 994 if we.re.MatchString(errmsg) { 995 matched = true 996 } else { 997 out = append(out, errmsg) 998 } 999 } 1000 if !matched { 1001 errs = append(errs, fmt.Errorf("%s:%d: no match for %#q in:\n\t%s", we.file, we.lineNum, we.reStr, strings.Join(out[n:], "\n\t"))) 1002 continue 1003 } 1004 } 1005 1006 if len(out) > 0 { 1007 errs = append(errs, fmt.Errorf("Unmatched Errors:")) 1008 for _, errLine := range out { 1009 errs = append(errs, fmt.Errorf("%s", errLine)) 1010 } 1011 } 1012 1013 if len(errs) == 0 { 1014 return nil 1015 } 1016 if len(errs) == 1 { 1017 return errs[0] 1018 } 1019 var buf bytes.Buffer 1020 fmt.Fprintf(&buf, "\n") 1021 for _, err := range errs { 1022 fmt.Fprintf(&buf, "%s\n", err.Error()) 1023 } 1024 return errors.New(buf.String()) 1025 } 1026 1027 func (t *test) updateErrors(out string, file string) { 1028 // Read in source file. 1029 src, err := os.ReadFile(file) 1030 if err != nil { 1031 fmt.Fprintln(os.Stderr, err) 1032 return 1033 } 1034 lines := strings.Split(string(src), "\n") 1035 // Remove old errors. 1036 for i, ln := range lines { 1037 pos := strings.Index(ln, " // ERROR ") 1038 if pos >= 0 { 1039 lines[i] = ln[:pos] 1040 } 1041 } 1042 // Parse new errors. 1043 errors := make(map[int]map[string]bool) 1044 tmpRe := regexp.MustCompile(`autotmp_[0-9]+`) 1045 for _, errStr := range splitOutput(out) { 1046 colon1 := strings.Index(errStr, ":") 1047 if colon1 < 0 || errStr[:colon1] != file { 1048 continue 1049 } 1050 colon2 := strings.Index(errStr[colon1+1:], ":") 1051 if colon2 < 0 { 1052 continue 1053 } 1054 colon2 += colon1 + 1 1055 line, err := strconv.Atoi(errStr[colon1+1 : colon2]) 1056 line-- 1057 if err != nil || line < 0 || line >= len(lines) { 1058 continue 1059 } 1060 msg := errStr[colon2+2:] 1061 for _, r := range []string{`\`, `*`, `+`, `[`, `]`, `(`, `)`} { 1062 msg = strings.Replace(msg, r, `\`+r, -1) 1063 } 1064 msg = strings.Replace(msg, `"`, `.`, -1) 1065 msg = tmpRe.ReplaceAllLiteralString(msg, `autotmp_[0-9]+`) 1066 if errors[line] == nil { 1067 errors[line] = make(map[string]bool) 1068 } 1069 errors[line][msg] = true 1070 } 1071 // Add new errors. 1072 for line, errs := range errors { 1073 var sorted []string 1074 for e := range errs { 1075 sorted = append(sorted, e) 1076 } 1077 sort.Strings(sorted) 1078 lines[line] += " // ERROR" 1079 for _, e := range sorted { 1080 lines[line] += fmt.Sprintf(` "%s$"`, e) 1081 } 1082 } 1083 // Write new file. 1084 err = os.WriteFile(file, []byte(strings.Join(lines, "\n")), 0o640) 1085 if err != nil { 1086 fmt.Fprintln(os.Stderr, err) 1087 return 1088 } 1089 // Polish. 1090 exec.Command("go", "fmt", file).CombinedOutput() 1091 } 1092 1093 // matchPrefix reports whether s is of the form ^(.*/)?prefix(:|[), 1094 // That is, it needs the file name prefix followed by a : or a [, 1095 // and possibly preceded by a directory name. 1096 func matchPrefix(s, prefix string) bool { 1097 i := strings.Index(s, ":") 1098 if i < 0 { 1099 return false 1100 } 1101 j := strings.LastIndex(s[:i], "/") 1102 s = s[j+1:] 1103 if len(s) <= len(prefix) || s[:len(prefix)] != prefix { 1104 return false 1105 } 1106 switch s[len(prefix)] { 1107 case '[', ':': 1108 return true 1109 } 1110 return false 1111 } 1112 1113 func partitionStrings(prefix string, strs []string) (matched, unmatched []string) { 1114 for _, s := range strs { 1115 if matchPrefix(s, prefix) { 1116 matched = append(matched, s) 1117 } else { 1118 unmatched = append(unmatched, s) 1119 } 1120 } 1121 return 1122 } 1123 1124 type wantedError struct { 1125 reStr string 1126 re *regexp.Regexp 1127 lineNum int 1128 file string 1129 prefix string 1130 } 1131 1132 var ( 1133 errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`) 1134 errQuotesRx = regexp.MustCompile(`"([^"]*)"`) 1135 lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`) 1136 ) 1137 1138 func (t *test) wantedErrors(file, short string) (errs []wantedError) { 1139 cache := make(map[string]*regexp.Regexp) 1140 1141 src, _ := os.ReadFile(file) 1142 for i, line := range strings.Split(string(src), "\n") { 1143 lineNum := i + 1 1144 if strings.Contains(line, "////") { 1145 // double comment disables ERROR 1146 continue 1147 } 1148 m := errRx.FindStringSubmatch(line) 1149 if m == nil { 1150 continue 1151 } 1152 all := m[1] 1153 mm := errQuotesRx.FindAllStringSubmatch(all, -1) 1154 if mm == nil { 1155 log.Fatalf("%s:%d: invalid errchk line: %s", t.goFileName(), lineNum, line) 1156 } 1157 for _, m := range mm { 1158 rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string { 1159 n := lineNum 1160 if strings.HasPrefix(m, "LINE+") { 1161 delta, _ := strconv.Atoi(m[5:]) 1162 n += delta 1163 } else if strings.HasPrefix(m, "LINE-") { 1164 delta, _ := strconv.Atoi(m[5:]) 1165 n -= delta 1166 } 1167 return fmt.Sprintf("%s:%d", short, n) 1168 }) 1169 re := cache[rx] 1170 if re == nil { 1171 var err error 1172 re, err = regexp.Compile(rx) 1173 if err != nil { 1174 log.Fatalf("%s:%d: invalid regexp \"%s\" in ERROR line: %v", t.goFileName(), lineNum, rx, err) 1175 } 1176 cache[rx] = re 1177 } 1178 prefix := fmt.Sprintf("%s:%d", short, lineNum) 1179 errs = append(errs, wantedError{ 1180 reStr: rx, 1181 re: re, 1182 prefix: prefix, 1183 lineNum: lineNum, 1184 file: short, 1185 }) 1186 } 1187 } 1188 1189 return 1190 } 1191 1192 // defaultRunOutputLimit returns the number of runoutput tests that 1193 // can be executed in parallel. 1194 func defaultRunOutputLimit() int { 1195 const maxArmCPU = 2 1196 1197 cpu := runtime.NumCPU() 1198 if runtime.GOARCH == "arm" && cpu > maxArmCPU { 1199 cpu = maxArmCPU 1200 } 1201 return cpu 1202 } 1203 1204 // checkShouldTest runs sanity checks on the shouldTest function. 1205 func checkShouldTest() { 1206 assert := func(ok bool, _ string) { 1207 if !ok { 1208 panic("fail") 1209 } 1210 } 1211 assertNot := func(ok bool, _ string) { assert(!ok, "") } 1212 1213 // Simple tests. 1214 assert(shouldTest("// +build linux", "linux", "arm")) 1215 assert(shouldTest("// +build !windows", "linux", "arm")) 1216 assertNot(shouldTest("// +build !windows", "windows", "amd64")) 1217 1218 // A file with no build tags will always be tested. 1219 assert(shouldTest("// This is a test.", "os", "arch")) 1220 1221 // Build tags separated by a space are OR-ed together. 1222 assertNot(shouldTest("// +build arm 386", "linux", "amd64")) 1223 1224 // Build tags separated by a comma are AND-ed together. 1225 assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64")) 1226 assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386")) 1227 1228 // Build tags on multiple lines are AND-ed together. 1229 assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64")) 1230 assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64")) 1231 1232 // Test that (!a OR !b) matches anything. 1233 assert(shouldTest("// +build !windows !plan9", "windows", "amd64")) 1234 1235 // GOPHERJS: Custom rule, test that don't run on nacl should also not run on js. 1236 assertNot(shouldTest("// +build !nacl,!plan9,!windows", "darwin", "js")) 1237 } 1238 1239 // envForDir returns a copy of the environment 1240 // suitable for running in the given directory. 1241 // The environment is the current process's environment 1242 // but with an updated $PWD, so that an os.Getwd in the 1243 // child will be faster. 1244 func envForDir(dir string) []string { 1245 env := os.Environ() 1246 for i, kv := range env { 1247 if strings.HasPrefix(kv, "PWD=") { 1248 env[i] = "PWD=" + dir 1249 return env 1250 } 1251 } 1252 env = append(env, "PWD="+dir) 1253 return env 1254 } 1255 1256 func getenv(key, def string) string { 1257 value := os.Getenv(key) 1258 if value != "" { 1259 return value 1260 } 1261 return def 1262 } 1263 1264 // splitQuoted splits the string s around each instance of one or more consecutive 1265 // white space characters while taking into account quotes and escaping, and 1266 // returns an array of substrings of s or an empty list if s contains only white space. 1267 // Single quotes and double quotes are recognized to prevent splitting within the 1268 // quoted region, and are removed from the resulting substrings. If a quote in s 1269 // isn't closed err will be set and r will have the unclosed argument as the 1270 // last element. The backslash is used for escaping. 1271 // 1272 // For example, the following string: 1273 // 1274 // a b:"c d" 'e''f' "g\"" 1275 // 1276 // Would be parsed as: 1277 // 1278 // []string{"a", "b:c d", "ef", `g"`} 1279 // 1280 // [copied from src/go/build/build.go] 1281 func splitQuoted(s string) (r []string, err error) { 1282 var args []string 1283 arg := make([]rune, len(s)) 1284 escaped := false 1285 quoted := false 1286 quote := '\x00' 1287 i := 0 1288 for _, rune := range s { 1289 switch { 1290 case escaped: 1291 escaped = false 1292 case rune == '\\': 1293 escaped = true 1294 continue 1295 case quote != '\x00': 1296 if rune == quote { 1297 quote = '\x00' 1298 continue 1299 } 1300 case rune == '"' || rune == '\'': 1301 quoted = true 1302 quote = rune 1303 continue 1304 case unicode.IsSpace(rune): 1305 if quoted || i > 0 { 1306 quoted = false 1307 args = append(args, string(arg[:i])) 1308 i = 0 1309 } 1310 continue 1311 } 1312 arg[i] = rune 1313 i++ 1314 } 1315 if quoted || i > 0 { 1316 args = append(args, string(arg[:i])) 1317 } 1318 if quote != 0 { 1319 err = errors.New("unclosed quote") 1320 } else if escaped { 1321 err = errors.New("unfinished escaping") 1322 } 1323 return args, err 1324 }