1
2
3
4
5
6 package testdir_test
7
8 import (
9 "bytes"
10 "encoding/json"
11 "errors"
12 "flag"
13 "fmt"
14 "go/build"
15 "go/build/constraint"
16 "hash/fnv"
17 "internal/testenv"
18 "io"
19 "io/fs"
20 "log"
21 "os"
22 "os/exec"
23 "path"
24 "path/filepath"
25 "regexp"
26 "runtime"
27 "slices"
28 "sort"
29 "strconv"
30 "strings"
31 "sync"
32 "testing"
33 "time"
34 "unicode"
35 )
36
37 var (
38 allCodegen = flag.Bool("all_codegen", defaultAllCodeGen(), "run all goos/goarch for codegen")
39 runSkips = flag.Bool("run_skips", false, "run skipped tests (ignore skip and build tags)")
40 linkshared = flag.Bool("linkshared", false, "")
41 updateErrors = flag.Bool("update_errors", false, "update error messages in test file based on compiler output")
42 runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run")
43 force = flag.Bool("f", false, "ignore expected-failure test lists")
44 target = flag.String("target", "", "cross-compile tests for `goos/goarch`")
45
46 shard = flag.Int("shard", 0, "shard index to run. Only applicable if -shards is non-zero.")
47 shards = flag.Int("shards", 0, "number of shards. If 0, all tests are run. This is used by the continuous build.")
48 )
49
50
51
52
53
54 func defaultAllCodeGen() bool {
55 return os.Getenv("GO_BUILDER_NAME") == "linux-amd64"
56 }
57
58 var (
59
60 goTool string
61 goos string
62 goarch string
63 cgoEnabled bool
64 goExperiment string
65 goDebug string
66 tmpDir string
67
68
69
70 dirs = []string{".", "ken", "chan", "interface", "internal/runtime/sys", "syntax", "dwarf", "fixedbugs", "codegen", "abi", "typeparam", "typeparam/mdempsky", "arenas"}
71 )
72
73
74
75
76
77 func Test(t *testing.T) {
78 if *target != "" {
79
80
81
82
83
84 goos, goarch, ok := strings.Cut(*target, "/")
85 if !ok {
86 t.Fatalf("bad -target flag %q, expected goos/goarch", *target)
87 }
88 t.Setenv("GOOS", goos)
89 t.Setenv("GOARCH", goarch)
90 }
91
92 goTool = testenv.GoToolPath(t)
93 cmd := exec.Command(goTool, "env", "-json")
94 stdout, err := cmd.StdoutPipe()
95 if err != nil {
96 t.Fatal("StdoutPipe:", err)
97 }
98 if err := cmd.Start(); err != nil {
99 t.Fatal("Start:", err)
100 }
101 var env struct {
102 GOOS string
103 GOARCH string
104 GOEXPERIMENT string
105 GODEBUG string
106 CGO_ENABLED string
107 }
108 if err := json.NewDecoder(stdout).Decode(&env); err != nil {
109 t.Fatal("Decode:", err)
110 }
111 if err := cmd.Wait(); err != nil {
112 t.Fatal("Wait:", err)
113 }
114 goos = env.GOOS
115 goarch = env.GOARCH
116 cgoEnabled, _ = strconv.ParseBool(env.CGO_ENABLED)
117 goExperiment = env.GOEXPERIMENT
118 goDebug = env.GODEBUG
119 tmpDir = t.TempDir()
120
121 common := testCommon{
122 gorootTestDir: filepath.Join(testenv.GOROOT(t), "test"),
123 runoutputGate: make(chan bool, *runoutputLimit),
124 }
125
126
127
128
129 if _, err := os.Stat(common.gorootTestDir); os.IsNotExist(err) {
130 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil {
131 t.Skipf("skipping: GOROOT/test not present")
132 }
133 }
134
135 for _, dir := range dirs {
136 for _, goFile := range goFiles(t, dir) {
137 test := test{testCommon: common, dir: dir, goFile: goFile}
138 t.Run(path.Join(dir, goFile), func(t *testing.T) {
139 t.Parallel()
140 test.T = t
141 testError := test.run()
142 wantError := test.expectFail() && !*force
143 if testError != nil {
144 if wantError {
145 t.Log(testError.Error() + " (expected)")
146 } else {
147 t.Fatal(testError)
148 }
149 } else if wantError {
150 t.Fatal("unexpected success")
151 }
152 })
153 }
154 }
155 }
156
157 func shardMatch(name string) bool {
158 if *shards <= 1 {
159 return true
160 }
161 h := fnv.New32()
162 io.WriteString(h, name)
163 return int(h.Sum32()%uint32(*shards)) == *shard
164 }
165
166 func goFiles(t *testing.T, dir string) []string {
167 files, err := os.ReadDir(filepath.Join(testenv.GOROOT(t), "test", dir))
168 if err != nil {
169 t.Fatal(err)
170 }
171 names := []string{}
172 for _, file := range files {
173 name := file.Name()
174 if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && shardMatch(name) {
175 names = append(names, name)
176 }
177 }
178 return names
179 }
180
181 type runCmd func(...string) ([]byte, error)
182
183 func compileFile(runcmd runCmd, longname string, flags []string) (out []byte, err error) {
184 cmd := []string{goTool, "tool", "compile", "-e", "-p=p", "-importcfg=" + stdlibImportcfgFile()}
185 cmd = append(cmd, flags...)
186 if *linkshared {
187 cmd = append(cmd, "-dynlink", "-installsuffix=dynlink")
188 }
189 cmd = append(cmd, longname)
190 return runcmd(cmd...)
191 }
192
193 func compileInDir(runcmd runCmd, dir string, flags []string, importcfg string, pkgname string, names ...string) (out []byte, err error) {
194 if importcfg == "" {
195 importcfg = stdlibImportcfgFile()
196 }
197 cmd := []string{goTool, "tool", "compile", "-e", "-D", "test", "-importcfg=" + importcfg}
198 if pkgname == "main" {
199 cmd = append(cmd, "-p=main")
200 } else {
201 pkgname = path.Join("test", strings.TrimSuffix(names[0], ".go"))
202 cmd = append(cmd, "-o", pkgname+".a", "-p", pkgname)
203 }
204 cmd = append(cmd, flags...)
205 if *linkshared {
206 cmd = append(cmd, "-dynlink", "-installsuffix=dynlink")
207 }
208 for _, name := range names {
209 cmd = append(cmd, filepath.Join(dir, name))
210 }
211 return runcmd(cmd...)
212 }
213
214 var stdlibImportcfg = sync.OnceValue(func() string {
215 cmd := exec.Command(goTool, "list", "-export", "-f", "{{if .Export}}packagefile {{.ImportPath}}={{.Export}}{{end}}", "std")
216 cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=")
217 output, err := cmd.Output()
218 if err, ok := err.(*exec.ExitError); ok && len(err.Stderr) != 0 {
219 log.Fatalf("'go list' failed: %v: %s", err, err.Stderr)
220 }
221 if err != nil {
222 log.Fatalf("'go list' failed: %v", err)
223 }
224 return string(output)
225 })
226
227 var stdlibImportcfgFile = sync.OnceValue(func() string {
228 filename := filepath.Join(tmpDir, "importcfg")
229 err := os.WriteFile(filename, []byte(stdlibImportcfg()), 0644)
230 if err != nil {
231 log.Fatal(err)
232 }
233 return filename
234 })
235
236 func linkFile(runcmd runCmd, goname string, importcfg string, ldflags []string) (err error) {
237 if importcfg == "" {
238 importcfg = stdlibImportcfgFile()
239 }
240 pfile := strings.Replace(goname, ".go", ".o", -1)
241 cmd := []string{goTool, "tool", "link", "-w", "-o", "a.exe", "-importcfg=" + importcfg}
242 if *linkshared {
243 cmd = append(cmd, "-linkshared", "-installsuffix=dynlink")
244 }
245 if ldflags != nil {
246 cmd = append(cmd, ldflags...)
247 }
248 cmd = append(cmd, pfile)
249 _, err = runcmd(cmd...)
250 return
251 }
252
253 type testCommon struct {
254
255 gorootTestDir string
256
257
258
259 runoutputGate chan bool
260 }
261
262
263 type test struct {
264 testCommon
265 *testing.T
266
267
268 dir, goFile string
269 }
270
271
272
273 func (t test) expectFail() bool {
274 failureSets := []map[string]bool{types2Failures}
275
276
277
278 switch goarch {
279 case "386", "arm", "mips", "mipsle":
280 failureSets = append(failureSets, types2Failures32Bit)
281 }
282
283 testName := path.Join(t.dir, t.goFile)
284
285 for _, set := range failureSets {
286 if set[testName] {
287 return true
288 }
289 }
290 return false
291 }
292
293 func (t test) goFileName() string {
294 return filepath.Join(t.dir, t.goFile)
295 }
296
297 func (t test) goDirName() string {
298 return filepath.Join(t.dir, strings.Replace(t.goFile, ".go", ".dir", -1))
299 }
300
301
302 func goDirFiles(dir string) (filter []fs.DirEntry, _ error) {
303 files, err := os.ReadDir(dir)
304 if err != nil {
305 return nil, err
306 }
307 for _, goFile := range files {
308 if filepath.Ext(goFile.Name()) == ".go" {
309 filter = append(filter, goFile)
310 }
311 }
312 return filter, nil
313 }
314
315 var packageRE = regexp.MustCompile(`(?m)^package ([\p{Lu}\p{Ll}\w]+)`)
316
317 func getPackageNameFromSource(fn string) (string, error) {
318 data, err := os.ReadFile(fn)
319 if err != nil {
320 return "", err
321 }
322 pkgname := packageRE.FindStringSubmatch(string(data))
323 if pkgname == nil {
324 return "", fmt.Errorf("cannot find package name in %s", fn)
325 }
326 return pkgname[1], nil
327 }
328
329
330 type goDirPkg struct {
331 name string
332 files []string
333 }
334
335
336
337
338 func goDirPackages(t *testing.T, dir string, singlefilepkgs bool) []*goDirPkg {
339 files, err := goDirFiles(dir)
340 if err != nil {
341 t.Fatal(err)
342 }
343 var pkgs []*goDirPkg
344 m := make(map[string]*goDirPkg)
345 for _, file := range files {
346 name := file.Name()
347 pkgname, err := getPackageNameFromSource(filepath.Join(dir, name))
348 if err != nil {
349 t.Fatal(err)
350 }
351 p, ok := m[pkgname]
352 if singlefilepkgs || !ok {
353 p = &goDirPkg{name: pkgname}
354 pkgs = append(pkgs, p)
355 m[pkgname] = p
356 }
357 p.files = append(p.files, name)
358 }
359 return pkgs
360 }
361
362 type context struct {
363 GOOS string
364 GOARCH string
365 cgoEnabled bool
366 noOptEnv bool
367 }
368
369
370
371 func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) {
372 if *runSkips {
373 return true, ""
374 }
375 for _, line := range strings.Split(src, "\n") {
376 if strings.HasPrefix(line, "package ") {
377 break
378 }
379
380 if expr, err := constraint.Parse(line); err == nil {
381 gcFlags := os.Getenv("GO_GCFLAGS")
382 ctxt := &context{
383 GOOS: goos,
384 GOARCH: goarch,
385 cgoEnabled: cgoEnabled,
386 noOptEnv: strings.Contains(gcFlags, "-N") || strings.Contains(gcFlags, "-l"),
387 }
388
389 if !expr.Eval(ctxt.match) {
390 return false, line
391 }
392 }
393 }
394 return true, ""
395 }
396
397 func (ctxt *context) match(name string) bool {
398 if name == "" {
399 return false
400 }
401
402
403
404 for _, c := range name {
405 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
406 return false
407 }
408 }
409
410 if slices.Contains(build.Default.ReleaseTags, name) {
411 return true
412 }
413
414 if strings.HasPrefix(name, "goexperiment.") {
415 return slices.Contains(build.Default.ToolTags, name)
416 }
417
418 if name == "cgo" && ctxt.cgoEnabled {
419 return true
420 }
421
422 if name == ctxt.GOOS || name == ctxt.GOARCH || name == "gc" {
423 return true
424 }
425
426 if ctxt.noOptEnv && name == "gcflags_noopt" {
427 return true
428 }
429
430 if name == "test_run" {
431 return true
432 }
433
434 return false
435 }
436
437
438
439
440
441 func (test) goGcflags() string {
442 return "-gcflags=all=" + os.Getenv("GO_GCFLAGS")
443 }
444
445 func (test) goGcflagsIsEmpty() bool {
446 return "" == os.Getenv("GO_GCFLAGS")
447 }
448
449 var errTimeout = errors.New("command exceeded time limit")
450
451
452
453
454
455
456
457
458
459
460 func (t test) run() error {
461 srcBytes, err := os.ReadFile(filepath.Join(t.gorootTestDir, t.goFileName()))
462 if err != nil {
463 t.Fatal("reading test case .go file:", err)
464 } else if bytes.HasPrefix(srcBytes, []byte{'\n'}) {
465 t.Fatal(".go file source starts with a newline")
466 }
467 src := string(srcBytes)
468
469
470
471 var action string
472 for actionSrc := src; action == "" && actionSrc != ""; {
473 var line string
474 line, actionSrc, _ = strings.Cut(actionSrc, "\n")
475 if constraint.IsGoBuild(line) || constraint.IsPlusBuild(line) {
476 continue
477 }
478 action = strings.TrimSpace(strings.TrimPrefix(line, "//"))
479 }
480 if action == "" {
481 t.Fatalf("execution recipe not found in GOROOT/test/%s", t.goFileName())
482 }
483
484
485 header, _, ok := strings.Cut(src, "\npackage")
486 if !ok {
487 header = action
488 }
489 if ok, why := shouldTest(header, goos, goarch); !ok {
490 t.Skip(why)
491 }
492
493 var args, flags, runenv []string
494 var tim int
495 wantError := false
496 wantAuto := false
497 singlefilepkgs := false
498 f, err := splitQuoted(action)
499 if err != nil {
500 t.Fatal("invalid test recipe:", err)
501 }
502 if len(f) > 0 {
503 action = f[0]
504 args = f[1:]
505 }
506
507
508 switch action {
509 case "compile", "compiledir", "build", "builddir", "buildrundir", "run", "buildrun", "runoutput", "rundir", "runindir", "asmcheck":
510
511 case "errorcheckandrundir":
512 wantError = false
513 case "errorcheckwithauto":
514 action = "errorcheck"
515 wantAuto = true
516 wantError = true
517 case "errorcheck", "errorcheckdir", "errorcheckoutput":
518 wantError = true
519 case "skip":
520 if *runSkips {
521 break
522 }
523 t.Skip("skip")
524 default:
525 t.Fatalf("unknown pattern: %q", action)
526 }
527
528 goexp := goExperiment
529 godebug := goDebug
530 gomodvers := ""
531
532
533 for len(args) > 0 && strings.HasPrefix(args[0], "-") {
534 switch args[0] {
535 case "-1":
536 wantError = true
537 case "-0":
538 wantError = false
539 case "-s":
540 singlefilepkgs = true
541 case "-t":
542 args = args[1:]
543 var err error
544 tim, err = strconv.Atoi(args[0])
545 if err != nil {
546 t.Fatalf("need number of seconds for -t timeout, got %s instead", args[0])
547 }
548 if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
549 timeoutScale, err := strconv.Atoi(s)
550 if err != nil {
551 t.Fatalf("failed to parse $GO_TEST_TIMEOUT_SCALE = %q as integer: %v", s, err)
552 }
553 tim *= timeoutScale
554 }
555 case "-goexperiment":
556 args = args[1:]
557 if goexp != "" {
558 goexp += ","
559 }
560 goexp += args[0]
561 runenv = append(runenv, "GOEXPERIMENT="+goexp)
562
563 case "-godebug":
564 args = args[1:]
565 if godebug != "" {
566 godebug += ","
567 }
568 godebug += args[0]
569 runenv = append(runenv, "GODEBUG="+godebug)
570
571 case "-gomodversion":
572 args = args[1:]
573 gomodvers = args[0]
574
575 default:
576 flags = append(flags, args[0])
577 }
578 args = args[1:]
579 }
580 if action == "errorcheck" {
581 found := false
582 for i, f := range flags {
583 if strings.HasPrefix(f, "-d=") {
584 flags[i] = f + ",ssa/check/on"
585 found = true
586 break
587 }
588 }
589 if !found {
590 flags = append(flags, "-d=ssa/check/on")
591 }
592 }
593
594 tempDir := t.TempDir()
595 err = os.Mkdir(filepath.Join(tempDir, "test"), 0755)
596 if err != nil {
597 t.Fatal(err)
598 }
599
600 err = os.WriteFile(filepath.Join(tempDir, t.goFile), srcBytes, 0644)
601 if err != nil {
602 t.Fatal(err)
603 }
604
605 var (
606 runInDir = tempDir
607 tempDirIsGOPATH = false
608 )
609 runcmd := func(args ...string) ([]byte, error) {
610 cmd := exec.Command(args[0], args[1:]...)
611 var buf bytes.Buffer
612 cmd.Stdout = &buf
613 cmd.Stderr = &buf
614 cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=")
615 if runInDir != "" {
616 cmd.Dir = runInDir
617
618 cmd.Env = append(cmd.Env, "PWD="+cmd.Dir)
619 } else {
620
621 cmd.Dir = t.gorootTestDir
622
623 cmd.Env = append(cmd.Env, "PWD="+cmd.Dir)
624 }
625 if tempDirIsGOPATH {
626 cmd.Env = append(cmd.Env, "GOPATH="+tempDir)
627 }
628 cmd.Env = append(cmd.Env, "STDLIB_IMPORTCFG="+stdlibImportcfgFile())
629 cmd.Env = append(cmd.Env, runenv...)
630
631 var err error
632
633 if tim != 0 {
634 err = cmd.Start()
635
636
637
638
639
640
641
642
643 if err == nil {
644 tick := time.NewTimer(time.Duration(tim) * time.Second)
645 done := make(chan error)
646 go func() {
647 done <- cmd.Wait()
648 }()
649 select {
650 case err = <-done:
651
652 case <-tick.C:
653 cmd.Process.Signal(os.Interrupt)
654 time.Sleep(1 * time.Second)
655 cmd.Process.Kill()
656 <-done
657 err = errTimeout
658 }
659 tick.Stop()
660 }
661 } else {
662 err = cmd.Run()
663 }
664 if err != nil && err != errTimeout {
665 err = fmt.Errorf("%s\n%s", err, buf.Bytes())
666 }
667 return buf.Bytes(), err
668 }
669
670 importcfg := func(pkgs []*goDirPkg) string {
671 cfg := stdlibImportcfg()
672 for _, pkg := range pkgs {
673 pkgpath := path.Join("test", strings.TrimSuffix(pkg.files[0], ".go"))
674 cfg += "\npackagefile " + pkgpath + "=" + filepath.Join(tempDir, pkgpath+".a")
675 }
676 filename := filepath.Join(tempDir, "importcfg")
677 err := os.WriteFile(filename, []byte(cfg), 0644)
678 if err != nil {
679 t.Fatal(err)
680 }
681 return filename
682 }
683
684 long := filepath.Join(t.gorootTestDir, t.goFileName())
685 switch action {
686 default:
687 t.Fatalf("unimplemented action %q", action)
688 panic("unreachable")
689
690 case "asmcheck":
691
692
693 ops := t.wantedAsmOpcodes(long)
694 self := runtime.GOOS + "/" + runtime.GOARCH
695 for _, env := range ops.Envs() {
696
697
698 if string(env) != self && !strings.HasPrefix(string(env), self+"/") && !*allCodegen {
699 continue
700 }
701
702 cmdline := []string{"build", "-gcflags", "-S=2"}
703
704
705 for i := 0; i < len(flags); i++ {
706 flag := flags[i]
707 switch {
708 case strings.HasPrefix(flag, "-gcflags="):
709 cmdline[2] += " " + strings.TrimPrefix(flag, "-gcflags=")
710 case strings.HasPrefix(flag, "--gcflags="):
711 cmdline[2] += " " + strings.TrimPrefix(flag, "--gcflags=")
712 case flag == "-gcflags", flag == "--gcflags":
713 i++
714 if i < len(flags) {
715 cmdline[2] += " " + flags[i]
716 }
717 default:
718 cmdline = append(cmdline, flag)
719 }
720 }
721
722 cmdline = append(cmdline, long)
723 cmd := exec.Command(goTool, cmdline...)
724 cmd.Env = append(os.Environ(), env.Environ()...)
725 if len(flags) > 0 && flags[0] == "-race" {
726 cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
727 }
728
729 var buf bytes.Buffer
730 cmd.Stdout, cmd.Stderr = &buf, &buf
731 if err := cmd.Run(); err != nil {
732 t.Log(env, "\n", cmd.Stderr)
733 return err
734 }
735
736 err := t.asmCheck(buf.String(), long, env, ops[env])
737 if err != nil {
738 return err
739 }
740 }
741 return nil
742
743 case "errorcheck":
744
745
746
747
748 cmdline := []string{goTool, "tool", "compile", "-p=p", "-d=panic", "-C", "-e", "-importcfg=" + stdlibImportcfgFile(), "-o", "a.o"}
749
750 cmdline = append(cmdline, flags...)
751 cmdline = append(cmdline, long)
752 out, err := runcmd(cmdline...)
753 if wantError {
754 if err == nil {
755 return fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
756 }
757 if err == errTimeout {
758 return fmt.Errorf("compilation timed out")
759 }
760 } else {
761 if err != nil {
762 return err
763 }
764 }
765 if *updateErrors {
766 t.updateErrors(string(out), long)
767 }
768 return t.errorCheck(string(out), wantAuto, long, t.goFile)
769
770 case "compile":
771
772 _, err := compileFile(runcmd, long, flags)
773 return err
774
775 case "compiledir":
776
777 longdir := filepath.Join(t.gorootTestDir, t.goDirName())
778 pkgs := goDirPackages(t.T, longdir, singlefilepkgs)
779 importcfgfile := importcfg(pkgs)
780
781 for _, pkg := range pkgs {
782 _, err := compileInDir(runcmd, longdir, flags, importcfgfile, pkg.name, pkg.files...)
783 if err != nil {
784 return err
785 }
786 }
787 return nil
788
789 case "errorcheckdir", "errorcheckandrundir":
790 flags = append(flags, "-d=panic")
791
792
793
794 longdir := filepath.Join(t.gorootTestDir, t.goDirName())
795 pkgs := goDirPackages(t.T, longdir, singlefilepkgs)
796 errPkg := len(pkgs) - 1
797 if wantError && action == "errorcheckandrundir" {
798
799
800 errPkg--
801 }
802 importcfgfile := importcfg(pkgs)
803 for i, pkg := range pkgs {
804 out, err := compileInDir(runcmd, longdir, flags, importcfgfile, pkg.name, pkg.files...)
805 if i == errPkg {
806 if wantError && err == nil {
807 return fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
808 } else if !wantError && err != nil {
809 return err
810 }
811 } else if err != nil {
812 return err
813 }
814 var fullshort []string
815 for _, name := range pkg.files {
816 fullshort = append(fullshort, filepath.Join(longdir, name), name)
817 }
818 err = t.errorCheck(string(out), wantAuto, fullshort...)
819 if err != nil {
820 return err
821 }
822 }
823 if action == "errorcheckdir" {
824 return nil
825 }
826 fallthrough
827
828 case "rundir":
829
830
831
832
833 longdir := filepath.Join(t.gorootTestDir, t.goDirName())
834 pkgs := goDirPackages(t.T, longdir, singlefilepkgs)
835
836 ldflags := []string{}
837 for i, fl := range flags {
838 if fl == "-ldflags" {
839 ldflags = flags[i+1:]
840 flags = flags[0:i]
841 break
842 }
843 }
844
845 importcfgfile := importcfg(pkgs)
846
847 for i, pkg := range pkgs {
848 _, err := compileInDir(runcmd, longdir, flags, importcfgfile, pkg.name, pkg.files...)
849
850
851 if err != nil && !(wantError && action == "errorcheckandrundir" && i == len(pkgs)-2) {
852 return err
853 }
854
855 if i == len(pkgs)-1 {
856 err = linkFile(runcmd, pkg.files[0], importcfgfile, ldflags)
857 if err != nil {
858 return err
859 }
860 var cmd []string
861 cmd = append(cmd, findExecCmd()...)
862 cmd = append(cmd, filepath.Join(tempDir, "a.exe"))
863 cmd = append(cmd, args...)
864 out, err := runcmd(cmd...)
865 if err != nil {
866 return err
867 }
868 t.checkExpectedOutput(out)
869 }
870 }
871 return nil
872
873 case "runindir":
874
875
876
877
878
879
880
881
882 tempDirIsGOPATH = true
883 srcDir := filepath.Join(t.gorootTestDir, t.goDirName())
884 modName := filepath.Base(srcDir)
885 gopathSrcDir := filepath.Join(tempDir, "src", modName)
886 runInDir = gopathSrcDir
887
888 if err := overlayDir(gopathSrcDir, srcDir); err != nil {
889 t.Fatal(err)
890 }
891
892 modVersion := gomodvers
893 if modVersion == "" {
894 modVersion = "1.14"
895 }
896 modFile := fmt.Sprintf("module %s\ngo %s\n", modName, modVersion)
897 if err := os.WriteFile(filepath.Join(gopathSrcDir, "go.mod"), []byte(modFile), 0666); err != nil {
898 t.Fatal(err)
899 }
900
901 cmd := []string{goTool, "run", t.goGcflags()}
902 if *linkshared {
903 cmd = append(cmd, "-linkshared")
904 }
905 cmd = append(cmd, flags...)
906 cmd = append(cmd, ".")
907 out, err := runcmd(cmd...)
908 if err != nil {
909 return err
910 }
911 return t.checkExpectedOutput(out)
912
913 case "build":
914
915 cmd := []string{goTool, "build", t.goGcflags()}
916 cmd = append(cmd, flags...)
917 cmd = append(cmd, "-o", "a.exe", long)
918 _, err := runcmd(cmd...)
919 return err
920
921 case "builddir", "buildrundir":
922
923
924 longdir := filepath.Join(t.gorootTestDir, t.goDirName())
925 files, err := os.ReadDir(longdir)
926 if err != nil {
927 t.Fatal(err)
928 }
929 var gos []string
930 var asms []string
931 for _, file := range files {
932 switch filepath.Ext(file.Name()) {
933 case ".go":
934 gos = append(gos, filepath.Join(longdir, file.Name()))
935 case ".s":
936 asms = append(asms, filepath.Join(longdir, file.Name()))
937 }
938 }
939 if len(asms) > 0 {
940 emptyHdrFile := filepath.Join(tempDir, "go_asm.h")
941 if err := os.WriteFile(emptyHdrFile, nil, 0666); err != nil {
942 t.Fatalf("write empty go_asm.h: %v", err)
943 }
944 cmd := []string{goTool, "tool", "asm", "-p=main", "-gensymabis", "-o", "symabis"}
945 cmd = append(cmd, asms...)
946 _, err = runcmd(cmd...)
947 if err != nil {
948 return err
949 }
950 }
951 var objs []string
952 cmd := []string{goTool, "tool", "compile", "-p=main", "-e", "-D", ".", "-importcfg=" + stdlibImportcfgFile(), "-o", "go.o"}
953 if len(asms) > 0 {
954 cmd = append(cmd, "-asmhdr", "go_asm.h", "-symabis", "symabis")
955 }
956 cmd = append(cmd, gos...)
957 _, err = runcmd(cmd...)
958 if err != nil {
959 return err
960 }
961 objs = append(objs, "go.o")
962 if len(asms) > 0 {
963 cmd = []string{goTool, "tool", "asm", "-p=main", "-e", "-I", ".", "-o", "asm.o"}
964 cmd = append(cmd, asms...)
965 _, err = runcmd(cmd...)
966 if err != nil {
967 return err
968 }
969 objs = append(objs, "asm.o")
970 }
971 cmd = []string{goTool, "tool", "pack", "c", "all.a"}
972 cmd = append(cmd, objs...)
973 _, err = runcmd(cmd...)
974 if err != nil {
975 return err
976 }
977 cmd = []string{goTool, "tool", "link", "-importcfg=" + stdlibImportcfgFile(), "-o", "a.exe", "all.a"}
978 _, err = runcmd(cmd...)
979 if err != nil {
980 return err
981 }
982
983 if action == "builddir" {
984 return nil
985 }
986 cmd = append(findExecCmd(), filepath.Join(tempDir, "a.exe"))
987 out, err := runcmd(cmd...)
988 if err != nil {
989 return err
990 }
991 return t.checkExpectedOutput(out)
992
993 case "buildrun":
994
995
996
997 cmd := []string{goTool, "build", t.goGcflags(), "-o", "a.exe"}
998 if *linkshared {
999 cmd = append(cmd, "-linkshared")
1000 }
1001 longDirGoFile := filepath.Join(filepath.Join(t.gorootTestDir, t.dir), t.goFile)
1002 cmd = append(cmd, flags...)
1003 cmd = append(cmd, longDirGoFile)
1004 _, err := runcmd(cmd...)
1005 if err != nil {
1006 return err
1007 }
1008 cmd = []string{"./a.exe"}
1009 out, err := runcmd(append(cmd, args...)...)
1010 if err != nil {
1011 return err
1012 }
1013
1014 return t.checkExpectedOutput(out)
1015
1016 case "run":
1017
1018
1019
1020 runInDir = ""
1021 var out []byte
1022 var err error
1023 if len(flags)+len(args) == 0 && t.goGcflagsIsEmpty() && !*linkshared && goarch == runtime.GOARCH && goos == runtime.GOOS && goexp == goExperiment && godebug == goDebug {
1024
1025
1026
1027
1028
1029
1030
1031 pkg := filepath.Join(tempDir, "pkg.a")
1032 if _, err := runcmd(goTool, "tool", "compile", "-p=main", "-importcfg="+stdlibImportcfgFile(), "-o", pkg, t.goFileName()); err != nil {
1033 return err
1034 }
1035 exe := filepath.Join(tempDir, "test.exe")
1036 cmd := []string{goTool, "tool", "link", "-s", "-w", "-importcfg=" + stdlibImportcfgFile()}
1037 cmd = append(cmd, "-o", exe, pkg)
1038 if _, err := runcmd(cmd...); err != nil {
1039 return err
1040 }
1041 out, err = runcmd(append([]string{exe}, args...)...)
1042 } else {
1043 cmd := []string{goTool, "run", t.goGcflags()}
1044 if *linkshared {
1045 cmd = append(cmd, "-linkshared")
1046 }
1047 cmd = append(cmd, flags...)
1048 cmd = append(cmd, t.goFileName())
1049 out, err = runcmd(append(cmd, args...)...)
1050 }
1051 if err != nil {
1052 return err
1053 }
1054 return t.checkExpectedOutput(out)
1055
1056 case "runoutput":
1057
1058
1059 t.runoutputGate <- true
1060 defer func() {
1061 <-t.runoutputGate
1062 }()
1063 runInDir = ""
1064 cmd := []string{goTool, "run", t.goGcflags()}
1065 if *linkshared {
1066 cmd = append(cmd, "-linkshared")
1067 }
1068 cmd = append(cmd, t.goFileName())
1069 out, err := runcmd(append(cmd, args...)...)
1070 if err != nil {
1071 return err
1072 }
1073 tfile := filepath.Join(tempDir, "tmp__.go")
1074 if err := os.WriteFile(tfile, out, 0666); err != nil {
1075 t.Fatalf("write tempfile: %v", err)
1076 }
1077 cmd = []string{goTool, "run", t.goGcflags()}
1078 if *linkshared {
1079 cmd = append(cmd, "-linkshared")
1080 }
1081 cmd = append(cmd, tfile)
1082 out, err = runcmd(cmd...)
1083 if err != nil {
1084 return err
1085 }
1086 return t.checkExpectedOutput(out)
1087
1088 case "errorcheckoutput":
1089
1090
1091 runInDir = ""
1092 cmd := []string{goTool, "run", t.goGcflags()}
1093 if *linkshared {
1094 cmd = append(cmd, "-linkshared")
1095 }
1096 cmd = append(cmd, t.goFileName())
1097 out, err := runcmd(append(cmd, args...)...)
1098 if err != nil {
1099 return err
1100 }
1101 tfile := filepath.Join(tempDir, "tmp__.go")
1102 err = os.WriteFile(tfile, out, 0666)
1103 if err != nil {
1104 t.Fatalf("write tempfile: %v", err)
1105 }
1106 cmdline := []string{goTool, "tool", "compile", "-importcfg=" + stdlibImportcfgFile(), "-p=p", "-d=panic", "-e", "-o", "a.o"}
1107 cmdline = append(cmdline, flags...)
1108 cmdline = append(cmdline, tfile)
1109 out, err = runcmd(cmdline...)
1110 if wantError {
1111 if err == nil {
1112 return fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
1113 }
1114 } else {
1115 if err != nil {
1116 return err
1117 }
1118 }
1119 return t.errorCheck(string(out), false, tfile, "tmp__.go")
1120 }
1121 }
1122
1123 var findExecCmd = sync.OnceValue(func() (execCmd []string) {
1124 if goos == runtime.GOOS && goarch == runtime.GOARCH {
1125 return nil
1126 }
1127 if path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch)); err == nil {
1128 execCmd = []string{path}
1129 }
1130 return execCmd
1131 })
1132
1133
1134
1135
1136 func (t test) checkExpectedOutput(gotBytes []byte) error {
1137 got := string(gotBytes)
1138 filename := filepath.Join(t.dir, t.goFile)
1139 filename = filename[:len(filename)-len(".go")]
1140 filename += ".out"
1141 b, err := os.ReadFile(filepath.Join(t.gorootTestDir, filename))
1142 if errors.Is(err, fs.ErrNotExist) {
1143
1144 b = nil
1145 } else if err != nil {
1146 return err
1147 }
1148 got = strings.Replace(got, "\r\n", "\n", -1)
1149 if got != string(b) {
1150 if err == nil {
1151 return fmt.Errorf("output does not match expected in %s. Instead saw\n%s", filename, got)
1152 } else {
1153 return fmt.Errorf("output should be empty when (optional) expected-output file %s is not present. Instead saw\n%s", filename, got)
1154 }
1155 }
1156 return nil
1157 }
1158
1159 func splitOutput(out string, wantAuto bool) []string {
1160
1161
1162
1163 var res []string
1164 for _, line := range strings.Split(out, "\n") {
1165 if strings.HasSuffix(line, "\r") {
1166 line = line[:len(line)-1]
1167 }
1168 if strings.HasPrefix(line, "\t") {
1169 res[len(res)-1] += "\n" + line
1170 } else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") {
1171 continue
1172 } else if strings.TrimSpace(line) != "" {
1173 res = append(res, line)
1174 }
1175 }
1176 return res
1177 }
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190 func (t test) errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
1191 defer func() {
1192 if testing.Verbose() && err != nil {
1193 t.Logf("gc output:\n%s", outStr)
1194 }
1195 }()
1196 var errs []error
1197 out := splitOutput(outStr, wantAuto)
1198
1199
1200 for i := range out {
1201 for j := 0; j < len(fullshort); j += 2 {
1202 full, short := fullshort[j], fullshort[j+1]
1203 out[i] = replacePrefix(out[i], full, short)
1204 }
1205 }
1206
1207 var want []wantedError
1208 for j := 0; j < len(fullshort); j += 2 {
1209 full, short := fullshort[j], fullshort[j+1]
1210 want = append(want, t.wantedErrors(full, short)...)
1211 }
1212
1213 for _, we := range want {
1214 var errmsgs []string
1215 if we.auto {
1216 errmsgs, out = partitionStrings("<autogenerated>", out)
1217 } else {
1218 errmsgs, out = partitionStrings(we.prefix, out)
1219 }
1220 if len(errmsgs) == 0 {
1221 errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr))
1222 continue
1223 }
1224 matched := false
1225 n := len(out)
1226 for _, errmsg := range errmsgs {
1227
1228
1229 text := errmsg
1230 if _, suffix, ok := strings.Cut(text, " "); ok {
1231 text = suffix
1232 }
1233 if we.re.MatchString(text) {
1234 matched = true
1235 } else {
1236 out = append(out, errmsg)
1237 }
1238 }
1239 if !matched {
1240 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")))
1241 continue
1242 }
1243 }
1244
1245 if len(out) > 0 {
1246 errs = append(errs, fmt.Errorf("Unmatched Errors:"))
1247 for _, errLine := range out {
1248 errs = append(errs, fmt.Errorf("%s", errLine))
1249 }
1250 }
1251
1252 if len(errs) == 0 {
1253 return nil
1254 }
1255 if len(errs) == 1 {
1256 return errs[0]
1257 }
1258 var buf bytes.Buffer
1259 fmt.Fprintf(&buf, "\n")
1260 for _, err := range errs {
1261 fmt.Fprintf(&buf, "%s\n", err.Error())
1262 }
1263 return errors.New(buf.String())
1264 }
1265
1266 func (test) updateErrors(out, file string) {
1267 base := path.Base(file)
1268
1269 src, err := os.ReadFile(file)
1270 if err != nil {
1271 fmt.Fprintln(os.Stderr, err)
1272 return
1273 }
1274 lines := strings.Split(string(src), "\n")
1275
1276 for i := range lines {
1277 lines[i], _, _ = strings.Cut(lines[i], " // ERROR ")
1278 }
1279
1280 errors := make(map[int]map[string]bool)
1281 tmpRe := regexp.MustCompile(`autotmp_\d+`)
1282 fileRe := regexp.MustCompile(`(\.go):\d+:`)
1283 for _, errStr := range splitOutput(out, false) {
1284 m := fileRe.FindStringSubmatchIndex(errStr)
1285 if len(m) != 4 {
1286 continue
1287 }
1288
1289 errFile := errStr[:m[3]]
1290 rest := errStr[m[3]+1:]
1291 if errFile != file {
1292 continue
1293 }
1294 lineStr, msg, ok := strings.Cut(rest, ":")
1295 if !ok {
1296 continue
1297 }
1298 line, err := strconv.Atoi(lineStr)
1299 line--
1300 if err != nil || line < 0 || line >= len(lines) {
1301 continue
1302 }
1303 msg = strings.Replace(msg, file, base, -1)
1304 msg = strings.TrimLeft(msg, " \t")
1305 for _, r := range []string{`\`, `*`, `+`, `?`, `[`, `]`, `(`, `)`} {
1306 msg = strings.Replace(msg, r, `\`+r, -1)
1307 }
1308 msg = strings.Replace(msg, `"`, `.`, -1)
1309 msg = tmpRe.ReplaceAllLiteralString(msg, `autotmp_[0-9]+`)
1310 if errors[line] == nil {
1311 errors[line] = make(map[string]bool)
1312 }
1313 errors[line][msg] = true
1314 }
1315
1316 for line, errs := range errors {
1317 var sorted []string
1318 for e := range errs {
1319 sorted = append(sorted, e)
1320 }
1321 sort.Strings(sorted)
1322 lines[line] += " // ERROR"
1323 for _, e := range sorted {
1324 lines[line] += fmt.Sprintf(` "%s$"`, e)
1325 }
1326 }
1327
1328 err = os.WriteFile(file, []byte(strings.Join(lines, "\n")), 0640)
1329 if err != nil {
1330 fmt.Fprintln(os.Stderr, err)
1331 return
1332 }
1333
1334 exec.Command(goTool, "fmt", file).CombinedOutput()
1335 }
1336
1337
1338
1339
1340 func matchPrefix(s, prefix string) bool {
1341 i := strings.Index(s, ":")
1342 if i < 0 {
1343 return false
1344 }
1345 j := strings.LastIndex(s[:i], "/")
1346 s = s[j+1:]
1347 if len(s) <= len(prefix) || s[:len(prefix)] != prefix {
1348 return false
1349 }
1350 switch s[len(prefix)] {
1351 case '[', ':':
1352 return true
1353 }
1354 return false
1355 }
1356
1357 func partitionStrings(prefix string, strs []string) (matched, unmatched []string) {
1358 for _, s := range strs {
1359 if matchPrefix(s, prefix) {
1360 matched = append(matched, s)
1361 } else {
1362 unmatched = append(unmatched, s)
1363 }
1364 }
1365 return
1366 }
1367
1368 type wantedError struct {
1369 reStr string
1370 re *regexp.Regexp
1371 lineNum int
1372 auto bool
1373 file string
1374 prefix string
1375 }
1376
1377 var (
1378 errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
1379 errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`)
1380 errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
1381 lineRx = regexp.MustCompile(`LINE(([+-])(\d+))?`)
1382 )
1383
1384 func (t test) wantedErrors(file, short string) (errs []wantedError) {
1385 cache := make(map[string]*regexp.Regexp)
1386
1387 src, _ := os.ReadFile(file)
1388 for i, line := range strings.Split(string(src), "\n") {
1389 lineNum := i + 1
1390 if strings.Contains(line, "////") {
1391
1392 continue
1393 }
1394 var auto bool
1395 m := errAutoRx.FindStringSubmatch(line)
1396 if m != nil {
1397 auto = true
1398 } else {
1399 m = errRx.FindStringSubmatch(line)
1400 }
1401 if m == nil {
1402 continue
1403 }
1404 all := m[1]
1405 mm := errQuotesRx.FindAllStringSubmatch(all, -1)
1406 if mm == nil {
1407 t.Fatalf("%s:%d: invalid errchk line: %s", t.goFileName(), lineNum, line)
1408 }
1409 for _, m := range mm {
1410 rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
1411 n := lineNum
1412 if strings.HasPrefix(m, "LINE+") {
1413 delta, _ := strconv.Atoi(m[5:])
1414 n += delta
1415 } else if strings.HasPrefix(m, "LINE-") {
1416 delta, _ := strconv.Atoi(m[5:])
1417 n -= delta
1418 }
1419 return fmt.Sprintf("%s:%d", short, n)
1420 })
1421 re := cache[rx]
1422 if re == nil {
1423 var err error
1424 re, err = regexp.Compile(rx)
1425 if err != nil {
1426 t.Fatalf("%s:%d: invalid regexp \"%s\" in ERROR line: %v", t.goFileName(), lineNum, rx, err)
1427 }
1428 cache[rx] = re
1429 }
1430 prefix := fmt.Sprintf("%s:%d", short, lineNum)
1431 errs = append(errs, wantedError{
1432 reStr: rx,
1433 re: re,
1434 prefix: prefix,
1435 auto: auto,
1436 lineNum: lineNum,
1437 file: short,
1438 })
1439 }
1440 }
1441
1442 return
1443 }
1444
1445 const (
1446
1447
1448
1449 reMatchCheck = `-?(?:\x60[^\x60]*\x60|"(?:[^"\\]|\\.)*")`
1450 )
1451
1452 var (
1453
1454 rxAsmComment = regexp.MustCompile(`^\s*(.*?)\s*(?://\s*(.+)\s*)?$`)
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471 rxAsmPlatform = regexp.MustCompile(`(\w+)(/[\w.]+)?(/\w*)?\s*:\s*(` + reMatchCheck + `(?:\s*,\s*` + reMatchCheck + `)*)`)
1472
1473
1474 rxAsmCheck = regexp.MustCompile(reMatchCheck)
1475
1476
1477
1478
1479 archVariants = map[string][]string{
1480 "386": {"GO386", "sse2", "softfloat"},
1481 "amd64": {"GOAMD64", "v1", "v2", "v3", "v4"},
1482 "arm": {"GOARM", "5", "6", "7", "7,softfloat"},
1483 "arm64": {"GOARM64", "v8.0", "v8.1"},
1484 "loong64": {},
1485 "mips": {"GOMIPS", "hardfloat", "softfloat"},
1486 "mips64": {"GOMIPS64", "hardfloat", "softfloat"},
1487 "ppc64": {"GOPPC64", "power8", "power9", "power10"},
1488 "ppc64le": {"GOPPC64", "power8", "power9", "power10"},
1489 "ppc64x": {},
1490 "s390x": {},
1491 "wasm": {},
1492 "riscv64": {"GORISCV64", "rva20u64", "rva22u64"},
1493 }
1494 )
1495
1496
1497 type wantedAsmOpcode struct {
1498 fileline string
1499 line int
1500 opcode *regexp.Regexp
1501 negative bool
1502 found bool
1503 }
1504
1505
1506
1507 type buildEnv string
1508
1509
1510
1511 func (b buildEnv) Environ() []string {
1512 fields := strings.Split(string(b), "/")
1513 if len(fields) != 3 {
1514 panic("invalid buildEnv string: " + string(b))
1515 }
1516 env := []string{"GOOS=" + fields[0], "GOARCH=" + fields[1]}
1517 if fields[2] != "" {
1518 env = append(env, archVariants[fields[1]][0]+"="+fields[2])
1519 }
1520 return env
1521 }
1522
1523
1524
1525
1526
1527 type asmChecks map[buildEnv]map[string][]wantedAsmOpcode
1528
1529
1530 func (a asmChecks) Envs() []buildEnv {
1531 var envs []buildEnv
1532 for e := range a {
1533 envs = append(envs, e)
1534 }
1535 sort.Slice(envs, func(i, j int) bool {
1536 return string(envs[i]) < string(envs[j])
1537 })
1538 return envs
1539 }
1540
1541 func (t test) wantedAsmOpcodes(fn string) asmChecks {
1542 ops := make(asmChecks)
1543
1544 comment := ""
1545 src, err := os.ReadFile(fn)
1546 if err != nil {
1547 t.Fatal(err)
1548 }
1549 for i, line := range strings.Split(string(src), "\n") {
1550 matches := rxAsmComment.FindStringSubmatch(line)
1551 code, cmt := matches[1], matches[2]
1552
1553
1554
1555 comment += " " + cmt
1556 if code == "" {
1557 continue
1558 }
1559
1560
1561
1562 lnum := fn + ":" + strconv.Itoa(i+1)
1563 for _, ac := range rxAsmPlatform.FindAllStringSubmatch(comment, -1) {
1564 archspec, allchecks := ac[1:4], ac[4]
1565
1566 var arch, subarch, os string
1567 switch {
1568 case archspec[2] != "":
1569 os, arch, subarch = archspec[0], archspec[1][1:], archspec[2][1:]
1570 case archspec[1] != "":
1571 os, arch, subarch = "linux", archspec[0], archspec[1][1:]
1572 default:
1573 os, arch, subarch = "linux", archspec[0], ""
1574 if arch == "wasm" {
1575 os = "js"
1576 }
1577 }
1578
1579 if _, ok := archVariants[arch]; !ok {
1580 t.Fatalf("%s:%d: unsupported architecture: %v", t.goFileName(), i+1, arch)
1581 }
1582
1583
1584 envs := make([]buildEnv, 0, 4)
1585 arches := []string{arch}
1586
1587 if arch == "ppc64x" {
1588 arches = []string{"ppc64", "ppc64le"}
1589 }
1590 for _, arch := range arches {
1591 if subarch != "" {
1592 envs = append(envs, buildEnv(os+"/"+arch+"/"+subarch))
1593 } else {
1594 subarchs := archVariants[arch]
1595 if len(subarchs) == 0 {
1596 envs = append(envs, buildEnv(os+"/"+arch+"/"))
1597 } else {
1598 for _, sa := range archVariants[arch][1:] {
1599 envs = append(envs, buildEnv(os+"/"+arch+"/"+sa))
1600 }
1601 }
1602 }
1603 }
1604
1605 for _, m := range rxAsmCheck.FindAllString(allchecks, -1) {
1606 negative := false
1607 if m[0] == '-' {
1608 negative = true
1609 m = m[1:]
1610 }
1611
1612 rxsrc, err := strconv.Unquote(m)
1613 if err != nil {
1614 t.Fatalf("%s:%d: error unquoting string: %v", t.goFileName(), i+1, err)
1615 }
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625 oprx, err := regexp.Compile("^" + rxsrc)
1626 if err != nil {
1627 t.Fatalf("%s:%d: %v", t.goFileName(), i+1, err)
1628 }
1629
1630 for _, env := range envs {
1631 if ops[env] == nil {
1632 ops[env] = make(map[string][]wantedAsmOpcode)
1633 }
1634 ops[env][lnum] = append(ops[env][lnum], wantedAsmOpcode{
1635 negative: negative,
1636 fileline: lnum,
1637 line: i + 1,
1638 opcode: oprx,
1639 })
1640 }
1641 }
1642 }
1643 comment = ""
1644 }
1645
1646 return ops
1647 }
1648
1649 func (t test) asmCheck(outStr string, fn string, env buildEnv, fullops map[string][]wantedAsmOpcode) error {
1650
1651
1652
1653
1654 functionMarkers := make([]int, 1)
1655 lineFuncMap := make(map[string]int)
1656
1657 lines := strings.Split(outStr, "\n")
1658 rxLine := regexp.MustCompile(fmt.Sprintf(`\((%s:\d+)\)\s+(.*)`, regexp.QuoteMeta(fn)))
1659
1660 for nl, line := range lines {
1661
1662 if len(line) > 0 && line[0] != '\t' {
1663 functionMarkers = append(functionMarkers, nl)
1664 }
1665
1666
1667
1668 matches := rxLine.FindStringSubmatch(line)
1669 if len(matches) == 0 {
1670 continue
1671 }
1672 srcFileLine, asm := matches[1], matches[2]
1673
1674
1675
1676
1677 lineFuncMap[srcFileLine] = len(functionMarkers) - 1
1678
1679
1680
1681 if ops, found := fullops[srcFileLine]; found {
1682 for i := range ops {
1683 if !ops[i].found && ops[i].opcode.FindString(asm) != "" {
1684 ops[i].found = true
1685 }
1686 }
1687 }
1688 }
1689 functionMarkers = append(functionMarkers, len(lines))
1690
1691 var failed []wantedAsmOpcode
1692 for _, ops := range fullops {
1693 for _, o := range ops {
1694
1695
1696 if o.negative == o.found {
1697 failed = append(failed, o)
1698 }
1699 }
1700 }
1701 if len(failed) == 0 {
1702 return nil
1703 }
1704
1705
1706 lastFunction := -1
1707 var errbuf bytes.Buffer
1708 fmt.Fprintln(&errbuf)
1709 sort.Slice(failed, func(i, j int) bool { return failed[i].line < failed[j].line })
1710 for _, o := range failed {
1711
1712
1713 funcIdx := lineFuncMap[o.fileline]
1714 if funcIdx != 0 && funcIdx != lastFunction {
1715 funcLines := lines[functionMarkers[funcIdx]:functionMarkers[funcIdx+1]]
1716 t.Log(strings.Join(funcLines, "\n"))
1717 lastFunction = funcIdx
1718 }
1719
1720 if o.negative {
1721 fmt.Fprintf(&errbuf, "%s:%d: %s: wrong opcode found: %q\n", t.goFileName(), o.line, env, o.opcode.String())
1722 } else {
1723 fmt.Fprintf(&errbuf, "%s:%d: %s: opcode not found: %q\n", t.goFileName(), o.line, env, o.opcode.String())
1724 }
1725 }
1726 return errors.New(errbuf.String())
1727 }
1728
1729
1730
1731 func defaultRunOutputLimit() int {
1732 const maxArmCPU = 2
1733
1734 cpu := runtime.NumCPU()
1735 if runtime.GOARCH == "arm" && cpu > maxArmCPU {
1736 cpu = maxArmCPU
1737 }
1738 return cpu
1739 }
1740
1741 func TestShouldTest(t *testing.T) {
1742 if *shard != 0 {
1743 t.Skipf("nothing to test on shard index %d", *shard)
1744 }
1745
1746 assert := func(ok bool, _ string) {
1747 t.Helper()
1748 if !ok {
1749 t.Error("test case failed")
1750 }
1751 }
1752 assertNot := func(ok bool, _ string) { t.Helper(); assert(!ok, "") }
1753
1754
1755 assert(shouldTest("// +build linux", "linux", "arm"))
1756 assert(shouldTest("// +build !windows", "linux", "arm"))
1757 assertNot(shouldTest("// +build !windows", "windows", "amd64"))
1758
1759
1760 assert(shouldTest("// This is a test.", "os", "arch"))
1761
1762
1763 assertNot(shouldTest("// +build arm 386", "linux", "amd64"))
1764
1765
1766 assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64"))
1767 assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386"))
1768
1769
1770 assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64"))
1771 assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64"))
1772
1773
1774 assert(shouldTest("// +build !windows !plan9", "windows", "amd64"))
1775
1776
1777 assert(shouldTest("//go:build go1.4", "linux", "amd64"))
1778 }
1779
1780
1781 func overlayDir(dstRoot, srcRoot string) error {
1782 dstRoot = filepath.Clean(dstRoot)
1783 if err := os.MkdirAll(dstRoot, 0777); err != nil {
1784 return err
1785 }
1786
1787 srcRoot, err := filepath.Abs(srcRoot)
1788 if err != nil {
1789 return err
1790 }
1791
1792 return filepath.WalkDir(srcRoot, func(srcPath string, d fs.DirEntry, err error) error {
1793 if err != nil || srcPath == srcRoot {
1794 return err
1795 }
1796
1797 suffix := strings.TrimPrefix(srcPath, srcRoot)
1798 for len(suffix) > 0 && suffix[0] == filepath.Separator {
1799 suffix = suffix[1:]
1800 }
1801 dstPath := filepath.Join(dstRoot, suffix)
1802
1803 var info fs.FileInfo
1804 if d.Type()&os.ModeSymlink != 0 {
1805 info, err = os.Stat(srcPath)
1806 } else {
1807 info, err = d.Info()
1808 }
1809 if err != nil {
1810 return err
1811 }
1812 perm := info.Mode() & os.ModePerm
1813
1814
1815
1816 if info.IsDir() {
1817 return os.MkdirAll(dstPath, perm|0200)
1818 }
1819
1820
1821 if err := os.Symlink(srcPath, dstPath); err == nil {
1822 return nil
1823 }
1824
1825
1826 src, err := os.Open(srcPath)
1827 if err != nil {
1828 return err
1829 }
1830 defer src.Close()
1831
1832 dst, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
1833 if err != nil {
1834 return err
1835 }
1836
1837 _, err = io.Copy(dst, src)
1838 if closeErr := dst.Close(); err == nil {
1839 err = closeErr
1840 }
1841 return err
1842 })
1843 }
1844
1845
1846
1847
1848
1849
1850
1851 var types2Failures = setOf(
1852 "shift1.go",
1853 "fixedbugs/issue10700.go",
1854 "fixedbugs/issue18331.go",
1855 "fixedbugs/issue18419.go",
1856 "fixedbugs/issue20233.go",
1857 "fixedbugs/issue20245.go",
1858 "fixedbugs/issue31053.go",
1859 )
1860
1861 var types2Failures32Bit = setOf(
1862 "printbig.go",
1863 "fixedbugs/bug114.go",
1864 "fixedbugs/issue23305.go",
1865 )
1866
1867
1868
1869
1870
1871 var _ = setOf(
1872 "import1.go",
1873 "initializerr.go",
1874 "typecheck.go",
1875
1876 "fixedbugs/bug176.go",
1877 "fixedbugs/bug195.go",
1878 "fixedbugs/bug412.go",
1879
1880 "fixedbugs/issue11614.go",
1881 "fixedbugs/issue17038.go",
1882 "fixedbugs/issue23732.go",
1883 "fixedbugs/issue4510.go",
1884 "fixedbugs/issue7525b.go",
1885 "fixedbugs/issue7525c.go",
1886 "fixedbugs/issue7525d.go",
1887 "fixedbugs/issue7525e.go",
1888 "fixedbugs/issue7525.go",
1889 )
1890
1891 func setOf(keys ...string) map[string]bool {
1892 m := make(map[string]bool, len(keys))
1893 for _, key := range keys {
1894 m[key] = true
1895 }
1896 return m
1897 }
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916 func splitQuoted(s string) (r []string, err error) {
1917 var args []string
1918 arg := make([]rune, len(s))
1919 escaped := false
1920 quoted := false
1921 quote := '\x00'
1922 i := 0
1923 for _, rune := range s {
1924 switch {
1925 case escaped:
1926 escaped = false
1927 case rune == '\\':
1928 escaped = true
1929 continue
1930 case quote != '\x00':
1931 if rune == quote {
1932 quote = '\x00'
1933 continue
1934 }
1935 case rune == '"' || rune == '\'':
1936 quoted = true
1937 quote = rune
1938 continue
1939 case unicode.IsSpace(rune):
1940 if quoted || i > 0 {
1941 quoted = false
1942 args = append(args, string(arg[:i]))
1943 i = 0
1944 }
1945 continue
1946 }
1947 arg[i] = rune
1948 i++
1949 }
1950 if quoted || i > 0 {
1951 args = append(args, string(arg[:i]))
1952 }
1953 if quote != 0 {
1954 err = errors.New("unclosed quote")
1955 } else if escaped {
1956 err = errors.New("unfinished escaping")
1957 }
1958 return args, err
1959 }
1960
1961
1962
1963
1964
1965
1966 func replacePrefix(s, old, new string) string {
1967 n := strings.Count(s, old)
1968 if n == 0 {
1969 return s
1970 }
1971
1972 s = strings.ReplaceAll(s, " "+old, " "+new)
1973 s = strings.ReplaceAll(s, "\n"+old, "\n"+new)
1974 s = strings.ReplaceAll(s, "\n\t"+old, "\n\t"+new)
1975 if strings.HasPrefix(s, old) {
1976 s = new + s[len(old):]
1977 }
1978 return s
1979 }
1980
View as plain text