3 // Copyright 2012 The Go Authors. All rights reserved.
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file.
7 // Run runs tests in the test directory.
34 verbose
= flag
.Bool("v", false, "verbose. if set, parallelism is set to 1.")
35 keep
= flag
.Bool("k", false, "keep. keep temporary directory.")
36 numParallel
= flag
.Int("n", runtime
.NumCPU(), "number of parallel tests to run")
37 summary
= flag
.Bool("summary", false, "show summary of results")
38 allCodegen
= flag
.Bool("all_codegen", defaultAllCodeGen(), "run all goos/goarch for codegen")
39 showSkips
= flag
.Bool("show_skips", false, "show skipped tests")
40 runSkips
= flag
.Bool("run_skips", false, "run skipped tests (ignore skip and build tags)")
41 linkshared
= flag
.Bool("linkshared", false, "")
42 updateErrors
= flag
.Bool("update_errors", false, "update error messages in test file based on compiler output")
43 runoutputLimit
= flag
.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run")
45 shard
= flag
.Int("shard", 0, "shard index to run. Only applicable if -shards is non-zero.")
46 shards
= flag
.Int("shards", 0, "number of shards. If 0, all tests are run. This is used by the continuous build.")
49 // defaultAllCodeGen returns the default value of the -all_codegen
50 // flag. By default, we prefer to be fast (returning false), except on
51 // the linux-amd64 builder that's already very fast, so we get more
52 // test coverage on trybots. See https://golang.org/issue/34297.
53 func defaultAllCodeGen() bool {
54 return os
.Getenv("GO_BUILDER_NAME") == "linux-amd64"
60 // dirs are the directories to look for *.go files in.
61 // TODO(bradfitz): just use all directories?
62 dirs
= []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "codegen", "runtime"}
64 // ratec controls the max number of tests running at a time.
67 // toRun is the channel of tests to run.
68 // It is nil until the first test is started.
71 // rungatec controls the max number of runoutput tests
72 // executed in parallel as they can each consume a lot of memory.
76 // maxTests is an upper bound on the total number of tests.
77 // It is used as a channel buffer size to make sure sends don't block.
83 goos
= getenv("GOOS", runtime
.GOOS
)
84 goarch
= getenv("GOARCH", runtime
.GOARCH
)
88 // Disable parallelism if printing or if using a simulator.
89 if *verbose ||
len(findExecCmd()) > 0 {
94 ratec
= make(chan bool, *numParallel
)
95 rungatec
= make(chan bool, *runoutputLimit
)
99 for _
, arg
:= range flag
.Args() {
100 if arg
== "-" || arg
== "--" {
102 // $ go run run.go - env.go
103 // $ go run run.go -- env.go
104 // $ go run run.go - ./fixedbugs
105 // $ go run run.go -- ./fixedbugs
108 if fi
, err
:= os
.Stat(arg
); err
== nil && fi
.IsDir() {
109 for _
, baseGoFile
:= range goFiles(arg
) {
110 tests
= append(tests
, startTest(arg
, baseGoFile
))
112 } else if strings
.HasSuffix(arg
, ".go") {
113 dir
, file
:= filepath
.Split(arg
)
114 tests
= append(tests
, startTest(dir
, file
))
116 log
.Fatalf("can't yet deal with non-directory and non-go file %q", arg
)
120 for _
, dir
:= range dirs
{
121 for _
, baseGoFile
:= range goFiles(dir
) {
122 tests
= append(tests
, startTest(dir
, baseGoFile
))
128 resCount
:= map[string]int{}
129 for _
, test
:= range tests
{
133 if e
, isSkip
:= test
.err
.(skipError
); isSkip
{
135 errStr
= "unexpected skip for " + path
.Join(test
.dir
, test
.gofile
) + ": " + string(e
)
140 errStr
= test
.err
.Error()
142 if status
== "FAIL" {
146 dt
:= fmt
.Sprintf("%.3fs", test
.dt
.Seconds())
147 if status
== "FAIL" {
148 fmt
.Printf("# go run run.go -- %s\n%s\nFAIL\t%s\t%s\n",
149 path
.Join(test
.dir
, test
.gofile
),
150 errStr
, test
.goFileName(), dt
)
156 fmt
.Printf("%s\t%s\t%s\n", status
, test
.goFileName(), dt
)
160 for k
, v
:= range resCount
{
161 fmt
.Printf("%5d %s\n", v
, k
)
170 // goTool reports the path of the go tool to use to run the tests.
171 // If possible, use the same Go used to run run.go, otherwise
172 // fallback to the go version found in the PATH.
173 func goTool() string {
175 if runtime
.GOOS
== "windows" {
178 path
:= filepath
.Join(runtime
.GOROOT(), "bin", "go"+exeSuffix
)
179 if _
, err
:= os
.Stat(path
); err
== nil {
182 // Just run "go" from PATH
186 func shardMatch(name
string) bool {
191 io
.WriteString(h
, name
)
192 return int(h
.Sum32()%uint
32(*shards
)) == *shard
195 func goFiles(dir
string) []string {
196 f
, err
:= os
.Open(dir
)
200 dirnames
, err
:= f
.Readdirnames(-1)
206 for _
, name
:= range dirnames
{
207 if !strings
.HasPrefix(name
, ".") && strings
.HasSuffix(name
, ".go") && shardMatch(name
) {
208 names
= append(names
, name
)
215 type runCmd
func(...string) ([]byte, error
)
217 func compileFile(runcmd runCmd
, longname
string, flags
[]string) (out
[]byte, err error
) {
218 cmd
:= []string{goTool(), "tool", "compile", "-e"}
219 cmd
= append(cmd
, flags
...)
221 cmd
= append(cmd
, "-dynlink", "-installsuffix=dynlink")
223 cmd
= append(cmd
, longname
)
224 return runcmd(cmd
...)
227 func compileInDir(runcmd runCmd
, dir
string, flags
[]string, localImports
bool, names
...string) (out
[]byte, err error
) {
228 cmd
:= []string{goTool(), "tool", "compile", "-e"}
230 // Set relative path for local imports and import search path to current dir.
231 cmd
= append(cmd
, "-D", ".", "-I", ".")
233 cmd
= append(cmd
, flags
...)
235 cmd
= append(cmd
, "-dynlink", "-installsuffix=dynlink")
237 for _
, name
:= range names
{
238 cmd
= append(cmd
, filepath
.Join(dir
, name
))
240 return runcmd(cmd
...)
243 func linkFile(runcmd runCmd
, goname
string, ldflags
[]string) (err error
) {
244 pfile
:= strings
.Replace(goname
, ".go", ".o", -1)
245 cmd
:= []string{goTool(), "tool", "link", "-w", "-o", "a.exe", "-L", "."}
247 cmd
= append(cmd
, "-linkshared", "-installsuffix=dynlink")
250 cmd
= append(cmd
, ldflags
...)
252 cmd
= append(cmd
, pfile
)
253 _
, err
= runcmd(cmd
...)
257 // skipError describes why a test was skipped.
258 type skipError
string
260 func (s skipError
) Error() string { return string(s
) }
262 // test holds the state of a test.
265 donec
chan bool // closed when done
275 func startTest(dir
, gofile
string) *test
{
279 donec
: make(chan bool, 1),
282 toRun
= make(chan *test
, maxTests
)
288 panic("toRun buffer size (maxTests) is too small")
293 // runTests runs tests in parallel, but respecting the order they
294 // were enqueued on the toRun channel.
306 var cwd
, _
= os
.Getwd()
308 func (t
*test
) goFileName() string {
309 return filepath
.Join(t
.dir
, t
.gofile
)
312 func (t
*test
) goDirName() string {
313 return filepath
.Join(t
.dir
, strings
.Replace(t
.gofile
, ".go", ".dir", -1))
316 func goDirFiles(longdir
string) (filter
[]os
.FileInfo
, err error
) {
317 files
, dirErr
:= ioutil
.ReadDir(longdir
)
321 for _
, gofile
:= range files
{
322 if filepath
.Ext(gofile
.Name()) == ".go" {
323 filter
= append(filter
, gofile
)
329 var packageRE
= regexp
.MustCompile(`(?m)^package ([\p{Lu}\p{Ll}\w]+)`)
331 func getPackageNameFromSource(fn
string) (string, error
) {
332 data
, err
:= ioutil
.ReadFile(fn
)
336 pkgname
:= packageRE
.FindStringSubmatch(string(data
))
338 return "", fmt
.Errorf("cannot find package name in %s", fn
)
340 return pkgname
[1], nil
343 // If singlefilepkgs is set, each file is considered a separate package
344 // even if the package names are the same.
345 func goDirPackages(longdir
string, singlefilepkgs
bool) ([][]string, error
) {
346 files
, err
:= goDirFiles(longdir
)
351 m
:= make(map[string]int)
352 for _
, file
:= range files
{
354 pkgname
, err
:= getPackageNameFromSource(filepath
.Join(longdir
, name
))
359 if singlefilepkgs ||
!ok
{
361 pkgs
= append(pkgs
, nil)
364 pkgs
[i
] = append(pkgs
[i
], name
)
369 type context
struct {
375 // shouldTest looks for build tags in a source file and returns
376 // whether the file should be used according to the tags.
377 func shouldTest(src
string, goos
, goarch
string) (ok
bool, whyNot
string) {
381 for _
, line
:= range strings
.Split(src
, "\n") {
382 line
= strings
.TrimSpace(line
)
383 if strings
.HasPrefix(line
, "//") {
388 line
= strings
.TrimSpace(line
)
389 if len(line
) == 0 || line
[0] != '+' {
392 gcFlags
:= os
.Getenv("GO_GCFLAGS")
396 noOptEnv
: strings
.Contains(gcFlags
, "-N") || strings
.Contains(gcFlags
, "-l"),
399 words
:= strings
.Fields(line
)
400 if words
[0] == "+build" {
402 for _
, word
:= range words
[1:] {
403 if ctxt
.match(word
) {
409 // no matching tag found.
418 func (ctxt
*context
) match(name
string) bool {
422 if i
:= strings
.Index(name
, ","); i
>= 0 {
423 // comma-separated list
424 return ctxt
.match(name
[:i
]) && ctxt
.match(name
[i
+1:])
426 if strings
.HasPrefix(name
, "!!") { // bad syntax, reject always
429 if strings
.HasPrefix(name
, "!") { // negation
430 return len(name
) > 1 && !ctxt
.match(name
[1:])
433 // Tags must be letters, digits, underscores or dots.
434 // Unlike in Go identifiers, all digits are fine (e.g., "386").
435 for _
, c
:= range name
{
436 if !unicode
.IsLetter(c
) && !unicode
.IsDigit(c
) && c
!= '_' && c
!= '.' {
441 if name
== ctxt
.GOOS || name
== ctxt
.GOARCH || name
== "gc" {
445 if ctxt
.noOptEnv
&& name
== "gcflags_noopt" {
449 if name
== "test_run" {
456 func init() { checkShouldTest() }
458 // goGcflags returns the -gcflags argument to use with go build / go run.
459 // This must match the flags used for building the standard library,
460 // or else the commands will rebuild any needed packages (like runtime)
462 func goGcflags() string {
463 return "-gcflags=all=" + os
.Getenv("GO_GCFLAGS")
466 func goGcflagsIsEmpty() bool {
467 return "" == os
.Getenv("GO_GCFLAGS")
471 func (t
*test
) run() {
474 t
.dt
= time
.Since(start
)
478 srcBytes
, err
:= ioutil
.ReadFile(t
.goFileName())
483 t
.src
= string(srcBytes
)
484 if t
.src
[0] == '\n' {
485 t
.err
= skipError("starts with newline")
489 // Execution recipe stops at first blank line.
490 pos
:= strings
.Index(t
.src
, "\n\n")
492 t
.err
= errors
.New("double newline not found")
495 action
:= t
.src
[:pos
]
496 if nl
:= strings
.Index(action
, "\n"); nl
>= 0 && strings
.Contains(action
[:nl
], "+build") {
498 action
= action
[nl
+1:]
500 action
= strings
.TrimPrefix(action
, "//")
502 // Check for build constraints only up to the actual code.
503 pkgPos
:= strings
.Index(t
.src
, "\npackage")
505 pkgPos
= pos
// some files are intentionally malformed
507 if ok
, why
:= shouldTest(t
.src
[:pkgPos
], goos
, goarch
); !ok
{
509 fmt
.Printf("%-20s %-20s: %s\n", "skip", t
.goFileName(), why
)
514 var args
, flags
[]string
518 singlefilepkgs
:= false
521 f
:= strings
.Fields(action
)
527 // TODO: Clean up/simplify this switch statement.
529 case "compile", "compiledir", "build", "builddir", "buildrundir", "run", "buildrun", "runoutput", "rundir", "runindir", "asmcheck":
531 case "errorcheckandrundir":
532 wantError
= false // should be no error if also will run
533 case "errorcheckwithauto":
534 action
= "errorcheck"
537 case "errorcheck", "errorcheckdir", "errorcheckoutput":
545 t
.err
= skipError("skipped; unknown pattern: " + action
)
550 for len(args
) > 0 && strings
.HasPrefix(args
[0], "-") {
557 singlefilepkgs
= true
561 // Do not set relative path for local imports to current dir,
562 // e.g. do not pass -D . -I . to the compiler.
563 // Used in fixedbugs/bug345.go to allow compilation and import of local pkg.
564 // See golang.org/issue/25635
566 case "-t": // timeout in seconds
569 tim
, err
= strconv
.Atoi(args
[0])
571 t
.err
= fmt
.Errorf("need number of seconds for -t timeout, got %s instead", args
[0])
575 flags
= append(flags
, args
[0])
579 if action
== "errorcheck" {
581 for i
, f
:= range flags
{
582 if strings
.HasPrefix(f
, "-d=") {
583 flags
[i
] = f
+ ",ssa/check/on"
589 flags
= append(flags
, "-d=ssa/check/on")
595 defer os
.RemoveAll(t
.tempDir
)
598 err
= ioutil
.WriteFile(filepath
.Join(t
.tempDir
, t
.gofile
), srcBytes
, 0644)
603 // A few tests (of things like the environment) require these to be set.
604 if os
.Getenv("GOOS") == "" {
605 os
.Setenv("GOOS", runtime
.GOOS
)
607 if os
.Getenv("GOARCH") == "" {
608 os
.Setenv("GOARCH", runtime
.GOARCH
)
613 tempDirIsGOPATH
= false
615 runcmd
:= func(args
...string) ([]byte, error
) {
616 cmd
:= exec
.Command(args
[0], args
[1:]...)
620 cmd
.Env
= append(os
.Environ(), "GOENV=off", "GOFLAGS=")
623 // Set PWD to match Dir to speed up os.Getwd in the child process.
624 cmd
.Env
= append(cmd
.Env
, "PWD="+cmd
.Dir
)
627 cmd
.Env
= append(cmd
.Env
, "GOPATH="+t
.tempDir
)
634 // This command-timeout code adapted from cmd/go/test.go
636 tick
:= time
.NewTimer(time
.Duration(tim
) * time
.Second
)
637 done
:= make(chan error
)
647 // err = errors.New("Test timeout")
655 err
= fmt
.Errorf("%s\n%s", err
, buf
.Bytes())
657 return buf
.Bytes(), err
660 long
:= filepath
.Join(cwd
, t
.goFileName())
663 t
.err
= fmt
.Errorf("unimplemented action %q", action
)
666 // Compile Go file and match the generated assembly
667 // against a set of regexps in comments.
668 ops
:= t
.wantedAsmOpcodes(long
)
669 self
:= runtime
.GOOS
+ "/" + runtime
.GOARCH
670 for _
, env
:= range ops
.Envs() {
671 // Only run checks relevant to the current GOOS/GOARCH,
672 // to avoid triggering a cross-compile of the runtime.
673 if string(env
) != self
&& !strings
.HasPrefix(string(env
), self
+"/") && !*allCodegen
{
676 // -S=2 forces outermost line numbers when disassembling inlined code.
677 cmdline
:= []string{"build", "-gcflags", "-S=2"}
679 // Append flags, but don't override -gcflags=-S=2; add to it instead.
680 for i
:= 0; i
< len(flags
); i
++ {
683 case strings
.HasPrefix(flag
, "-gcflags="):
684 cmdline
[2] += " " + strings
.TrimPrefix(flag
, "-gcflags=")
685 case strings
.HasPrefix(flag
, "--gcflags="):
686 cmdline
[2] += " " + strings
.TrimPrefix(flag
, "--gcflags=")
687 case flag
== "-gcflags", flag
== "--gcflags":
690 cmdline
[2] += " " + flags
[i
]
693 cmdline
= append(cmdline
, flag
)
697 cmdline
= append(cmdline
, long
)
698 cmd
:= exec
.Command(goTool(), cmdline
...)
699 cmd
.Env
= append(os
.Environ(), env
.Environ()...)
700 if len(flags
) > 0 && flags
[0] == "-race" {
701 cmd
.Env
= append(cmd
.Env
, "CGO_ENABLED=1")
705 cmd
.Stdout
, cmd
.Stderr
= &buf
, &buf
706 if err
:= cmd
.Run(); err
!= nil {
707 fmt
.Println(env
, "\n", cmd
.Stderr
)
712 t
.err
= t
.asmCheck(buf
.String(), long
, env
, ops
[env
])
721 // Fail if wantError is true and compilation was successful and vice versa.
722 // Match errors produced by gc against errors in comments.
723 // TODO(gri) remove need for -C (disable printing of columns in error messages)
724 cmdline
:= []string{goTool(), "tool", "compile", "-C", "-e", "-o", "a.o"}
725 // No need to add -dynlink even if linkshared if we're just checking for errors...
726 cmdline
= append(cmdline
, flags
...)
727 cmdline
= append(cmdline
, long
)
728 out
, err
:= runcmd(cmdline
...)
731 t
.err
= fmt
.Errorf("compilation succeeded unexpectedly\n%s", out
)
741 t
.updateErrors(string(out
), long
)
743 t
.err
= t
.errorCheck(string(out
), wantAuto
, long
, t
.gofile
)
748 _
, t
.err
= compileFile(runcmd
, long
, flags
)
751 // Compile all files in the directory as packages in lexicographic order.
752 longdir
:= filepath
.Join(cwd
, t
.goDirName())
753 pkgs
, err
:= goDirPackages(longdir
, singlefilepkgs
)
758 for _
, gofiles
:= range pkgs
{
759 _
, t
.err
= compileInDir(runcmd
, longdir
, flags
, localImports
, gofiles
...)
765 case "errorcheckdir", "errorcheckandrundir":
766 // Compile and errorCheck all files in the directory as packages in lexicographic order.
767 // If errorcheckdir and wantError, compilation of the last package must fail.
768 // If errorcheckandrundir and wantError, compilation of the package prior the last must fail.
769 longdir
:= filepath
.Join(cwd
, t
.goDirName())
770 pkgs
, err
:= goDirPackages(longdir
, singlefilepkgs
)
775 errPkg
:= len(pkgs
) - 1
776 if wantError
&& action
== "errorcheckandrundir" {
777 // The last pkg should compiled successfully and will be run in next case.
778 // Preceding pkg must return an error from compileInDir.
781 for i
, gofiles
:= range pkgs
{
782 out
, err
:= compileInDir(runcmd
, longdir
, flags
, localImports
, gofiles
...)
784 if wantError
&& err
== nil {
785 t
.err
= fmt
.Errorf("compilation succeeded unexpectedly\n%s", out
)
787 } else if !wantError
&& err
!= nil {
791 } else if err
!= nil {
795 var fullshort
[]string
796 for _
, name
:= range gofiles
{
797 fullshort
= append(fullshort
, filepath
.Join(longdir
, name
), name
)
799 t
.err
= t
.errorCheck(string(out
), wantAuto
, fullshort
...)
804 if action
== "errorcheckdir" {
810 // Compile all files in the directory as packages in lexicographic order.
811 // In case of errorcheckandrundir, ignore failed compilation of the package before the last.
812 // Link as if the last file is the main package, run it.
813 // Verify the expected output.
814 longdir
:= filepath
.Join(cwd
, t
.goDirName())
815 pkgs
, err
:= goDirPackages(longdir
, singlefilepkgs
)
820 // Split flags into gcflags and ldflags
821 ldflags
:= []string{}
822 for i
, fl
:= range flags
{
823 if fl
== "-ldflags" {
824 ldflags
= flags
[i
+1:]
830 for i
, gofiles
:= range pkgs
{
832 pflags
= append(pflags
, flags
...)
834 fp
:= filepath
.Join(longdir
, gofiles
[0])
835 pkgname
, err
:= getPackageNameFromSource(fp
)
839 pflags
= append(pflags
, "-p", pkgname
)
841 _
, err
:= compileInDir(runcmd
, longdir
, pflags
, localImports
, gofiles
...)
842 // Allow this package compilation fail based on conditions below;
843 // its errors were checked in previous case.
844 if err
!= nil && !(wantError
&& action
== "errorcheckandrundir" && i
== len(pkgs
)-2) {
848 if i
== len(pkgs
)-1 {
849 err
= linkFile(runcmd
, gofiles
[0], ldflags
)
855 cmd
= append(cmd
, findExecCmd()...)
856 cmd
= append(cmd
, filepath
.Join(t
.tempDir
, "a.exe"))
857 cmd
= append(cmd
, args
...)
858 out
, err
:= runcmd(cmd
...)
863 if strings
.Replace(string(out
), "\r\n", "\n", -1) != t
.expectedOutput() {
864 t
.err
= fmt
.Errorf("incorrect output\n%s", out
)
870 // Make a shallow copy of t.goDirName() in its own module and GOPATH, and
871 // run "go run ." in it. The module path (and hence import path prefix) of
872 // the copy is equal to the basename of the source directory.
874 // It's used when test a requires a full 'go build' in order to compile
875 // the sources, such as when importing multiple packages (issue29612.dir)
876 // or compiling a package containing assembly files (see issue15609.dir),
877 // but still needs to be run to verify the expected output.
878 tempDirIsGOPATH
= true
879 srcDir
:= t
.goDirName()
880 modName
:= filepath
.Base(srcDir
)
881 gopathSrcDir
:= filepath
.Join(t
.tempDir
, "src", modName
)
882 runInDir
= gopathSrcDir
884 if err
:= overlayDir(gopathSrcDir
, srcDir
); err
!= nil {
889 modFile
:= fmt
.Sprintf("module %s\ngo 1.14\n", modName
)
890 if err
:= ioutil
.WriteFile(filepath
.Join(gopathSrcDir
, "go.mod"), []byte(modFile
), 0666); err
!= nil {
895 cmd
:= []string{goTool(), "run", goGcflags()}
897 cmd
= append(cmd
, "-linkshared")
899 cmd
= append(cmd
, ".")
900 out
, err
:= runcmd(cmd
...)
905 if strings
.Replace(string(out
), "\r\n", "\n", -1) != t
.expectedOutput() {
906 t
.err
= fmt
.Errorf("incorrect output\n%s", out
)
911 _
, err
:= runcmd(goTool(), "build", goGcflags(), "-o", "a.exe", long
)
916 case "builddir", "buildrundir":
917 // Build an executable from all the .go and .s files in a subdirectory.
918 // Run it and verify its output in the buildrundir case.
919 longdir
:= filepath
.Join(cwd
, t
.goDirName())
920 files
, dirErr
:= ioutil
.ReadDir(longdir
)
927 for _
, file
:= range files
{
928 switch filepath
.Ext(file
.Name()) {
930 gos
= append(gos
, filepath
.Join(longdir
, file
.Name()))
932 asms
= append(asms
, filepath
.Join(longdir
, file
.Name()))
937 emptyHdrFile
:= filepath
.Join(t
.tempDir
, "go_asm.h")
938 if err
:= ioutil
.WriteFile(emptyHdrFile
, nil, 0666); err
!= nil {
939 t
.err
= fmt
.Errorf("write empty go_asm.h: %s", err
)
942 cmd
:= []string{goTool(), "tool", "asm", "-gensymabis", "-o", "symabis"}
943 cmd
= append(cmd
, asms
...)
944 _
, err
= runcmd(cmd
...)
951 cmd
:= []string{goTool(), "tool", "compile", "-e", "-D", ".", "-I", ".", "-o", "go.o"}
953 cmd
= append(cmd
, "-asmhdr", "go_asm.h", "-symabis", "symabis")
955 cmd
= append(cmd
, gos
...)
956 _
, err
:= runcmd(cmd
...)
961 objs
= append(objs
, "go.o")
963 cmd
= []string{goTool(), "tool", "asm", "-e", "-I", ".", "-o", "asm.o"}
964 cmd
= append(cmd
, asms
...)
965 _
, err
= runcmd(cmd
...)
970 objs
= append(objs
, "asm.o")
972 cmd
= []string{goTool(), "tool", "pack", "c", "all.a"}
973 cmd
= append(cmd
, objs
...)
974 _
, err
= runcmd(cmd
...)
979 cmd
= []string{goTool(), "tool", "link", "-o", "a.exe", "all.a"}
980 _
, err
= runcmd(cmd
...)
985 if action
== "buildrundir" {
986 cmd
= append(findExecCmd(), filepath
.Join(t
.tempDir
, "a.exe"))
987 out
, err
:= runcmd(cmd
...)
992 if strings
.Replace(string(out
), "\r\n", "\n", -1) != t
.expectedOutput() {
993 t
.err
= fmt
.Errorf("incorrect output\n%s", out
)
998 // Build an executable from Go file, then run it, verify its output.
999 // Useful for timeout tests where failure mode is infinite loop.
1000 // TODO: not supported on NaCl
1001 cmd
:= []string{goTool(), "build", goGcflags(), "-o", "a.exe"}
1003 cmd
= append(cmd
, "-linkshared")
1005 longdirgofile
:= filepath
.Join(filepath
.Join(cwd
, t
.dir
), t
.gofile
)
1006 cmd
= append(cmd
, flags
...)
1007 cmd
= append(cmd
, longdirgofile
)
1008 _
, err
:= runcmd(cmd
...)
1013 cmd
= []string{"./a.exe"}
1014 out
, err
:= runcmd(append(cmd
, args
...)...)
1020 if strings
.Replace(string(out
), "\r\n", "\n", -1) != t
.expectedOutput() {
1021 t
.err
= fmt
.Errorf("incorrect output\n%s", out
)
1025 // Run Go file if no special go command flags are provided;
1026 // otherwise build an executable and run it.
1027 // Verify the output.
1031 if len(flags
)+len(args
) == 0 && goGcflagsIsEmpty() && !*linkshared
&& goarch
== runtime
.GOARCH
&& goos
== runtime
.GOOS
{
1032 // If we're not using special go command flags,
1033 // skip all the go command machinery.
1034 // This avoids any time the go command would
1035 // spend checking whether, for example, the installed
1036 // package runtime is up to date.
1037 // Because we run lots of trivial test programs,
1038 // the time adds up.
1039 pkg
:= filepath
.Join(t
.tempDir
, "pkg.a")
1040 if _
, err
:= runcmd(goTool(), "tool", "compile", "-o", pkg
, t
.goFileName()); err
!= nil {
1044 exe
:= filepath
.Join(t
.tempDir
, "test.exe")
1045 cmd
:= []string{goTool(), "tool", "link", "-s", "-w"}
1046 cmd
= append(cmd
, "-o", exe
, pkg
)
1047 if _
, err
:= runcmd(cmd
...); err
!= nil {
1051 out
, err
= runcmd(append([]string{exe
}, args
...)...)
1053 cmd
:= []string{goTool(), "run", goGcflags()}
1055 cmd
= append(cmd
, "-linkshared")
1057 cmd
= append(cmd
, flags
...)
1058 cmd
= append(cmd
, t
.goFileName())
1059 out
, err
= runcmd(append(cmd
, args
...)...)
1065 if strings
.Replace(string(out
), "\r\n", "\n", -1) != t
.expectedOutput() {
1066 t
.err
= fmt
.Errorf("incorrect output\n%s", out
)
1070 // Run Go file and write its output into temporary Go file.
1071 // Run generated Go file and verify its output.
1077 cmd
:= []string{goTool(), "run", goGcflags()}
1079 cmd
= append(cmd
, "-linkshared")
1081 cmd
= append(cmd
, t
.goFileName())
1082 out
, err
:= runcmd(append(cmd
, args
...)...)
1087 tfile
:= filepath
.Join(t
.tempDir
, "tmp__.go")
1088 if err
:= ioutil
.WriteFile(tfile
, out
, 0666); err
!= nil {
1089 t
.err
= fmt
.Errorf("write tempfile:%s", err
)
1092 cmd
= []string{goTool(), "run", goGcflags()}
1094 cmd
= append(cmd
, "-linkshared")
1096 cmd
= append(cmd
, tfile
)
1097 out
, err
= runcmd(cmd
...)
1102 if string(out
) != t
.expectedOutput() {
1103 t
.err
= fmt
.Errorf("incorrect output\n%s", out
)
1106 case "errorcheckoutput":
1107 // Run Go file and write its output into temporary Go file.
1108 // Compile and errorCheck generated Go file.
1110 cmd
:= []string{goTool(), "run", goGcflags()}
1112 cmd
= append(cmd
, "-linkshared")
1114 cmd
= append(cmd
, t
.goFileName())
1115 out
, err
:= runcmd(append(cmd
, args
...)...)
1120 tfile
:= filepath
.Join(t
.tempDir
, "tmp__.go")
1121 err
= ioutil
.WriteFile(tfile
, out
, 0666)
1123 t
.err
= fmt
.Errorf("write tempfile:%s", err
)
1126 cmdline
:= []string{goTool(), "tool", "compile", "-e", "-o", "a.o"}
1127 cmdline
= append(cmdline
, flags
...)
1128 cmdline
= append(cmdline
, tfile
)
1129 out
, err
= runcmd(cmdline
...)
1132 t
.err
= fmt
.Errorf("compilation succeeded unexpectedly\n%s", out
)
1141 t
.err
= t
.errorCheck(string(out
), false, tfile
, "tmp__.go")
1146 var execCmd
[]string
1148 func findExecCmd() []string {
1152 execCmd
= []string{} // avoid work the second time
1153 if goos
== runtime
.GOOS
&& goarch
== runtime
.GOARCH
{
1156 path
, err
:= exec
.LookPath(fmt
.Sprintf("go_%s_%s_exec", goos
, goarch
))
1158 execCmd
= []string{path
}
1163 func (t
*test
) String() string {
1164 return filepath
.Join(t
.dir
, t
.gofile
)
1167 func (t
*test
) makeTempDir() {
1169 t
.tempDir
, err
= ioutil
.TempDir("", "")
1174 log
.Printf("Temporary directory is %s", t
.tempDir
)
1178 func (t
*test
) expectedOutput() string {
1179 filename
:= filepath
.Join(t
.dir
, t
.gofile
)
1180 filename
= filename
[:len(filename
)-len(".go")]
1182 b
, _
:= ioutil
.ReadFile(filename
)
1186 func splitOutput(out
string, wantAuto
bool) []string {
1187 // gc error messages continue onto additional lines with leading tabs.
1188 // Split the output at the beginning of each line that doesn't begin with a tab.
1189 // <autogenerated> lines are impossible to match so those are filtered out.
1191 for _
, line
:= range strings
.Split(out
, "\n") {
1192 if strings
.HasSuffix(line
, "\r") { // remove '\r', output by compiler on windows
1193 line
= line
[:len(line
)-1]
1195 if strings
.HasPrefix(line
, "\t") {
1196 res
[len(res
)-1] += "\n" + line
1197 } else if strings
.HasPrefix(line
, "go tool") || strings
.HasPrefix(line
, "#") ||
!wantAuto
&& strings
.HasPrefix(line
, "<autogenerated>") {
1199 } else if strings
.TrimSpace(line
) != "" {
1200 res
= append(res
, line
)
1206 // errorCheck matches errors in outStr against comments in source files.
1207 // For each line of the source files which should generate an error,
1208 // there should be a comment of the form // ERROR "regexp".
1209 // If outStr has an error for a line which has no such comment,
1210 // this function will report an error.
1211 // Likewise if outStr does not have an error for a line which has a comment,
1212 // or if the error message does not match the <regexp>.
1213 // The <regexp> syntax is Perl but it's best to stick to egrep.
1215 // Sources files are supplied as fullshort slice.
1216 // It consists of pairs: full path to source file and its base name.
1217 func (t
*test
) errorCheck(outStr
string, wantAuto
bool, fullshort
...string) (err error
) {
1219 if *verbose
&& err
!= nil {
1220 log
.Printf("%s gc output:\n%s", t
, outStr
)
1224 out
:= splitOutput(outStr
, wantAuto
)
1226 // Cut directory name.
1227 for i
:= range out
{
1228 for j
:= 0; j
< len(fullshort
); j
+= 2 {
1229 full
, short
:= fullshort
[j
], fullshort
[j
+1]
1230 out
[i
] = strings
.Replace(out
[i
], full
, short
, -1)
1234 var want
[]wantedError
1235 for j
:= 0; j
< len(fullshort
); j
+= 2 {
1236 full
, short
:= fullshort
[j
], fullshort
[j
+1]
1237 want
= append(want
, t
.wantedErrors(full
, short
)...)
1240 for _
, we
:= range want
{
1241 var errmsgs
[]string
1243 errmsgs
, out
= partitionStrings("<autogenerated>", out
)
1245 errmsgs
, out
= partitionStrings(we
.prefix
, out
)
1247 if len(errmsgs
) == 0 {
1248 errs
= append(errs
, fmt
.Errorf("%s:%d: missing error %q", we
.file
, we
.lineNum
, we
.reStr
))
1253 for _
, errmsg
:= range errmsgs
{
1254 // Assume errmsg says "file:line: foo".
1255 // Cut leading "file:line: " to avoid accidental matching of file name instead of message.
1257 if i
:= strings
.Index(text
, " "); i
>= 0 {
1260 if we
.re
.MatchString(text
) {
1263 out
= append(out
, errmsg
)
1267 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")))
1273 errs
= append(errs
, fmt
.Errorf("Unmatched Errors:"))
1274 for _
, errLine
:= range out
{
1275 errs
= append(errs
, fmt
.Errorf("%s", errLine
))
1285 var buf bytes
.Buffer
1286 fmt
.Fprintf(&buf
, "\n")
1287 for _
, err
:= range errs
{
1288 fmt
.Fprintf(&buf
, "%s\n", err
.Error())
1290 return errors
.New(buf
.String())
1293 func (t
*test
) updateErrors(out
, file
string) {
1294 base
:= path
.Base(file
)
1295 // Read in source file.
1296 src
, err
:= ioutil
.ReadFile(file
)
1298 fmt
.Fprintln(os
.Stderr
, err
)
1301 lines
:= strings
.Split(string(src
), "\n")
1302 // Remove old errors.
1303 for i
, ln
:= range lines
{
1304 pos
:= strings
.Index(ln
, " // ERROR ")
1309 // Parse new errors.
1310 errors
:= make(map[int]map[string]bool)
1311 tmpRe
:= regexp
.MustCompile(`autotmp_[0-9]+`)
1312 for _
, errStr
:= range splitOutput(out
, false) {
1313 colon1
:= strings
.Index(errStr
, ":")
1314 if colon1
< 0 || errStr
[:colon1
] != file
{
1317 colon2
:= strings
.Index(errStr
[colon1
+1:], ":")
1321 colon2
+= colon1
+ 1
1322 line
, err
:= strconv
.Atoi(errStr
[colon1
+1 : colon2
])
1324 if err
!= nil || line
< 0 || line
>= len(lines
) {
1327 msg
:= errStr
[colon2
+2:]
1328 msg
= strings
.Replace(msg
, file
, base
, -1) // normalize file mentions in error itself
1329 msg
= strings
.TrimLeft(msg
, " \t")
1330 for _
, r
:= range []string{`\`, `*`, `+`, `?`, `[`, `]`, `(`, `)`} {
1331 msg
= strings
.Replace(msg
, r
, `\`+r
, -1)
1333 msg
= strings
.Replace(msg
, `"`, `.`, -1)
1334 msg
= tmpRe
.ReplaceAllLiteralString(msg
, `autotmp_[0-9]+`)
1335 if errors
[line
] == nil {
1336 errors
[line
] = make(map[string]bool)
1338 errors
[line
][msg
] = true
1341 for line
, errs
:= range errors
{
1343 for e
:= range errs
{
1344 sorted
= append(sorted
, e
)
1346 sort
.Strings(sorted
)
1347 lines
[line
] += " // ERROR"
1348 for _
, e
:= range sorted
{
1349 lines
[line
] += fmt
.Sprintf(` "%s$"`, e
)
1353 err
= ioutil
.WriteFile(file
, []byte(strings
.Join(lines
, "\n")), 0640)
1355 fmt
.Fprintln(os
.Stderr
, err
)
1359 exec
.Command(goTool(), "fmt", file
).CombinedOutput()
1362 // matchPrefix reports whether s is of the form ^(.*/)?prefix(:|[),
1363 // That is, it needs the file name prefix followed by a : or a [,
1364 // and possibly preceded by a directory name.
1365 func matchPrefix(s
, prefix
string) bool {
1366 i
:= strings
.Index(s
, ":")
1370 j
:= strings
.LastIndex(s
[:i
], "/")
1372 if len(s
) <= len(prefix
) || s
[:len(prefix
)] != prefix
{
1375 switch s
[len(prefix
)] {
1382 func partitionStrings(prefix
string, strs
[]string) (matched
, unmatched
[]string) {
1383 for _
, s
:= range strs
{
1384 if matchPrefix(s
, prefix
) {
1385 matched
= append(matched
, s
)
1387 unmatched
= append(unmatched
, s
)
1393 type wantedError
struct {
1397 auto
bool // match <autogenerated> line
1403 errRx
= regexp
.MustCompile(`// (?:GC_)?ERROR (.*)`)
1404 errAutoRx
= regexp
.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`)
1405 errQuotesRx
= regexp
.MustCompile(`"([^"]*)"`)
1406 lineRx
= regexp
.MustCompile(`LINE(([+-])([0-9]+))?`)
1409 func (t
*test
) wantedErrors(file
, short
string) (errs
[]wantedError
) {
1410 cache
:= make(map[string]*regexp
.Regexp
)
1412 src
, _
:= ioutil
.ReadFile(file
)
1413 for i
, line
:= range strings
.Split(string(src
), "\n") {
1415 if strings
.Contains(line
, "////") {
1416 // double comment disables ERROR
1420 m
:= errAutoRx
.FindStringSubmatch(line
)
1424 m
= errRx
.FindStringSubmatch(line
)
1430 mm
:= errQuotesRx
.FindAllStringSubmatch(all
, -1)
1432 log
.Fatalf("%s:%d: invalid errchk line: %s", t
.goFileName(), lineNum
, line
)
1434 for _
, m
:= range mm
{
1435 rx
:= lineRx
.ReplaceAllStringFunc(m
[1], func(m
string) string {
1437 if strings
.HasPrefix(m
, "LINE+") {
1438 delta
, _
:= strconv
.Atoi(m
[5:])
1440 } else if strings
.HasPrefix(m
, "LINE-") {
1441 delta
, _
:= strconv
.Atoi(m
[5:])
1444 return fmt
.Sprintf("%s:%d", short
, n
)
1449 re
, err
= regexp
.Compile(rx
)
1451 log
.Fatalf("%s:%d: invalid regexp \"%s\" in ERROR line: %v", t
.goFileName(), lineNum
, rx
, err
)
1455 prefix
:= fmt
.Sprintf("%s:%d", short
, lineNum
)
1456 errs
= append(errs
, wantedError
{
1471 // Regexp to match a single opcode check: optionally begin with "-" (to indicate
1472 // a negative check), followed by a string literal enclosed in "" or ``. For "",
1473 // backslashes must be handled.
1474 reMatchCheck
= `-?(?:\x60[^\x60]*\x60|"(?:[^"\\]|\\.)*")`
1478 // Regexp to split a line in code and comment, trimming spaces
1479 rxAsmComment
= regexp
.MustCompile(`^\s*(.*?)\s*(?://\s*(.+)\s*)?$`)
1481 // Regexp to extract an architecture check: architecture name (or triplet),
1482 // followed by semi-colon, followed by a comma-separated list of opcode checks.
1483 // Extraneous spaces are ignored.
1484 rxAsmPlatform
= regexp
.MustCompile(`(\w+)(/\w+)?(/\w*)?\s*:\s*(` + reMatchCheck
+ `(?:\s*,\s*` + reMatchCheck
+ `)*)`)
1486 // Regexp to extract a single opcoded check
1487 rxAsmCheck
= regexp
.MustCompile(reMatchCheck
)
1489 // List of all architecture variants. Key is the GOARCH architecture,
1490 // value[0] is the variant-changing environment variable, and values[1:]
1491 // are the supported variants.
1492 archVariants
= map[string][]string{
1493 "386": {"GO386", "sse2", "softfloat"},
1495 "arm": {"GOARM", "5", "6", "7"},
1497 "mips": {"GOMIPS", "hardfloat", "softfloat"},
1498 "mips64": {"GOMIPS64", "hardfloat", "softfloat"},
1499 "ppc64": {"GOPPC64", "power8", "power9"},
1500 "ppc64le": {"GOPPC64", "power8", "power9"},
1506 // wantedAsmOpcode is a single asmcheck check
1507 type wantedAsmOpcode
struct {
1508 fileline
string // original source file/line (eg: "/path/foo.go:45")
1509 line
int // original source line
1510 opcode
*regexp
.Regexp
// opcode check to be performed on assembly output
1511 negative
bool // true if the check is supposed to fail rather than pass
1512 found
bool // true if the opcode check matched at least one in the output
1515 // A build environment triplet separated by slashes (eg: linux/386/sse2).
1516 // The third field can be empty if the arch does not support variants (eg: "plan9/amd64/")
1517 type buildEnv
string
1519 // Environ returns the environment it represents in cmd.Environ() "key=val" format
1520 // For instance, "linux/386/sse2".Environ() returns {"GOOS=linux", "GOARCH=386", "GO386=sse2"}
1521 func (b buildEnv
) Environ() []string {
1522 fields
:= strings
.Split(string(b
), "/")
1523 if len(fields
) != 3 {
1524 panic("invalid buildEnv string: " + string(b
))
1526 env
:= []string{"GOOS=" + fields
[0], "GOARCH=" + fields
[1]}
1527 if fields
[2] != "" {
1528 env
= append(env
, archVariants
[fields
[1]][0]+"="+fields
[2])
1533 // asmChecks represents all the asmcheck checks present in a test file
1534 // The outer map key is the build triplet in which the checks must be performed.
1535 // The inner map key represent the source file line ("filename.go:1234") at which the
1536 // checks must be performed.
1537 type asmChecks
map[buildEnv
]map[string][]wantedAsmOpcode
1539 // Envs returns all the buildEnv in which at least one check is present
1540 func (a asmChecks
) Envs() []buildEnv
{
1543 envs
= append(envs
, e
)
1545 sort
.Slice(envs
, func(i
, j
int) bool {
1546 return string(envs
[i
]) < string(envs
[j
])
1551 func (t
*test
) wantedAsmOpcodes(fn
string) asmChecks
{
1552 ops
:= make(asmChecks
)
1555 src
, _
:= ioutil
.ReadFile(fn
)
1556 for i
, line
:= range strings
.Split(string(src
), "\n") {
1557 matches
:= rxAsmComment
.FindStringSubmatch(line
)
1558 code
, cmt
:= matches
[1], matches
[2]
1560 // Keep comments pending in the comment variable until
1561 // we find a line that contains some code.
1562 comment
+= " " + cmt
1567 // Parse and extract any architecture check from comments,
1568 // made by one architecture name and multiple checks.
1569 lnum
:= fn
+ ":" + strconv
.Itoa(i
+1)
1570 for _
, ac
:= range rxAsmPlatform
.FindAllStringSubmatch(comment
, -1) {
1571 archspec
, allchecks
:= ac
[1:4], ac
[4]
1573 var arch
, subarch
, os
string
1575 case archspec
[2] != "": // 3 components: "linux/386/sse2"
1576 os
, arch
, subarch
= archspec
[0], archspec
[1][1:], archspec
[2][1:]
1577 case archspec
[1] != "": // 2 components: "386/sse2"
1578 os
, arch
, subarch
= "linux", archspec
[0], archspec
[1][1:]
1579 default: // 1 component: "386"
1580 os
, arch
, subarch
= "linux", archspec
[0], ""
1586 if _
, ok
:= archVariants
[arch
]; !ok
{
1587 log
.Fatalf("%s:%d: unsupported architecture: %v", t
.goFileName(), i
+1, arch
)
1590 // Create the build environments corresponding the above specifiers
1591 envs
:= make([]buildEnv
, 0, 4)
1593 envs
= append(envs
, buildEnv(os
+"/"+arch
+"/"+subarch
))
1595 subarchs
:= archVariants
[arch
]
1596 if len(subarchs
) == 0 {
1597 envs
= append(envs
, buildEnv(os
+"/"+arch
+"/"))
1599 for _
, sa
:= range archVariants
[arch
][1:] {
1600 envs
= append(envs
, buildEnv(os
+"/"+arch
+"/"+sa
))
1605 for _
, m
:= range rxAsmCheck
.FindAllString(allchecks
, -1) {
1612 rxsrc
, err
:= strconv
.Unquote(m
)
1614 log
.Fatalf("%s:%d: error unquoting string: %v", t
.goFileName(), i
+1, err
)
1617 // Compile the checks as regular expressions. Notice that we
1618 // consider checks as matching from the beginning of the actual
1619 // assembler source (that is, what is left on each line of the
1620 // compile -S output after we strip file/line info) to avoid
1621 // trivial bugs such as "ADD" matching "FADD". This
1622 // doesn't remove genericity: it's still possible to write
1623 // something like "F?ADD", but we make common cases simpler
1625 oprx
, err
:= regexp
.Compile("^" + rxsrc
)
1627 log
.Fatalf("%s:%d: %v", t
.goFileName(), i
+1, err
)
1630 for _
, env
:= range envs
{
1631 if ops
[env
] == nil {
1632 ops
[env
] = make(map[string][]wantedAsmOpcode
)
1634 ops
[env
][lnum
] = append(ops
[env
][lnum
], wantedAsmOpcode
{
1649 func (t
*test
) asmCheck(outStr
string, fn
string, env buildEnv
, fullops
map[string][]wantedAsmOpcode
) (err error
) {
1650 // The assembly output contains the concatenated dump of multiple functions.
1651 // the first line of each function begins at column 0, while the rest is
1652 // indented by a tabulation. These data structures help us index the
1653 // output by function.
1654 functionMarkers
:= make([]int, 1)
1655 lineFuncMap
:= make(map[string]int)
1657 lines
:= strings
.Split(outStr
, "\n")
1658 rxLine
:= regexp
.MustCompile(fmt
.Sprintf(`\((%s:\d+)\)\s+(.*)`, regexp
.QuoteMeta(fn
)))
1660 for nl
, line
:= range lines
{
1661 // Check if this line begins a function
1662 if len(line
) > 0 && line
[0] != '\t' {
1663 functionMarkers
= append(functionMarkers
, nl
)
1666 // Search if this line contains a assembly opcode (which is prefixed by the
1667 // original source file/line in parenthesis)
1668 matches
:= rxLine
.FindStringSubmatch(line
)
1669 if len(matches
) == 0 {
1672 srcFileLine
, asm
:= matches
[1], matches
[2]
1674 // Associate the original file/line information to the current
1675 // function in the output; it will be useful to dump it in case
1677 lineFuncMap
[srcFileLine
] = len(functionMarkers
) - 1
1679 // If there are opcode checks associated to this source file/line,
1681 if ops
, found
:= fullops
[srcFileLine
]; found
{
1682 for i
:= range ops
{
1683 if !ops
[i
].found
&& ops
[i
].opcode
.FindString(asm
) != "" {
1689 functionMarkers
= append(functionMarkers
, len(lines
))
1691 var failed
[]wantedAsmOpcode
1692 for _
, ops
:= range fullops
{
1693 for _
, o
:= range ops
{
1694 // There's a failure if a negative match was found,
1695 // or a positive match was not found.
1696 if o
.negative
== o
.found
{
1697 failed
= append(failed
, o
)
1701 if len(failed
) == 0 {
1705 // At least one asmcheck failed; report them
1706 sort
.Slice(failed
, func(i
, j
int) bool {
1707 return failed
[i
].line
< failed
[j
].line
1711 var errbuf bytes
.Buffer
1712 fmt
.Fprintln(&errbuf
)
1713 for _
, o
:= range failed
{
1714 // Dump the function in which this opcode check was supposed to
1716 funcIdx
:= lineFuncMap
[o
.fileline
]
1717 if funcIdx
!= 0 && funcIdx
!= lastFunction
{
1718 funcLines
:= lines
[functionMarkers
[funcIdx
]:functionMarkers
[funcIdx
+1]]
1719 log
.Println(strings
.Join(funcLines
, "\n"))
1720 lastFunction
= funcIdx
// avoid printing same function twice
1724 fmt
.Fprintf(&errbuf
, "%s:%d: %s: wrong opcode found: %q\n", t
.goFileName(), o
.line
, env
, o
.opcode
.String())
1726 fmt
.Fprintf(&errbuf
, "%s:%d: %s: opcode not found: %q\n", t
.goFileName(), o
.line
, env
, o
.opcode
.String())
1729 err
= errors
.New(errbuf
.String())
1733 // defaultRunOutputLimit returns the number of runoutput tests that
1734 // can be executed in parallel.
1735 func defaultRunOutputLimit() int {
1738 cpu
:= runtime
.NumCPU()
1739 if runtime
.GOARCH
== "arm" && cpu
> maxArmCPU
{
1745 // checkShouldTest runs sanity checks on the shouldTest function.
1746 func checkShouldTest() {
1747 assert
:= func(ok
bool, _
string) {
1752 assertNot
:= func(ok
bool, _
string) { assert(!ok
, "") }
1755 assert(shouldTest("// +build linux", "linux", "arm"))
1756 assert(shouldTest("// +build !windows", "linux", "arm"))
1757 assertNot(shouldTest("// +build !windows", "windows", "amd64"))
1759 // A file with no build tags will always be tested.
1760 assert(shouldTest("// This is a test.", "os", "arch"))
1762 // Build tags separated by a space are OR-ed together.
1763 assertNot(shouldTest("// +build arm 386", "linux", "amd64"))
1765 // Build tags separated by a comma are AND-ed together.
1766 assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64"))
1767 assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386"))
1769 // Build tags on multiple lines are AND-ed together.
1770 assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64"))
1771 assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64"))
1773 // Test that (!a OR !b) matches anything.
1774 assert(shouldTest("// +build !windows !plan9", "windows", "amd64"))
1777 func getenv(key
, def
string) string {
1778 value
:= os
.Getenv(key
)
1785 // overlayDir makes a minimal-overhead copy of srcRoot in which new files may be added.
1786 func overlayDir(dstRoot
, srcRoot
string) error
{
1787 dstRoot
= filepath
.Clean(dstRoot
)
1788 if err
:= os
.MkdirAll(dstRoot
, 0777); err
!= nil {
1792 srcRoot
, err
:= filepath
.Abs(srcRoot
)
1797 return filepath
.WalkDir(srcRoot
, func(srcPath
string, d fs
.DirEntry
, err error
) error
{
1798 if err
!= nil || srcPath
== srcRoot
{
1802 suffix
:= strings
.TrimPrefix(srcPath
, srcRoot
)
1803 for len(suffix
) > 0 && suffix
[0] == filepath
.Separator
{
1806 dstPath
:= filepath
.Join(dstRoot
, suffix
)
1808 var info fs
.FileInfo
1809 if d
.Type()&os
.ModeSymlink
!= 0 {
1810 info
, err
= os
.Stat(srcPath
)
1812 info
, err
= d
.Info()
1817 perm
:= info
.Mode() & os
.ModePerm
1819 // Always copy directories (don't symlink them).
1820 // If we add a file in the overlay, we don't want to add it in the original.
1822 return os
.MkdirAll(dstPath
, perm|
0200)
1825 // If the OS supports symlinks, use them instead of copying bytes.
1826 if err
:= os
.Symlink(srcPath
, dstPath
); err
== nil {
1830 // Otherwise, copy the bytes.
1831 src
, err
:= os
.Open(srcPath
)
1837 dst
, err
:= os
.OpenFile(dstPath
, os
.O_WRONLY|os
.O_CREATE|os
.O_EXCL
, perm
)
1842 _
, err
= io
.Copy(dst
, src
)
1843 if closeErr
:= dst
.Close(); err
== nil {