modernc.org/gc@v1.0.1-0.20240304020402-f0dba7c97c2b/errchk_test.go (about) 1 // Copyright 2012 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 GO-LICENSE file. 4 5 // Modifications: Copyright 2016 The GC Authors. All rights reserved. 6 // Use of this source code is governed by a BSD-style 7 // license that can be found in the LICENSE file. 8 9 // Run runs tests in the test directory. 10 // 11 // TODO(bradfitz): docs of some sort, once we figure out how we're changing 12 // headers of files 13 14 // Based on https://github.com/golang/go/blob/fe8a0d12b14108cbe2408b417afcaab722b0727c/test/run.go 15 16 package gc // import "modernc.org/gc" 17 18 import ( 19 "bytes" 20 "errors" 21 "flag" 22 "fmt" 23 "hash/fnv" 24 "io" 25 "io/ioutil" 26 "log" 27 "os" 28 "os/exec" 29 "path" 30 "path/filepath" 31 "regexp" 32 "runtime" 33 "sort" 34 "strconv" 35 "strings" 36 "testing" 37 "time" 38 "unicode" 39 ) 40 41 var ( 42 verbose = flag.Bool("chk.v", false, "verbose. if set, parallelism is set to 1.") 43 keep = flag.Bool("chk.k", false, "keep. keep temporary directory.") 44 numParallel = flag.Int("chk.n", runtime.NumCPU(), "number of parallel tests to run") 45 summary = flag.Bool("chk.summary", false, "show summary of results") 46 showSkips = flag.Bool("chk.show_skips", false, "show skipped tests") 47 runSkips = flag.Bool("chk.run_skips", false, "run skipped tests (ignore skip and build tags)") 48 linkshared = flag.Bool("chk.linkshared", false, "") 49 updateErrors = flag.Bool("chk.update_errors", false, "update error messages in test file based on compiler output") 50 runoutputLimit = flag.Int("chk.l", defaultRunOutputLimit(), "number of parallel runoutput tests to run") 51 52 shard = flag.Int("chk.shard", 0, "shard index to run. Only applicable if -shards is non-zero.") 53 shards = flag.Int("chk.shards", 0, "number of shards. If 0, all tests are run. This is used by the continuous build.") 54 ) 55 56 var ( 57 goos, goarch string 58 59 // dirs are the directories to look for *.go files in. 60 // TODO(bradfitz): just use all directories? 61 dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs"} 62 63 // ratec controls the max number of tests running at a time. 64 ratec chan bool 65 66 // toRun is the channel of tests to run. 67 // It is nil until the first test is started. 68 toRun chan *test 69 70 // rungatec controls the max number of runoutput tests 71 // executed in parallel as they can each consume a lot of memory. 72 rungatec chan bool 73 ) 74 75 // maxTests is an upper bound on the total number of tests. 76 // It is used as a channel buffer size to make sure sends don't block. 77 const maxTests = 5000 78 79 func errchkMain() error { 80 // flag.Parse() // We're not more in main() 81 82 goos = getenv("GOOS", runtime.GOOS) 83 goarch = getenv("GOARCH", runtime.GOARCH) 84 85 findExecCmd() 86 87 // Disable parallelism if printing or if using a simulator. 88 if *verbose || len(findExecCmd()) > 0 { 89 *numParallel = 1 90 } 91 92 ratec = make(chan bool, *numParallel) 93 rungatec = make(chan bool, *runoutputLimit) 94 95 var tests []*test 96 if flag.NArg() > 0 { 97 for _, arg := range flag.Args() { 98 if arg == "-" || arg == "--" { 99 // Permit running: 100 // $ go run run.go - env.go 101 // $ go run run.go -- env.go 102 // $ go run run.go - ./fixedbugs 103 // $ go run run.go -- ./fixedbugs 104 continue 105 } 106 if fi, err := os.Stat(arg); err == nil && fi.IsDir() { 107 for _, baseGoFile := range goFiles(arg) { 108 tests = append(tests, startTest(arg, baseGoFile)) 109 } 110 } else if strings.HasSuffix(arg, ".go") { 111 dir, file := filepath.Split(arg) 112 tests = append(tests, startTest(dir, file)) 113 } else { 114 log.Fatalf("can't yet deal with non-directory and non-go file %q", arg) 115 } 116 } 117 } else { 118 for _, dir := range dirs { 119 for _, baseGoFile := range goFiles(dir) { 120 tests = append(tests, startTest(dir, baseGoFile)) 121 } 122 } 123 } 124 125 failed := false 126 resCount := map[string]int{} 127 for _, test := range tests { 128 <-test.donec 129 status := "ok " 130 errStr := "" 131 if e, isSkip := test.err.(skipError); isSkip { 132 test.err = nil 133 errStr = "unexpected skip for " + path.Join(test.dir, test.gofile) + ": " + string(e) 134 status = "FAIL" 135 } 136 if test.err != nil { 137 status = "FAIL" 138 errStr = test.err.Error() 139 } 140 if status == "FAIL" { 141 failed = true 142 } 143 resCount[status]++ 144 dt := fmt.Sprintf("%.3fs", test.dt.Seconds()) 145 if status == "FAIL" { 146 //fmt.Printf("# go run run.go -- %s\n%s\nFAIL\t%s\t%s\n", 147 fmt.Printf("# go run run.go -- %s\n%s\nFAIL\t%s\n", 148 path.Join(test.dir, test.gofile), 149 // errStr, test.goFileName(), dt) 150 errStr, test.goFileName()) 151 continue 152 } 153 if !*verbose { 154 continue 155 } 156 fmt.Printf("%s\t%s\t%s\n", status, test.goFileName(), dt) 157 } 158 159 if *summary { 160 for k, v := range resCount { 161 fmt.Printf("%5d %s\n", v, k) 162 } 163 } 164 165 if failed { 166 // os.Exit(1) 167 return fmt.Errorf("os.Exit(1)") 168 } 169 170 return nil 171 } 172 173 func toolPath(name string) string { 174 p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name) 175 if _, err := os.Stat(p); err != nil { 176 log.Fatalf("didn't find binary at %s", p) 177 } 178 return p 179 } 180 181 func shardMatch(name string) bool { 182 if *shards == 0 { 183 return true 184 } 185 h := fnv.New32() 186 io.WriteString(h, name) 187 return int(h.Sum32()%uint32(*shards)) == *shard 188 } 189 190 func goFiles(dir string) []string { 191 f, err := os.Open(dir) 192 check(err) 193 dirnames, err := f.Readdirnames(-1) 194 check(err) 195 names := []string{} 196 for _, name := range dirnames { 197 if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && shardMatch(name) { 198 names = append(names, name) 199 } 200 } 201 sort.Strings(names) 202 return names 203 } 204 205 type runCmd func(...string) ([]byte, error) 206 207 func compileFile(runcmd runCmd, longname string, flags []string) (out []byte, err error) { 208 cmd := []string{"go", "tool", "compile", "-e"} 209 cmd = append(cmd, flags...) 210 if *linkshared { 211 cmd = append(cmd, "-dynlink", "-installsuffix=dynlink") 212 } 213 cmd = append(cmd, longname) 214 return runcmd(cmd...) 215 } 216 217 func compileInDir(runcmd runCmd, dir string, flags []string, names ...string) (out []byte, err error) { 218 cmd := []string{"go", "tool", "compile", "-e", "-D", ".", "-I", "."} 219 cmd = append(cmd, flags...) 220 if *linkshared { 221 cmd = append(cmd, "-dynlink", "-installsuffix=dynlink") 222 } 223 for _, name := range names { 224 cmd = append(cmd, filepath.Join(dir, name)) 225 } 226 return runcmd(cmd...) 227 } 228 229 func linkFile(runcmd runCmd, goname string) (err error) { 230 pfile := strings.Replace(goname, ".go", ".o", -1) 231 cmd := []string{"go", "tool", "link", "-w", "-o", "a.exe", "-L", "."} 232 if *linkshared { 233 cmd = append(cmd, "-linkshared", "-installsuffix=dynlink") 234 } 235 cmd = append(cmd, pfile) 236 _, err = runcmd(cmd...) 237 return 238 } 239 240 // skipError describes why a test was skipped. 241 type skipError string 242 243 func (s skipError) Error() string { return string(s) } 244 245 func check(err error) { 246 if err != nil { 247 log.Fatal(err) 248 } 249 } 250 251 // test holds the state of a test. 252 type test struct { 253 dir, gofile string 254 donec chan bool // closed when done 255 dt time.Duration 256 257 src string 258 259 tempDir string 260 err error 261 262 ctx *Context 263 } 264 265 // startTest 266 func startTest(dir, gofile string) *test { 267 t := &test{ 268 dir: dir, 269 gofile: gofile, 270 donec: make(chan bool, 1), 271 } 272 if toRun == nil { 273 toRun = make(chan *test, maxTests) 274 go runTests() 275 } 276 select { 277 case toRun <- t: 278 default: 279 panic("toRun buffer size (maxTests) is too small") 280 } 281 return t 282 } 283 284 // runTests runs tests in parallel, but respecting the order they 285 // were enqueued on the toRun channel. 286 func runTests() { 287 for { 288 ratec <- true 289 t := <-toRun 290 go func() { 291 t.run() 292 <-ratec 293 }() 294 } 295 } 296 297 var cwd, _ = os.Getwd() 298 299 func (t *test) goFileName() string { 300 return filepath.Join(t.dir, t.gofile) 301 } 302 303 func (t *test) goDirName() string { 304 return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1)) 305 } 306 307 func goDirFiles(longdir string) (filter []os.FileInfo, err error) { 308 files, dirErr := ioutil.ReadDir(longdir) 309 if dirErr != nil { 310 return nil, dirErr 311 } 312 for _, gofile := range files { 313 if filepath.Ext(gofile.Name()) == ".go" { 314 filter = append(filter, gofile) 315 } 316 } 317 return 318 } 319 320 var packageRE = regexp.MustCompile(`(?m)^package (\w+)`) 321 322 // If singlefilepkgs is set, each file is considered a separate package 323 // even if the package names are the same. 324 func goDirPackages(longdir string, singlefilepkgs bool) ([][]string, error) { 325 files, err := goDirFiles(longdir) 326 if err != nil { 327 return nil, err 328 } 329 var pkgs [][]string 330 m := make(map[string]int) 331 for _, file := range files { 332 name := file.Name() 333 data, err := ioutil.ReadFile(filepath.Join(longdir, name)) 334 if err != nil { 335 return nil, err 336 } 337 pkgname := packageRE.FindStringSubmatch(string(data)) 338 if pkgname == nil { 339 return nil, fmt.Errorf("cannot find package name in %s", name) 340 } 341 i, ok := m[pkgname[1]] 342 if singlefilepkgs || !ok { 343 i = len(pkgs) 344 pkgs = append(pkgs, nil) 345 m[pkgname[1]] = i 346 } 347 pkgs[i] = append(pkgs[i], name) 348 } 349 return pkgs, nil 350 } 351 352 type context struct { 353 GOOS string 354 GOARCH string 355 } 356 357 // shouldTest looks for build tags in a source file and returns 358 // whether the file should be used according to the tags. 359 func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) { 360 if *runSkips { 361 return true, "" 362 } 363 for _, line := range strings.Split(src, "\n") { 364 line = strings.TrimSpace(line) 365 if strings.HasPrefix(line, "//") { 366 line = line[2:] 367 } else { 368 continue 369 } 370 line = strings.TrimSpace(line) 371 if len(line) == 0 || line[0] != '+' { 372 continue 373 } 374 ctxt := &context{ 375 GOOS: goos, 376 GOARCH: goarch, 377 } 378 words := strings.Fields(line) 379 if words[0] == "+build" { 380 ok := false 381 for _, word := range words[1:] { 382 if ctxt.match(word) { 383 ok = true 384 break 385 } 386 } 387 if !ok { 388 // no matching tag found. 389 return false, line 390 } 391 } 392 } 393 // no build tags 394 return true, "" 395 } 396 397 func (ctxt *context) match(name string) bool { 398 if name == "" { 399 return false 400 } 401 if i := strings.Index(name, ","); i >= 0 { 402 // comma-separated list 403 return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) 404 } 405 if strings.HasPrefix(name, "!!") { // bad syntax, reject always 406 return false 407 } 408 if strings.HasPrefix(name, "!") { // negation 409 return len(name) > 1 && !ctxt.match(name[1:]) 410 } 411 412 // Tags must be letters, digits, underscores or dots. 413 // Unlike in Go identifiers, all digits are fine (e.g., "386"). 414 for _, c := range name { 415 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { 416 return false 417 } 418 } 419 420 if name == ctxt.GOOS || name == ctxt.GOARCH { 421 return true 422 } 423 424 if name == "test_run" { 425 return true 426 } 427 428 return false 429 } 430 431 func init() { checkShouldTest() } 432 433 // goGcflags returns the -gcflags argument to use with go build / go run. 434 // This must match the flags used for building the standard library, 435 // or else the commands will rebuild any needed packages (like runtime) 436 // over and over. 437 func goGcflags() string { 438 return "-gcflags=" + os.Getenv("GO_GCFLAGS") 439 } 440 441 // run runs a test. 442 func (t *test) run() { 443 defer func() { t.ctx = nil }() 444 445 start := time.Now() 446 defer func() { 447 t.dt = time.Since(start) 448 close(t.donec) 449 }() 450 451 srcBytes, err := ioutil.ReadFile(t.goFileName()) 452 if err != nil { 453 t.err = err 454 return 455 } 456 t.src = string(srcBytes) 457 if t.src[0] == '\n' { 458 t.err = skipError("starts with newline") 459 return 460 } 461 462 // Execution recipe stops at first blank line. 463 pos := strings.Index(t.src, "\n\n") 464 if pos == -1 { 465 t.err = errors.New("double newline not found") 466 return 467 } 468 action := t.src[:pos] 469 if nl := strings.Index(action, "\n"); nl >= 0 && strings.Contains(action[:nl], "+build") { 470 // skip first line 471 action = action[nl+1:] 472 } 473 action = strings.TrimPrefix(action, "//") 474 475 // Check for build constraints only up to the actual code. 476 pkgPos := strings.Index(t.src, "\npackage") 477 if pkgPos == -1 { 478 pkgPos = pos // some files are intentionally malformed 479 } 480 if ok, why := shouldTest(t.src[:pkgPos], goos, goarch); !ok { 481 if *showSkips { 482 fmt.Printf("%-20s %-20s: %s\n", "skip", t.goFileName(), why) 483 } 484 return 485 } 486 487 var args, flags []string 488 var tim int 489 wantError := false 490 wantAuto := false 491 singlefilepkgs := false 492 f := strings.Fields(action) 493 if len(f) > 0 { 494 action = f[0] 495 args = f[1:] 496 } 497 498 // TODO: Clean up/simplify this switch statement. 499 switch action { 500 case "rundircmpout": 501 action = "rundir" 502 case "cmpout": 503 action = "run" // the run case already looks for <dir>/<test>.out files 504 case "compile", "compiledir", "build", "builddir", "run", "buildrun", "runoutput", "rundir": 505 // nothing to do 506 case "errorcheckandrundir": 507 wantError = false // should be no error if also will run 508 case "errorcheckwithauto": 509 action = "errorcheck" 510 wantAuto = true 511 wantError = true 512 case "errorcheck", "errorcheckdir", "errorcheckoutput": 513 wantError = true 514 case "skip": 515 if *runSkips { 516 break 517 } 518 return 519 default: 520 t.err = skipError("skipped; unknown pattern: " + action) 521 return 522 } 523 524 // collect flags 525 for len(args) > 0 && strings.HasPrefix(args[0], "-") { 526 switch args[0] { 527 case "-0": 528 wantError = false 529 case "-s": 530 singlefilepkgs = true 531 case "-t": // timeout in seconds 532 args = args[1:] 533 var err error 534 tim, err = strconv.Atoi(args[0]) 535 if err != nil { 536 t.err = fmt.Errorf("need number of seconds for -t timeout, got %s instead", args[0]) 537 } 538 539 default: 540 flags = append(flags, args[0]) 541 } 542 args = args[1:] 543 } 544 545 t.makeTempDir() 546 if !*keep { 547 defer os.RemoveAll(t.tempDir) 548 } 549 550 err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644) 551 check(err) 552 553 // A few tests (of things like the environment) require these to be set. 554 if os.Getenv("GOOS") == "" { 555 os.Setenv("GOOS", runtime.GOOS) 556 } 557 if os.Getenv("GOARCH") == "" { 558 os.Setenv("GOARCH", runtime.GOARCH) 559 } 560 561 useTmp := true 562 runcmd := func(args ...string) ([]byte, error) { 563 cmd := exec.Command(args[0], args[1:]...) 564 var buf bytes.Buffer 565 cmd.Stdout = &buf 566 cmd.Stderr = &buf 567 if useTmp { 568 cmd.Dir = t.tempDir 569 cmd.Env = envForDir(cmd.Dir) 570 } else { 571 cmd.Env = os.Environ() 572 } 573 574 var err error 575 576 if tim != 0 { 577 panic("XTODO569") 578 err = cmd.Start() 579 // This command-timeout code adapted from cmd/go/test.go 580 if err == nil { 581 tick := time.NewTimer(time.Duration(tim) * time.Second) 582 done := make(chan error) 583 panic("XTODO575") 584 go func() { 585 done <- cmd.Wait() 586 }() 587 select { 588 case err = <-done: 589 // ok 590 case <-tick.C: 591 cmd.Process.Kill() 592 err = <-done 593 // err = errors.New("Test timeout") 594 } 595 tick.Stop() 596 } 597 } else { 598 // err = cmd.Run() 599 err = proc(t, cmd).Run() 600 } 601 if err != nil { 602 err = fmt.Errorf("%s\n%s", err, buf.Bytes()) 603 } 604 return buf.Bytes(), err 605 } 606 607 long := filepath.Join(cwd, t.goFileName()) 608 switch action { 609 default: 610 t.err = fmt.Errorf("unimplemented action %q", action) 611 612 case "errorcheck": 613 // TODO(gri) remove need for -C (disable printing of columns in error messages) 614 cmdline := []string{"go", "tool", "compile", "-C", "-e", "-o", "a.o"} 615 // No need to add -dynlink even if linkshared if we're just checking for errors... 616 cmdline = append(cmdline, flags...) 617 cmdline = append(cmdline, long) 618 out, err := runcmd(cmdline...) 619 if wantError { 620 if err == nil { 621 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 622 return 623 } 624 } else { 625 if err != nil { 626 t.err = err 627 return 628 } 629 } 630 if *updateErrors { 631 t.updateErrors(string(out), long) 632 } 633 t.err = t.errorCheck(string(out), wantAuto, long, t.gofile) 634 return 635 636 case "compile": 637 _, t.err = compileFile(runcmd, long, flags) 638 639 case "compiledir": 640 // Compile all files in the directory in lexicographic order. 641 longdir := filepath.Join(cwd, t.goDirName()) 642 pkgs, err := goDirPackages(longdir, singlefilepkgs) 643 if err != nil { 644 t.err = err 645 return 646 } 647 for _, gofiles := range pkgs { 648 _, t.err = compileInDir(runcmd, longdir, flags, gofiles...) 649 if t.err != nil { 650 return 651 } 652 } 653 654 case "errorcheckdir", "errorcheckandrundir": 655 // errorcheck all files in lexicographic order 656 // useful for finding importing errors 657 longdir := filepath.Join(cwd, t.goDirName()) 658 pkgs, err := goDirPackages(longdir, singlefilepkgs) 659 if err != nil { 660 t.err = err 661 return 662 } 663 for i, gofiles := range pkgs { 664 out, err := compileInDir(runcmd, longdir, flags, gofiles...) 665 if i == len(pkgs)-1 { 666 if wantError && err == nil { 667 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 668 return 669 } else if !wantError && err != nil { 670 t.err = err 671 return 672 } 673 } else if err != nil { 674 t.err = err 675 return 676 } 677 var fullshort []string 678 for _, name := range gofiles { 679 fullshort = append(fullshort, filepath.Join(longdir, name), name) 680 } 681 t.err = t.errorCheck(string(out), wantAuto, fullshort...) 682 if t.err != nil { 683 break 684 } 685 } 686 if action == "errorcheckdir" { 687 return 688 } 689 fallthrough 690 691 case "rundir": 692 // Compile all files in the directory in lexicographic order. 693 // then link as if the last file is the main package and run it 694 longdir := filepath.Join(cwd, t.goDirName()) 695 pkgs, err := goDirPackages(longdir, singlefilepkgs) 696 if err != nil { 697 t.err = err 698 return 699 } 700 for i, gofiles := range pkgs { 701 _, err := compileInDir(runcmd, longdir, flags, gofiles...) 702 if err != nil { 703 t.err = err 704 return 705 } 706 if i == len(pkgs)-1 { 707 err = linkFile(runcmd, gofiles[0]) 708 if err != nil { 709 t.err = err 710 return 711 } 712 var cmd []string 713 cmd = append(cmd, findExecCmd()...) 714 cmd = append(cmd, filepath.Join(t.tempDir, "a.exe")) 715 cmd = append(cmd, args...) 716 out, err := runcmd(cmd...) 717 if err != nil { 718 t.err = err 719 return 720 } 721 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 722 t.err = fmt.Errorf("incorrect output\n%s", out) 723 } 724 } 725 } 726 727 case "build": 728 _, err := runcmd("go", "build", goGcflags(), "-o", "a.exe", long) 729 if err != nil { 730 t.err = err 731 } 732 733 case "builddir": 734 // Build an executable from all the .go and .s files in a subdirectory. 735 useTmp = true 736 longdir := filepath.Join(cwd, t.goDirName()) 737 files, dirErr := ioutil.ReadDir(longdir) 738 if dirErr != nil { 739 t.err = dirErr 740 break 741 } 742 var gos []os.FileInfo 743 var asms []os.FileInfo 744 for _, file := range files { 745 switch filepath.Ext(file.Name()) { 746 case ".go": 747 gos = append(gos, file) 748 case ".s": 749 asms = append(asms, file) 750 } 751 752 } 753 var objs []string 754 cmd := []string{"go", "tool", "compile", "-e", "-D", ".", "-I", ".", "-o", "go.o"} 755 if len(asms) > 0 { 756 cmd = append(cmd, "-asmhdr", "go_asm.h") 757 } 758 for _, file := range gos { 759 cmd = append(cmd, filepath.Join(longdir, file.Name())) 760 } 761 _, err := runcmd(cmd...) 762 if err != nil { 763 t.err = err 764 break 765 } 766 objs = append(objs, "go.o") 767 if len(asms) > 0 { 768 cmd = []string{"go", "tool", "asm", "-e", "-I", ".", "-o", "asm.o"} 769 for _, file := range asms { 770 cmd = append(cmd, filepath.Join(longdir, file.Name())) 771 } 772 _, err = runcmd(cmd...) 773 if err != nil { 774 t.err = err 775 break 776 } 777 objs = append(objs, "asm.o") 778 } 779 cmd = []string{"go", "tool", "pack", "c", "all.a"} 780 cmd = append(cmd, objs...) 781 _, err = runcmd(cmd...) 782 if err != nil { 783 t.err = err 784 break 785 } 786 cmd = []string{"go", "tool", "link", "all.a"} 787 _, err = runcmd(cmd...) 788 if err != nil { 789 t.err = err 790 break 791 } 792 793 case "buildrun": // build binary, then run binary, instead of go run. Useful for timeout tests where failure mode is infinite loop. 794 // TODO: not supported on NaCl 795 useTmp = true 796 cmd := []string{"go", "build", goGcflags(), "-o", "a.exe"} 797 if *linkshared { 798 cmd = append(cmd, "-linkshared") 799 } 800 longdirgofile := filepath.Join(filepath.Join(cwd, t.dir), t.gofile) 801 cmd = append(cmd, flags...) 802 cmd = append(cmd, longdirgofile) 803 out, err := runcmd(cmd...) 804 if err != nil { 805 t.err = err 806 return 807 } 808 cmd = []string{"./a.exe"} 809 out, err = runcmd(append(cmd, args...)...) 810 if err != nil { 811 t.err = err 812 return 813 } 814 815 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 816 t.err = fmt.Errorf("incorrect output\n%s", out) 817 } 818 819 case "run": 820 useTmp = false 821 var out []byte 822 var err error 823 if len(flags)+len(args) == 0 && goGcflags() == "" && !*linkshared { 824 // If we're not using special go command flags, 825 // skip all the go command machinery. 826 // This avoids any time the go command would 827 // spend checking whether, for example, the installed 828 // package runtime is up to date. 829 // Because we run lots of trivial test programs, 830 // the time adds up. 831 pkg := filepath.Join(t.tempDir, "pkg.a") 832 if _, err := runcmd("go", "tool", "compile", "-o", pkg, t.goFileName()); err != nil { 833 t.err = err 834 return 835 } 836 exe := filepath.Join(t.tempDir, "test.exe") 837 cmd := []string{"go", "tool", "link", "-s", "-w"} 838 cmd = append(cmd, "-o", exe, pkg) 839 if _, err := runcmd(cmd...); err != nil { 840 t.err = err 841 return 842 } 843 out, err = runcmd(append([]string{exe}, args...)...) 844 } else { 845 cmd := []string{"go", "run", goGcflags()} 846 if *linkshared { 847 cmd = append(cmd, "-linkshared") 848 } 849 cmd = append(cmd, flags...) 850 cmd = append(cmd, t.goFileName()) 851 out, err = runcmd(append(cmd, args...)...) 852 } 853 if err != nil { 854 t.err = err 855 return 856 } 857 if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() { 858 t.err = fmt.Errorf("incorrect output\n%s", out) 859 } 860 861 case "runoutput": 862 rungatec <- true 863 defer func() { 864 <-rungatec 865 }() 866 useTmp = false 867 cmd := []string{"go", "run", goGcflags()} 868 if *linkshared { 869 cmd = append(cmd, "-linkshared") 870 } 871 cmd = append(cmd, t.goFileName()) 872 out, err := runcmd(append(cmd, args...)...) 873 if err != nil { 874 t.err = err 875 return 876 } 877 tfile := filepath.Join(t.tempDir, "tmp__.go") 878 if err := ioutil.WriteFile(tfile, out, 0666); err != nil { 879 t.err = fmt.Errorf("write tempfile:%s", err) 880 return 881 } 882 cmd = []string{"go", "run", goGcflags()} 883 if *linkshared { 884 cmd = append(cmd, "-linkshared") 885 } 886 cmd = append(cmd, tfile) 887 out, err = runcmd(cmd...) 888 if err != nil { 889 t.err = err 890 return 891 } 892 if string(out) != t.expectedOutput() { 893 t.err = fmt.Errorf("incorrect output\n%s", out) 894 } 895 896 case "errorcheckoutput": 897 useTmp = false 898 cmd := []string{"go", "run", goGcflags()} 899 if *linkshared { 900 cmd = append(cmd, "-linkshared") 901 } 902 cmd = append(cmd, t.goFileName()) 903 out, err := runcmd(append(cmd, args...)...) 904 if err != nil { 905 t.err = err 906 return 907 } 908 tfile := filepath.Join(t.tempDir, "tmp__.go") 909 err = ioutil.WriteFile(tfile, out, 0666) 910 if err != nil { 911 t.err = fmt.Errorf("write tempfile:%s", err) 912 return 913 } 914 cmdline := []string{"go", "tool", "compile", "-e", "-o", "a.o"} 915 cmdline = append(cmdline, flags...) 916 cmdline = append(cmdline, tfile) 917 out, err = runcmd(cmdline...) 918 if wantError { 919 if err == nil { 920 t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out) 921 return 922 } 923 } else { 924 if err != nil { 925 t.err = err 926 return 927 } 928 } 929 t.err = t.errorCheck(string(out), false, tfile, "tmp__.go") 930 return 931 } 932 } 933 934 var execCmd []string 935 936 func findExecCmd() []string { 937 if execCmd != nil { 938 return execCmd 939 } 940 execCmd = []string{} // avoid work the second time 941 if goos == runtime.GOOS && goarch == runtime.GOARCH { 942 return execCmd 943 } 944 path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch)) 945 if err == nil { 946 execCmd = []string{path} 947 } 948 return execCmd 949 } 950 951 func (t *test) String() string { 952 return filepath.Join(t.dir, t.gofile) 953 } 954 955 func (t *test) makeTempDir() { 956 var err error 957 t.tempDir, err = ioutil.TempDir("", "") 958 check(err) 959 if *keep { 960 log.Printf("Temporary directory is %s", t.tempDir) 961 } 962 } 963 964 func (t *test) expectedOutput() string { 965 filename := filepath.Join(t.dir, t.gofile) 966 filename = filename[:len(filename)-len(".go")] 967 filename += ".out" 968 b, _ := ioutil.ReadFile(filename) 969 return string(b) 970 } 971 972 func splitOutput(out string, wantAuto bool) []string { 973 // gc error messages continue onto additional lines with leading tabs. 974 // Split the output at the beginning of each line that doesn't begin with a tab. 975 // <autogenerated> lines are impossible to match so those are filtered out. 976 var res []string 977 for _, line := range strings.Split(out, "\n") { 978 if strings.HasSuffix(line, "\r") { // remove '\r', output by compiler on windows 979 line = line[:len(line)-1] 980 } 981 if strings.HasPrefix(line, "\t") { 982 res[len(res)-1] += "\n" + line 983 } else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") { 984 continue 985 } else if strings.TrimSpace(line) != "" { 986 res = append(res, line) 987 } 988 } 989 return res 990 } 991 992 func (t *test) errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) { 993 defer func() { 994 if *verbose && err != nil { 995 log.Printf("%s gc output:\n%s", t, outStr) 996 } 997 }() 998 var errs []error 999 out := splitOutput(outStr, wantAuto) 1000 1001 // Cut directory name. 1002 for i := range out { 1003 for j := 0; j < len(fullshort); j += 2 { 1004 full, short := fullshort[j], fullshort[j+1] 1005 out[i] = strings.Replace(out[i], full, short, -1) 1006 } 1007 } 1008 1009 var want []wantedError 1010 for j := 0; j < len(fullshort); j += 2 { 1011 full, short := fullshort[j], fullshort[j+1] 1012 want = append(want, t.wantedErrors(full, short)...) 1013 } 1014 1015 for _, we := range want { 1016 var errmsgs []string 1017 if we.auto { 1018 errmsgs, out = partitionStrings("<autogenerated>", out) 1019 } else { 1020 errmsgs, out = partitionStrings(we.prefix, out) 1021 } 1022 if len(errmsgs) == 0 { 1023 errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr)) 1024 continue 1025 } 1026 matched := false 1027 n := len(out) 1028 for _, errmsg := range errmsgs { 1029 // Assume errmsg says "file:line: foo". 1030 // Cut leading "file:line: " to avoid accidental matching of file name instead of message. 1031 text := errmsg 1032 if i := strings.Index(text, " "); i >= 0 { 1033 text = text[i+1:] 1034 } 1035 if we.re.MatchString(text) { 1036 matched = true 1037 } else { 1038 out = append(out, errmsg) 1039 } 1040 } 1041 if !matched { 1042 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"))) 1043 continue 1044 } 1045 } 1046 1047 if len(out) > 0 { 1048 errs = append(errs, fmt.Errorf("Unmatched Errors:")) 1049 for _, errLine := range out { 1050 errs = append(errs, fmt.Errorf("%s", errLine)) 1051 } 1052 } 1053 1054 if len(errs) == 0 { 1055 return nil 1056 } 1057 if len(errs) == 1 { 1058 return errs[0] 1059 } 1060 var buf bytes.Buffer 1061 fmt.Fprintf(&buf, "\n") 1062 for _, err := range errs { 1063 fmt.Fprintf(&buf, "%s\n", err.Error()) 1064 } 1065 return errors.New(buf.String()) 1066 } 1067 1068 func (t *test) updateErrors(out, file string) { 1069 base := path.Base(file) 1070 // Read in source file. 1071 src, err := ioutil.ReadFile(file) 1072 if err != nil { 1073 fmt.Fprintln(os.Stderr, err) 1074 return 1075 } 1076 lines := strings.Split(string(src), "\n") 1077 // Remove old errors. 1078 for i, ln := range lines { 1079 pos := strings.Index(ln, " // ERROR ") 1080 if pos >= 0 { 1081 lines[i] = ln[:pos] 1082 } 1083 } 1084 // Parse new errors. 1085 errors := make(map[int]map[string]bool) 1086 tmpRe := regexp.MustCompile(`autotmp_[0-9]+`) 1087 for _, errStr := range splitOutput(out, false) { 1088 colon1 := strings.Index(errStr, ":") 1089 if colon1 < 0 || errStr[:colon1] != file { 1090 continue 1091 } 1092 colon2 := strings.Index(errStr[colon1+1:], ":") 1093 if colon2 < 0 { 1094 continue 1095 } 1096 colon2 += colon1 + 1 1097 line, err := strconv.Atoi(errStr[colon1+1 : colon2]) 1098 line-- 1099 if err != nil || line < 0 || line >= len(lines) { 1100 continue 1101 } 1102 msg := errStr[colon2+2:] 1103 msg = strings.Replace(msg, file, base, -1) // normalize file mentions in error itself 1104 msg = strings.TrimLeft(msg, " \t") 1105 for _, r := range []string{`\`, `*`, `+`, `[`, `]`, `(`, `)`} { 1106 msg = strings.Replace(msg, r, `\`+r, -1) 1107 } 1108 msg = strings.Replace(msg, `"`, `.`, -1) 1109 msg = tmpRe.ReplaceAllLiteralString(msg, `autotmp_[0-9]+`) 1110 if errors[line] == nil { 1111 errors[line] = make(map[string]bool) 1112 } 1113 errors[line][msg] = true 1114 } 1115 // Add new errors. 1116 for line, errs := range errors { 1117 var sorted []string 1118 for e := range errs { 1119 sorted = append(sorted, e) 1120 } 1121 sort.Strings(sorted) 1122 lines[line] += " // ERROR" 1123 for _, e := range sorted { 1124 lines[line] += fmt.Sprintf(` "%s$"`, e) 1125 } 1126 } 1127 // Write new file. 1128 err = ioutil.WriteFile(file, []byte(strings.Join(lines, "\n")), 0640) 1129 if err != nil { 1130 fmt.Fprintln(os.Stderr, err) 1131 return 1132 } 1133 // Polish. 1134 panic("XTODO1123") 1135 exec.Command("go", "fmt", file).CombinedOutput() 1136 } 1137 1138 // matchPrefix reports whether s is of the form ^(.*/)?prefix(:|[), 1139 // That is, it needs the file name prefix followed by a : or a [, 1140 // and possibly preceded by a directory name. 1141 func matchPrefix(s, prefix string) bool { 1142 i := strings.Index(s, ":") 1143 if i < 0 { 1144 return false 1145 } 1146 j := strings.LastIndex(s[:i], "/") 1147 s = s[j+1:] 1148 if len(s) <= len(prefix) || s[:len(prefix)] != prefix { 1149 return false 1150 } 1151 switch s[len(prefix)] { 1152 case '[', ':': 1153 return true 1154 } 1155 return false 1156 } 1157 1158 func partitionStrings(prefix string, strs []string) (matched, unmatched []string) { 1159 for _, s := range strs { 1160 if matchPrefix(s, prefix) { 1161 matched = append(matched, s) 1162 } else { 1163 unmatched = append(unmatched, s) 1164 } 1165 } 1166 return 1167 } 1168 1169 type wantedError struct { 1170 reStr string 1171 re *regexp.Regexp 1172 lineNum int 1173 auto bool // match <autogenerated> line 1174 file string 1175 prefix string 1176 } 1177 1178 var ( 1179 errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`) 1180 errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`) 1181 errQuotesRx = regexp.MustCompile(`"([^"]*)"`) 1182 lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`) 1183 ) 1184 1185 func (t *test) wantedErrors(file, short string) (errs []wantedError) { 1186 cache := make(map[string]*regexp.Regexp) 1187 1188 src, _ := ioutil.ReadFile(file) 1189 for i, line := range strings.Split(string(src), "\n") { 1190 lineNum := i + 1 1191 if strings.Contains(line, "////") { 1192 // double comment disables ERROR 1193 continue 1194 } 1195 var auto bool 1196 m := errAutoRx.FindStringSubmatch(line) 1197 if m != nil { 1198 auto = true 1199 } else { 1200 m = errRx.FindStringSubmatch(line) 1201 } 1202 if m == nil { 1203 continue 1204 } 1205 all := m[1] 1206 mm := errQuotesRx.FindAllStringSubmatch(all, -1) 1207 if mm == nil { 1208 log.Fatalf("%s:%d: invalid errchk line: %s", t.goFileName(), lineNum, line) 1209 } 1210 for _, m := range mm { 1211 rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string { 1212 n := lineNum 1213 if strings.HasPrefix(m, "LINE+") { 1214 delta, _ := strconv.Atoi(m[5:]) 1215 n += delta 1216 } else if strings.HasPrefix(m, "LINE-") { 1217 delta, _ := strconv.Atoi(m[5:]) 1218 n -= delta 1219 } 1220 return fmt.Sprintf("%s:%d", short, n) 1221 }) 1222 re := cache[rx] 1223 if re == nil { 1224 var err error 1225 re, err = regexp.Compile(rx) 1226 if err != nil { 1227 log.Fatalf("%s:%d: invalid regexp \"%s\" in ERROR line: %v", t.goFileName(), lineNum, rx, err) 1228 } 1229 cache[rx] = re 1230 } 1231 prefix := fmt.Sprintf("%s:%d", short, lineNum) 1232 errs = append(errs, wantedError{ 1233 reStr: rx, 1234 re: re, 1235 prefix: prefix, 1236 auto: auto, 1237 lineNum: lineNum, 1238 file: short, 1239 }) 1240 } 1241 } 1242 1243 return 1244 } 1245 1246 // defaultRunOutputLimit returns the number of runoutput tests that 1247 // can be executed in parallel. 1248 func defaultRunOutputLimit() int { 1249 const maxArmCPU = 2 1250 1251 cpu := runtime.NumCPU() 1252 if runtime.GOARCH == "arm" && cpu > maxArmCPU { 1253 cpu = maxArmCPU 1254 } 1255 return cpu 1256 } 1257 1258 // checkShouldTest runs sanity checks on the shouldTest function. 1259 func checkShouldTest() { 1260 assert := func(ok bool, _ string) { 1261 if !ok { 1262 panic("fail") 1263 } 1264 } 1265 assertNot := func(ok bool, _ string) { assert(!ok, "") } 1266 1267 // Simple tests. 1268 assert(shouldTest("// +build linux", "linux", "arm")) 1269 assert(shouldTest("// +build !windows", "linux", "arm")) 1270 assertNot(shouldTest("// +build !windows", "windows", "amd64")) 1271 1272 // A file with no build tags will always be tested. 1273 assert(shouldTest("// This is a test.", "os", "arch")) 1274 1275 // Build tags separated by a space are OR-ed together. 1276 assertNot(shouldTest("// +build arm 386", "linux", "amd64")) 1277 1278 // Build tags separated by a comma are AND-ed together. 1279 assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64")) 1280 assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386")) 1281 1282 // Build tags on multiple lines are AND-ed together. 1283 assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64")) 1284 assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64")) 1285 1286 // Test that (!a OR !b) matches anything. 1287 assert(shouldTest("// +build !windows !plan9", "windows", "amd64")) 1288 } 1289 1290 // envForDir returns a copy of the environment 1291 // suitable for running in the given directory. 1292 // The environment is the current process's environment 1293 // but with an updated $PWD, so that an os.Getwd in the 1294 // child will be faster. 1295 func envForDir(dir string) []string { 1296 env := os.Environ() 1297 for i, kv := range env { 1298 if strings.HasPrefix(kv, "PWD=") { 1299 env[i] = "PWD=" + dir 1300 return env 1301 } 1302 } 1303 env = append(env, "PWD="+dir) 1304 return env 1305 } 1306 1307 func getenv(key, def string) string { 1308 value := os.Getenv(key) 1309 if value != "" { 1310 return value 1311 } 1312 return def 1313 } 1314 1315 // ============================================================================ 1316 1317 func TestErrchk(t *testing.T) { 1318 if *oNoErrchk { 1319 t.Log("disabled by -noerrchk") 1320 return 1321 } 1322 1323 const dir = "testdata/errchk/test/" 1324 dir0, err := os.Getwd() 1325 if err != nil { 1326 t.Fatal(err) 1327 } 1328 1329 defer func() { 1330 if err := os.Chdir(dir0); err != nil { 1331 t.Error(err) 1332 } 1333 }() 1334 1335 if err := os.Chdir(dir); err != nil { 1336 t.Fatal(err) 1337 } 1338 1339 cwd, err = os.Getwd() 1340 if err != nil { 1341 t.Fatal(err) 1342 } 1343 1344 if err := errchkMain(); err != nil { 1345 t.Fatal(err) 1346 } 1347 } 1348 1349 type vm struct { 1350 cmd *exec.Cmd 1351 t *test 1352 } 1353 1354 func proc(t *test, cmd *exec.Cmd) *vm { 1355 if t.ctx == nil { 1356 var err error 1357 if t.ctx, err = newTestContext(); err != nil { 1358 panic(err) 1359 } 1360 1361 t.ctx.tweaks.errchk = true 1362 } 1363 return &vm{t: t, cmd: cmd} 1364 } 1365 1366 func (v *vm) opt(list []Option) { 1367 ctx := v.t.ctx 1368 ctx.tweaks = tweaks{} 1369 for _, v := range list { 1370 if err := v(ctx); err != nil { 1371 panic(err) 1372 } 1373 } 1374 ctx.tweaks.errchk = true 1375 } 1376 1377 func (v *vm) Run() error { 1378 if *oTrc { 1379 fmt.Println(v.cmd.Args) 1380 } 1381 args := v.cmd.Args 1382 if len(args) == 0 { 1383 panic("not enough arguments to exec.Run") 1384 } 1385 1386 switch arg := args[0]; arg { 1387 case "go": 1388 return v.Go(args[1:]) 1389 default: 1390 panic(arg) 1391 } 1392 } 1393 1394 func (v *vm) Go(args []string) error { 1395 if len(args) == 0 { 1396 panic("not enough arguments to 'go'") 1397 } 1398 1399 switch arg := args[0]; arg { 1400 case "build": 1401 return v.GoBuild(args[1:]) 1402 case "run": 1403 return v.GoRun(args[1:]) 1404 case "tool": 1405 return v.GoTool(args[1:]) 1406 default: 1407 panic(arg) 1408 } 1409 } 1410 1411 func (v *vm) GoBuild(args []string) error { 1412 if len(args) == 0 { 1413 panic("not enough arguments to 'go build'") 1414 } 1415 1416 flags := flag.NewFlagSet("go build", flag.ContinueOnError) 1417 gcflags := flags.String("gcflags", "", "'[pattern=]arg list' arguments to pass on each go tool compile invocation.") 1418 _ = flags.String("o", "", "forces build to write the resulting executable or object to the named output file.") 1419 if err := flags.Parse(args); err != nil { 1420 panic(err) 1421 } 1422 1423 if flags.NArg() == 0 { 1424 panic("not enough qrguments to 'go build'") 1425 } 1426 1427 var opts []Option 1428 1429 if s := *gcflags; s != "" { 1430 panic(s) 1431 } 1432 1433 v.opt(opts) 1434 if _, err := v.t.ctx.Build(flags.Args()); err != nil { 1435 fmt.Fprintf(v.cmd.Stdout, "%s", err) 1436 return err 1437 } 1438 1439 return nil 1440 } 1441 1442 func (v *vm) GoRun(args []string) error { 1443 if len(args) == 0 { 1444 panic("not enough arguments to 'go run'") 1445 } 1446 1447 flags := flag.NewFlagSet("go run", flag.ContinueOnError) 1448 gcflags := flags.String("gcflags", "", "'[pattern=]arg list' arguments to pass on each go tool compile invocation.") 1449 if err := flags.Parse(args); err != nil { 1450 panic(err) 1451 } 1452 1453 if flags.NArg() == 0 { 1454 panic("not enough arguments to 'go run'") 1455 } 1456 1457 var opts []Option 1458 1459 if s := *gcflags; s != "" { 1460 switch s { 1461 case "-l=4": 1462 default: 1463 panic(fmt.Sprintf("%q", s)) 1464 } 1465 } 1466 1467 v.opt(opts) 1468 if _, err := v.t.ctx.Build(flags.Args()); err != nil { 1469 fmt.Fprintf(v.cmd.Stdout, "%s", err) 1470 } 1471 1472 //TODO ctx.Build ok, actually execute 1473 return nil 1474 } 1475 1476 func (v *vm) GoTool(args []string) error { 1477 if len(args) == 0 { 1478 panic("not enough arguments 'go tool'") 1479 } 1480 1481 switch arg := args[0]; arg { 1482 case "asm": 1483 return fmt.Errorf("XTODO 1469 %v", v.cmd.Args) 1484 case "compile": 1485 return v.GoToolCompile(args[1:]) 1486 case "link": 1487 return fmt.Errorf("XTODO 1472 %v", v.cmd.Args) 1488 default: 1489 panic(arg) 1490 } 1491 return nil 1492 } 1493 1494 func (v *vm) GoToolCompile(args []string) error { 1495 if len(args) == 0 { 1496 panic("not enough arguments 'go tool compile'") 1497 } 1498 1499 flags := flag.NewFlagSet("go tool compile", flag.ContinueOnError) 1500 1501 C := flags.Bool("C", false, "disable printing of columns in error messages") 1502 D := flags.String("D", "", "set relative path for local imports") 1503 I := flags.String("I", "", "add directory to import search path") 1504 _ = flags.Bool("+", false, "compiling runtime") 1505 _ = flags.Bool("N", false, "disable optimizations") 1506 _ = flags.Bool("l", false, "disable inlining") 1507 _ = flags.Bool("live", false, "debug liveness analysis") 1508 _ = flags.Bool("m", false, "print optimization decisions") 1509 _ = flags.Bool("race", false, "enable race detector") 1510 _ = flags.Bool("std", false, "compiling standard library") 1511 _ = flags.Bool("wb", true, "enable write barrier") 1512 _ = flags.Int("c", 1, "concurrency during compilation, 1 means no concurrency (default 1)") 1513 _ = flags.String("asmhdr", "", "write assembly header to file") 1514 _ = flags.String("d", "", "print debug information about items in list; try -d help") 1515 _ = flags.String("o", "", "write output to file") 1516 e := flags.Bool("e", false, "no limit on number of errors reported") 1517 1518 w := 0 1519 for _, v := range args { 1520 if strings.HasPrefix(v, "-l=") { 1521 continue 1522 } 1523 1524 args[w] = v 1525 w++ 1526 } 1527 args = args[:w] 1528 1529 if err := flags.Parse(args); err != nil { 1530 panic(err) 1531 } 1532 1533 var opts []Option 1534 1535 if *C { 1536 opts = append(opts, NoErrorColumns()) 1537 } 1538 if s := *D; s != "" { 1539 opts = append(opts, LocalImportsPath(s)) 1540 } 1541 if s := *I; s != "" { 1542 opts = append(opts, addSearchPath(s)) 1543 } 1544 if *e { 1545 opts = append(opts, NoErrorLimit()) 1546 } 1547 1548 v.opt(opts) 1549 p, err := v.t.ctx.Build(flags.Args()) 1550 if err != nil { 1551 fmt.Fprintf(v.cmd.Stdout, "%s", err) 1552 return err 1553 } 1554 1555 switch { 1556 case flags.NArg() == 1: 1557 ip := filepath.Join(filepath.Dir(flags.Arg(0)), p.Name) 1558 ctx := v.t.ctx 1559 ctx.packagesMu.Lock() 1560 if _, ok := ctx.packages[ip]; ok { 1561 ctx.packagesMu.Unlock() 1562 return fmt.Errorf("XTODO 1561 %v", v.cmd.Args) 1563 } 1564 1565 ctx.packages[ip] = p 1566 ctx.packagesMu.Unlock() 1567 default: 1568 return fmt.Errorf("XTODO 1567 %v", v.cmd.Args) 1569 } 1570 return nil 1571 }