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