]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/internal/testdir/testdir_test.go
reflect: fix ArenaNew to match documentation
[gostls13.git] / src / cmd / internal / testdir / testdir_test.go
1 // Copyright 2012 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Package testdir_test runs tests in the GOROOT/test directory.
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         "sort"
28         "strconv"
29         "strings"
30         "sync"
31         "testing"
32         "time"
33         "unicode"
34 )
35
36 var (
37         allCodegen     = flag.Bool("all_codegen", defaultAllCodeGen(), "run all goos/goarch for codegen")
38         runSkips       = flag.Bool("run_skips", false, "run skipped tests (ignore skip and build tags)")
39         linkshared     = flag.Bool("linkshared", false, "")
40         updateErrors   = flag.Bool("update_errors", false, "update error messages in test file based on compiler output")
41         runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run")
42         force          = flag.Bool("f", false, "ignore expected-failure test lists")
43         target         = flag.String("target", "", "cross-compile tests for `goos/goarch`")
44
45         shard  = flag.Int("shard", 0, "shard index to run. Only applicable if -shards is non-zero.")
46         shards = flag.Int("shards", 0, "number of shards. If 0, all tests are run. This is used by the continuous build.")
47 )
48
49 // defaultAllCodeGen returns the default value of the -all_codegen
50 // flag. By default, we prefer to be fast (returning false), except on
51 // the linux-amd64 builder that's already very fast, so we get more
52 // test coverage on trybots. See https://go.dev/issue/34297.
53 func defaultAllCodeGen() bool {
54         return os.Getenv("GO_BUILDER_NAME") == "linux-amd64"
55 }
56
57 var (
58         // Package-scoped variables that are initialized at the start of Test.
59         goTool       string
60         goos         string // Target GOOS
61         goarch       string // Target GOARCH
62         cgoEnabled   bool
63         goExperiment string
64
65         // dirs are the directories to look for *.go files in.
66         // TODO(bradfitz): just use all directories?
67         dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "codegen", "runtime", "abi", "typeparam", "typeparam/mdempsky", "arenas"}
68 )
69
70 // Test is the main entrypoint that runs tests in the GOROOT/test directory.
71 //
72 // Each .go file test case in GOROOT/test is registered as a subtest with a
73 // a full name like "Test/fixedbugs/bug000.go" ('/'-separated relative path).
74 func Test(t *testing.T) {
75         if *target != "" {
76                 // When -target is set, propagate it to GOOS/GOARCH in our environment
77                 // so that all commands run with the target GOOS/GOARCH.
78                 //
79                 // We do this before even calling "go env", because GOOS/GOARCH can
80                 // affect other settings we get from go env (notably CGO_ENABLED).
81                 goos, goarch, ok := strings.Cut(*target, "/")
82                 if !ok {
83                         t.Fatalf("bad -target flag %q, expected goos/goarch", *target)
84                 }
85                 t.Setenv("GOOS", goos)
86                 t.Setenv("GOARCH", goarch)
87         }
88
89         goTool = testenv.GoToolPath(t)
90         cmd := exec.Command(goTool, "env", "-json")
91         stdout, err := cmd.StdoutPipe()
92         if err != nil {
93                 t.Fatal("StdoutPipe:", err)
94         }
95         if err := cmd.Start(); err != nil {
96                 t.Fatal("Start:", err)
97         }
98         var env struct {
99                 GOOS         string
100                 GOARCH       string
101                 GOEXPERIMENT string
102                 CGO_ENABLED  string
103         }
104         if err := json.NewDecoder(stdout).Decode(&env); err != nil {
105                 t.Fatal("Decode:", err)
106         }
107         if err := cmd.Wait(); err != nil {
108                 t.Fatal("Wait:", err)
109         }
110         goos = env.GOOS
111         goarch = env.GOARCH
112         cgoEnabled, _ = strconv.ParseBool(env.CGO_ENABLED)
113         goExperiment = env.GOEXPERIMENT
114
115         common := testCommon{
116                 gorootTestDir: filepath.Join(testenv.GOROOT(t), "test"),
117                 runoutputGate: make(chan bool, *runoutputLimit),
118         }
119
120         for _, dir := range dirs {
121                 for _, goFile := range goFiles(t, dir) {
122                         test := test{testCommon: common, dir: dir, goFile: goFile}
123                         t.Run(path.Join(dir, goFile), func(t *testing.T) {
124                                 t.Parallel()
125                                 test.T = t
126                                 testError := test.run()
127                                 wantError := test.expectFail() && !*force
128                                 if testError != nil {
129                                         if wantError {
130                                                 t.Log(testError.Error() + " (expected)")
131                                         } else {
132                                                 t.Fatal(testError)
133                                         }
134                                 } else if wantError {
135                                         t.Fatal("unexpected success")
136                                 }
137                         })
138                 }
139         }
140 }
141
142 func shardMatch(name string) bool {
143         if *shards <= 1 {
144                 return true
145         }
146         h := fnv.New32()
147         io.WriteString(h, name)
148         return int(h.Sum32()%uint32(*shards)) == *shard
149 }
150
151 func goFiles(t *testing.T, dir string) []string {
152         f, err := os.Open(filepath.Join(testenv.GOROOT(t), "test", dir))
153         if err != nil {
154                 t.Fatal(err)
155         }
156         dirnames, err := f.Readdirnames(-1)
157         f.Close()
158         if err != nil {
159                 t.Fatal(err)
160         }
161         names := []string{}
162         for _, name := range dirnames {
163                 if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && shardMatch(name) {
164                         names = append(names, name)
165                 }
166         }
167         sort.Strings(names)
168         return names
169 }
170
171 type runCmd func(...string) ([]byte, error)
172
173 func compileFile(runcmd runCmd, longname string, flags []string) (out []byte, err error) {
174         cmd := []string{goTool, "tool", "compile", "-e", "-p=p", "-importcfg=" + stdlibImportcfgFile()}
175         cmd = append(cmd, flags...)
176         if *linkshared {
177                 cmd = append(cmd, "-dynlink", "-installsuffix=dynlink")
178         }
179         cmd = append(cmd, longname)
180         return runcmd(cmd...)
181 }
182
183 func compileInDir(runcmd runCmd, dir string, flags []string, importcfg string, pkgname string, names ...string) (out []byte, err error) {
184         if importcfg == "" {
185                 importcfg = stdlibImportcfgFile()
186         }
187         cmd := []string{goTool, "tool", "compile", "-e", "-D", "test", "-importcfg=" + importcfg}
188         if pkgname == "main" {
189                 cmd = append(cmd, "-p=main")
190         } else {
191                 pkgname = path.Join("test", strings.TrimSuffix(names[0], ".go"))
192                 cmd = append(cmd, "-o", pkgname+".a", "-p", pkgname)
193         }
194         cmd = append(cmd, flags...)
195         if *linkshared {
196                 cmd = append(cmd, "-dynlink", "-installsuffix=dynlink")
197         }
198         for _, name := range names {
199                 cmd = append(cmd, filepath.Join(dir, name))
200         }
201         return runcmd(cmd...)
202 }
203
204 var stdlibImportcfgStringOnce sync.Once // TODO(#56102): Use sync.OnceValue once available. Also below.
205 var stdlibImportcfgString string
206
207 func stdlibImportcfg() string {
208         stdlibImportcfgStringOnce.Do(func() {
209                 output, err := exec.Command(goTool, "list", "-export", "-f", "{{if .Export}}packagefile {{.ImportPath}}={{.Export}}{{end}}", "std").Output()
210                 if err != nil {
211                         log.Fatal(err)
212                 }
213                 stdlibImportcfgString = string(output)
214         })
215         return stdlibImportcfgString
216 }
217
218 var stdlibImportcfgFilenameOnce sync.Once
219 var stdlibImportcfgFilename string
220
221 func stdlibImportcfgFile() string {
222         stdlibImportcfgFilenameOnce.Do(func() {
223                 tmpdir, err := os.MkdirTemp("", "importcfg")
224                 if err != nil {
225                         log.Fatal(err)
226                 }
227                 filename := filepath.Join(tmpdir, "importcfg")
228                 err = os.WriteFile(filename, []byte(stdlibImportcfg()), 0644)
229                 if err != nil {
230                         log.Fatal(err)
231                 }
232                 stdlibImportcfgFilename = filename
233         })
234         return stdlibImportcfgFilename
235 }
236
237 func linkFile(runcmd runCmd, goname string, importcfg string, ldflags []string) (err error) {
238         if importcfg == "" {
239                 importcfg = stdlibImportcfgFile()
240         }
241         pfile := strings.Replace(goname, ".go", ".o", -1)
242         cmd := []string{goTool, "tool", "link", "-w", "-o", "a.exe", "-importcfg=" + importcfg}
243         if *linkshared {
244                 cmd = append(cmd, "-linkshared", "-installsuffix=dynlink")
245         }
246         if ldflags != nil {
247                 cmd = append(cmd, ldflags...)
248         }
249         cmd = append(cmd, pfile)
250         _, err = runcmd(cmd...)
251         return
252 }
253
254 type testCommon struct {
255         // gorootTestDir is the GOROOT/test directory path.
256         gorootTestDir string
257
258         // runoutputGate controls the max number of runoutput tests
259         // executed in parallel as they can each consume a lot of memory.
260         runoutputGate chan bool
261 }
262
263 // test is a single test case in the GOROOT/test directory.
264 type test struct {
265         testCommon
266         *testing.T
267         // dir and goFile identify the test case.
268         // For example, "fixedbugs", "bug000.go".
269         dir, goFile string
270 }
271
272 // expectFail reports whether the (overall) test recipe is
273 // expected to fail under the current build+test configuration.
274 func (t test) expectFail() bool {
275         failureSets := []map[string]bool{types2Failures}
276
277         // Note: gccgo supports more 32-bit architectures than this, but
278         // hopefully the 32-bit failures are fixed before this matters.
279         switch goarch {
280         case "386", "arm", "mips", "mipsle":
281                 failureSets = append(failureSets, types2Failures32Bit)
282         }
283
284         testName := path.Join(t.dir, t.goFile) // Test name is '/'-separated.
285
286         for _, set := range failureSets {
287                 if set[testName] {
288                         return true
289                 }
290         }
291         return false
292 }
293
294 func (t test) goFileName() string {
295         return filepath.Join(t.dir, t.goFile)
296 }
297
298 func (t test) goDirName() string {
299         return filepath.Join(t.dir, strings.Replace(t.goFile, ".go", ".dir", -1))
300 }
301
302 // goDirFiles returns .go files in dir.
303 func goDirFiles(dir string) (filter []fs.DirEntry, _ error) {
304         files, err := os.ReadDir(dir)
305         if err != nil {
306                 return nil, err
307         }
308         for _, goFile := range files {
309                 if filepath.Ext(goFile.Name()) == ".go" {
310                         filter = append(filter, goFile)
311                 }
312         }
313         return filter, nil
314 }
315
316 var packageRE = regexp.MustCompile(`(?m)^package ([\p{Lu}\p{Ll}\w]+)`)
317
318 func getPackageNameFromSource(fn string) (string, error) {
319         data, err := os.ReadFile(fn)
320         if err != nil {
321                 return "", err
322         }
323         pkgname := packageRE.FindStringSubmatch(string(data))
324         if pkgname == nil {
325                 return "", fmt.Errorf("cannot find package name in %s", fn)
326         }
327         return pkgname[1], nil
328 }
329
330 // goDirPkg represents a Go package in some directory.
331 type goDirPkg struct {
332         name  string
333         files []string
334 }
335
336 // goDirPackages returns distinct Go packages in dir.
337 // If singlefilepkgs is set, each file is considered a separate package
338 // even if the package names are the same.
339 func goDirPackages(t *testing.T, dir string, singlefilepkgs bool) []*goDirPkg {
340         files, err := goDirFiles(dir)
341         if err != nil {
342                 t.Fatal(err)
343         }
344         var pkgs []*goDirPkg
345         m := make(map[string]*goDirPkg)
346         for _, file := range files {
347                 name := file.Name()
348                 pkgname, err := getPackageNameFromSource(filepath.Join(dir, name))
349                 if err != nil {
350                         t.Fatal(err)
351                 }
352                 p, ok := m[pkgname]
353                 if singlefilepkgs || !ok {
354                         p = &goDirPkg{name: pkgname}
355                         pkgs = append(pkgs, p)
356                         m[pkgname] = p
357                 }
358                 p.files = append(p.files, name)
359         }
360         return pkgs
361 }
362
363 type context struct {
364         GOOS       string
365         GOARCH     string
366         cgoEnabled bool
367         noOptEnv   bool
368 }
369
370 // shouldTest looks for build tags in a source file and returns
371 // whether the file should be used according to the tags.
372 func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) {
373         if *runSkips {
374                 return true, ""
375         }
376         for _, line := range strings.Split(src, "\n") {
377                 if strings.HasPrefix(line, "package ") {
378                         break
379                 }
380
381                 if expr, err := constraint.Parse(line); err == nil {
382                         gcFlags := os.Getenv("GO_GCFLAGS")
383                         ctxt := &context{
384                                 GOOS:       goos,
385                                 GOARCH:     goarch,
386                                 cgoEnabled: cgoEnabled,
387                                 noOptEnv:   strings.Contains(gcFlags, "-N") || strings.Contains(gcFlags, "-l"),
388                         }
389
390                         if !expr.Eval(ctxt.match) {
391                                 return false, line
392                         }
393                 }
394         }
395         return true, ""
396 }
397
398 func (ctxt *context) match(name string) bool {
399         if name == "" {
400                 return false
401         }
402
403         // Tags must be letters, digits, underscores or dots.
404         // Unlike in Go identifiers, all digits are fine (e.g., "386").
405         for _, c := range name {
406                 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
407                         return false
408                 }
409         }
410
411         if strings.HasPrefix(name, "goexperiment.") {
412                 for _, tag := range build.Default.ToolTags {
413                         if tag == name {
414                                 return true
415                         }
416                 }
417                 return false
418         }
419
420         if name == "cgo" && ctxt.cgoEnabled {
421                 return true
422         }
423
424         if name == ctxt.GOOS || name == ctxt.GOARCH || name == "gc" {
425                 return true
426         }
427
428         if ctxt.noOptEnv && name == "gcflags_noopt" {
429                 return true
430         }
431
432         if name == "test_run" {
433                 return true
434         }
435
436         return false
437 }
438
439 // goGcflags returns the -gcflags argument to use with go build / go run.
440 // This must match the flags used for building the standard library,
441 // or else the commands will rebuild any needed packages (like runtime)
442 // over and over.
443 func (test) goGcflags() string {
444         return "-gcflags=all=" + os.Getenv("GO_GCFLAGS")
445 }
446
447 func (test) goGcflagsIsEmpty() bool {
448         return "" == os.Getenv("GO_GCFLAGS")
449 }
450
451 var errTimeout = errors.New("command exceeded time limit")
452
453 // run runs the test case.
454 //
455 // When there is a problem, run uses t.Fatal to signify that it's an unskippable
456 // infrastructure error (such as failing to read an input file or the test recipe
457 // being malformed), or it returns a non-nil error to signify a test case error.
458 //
459 // t.Error isn't used here to give the caller the opportunity to decide whether
460 // the test case failing is expected before promoting it to a real test failure.
461 // See expectFail and -f flag.
462 func (t test) run() error {
463         srcBytes, err := os.ReadFile(filepath.Join(t.gorootTestDir, t.goFileName()))
464         if err != nil {
465                 t.Fatal("reading test case .go file:", err)
466         } else if bytes.HasPrefix(srcBytes, []byte{'\n'}) {
467                 t.Fatal(".go file source starts with a newline")
468         }
469         src := string(srcBytes)
470
471         // Execution recipe stops at first blank line.
472         action, _, ok := strings.Cut(src, "\n\n")
473         if !ok {
474                 t.Fatalf("double newline ending execution recipe not found in GOROOT/test/%s", t.goFileName())
475         }
476         if firstLine, rest, ok := strings.Cut(action, "\n"); ok && strings.Contains(firstLine, "+build") {
477                 // skip first line
478                 action = rest
479         }
480         action = strings.TrimPrefix(action, "//")
481
482         // Check for build constraints only up to the actual code.
483         header, _, ok := strings.Cut(src, "\npackage")
484         if !ok {
485                 header = action // some files are intentionally malformed
486         }
487         if ok, why := shouldTest(header, goos, goarch); !ok {
488                 t.Skip(why)
489         }
490
491         var args, flags, runenv []string
492         var tim int
493         wantError := false
494         wantAuto := false
495         singlefilepkgs := false
496         f, err := splitQuoted(action)
497         if err != nil {
498                 t.Fatal("invalid test recipe:", err)
499         }
500         if len(f) > 0 {
501                 action = f[0]
502                 args = f[1:]
503         }
504
505         // TODO: Clean up/simplify this switch statement.
506         switch action {
507         case "compile", "compiledir", "build", "builddir", "buildrundir", "run", "buildrun", "runoutput", "rundir", "runindir", "asmcheck":
508                 // nothing to do
509         case "errorcheckandrundir":
510                 wantError = false // should be no error if also will run
511         case "errorcheckwithauto":
512                 action = "errorcheck"
513                 wantAuto = true
514                 wantError = true
515         case "errorcheck", "errorcheckdir", "errorcheckoutput":
516                 wantError = true
517         case "skip":
518                 if *runSkips {
519                         break
520                 }
521                 t.Skip("skip")
522         default:
523                 t.Fatalf("unknown pattern: %q", action)
524         }
525
526         goexp := goExperiment
527
528         // collect flags
529         for len(args) > 0 && strings.HasPrefix(args[0], "-") {
530                 switch args[0] {
531                 case "-1":
532                         wantError = true
533                 case "-0":
534                         wantError = false
535                 case "-s":
536                         singlefilepkgs = true
537                 case "-t": // timeout in seconds
538                         args = args[1:]
539                         var err error
540                         tim, err = strconv.Atoi(args[0])
541                         if err != nil {
542                                 t.Fatalf("need number of seconds for -t timeout, got %s instead", args[0])
543                         }
544                         if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
545                                 timeoutScale, err := strconv.Atoi(s)
546                                 if err != nil {
547                                         t.Fatalf("failed to parse $GO_TEST_TIMEOUT_SCALE = %q as integer: %v", s, err)
548                                 }
549                                 tim *= timeoutScale
550                         }
551                 case "-goexperiment": // set GOEXPERIMENT environment
552                         args = args[1:]
553                         if goexp != "" {
554                                 goexp += ","
555                         }
556                         goexp += args[0]
557                         runenv = append(runenv, "GOEXPERIMENT="+goexp)
558
559                 default:
560                         flags = append(flags, args[0])
561                 }
562                 args = args[1:]
563         }
564         if action == "errorcheck" {
565                 found := false
566                 for i, f := range flags {
567                         if strings.HasPrefix(f, "-d=") {
568                                 flags[i] = f + ",ssa/check/on"
569                                 found = true
570                                 break
571                         }
572                 }
573                 if !found {
574                         flags = append(flags, "-d=ssa/check/on")
575                 }
576         }
577
578         tempDir := t.TempDir()
579         err = os.Mkdir(filepath.Join(tempDir, "test"), 0755)
580         if err != nil {
581                 t.Fatal(err)
582         }
583
584         err = os.WriteFile(filepath.Join(tempDir, t.goFile), srcBytes, 0644)
585         if err != nil {
586                 t.Fatal(err)
587         }
588
589         var (
590                 runInDir        = tempDir
591                 tempDirIsGOPATH = false
592         )
593         runcmd := func(args ...string) ([]byte, error) {
594                 cmd := exec.Command(args[0], args[1:]...)
595                 var buf bytes.Buffer
596                 cmd.Stdout = &buf
597                 cmd.Stderr = &buf
598                 cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=")
599                 if runInDir != "" {
600                         cmd.Dir = runInDir
601                         // Set PWD to match Dir to speed up os.Getwd in the child process.
602                         cmd.Env = append(cmd.Env, "PWD="+cmd.Dir)
603                 } else {
604                         // Default to running in the GOROOT/test directory.
605                         cmd.Dir = t.gorootTestDir
606                         // Set PWD to match Dir to speed up os.Getwd in the child process.
607                         cmd.Env = append(cmd.Env, "PWD="+cmd.Dir)
608                 }
609                 if tempDirIsGOPATH {
610                         cmd.Env = append(cmd.Env, "GOPATH="+tempDir)
611                 }
612                 cmd.Env = append(cmd.Env, "STDLIB_IMPORTCFG="+stdlibImportcfgFile())
613                 cmd.Env = append(cmd.Env, runenv...)
614
615                 var err error
616
617                 if tim != 0 {
618                         err = cmd.Start()
619                         // This command-timeout code adapted from cmd/go/test.go
620                         // Note: the Go command uses a more sophisticated timeout
621                         // strategy, first sending SIGQUIT (if appropriate for the
622                         // OS in question) to try to trigger a stack trace, then
623                         // finally much later SIGKILL. If timeouts prove to be a
624                         // common problem here, it would be worth porting over
625                         // that code as well. See https://do.dev/issue/50973
626                         // for more discussion.
627                         if err == nil {
628                                 tick := time.NewTimer(time.Duration(tim) * time.Second)
629                                 done := make(chan error)
630                                 go func() {
631                                         done <- cmd.Wait()
632                                 }()
633                                 select {
634                                 case err = <-done:
635                                         // ok
636                                 case <-tick.C:
637                                         cmd.Process.Signal(os.Interrupt)
638                                         time.Sleep(1 * time.Second)
639                                         cmd.Process.Kill()
640                                         <-done
641                                         err = errTimeout
642                                 }
643                                 tick.Stop()
644                         }
645                 } else {
646                         err = cmd.Run()
647                 }
648                 if err != nil && err != errTimeout {
649                         err = fmt.Errorf("%s\n%s", err, buf.Bytes())
650                 }
651                 return buf.Bytes(), err
652         }
653
654         importcfg := func(pkgs []*goDirPkg) string {
655                 cfg := stdlibImportcfg()
656                 for _, pkg := range pkgs {
657                         pkgpath := path.Join("test", strings.TrimSuffix(pkg.files[0], ".go"))
658                         cfg += "\npackagefile " + pkgpath + "=" + filepath.Join(tempDir, pkgpath+".a")
659                 }
660                 filename := filepath.Join(tempDir, "importcfg")
661                 err := os.WriteFile(filename, []byte(cfg), 0644)
662                 if err != nil {
663                         t.Fatal(err)
664                 }
665                 return filename
666         }
667
668         long := filepath.Join(t.gorootTestDir, t.goFileName())
669         switch action {
670         default:
671                 t.Fatalf("unimplemented action %q", action)
672                 panic("unreachable")
673
674         case "asmcheck":
675                 // Compile Go file and match the generated assembly
676                 // against a set of regexps in comments.
677                 ops := t.wantedAsmOpcodes(long)
678                 self := runtime.GOOS + "/" + runtime.GOARCH
679                 for _, env := range ops.Envs() {
680                         // Only run checks relevant to the current GOOS/GOARCH,
681                         // to avoid triggering a cross-compile of the runtime.
682                         if string(env) != self && !strings.HasPrefix(string(env), self+"/") && !*allCodegen {
683                                 continue
684                         }
685                         // -S=2 forces outermost line numbers when disassembling inlined code.
686                         cmdline := []string{"build", "-gcflags", "-S=2"}
687
688                         // Append flags, but don't override -gcflags=-S=2; add to it instead.
689                         for i := 0; i < len(flags); i++ {
690                                 flag := flags[i]
691                                 switch {
692                                 case strings.HasPrefix(flag, "-gcflags="):
693                                         cmdline[2] += " " + strings.TrimPrefix(flag, "-gcflags=")
694                                 case strings.HasPrefix(flag, "--gcflags="):
695                                         cmdline[2] += " " + strings.TrimPrefix(flag, "--gcflags=")
696                                 case flag == "-gcflags", flag == "--gcflags":
697                                         i++
698                                         if i < len(flags) {
699                                                 cmdline[2] += " " + flags[i]
700                                         }
701                                 default:
702                                         cmdline = append(cmdline, flag)
703                                 }
704                         }
705
706                         cmdline = append(cmdline, long)
707                         cmd := exec.Command(goTool, cmdline...)
708                         cmd.Env = append(os.Environ(), env.Environ()...)
709                         if len(flags) > 0 && flags[0] == "-race" {
710                                 cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
711                         }
712
713                         var buf bytes.Buffer
714                         cmd.Stdout, cmd.Stderr = &buf, &buf
715                         if err := cmd.Run(); err != nil {
716                                 t.Log(env, "\n", cmd.Stderr)
717                                 return err
718                         }
719
720                         err := t.asmCheck(buf.String(), long, env, ops[env])
721                         if err != nil {
722                                 return err
723                         }
724                 }
725                 return nil
726
727         case "errorcheck":
728                 // Compile Go file.
729                 // Fail if wantError is true and compilation was successful and vice versa.
730                 // Match errors produced by gc against errors in comments.
731                 // TODO(gri) remove need for -C (disable printing of columns in error messages)
732                 cmdline := []string{goTool, "tool", "compile", "-p=p", "-d=panic", "-C", "-e", "-importcfg=" + stdlibImportcfgFile(), "-o", "a.o"}
733                 // No need to add -dynlink even if linkshared if we're just checking for errors...
734                 cmdline = append(cmdline, flags...)
735                 cmdline = append(cmdline, long)
736                 out, err := runcmd(cmdline...)
737                 if wantError {
738                         if err == nil {
739                                 return fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
740                         }
741                         if err == errTimeout {
742                                 return fmt.Errorf("compilation timed out")
743                         }
744                 } else {
745                         if err != nil {
746                                 return err
747                         }
748                 }
749                 if *updateErrors {
750                         t.updateErrors(string(out), long)
751                 }
752                 return t.errorCheck(string(out), wantAuto, long, t.goFile)
753
754         case "compile":
755                 // Compile Go file.
756                 _, err := compileFile(runcmd, long, flags)
757                 return err
758
759         case "compiledir":
760                 // Compile all files in the directory as packages in lexicographic order.
761                 longdir := filepath.Join(t.gorootTestDir, t.goDirName())
762                 pkgs := goDirPackages(t.T, longdir, singlefilepkgs)
763                 importcfgfile := importcfg(pkgs)
764
765                 for _, pkg := range pkgs {
766                         _, err := compileInDir(runcmd, longdir, flags, importcfgfile, pkg.name, pkg.files...)
767                         if err != nil {
768                                 return err
769                         }
770                 }
771                 return nil
772
773         case "errorcheckdir", "errorcheckandrundir":
774                 flags = append(flags, "-d=panic")
775                 // Compile and errorCheck all files in the directory as packages in lexicographic order.
776                 // If errorcheckdir and wantError, compilation of the last package must fail.
777                 // If errorcheckandrundir and wantError, compilation of the package prior the last must fail.
778                 longdir := filepath.Join(t.gorootTestDir, t.goDirName())
779                 pkgs := goDirPackages(t.T, longdir, singlefilepkgs)
780                 errPkg := len(pkgs) - 1
781                 if wantError && action == "errorcheckandrundir" {
782                         // The last pkg should compiled successfully and will be run in next case.
783                         // Preceding pkg must return an error from compileInDir.
784                         errPkg--
785                 }
786                 importcfgfile := importcfg(pkgs)
787                 for i, pkg := range pkgs {
788                         out, err := compileInDir(runcmd, longdir, flags, importcfgfile, pkg.name, pkg.files...)
789                         if i == errPkg {
790                                 if wantError && err == nil {
791                                         return fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
792                                 } else if !wantError && err != nil {
793                                         return err
794                                 }
795                         } else if err != nil {
796                                 return err
797                         }
798                         var fullshort []string
799                         for _, name := range pkg.files {
800                                 fullshort = append(fullshort, filepath.Join(longdir, name), name)
801                         }
802                         err = t.errorCheck(string(out), wantAuto, fullshort...)
803                         if err != nil {
804                                 return err
805                         }
806                 }
807                 if action == "errorcheckdir" {
808                         return nil
809                 }
810                 fallthrough
811
812         case "rundir":
813                 // Compile all files in the directory as packages in lexicographic order.
814                 // In case of errorcheckandrundir, ignore failed compilation of the package before the last.
815                 // Link as if the last file is the main package, run it.
816                 // Verify the expected output.
817                 longdir := filepath.Join(t.gorootTestDir, t.goDirName())
818                 pkgs := goDirPackages(t.T, longdir, singlefilepkgs)
819                 // Split flags into gcflags and ldflags
820                 ldflags := []string{}
821                 for i, fl := range flags {
822                         if fl == "-ldflags" {
823                                 ldflags = flags[i+1:]
824                                 flags = flags[0:i]
825                                 break
826                         }
827                 }
828
829                 importcfgfile := importcfg(pkgs)
830
831                 for i, pkg := range pkgs {
832                         _, err := compileInDir(runcmd, longdir, flags, importcfgfile, pkg.name, pkg.files...)
833                         // Allow this package compilation fail based on conditions below;
834                         // its errors were checked in previous case.
835                         if err != nil && !(wantError && action == "errorcheckandrundir" && i == len(pkgs)-2) {
836                                 return err
837                         }
838
839                         if i == len(pkgs)-1 {
840                                 err = linkFile(runcmd, pkg.files[0], importcfgfile, ldflags)
841                                 if err != nil {
842                                         return err
843                                 }
844                                 var cmd []string
845                                 cmd = append(cmd, findExecCmd()...)
846                                 cmd = append(cmd, filepath.Join(tempDir, "a.exe"))
847                                 cmd = append(cmd, args...)
848                                 out, err := runcmd(cmd...)
849                                 if err != nil {
850                                         return err
851                                 }
852                                 t.checkExpectedOutput(out)
853                         }
854                 }
855                 return nil
856
857         case "runindir":
858                 // Make a shallow copy of t.goDirName() in its own module and GOPATH, and
859                 // run "go run ." in it. The module path (and hence import path prefix) of
860                 // the copy is equal to the basename of the source directory.
861                 //
862                 // It's used when test a requires a full 'go build' in order to compile
863                 // the sources, such as when importing multiple packages (issue29612.dir)
864                 // or compiling a package containing assembly files (see issue15609.dir),
865                 // but still needs to be run to verify the expected output.
866                 tempDirIsGOPATH = true
867                 srcDir := filepath.Join(t.gorootTestDir, t.goDirName())
868                 modName := filepath.Base(srcDir)
869                 gopathSrcDir := filepath.Join(tempDir, "src", modName)
870                 runInDir = gopathSrcDir
871
872                 if err := overlayDir(gopathSrcDir, srcDir); err != nil {
873                         t.Fatal(err)
874                 }
875
876                 modFile := fmt.Sprintf("module %s\ngo 1.14\n", modName)
877                 if err := os.WriteFile(filepath.Join(gopathSrcDir, "go.mod"), []byte(modFile), 0666); err != nil {
878                         t.Fatal(err)
879                 }
880
881                 cmd := []string{goTool, "run", t.goGcflags()}
882                 if *linkshared {
883                         cmd = append(cmd, "-linkshared")
884                 }
885                 cmd = append(cmd, flags...)
886                 cmd = append(cmd, ".")
887                 out, err := runcmd(cmd...)
888                 if err != nil {
889                         return err
890                 }
891                 return t.checkExpectedOutput(out)
892
893         case "build":
894                 // Build Go file.
895                 cmd := []string{goTool, "build", t.goGcflags()}
896                 cmd = append(cmd, flags...)
897                 cmd = append(cmd, "-o", "a.exe", long)
898                 _, err := runcmd(cmd...)
899                 return err
900
901         case "builddir", "buildrundir":
902                 // Build an executable from all the .go and .s files in a subdirectory.
903                 // Run it and verify its output in the buildrundir case.
904                 longdir := filepath.Join(t.gorootTestDir, t.goDirName())
905                 files, err := os.ReadDir(longdir)
906                 if err != nil {
907                         t.Fatal(err)
908                 }
909                 var gos []string
910                 var asms []string
911                 for _, file := range files {
912                         switch filepath.Ext(file.Name()) {
913                         case ".go":
914                                 gos = append(gos, filepath.Join(longdir, file.Name()))
915                         case ".s":
916                                 asms = append(asms, filepath.Join(longdir, file.Name()))
917                         }
918
919                 }
920                 if len(asms) > 0 {
921                         emptyHdrFile := filepath.Join(tempDir, "go_asm.h")
922                         if err := os.WriteFile(emptyHdrFile, nil, 0666); err != nil {
923                                 t.Fatalf("write empty go_asm.h: %v", err)
924                         }
925                         cmd := []string{goTool, "tool", "asm", "-p=main", "-gensymabis", "-o", "symabis"}
926                         cmd = append(cmd, asms...)
927                         _, err = runcmd(cmd...)
928                         if err != nil {
929                                 return err
930                         }
931                 }
932                 var objs []string
933                 cmd := []string{goTool, "tool", "compile", "-p=main", "-e", "-D", ".", "-importcfg=" + stdlibImportcfgFile(), "-o", "go.o"}
934                 if len(asms) > 0 {
935                         cmd = append(cmd, "-asmhdr", "go_asm.h", "-symabis", "symabis")
936                 }
937                 cmd = append(cmd, gos...)
938                 _, err = runcmd(cmd...)
939                 if err != nil {
940                         return err
941                 }
942                 objs = append(objs, "go.o")
943                 if len(asms) > 0 {
944                         cmd = []string{goTool, "tool", "asm", "-p=main", "-e", "-I", ".", "-o", "asm.o"}
945                         cmd = append(cmd, asms...)
946                         _, err = runcmd(cmd...)
947                         if err != nil {
948                                 return err
949                         }
950                         objs = append(objs, "asm.o")
951                 }
952                 cmd = []string{goTool, "tool", "pack", "c", "all.a"}
953                 cmd = append(cmd, objs...)
954                 _, err = runcmd(cmd...)
955                 if err != nil {
956                         return err
957                 }
958                 cmd = []string{goTool, "tool", "link", "-importcfg=" + stdlibImportcfgFile(), "-o", "a.exe", "all.a"}
959                 _, err = runcmd(cmd...)
960                 if err != nil {
961                         return err
962                 }
963
964                 if action == "builddir" {
965                         return nil
966                 }
967                 cmd = append(findExecCmd(), filepath.Join(tempDir, "a.exe"))
968                 out, err := runcmd(cmd...)
969                 if err != nil {
970                         return err
971                 }
972                 return t.checkExpectedOutput(out)
973
974         case "buildrun":
975                 // Build an executable from Go file, then run it, verify its output.
976                 // Useful for timeout tests where failure mode is infinite loop.
977                 // TODO: not supported on NaCl
978                 cmd := []string{goTool, "build", t.goGcflags(), "-o", "a.exe"}
979                 if *linkshared {
980                         cmd = append(cmd, "-linkshared")
981                 }
982                 longDirGoFile := filepath.Join(filepath.Join(t.gorootTestDir, t.dir), t.goFile)
983                 cmd = append(cmd, flags...)
984                 cmd = append(cmd, longDirGoFile)
985                 _, err := runcmd(cmd...)
986                 if err != nil {
987                         return err
988                 }
989                 cmd = []string{"./a.exe"}
990                 out, err := runcmd(append(cmd, args...)...)
991                 if err != nil {
992                         return err
993                 }
994
995                 return t.checkExpectedOutput(out)
996
997         case "run":
998                 // Run Go file if no special go command flags are provided;
999                 // otherwise build an executable and run it.
1000                 // Verify the output.
1001                 runInDir = ""
1002                 var out []byte
1003                 var err error
1004                 if len(flags)+len(args) == 0 && t.goGcflagsIsEmpty() && !*linkshared && goarch == runtime.GOARCH && goos == runtime.GOOS && goexp == goExperiment {
1005                         // If we're not using special go command flags,
1006                         // skip all the go command machinery.
1007                         // This avoids any time the go command would
1008                         // spend checking whether, for example, the installed
1009                         // package runtime is up to date.
1010                         // Because we run lots of trivial test programs,
1011                         // the time adds up.
1012                         pkg := filepath.Join(tempDir, "pkg.a")
1013                         if _, err := runcmd(goTool, "tool", "compile", "-p=main", "-importcfg="+stdlibImportcfgFile(), "-o", pkg, t.goFileName()); err != nil {
1014                                 return err
1015                         }
1016                         exe := filepath.Join(tempDir, "test.exe")
1017                         cmd := []string{goTool, "tool", "link", "-s", "-w", "-importcfg=" + stdlibImportcfgFile()}
1018                         cmd = append(cmd, "-o", exe, pkg)
1019                         if _, err := runcmd(cmd...); err != nil {
1020                                 return err
1021                         }
1022                         out, err = runcmd(append([]string{exe}, args...)...)
1023                 } else {
1024                         cmd := []string{goTool, "run", t.goGcflags()}
1025                         if *linkshared {
1026                                 cmd = append(cmd, "-linkshared")
1027                         }
1028                         cmd = append(cmd, flags...)
1029                         cmd = append(cmd, t.goFileName())
1030                         out, err = runcmd(append(cmd, args...)...)
1031                 }
1032                 if err != nil {
1033                         return err
1034                 }
1035                 return t.checkExpectedOutput(out)
1036
1037         case "runoutput":
1038                 // Run Go file and write its output into temporary Go file.
1039                 // Run generated Go file and verify its output.
1040                 t.runoutputGate <- true
1041                 defer func() {
1042                         <-t.runoutputGate
1043                 }()
1044                 runInDir = ""
1045                 cmd := []string{goTool, "run", t.goGcflags()}
1046                 if *linkshared {
1047                         cmd = append(cmd, "-linkshared")
1048                 }
1049                 cmd = append(cmd, t.goFileName())
1050                 out, err := runcmd(append(cmd, args...)...)
1051                 if err != nil {
1052                         return err
1053                 }
1054                 tfile := filepath.Join(tempDir, "tmp__.go")
1055                 if err := os.WriteFile(tfile, out, 0666); err != nil {
1056                         t.Fatalf("write tempfile: %v", err)
1057                 }
1058                 cmd = []string{goTool, "run", t.goGcflags()}
1059                 if *linkshared {
1060                         cmd = append(cmd, "-linkshared")
1061                 }
1062                 cmd = append(cmd, tfile)
1063                 out, err = runcmd(cmd...)
1064                 if err != nil {
1065                         return err
1066                 }
1067                 return t.checkExpectedOutput(out)
1068
1069         case "errorcheckoutput":
1070                 // Run Go file and write its output into temporary Go file.
1071                 // Compile and errorCheck generated Go file.
1072                 runInDir = ""
1073                 cmd := []string{goTool, "run", t.goGcflags()}
1074                 if *linkshared {
1075                         cmd = append(cmd, "-linkshared")
1076                 }
1077                 cmd = append(cmd, t.goFileName())
1078                 out, err := runcmd(append(cmd, args...)...)
1079                 if err != nil {
1080                         return err
1081                 }
1082                 tfile := filepath.Join(tempDir, "tmp__.go")
1083                 err = os.WriteFile(tfile, out, 0666)
1084                 if err != nil {
1085                         t.Fatalf("write tempfile: %v", err)
1086                 }
1087                 cmdline := []string{goTool, "tool", "compile", "-importcfg=" + stdlibImportcfgFile(), "-p=p", "-d=panic", "-e", "-o", "a.o"}
1088                 cmdline = append(cmdline, flags...)
1089                 cmdline = append(cmdline, tfile)
1090                 out, err = runcmd(cmdline...)
1091                 if wantError {
1092                         if err == nil {
1093                                 return fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
1094                         }
1095                 } else {
1096                         if err != nil {
1097                                 return err
1098                         }
1099                 }
1100                 return t.errorCheck(string(out), false, tfile, "tmp__.go")
1101         }
1102 }
1103
1104 var execCmdOnce sync.Once
1105 var execCmd []string
1106
1107 func findExecCmd() []string {
1108         execCmdOnce.Do(func() {
1109                 if goos == runtime.GOOS && goarch == runtime.GOARCH {
1110                         // Do nothing.
1111                 } else if path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch)); err == nil {
1112                         execCmd = []string{path}
1113                 }
1114         })
1115         return execCmd
1116 }
1117
1118 // checkExpectedOutput compares the output from compiling and/or running with the contents
1119 // of the corresponding reference output file, if any (replace ".go" with ".out").
1120 // If they don't match, fail with an informative message.
1121 func (t test) checkExpectedOutput(gotBytes []byte) error {
1122         got := string(gotBytes)
1123         filename := filepath.Join(t.dir, t.goFile)
1124         filename = filename[:len(filename)-len(".go")]
1125         filename += ".out"
1126         b, err := os.ReadFile(filepath.Join(t.gorootTestDir, filename))
1127         if errors.Is(err, fs.ErrNotExist) {
1128                 // File is allowed to be missing, in which case output should be empty.
1129                 b = nil
1130         } else if err != nil {
1131                 return err
1132         }
1133         got = strings.Replace(got, "\r\n", "\n", -1)
1134         if got != string(b) {
1135                 if err == nil {
1136                         return fmt.Errorf("output does not match expected in %s. Instead saw\n%s", filename, got)
1137                 } else {
1138                         return fmt.Errorf("output should be empty when (optional) expected-output file %s is not present. Instead saw\n%s", filename, got)
1139                 }
1140         }
1141         return nil
1142 }
1143
1144 func splitOutput(out string, wantAuto bool) []string {
1145         // gc error messages continue onto additional lines with leading tabs.
1146         // Split the output at the beginning of each line that doesn't begin with a tab.
1147         // <autogenerated> lines are impossible to match so those are filtered out.
1148         var res []string
1149         for _, line := range strings.Split(out, "\n") {
1150                 if strings.HasSuffix(line, "\r") { // remove '\r', output by compiler on windows
1151                         line = line[:len(line)-1]
1152                 }
1153                 if strings.HasPrefix(line, "\t") {
1154                         res[len(res)-1] += "\n" + line
1155                 } else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") {
1156                         continue
1157                 } else if strings.TrimSpace(line) != "" {
1158                         res = append(res, line)
1159                 }
1160         }
1161         return res
1162 }
1163
1164 // errorCheck matches errors in outStr against comments in source files.
1165 // For each line of the source files which should generate an error,
1166 // there should be a comment of the form // ERROR "regexp".
1167 // If outStr has an error for a line which has no such comment,
1168 // this function will report an error.
1169 // Likewise if outStr does not have an error for a line which has a comment,
1170 // or if the error message does not match the <regexp>.
1171 // The <regexp> syntax is Perl but it's best to stick to egrep.
1172 //
1173 // Sources files are supplied as fullshort slice.
1174 // It consists of pairs: full path to source file and its base name.
1175 func (t test) errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
1176         defer func() {
1177                 if testing.Verbose() && err != nil {
1178                         t.Logf("gc output:\n%s", outStr)
1179                 }
1180         }()
1181         var errs []error
1182         out := splitOutput(outStr, wantAuto)
1183
1184         // Cut directory name.
1185         for i := range out {
1186                 for j := 0; j < len(fullshort); j += 2 {
1187                         full, short := fullshort[j], fullshort[j+1]
1188                         out[i] = strings.Replace(out[i], full, short, -1)
1189                 }
1190         }
1191
1192         var want []wantedError
1193         for j := 0; j < len(fullshort); j += 2 {
1194                 full, short := fullshort[j], fullshort[j+1]
1195                 want = append(want, t.wantedErrors(full, short)...)
1196         }
1197
1198         for _, we := range want {
1199                 var errmsgs []string
1200                 if we.auto {
1201                         errmsgs, out = partitionStrings("<autogenerated>", out)
1202                 } else {
1203                         errmsgs, out = partitionStrings(we.prefix, out)
1204                 }
1205                 if len(errmsgs) == 0 {
1206                         errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr))
1207                         continue
1208                 }
1209                 matched := false
1210                 n := len(out)
1211                 for _, errmsg := range errmsgs {
1212                         // Assume errmsg says "file:line: foo".
1213                         // Cut leading "file:line: " to avoid accidental matching of file name instead of message.
1214                         text := errmsg
1215                         if _, suffix, ok := strings.Cut(text, " "); ok {
1216                                 text = suffix
1217                         }
1218                         if we.re.MatchString(text) {
1219                                 matched = true
1220                         } else {
1221                                 out = append(out, errmsg)
1222                         }
1223                 }
1224                 if !matched {
1225                         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")))
1226                         continue
1227                 }
1228         }
1229
1230         if len(out) > 0 {
1231                 errs = append(errs, fmt.Errorf("Unmatched Errors:"))
1232                 for _, errLine := range out {
1233                         errs = append(errs, fmt.Errorf("%s", errLine))
1234                 }
1235         }
1236
1237         if len(errs) == 0 {
1238                 return nil
1239         }
1240         if len(errs) == 1 {
1241                 return errs[0]
1242         }
1243         var buf bytes.Buffer
1244         fmt.Fprintf(&buf, "\n")
1245         for _, err := range errs {
1246                 fmt.Fprintf(&buf, "%s\n", err.Error())
1247         }
1248         return errors.New(buf.String())
1249 }
1250
1251 func (test) updateErrors(out, file string) {
1252         base := path.Base(file)
1253         // Read in source file.
1254         src, err := os.ReadFile(file)
1255         if err != nil {
1256                 fmt.Fprintln(os.Stderr, err)
1257                 return
1258         }
1259         lines := strings.Split(string(src), "\n")
1260         // Remove old errors.
1261         for i := range lines {
1262                 lines[i], _, _ = strings.Cut(lines[i], " // ERROR ")
1263         }
1264         // Parse new errors.
1265         errors := make(map[int]map[string]bool)
1266         tmpRe := regexp.MustCompile(`autotmp_\d+`)
1267         for _, errStr := range splitOutput(out, false) {
1268                 errFile, rest, ok := strings.Cut(errStr, ":")
1269                 if !ok || errFile != file {
1270                         continue
1271                 }
1272                 lineStr, msg, ok := strings.Cut(rest, ":")
1273                 if !ok {
1274                         continue
1275                 }
1276                 line, err := strconv.Atoi(lineStr)
1277                 line--
1278                 if err != nil || line < 0 || line >= len(lines) {
1279                         continue
1280                 }
1281                 msg = strings.Replace(msg, file, base, -1) // normalize file mentions in error itself
1282                 msg = strings.TrimLeft(msg, " \t")
1283                 for _, r := range []string{`\`, `*`, `+`, `?`, `[`, `]`, `(`, `)`} {
1284                         msg = strings.Replace(msg, r, `\`+r, -1)
1285                 }
1286                 msg = strings.Replace(msg, `"`, `.`, -1)
1287                 msg = tmpRe.ReplaceAllLiteralString(msg, `autotmp_[0-9]+`)
1288                 if errors[line] == nil {
1289                         errors[line] = make(map[string]bool)
1290                 }
1291                 errors[line][msg] = true
1292         }
1293         // Add new errors.
1294         for line, errs := range errors {
1295                 var sorted []string
1296                 for e := range errs {
1297                         sorted = append(sorted, e)
1298                 }
1299                 sort.Strings(sorted)
1300                 lines[line] += " // ERROR"
1301                 for _, e := range sorted {
1302                         lines[line] += fmt.Sprintf(` "%s$"`, e)
1303                 }
1304         }
1305         // Write new file.
1306         err = os.WriteFile(file, []byte(strings.Join(lines, "\n")), 0640)
1307         if err != nil {
1308                 fmt.Fprintln(os.Stderr, err)
1309                 return
1310         }
1311         // Polish.
1312         exec.Command(goTool, "fmt", file).CombinedOutput()
1313 }
1314
1315 // matchPrefix reports whether s is of the form ^(.*/)?prefix(:|[),
1316 // That is, it needs the file name prefix followed by a : or a [,
1317 // and possibly preceded by a directory name.
1318 func matchPrefix(s, prefix string) bool {
1319         i := strings.Index(s, ":")
1320         if i < 0 {
1321                 return false
1322         }
1323         j := strings.LastIndex(s[:i], "/")
1324         s = s[j+1:]
1325         if len(s) <= len(prefix) || s[:len(prefix)] != prefix {
1326                 return false
1327         }
1328         switch s[len(prefix)] {
1329         case '[', ':':
1330                 return true
1331         }
1332         return false
1333 }
1334
1335 func partitionStrings(prefix string, strs []string) (matched, unmatched []string) {
1336         for _, s := range strs {
1337                 if matchPrefix(s, prefix) {
1338                         matched = append(matched, s)
1339                 } else {
1340                         unmatched = append(unmatched, s)
1341                 }
1342         }
1343         return
1344 }
1345
1346 type wantedError struct {
1347         reStr   string
1348         re      *regexp.Regexp
1349         lineNum int
1350         auto    bool // match <autogenerated> line
1351         file    string
1352         prefix  string
1353 }
1354
1355 var (
1356         errRx       = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
1357         errAutoRx   = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`)
1358         errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
1359         lineRx      = regexp.MustCompile(`LINE(([+-])(\d+))?`)
1360 )
1361
1362 func (t test) wantedErrors(file, short string) (errs []wantedError) {
1363         cache := make(map[string]*regexp.Regexp)
1364
1365         src, _ := os.ReadFile(file)
1366         for i, line := range strings.Split(string(src), "\n") {
1367                 lineNum := i + 1
1368                 if strings.Contains(line, "////") {
1369                         // double comment disables ERROR
1370                         continue
1371                 }
1372                 var auto bool
1373                 m := errAutoRx.FindStringSubmatch(line)
1374                 if m != nil {
1375                         auto = true
1376                 } else {
1377                         m = errRx.FindStringSubmatch(line)
1378                 }
1379                 if m == nil {
1380                         continue
1381                 }
1382                 all := m[1]
1383                 mm := errQuotesRx.FindAllStringSubmatch(all, -1)
1384                 if mm == nil {
1385                         t.Fatalf("%s:%d: invalid errchk line: %s", t.goFileName(), lineNum, line)
1386                 }
1387                 for _, m := range mm {
1388                         rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
1389                                 n := lineNum
1390                                 if strings.HasPrefix(m, "LINE+") {
1391                                         delta, _ := strconv.Atoi(m[5:])
1392                                         n += delta
1393                                 } else if strings.HasPrefix(m, "LINE-") {
1394                                         delta, _ := strconv.Atoi(m[5:])
1395                                         n -= delta
1396                                 }
1397                                 return fmt.Sprintf("%s:%d", short, n)
1398                         })
1399                         re := cache[rx]
1400                         if re == nil {
1401                                 var err error
1402                                 re, err = regexp.Compile(rx)
1403                                 if err != nil {
1404                                         t.Fatalf("%s:%d: invalid regexp \"%s\" in ERROR line: %v", t.goFileName(), lineNum, rx, err)
1405                                 }
1406                                 cache[rx] = re
1407                         }
1408                         prefix := fmt.Sprintf("%s:%d", short, lineNum)
1409                         errs = append(errs, wantedError{
1410                                 reStr:   rx,
1411                                 re:      re,
1412                                 prefix:  prefix,
1413                                 auto:    auto,
1414                                 lineNum: lineNum,
1415                                 file:    short,
1416                         })
1417                 }
1418         }
1419
1420         return
1421 }
1422
1423 const (
1424         // Regexp to match a single opcode check: optionally begin with "-" (to indicate
1425         // a negative check), followed by a string literal enclosed in "" or ``. For "",
1426         // backslashes must be handled.
1427         reMatchCheck = `-?(?:\x60[^\x60]*\x60|"(?:[^"\\]|\\.)*")`
1428 )
1429
1430 var (
1431         // Regexp to split a line in code and comment, trimming spaces
1432         rxAsmComment = regexp.MustCompile(`^\s*(.*?)\s*(?://\s*(.+)\s*)?$`)
1433
1434         // Regexp to extract an architecture check: architecture name (or triplet),
1435         // followed by semi-colon, followed by a comma-separated list of opcode checks.
1436         // Extraneous spaces are ignored.
1437         rxAsmPlatform = regexp.MustCompile(`(\w+)(/\w+)?(/\w*)?\s*:\s*(` + reMatchCheck + `(?:\s*,\s*` + reMatchCheck + `)*)`)
1438
1439         // Regexp to extract a single opcoded check
1440         rxAsmCheck = regexp.MustCompile(reMatchCheck)
1441
1442         // List of all architecture variants. Key is the GOARCH architecture,
1443         // value[0] is the variant-changing environment variable, and values[1:]
1444         // are the supported variants.
1445         archVariants = map[string][]string{
1446                 "386":     {"GO386", "sse2", "softfloat"},
1447                 "amd64":   {"GOAMD64", "v1", "v2", "v3", "v4"},
1448                 "arm":     {"GOARM", "5", "6", "7"},
1449                 "arm64":   {},
1450                 "loong64": {},
1451                 "mips":    {"GOMIPS", "hardfloat", "softfloat"},
1452                 "mips64":  {"GOMIPS64", "hardfloat", "softfloat"},
1453                 "ppc64":   {"GOPPC64", "power8", "power9", "power10"},
1454                 "ppc64le": {"GOPPC64", "power8", "power9", "power10"},
1455                 "ppc64x":  {}, // A pseudo-arch representing both ppc64 and ppc64le
1456                 "s390x":   {},
1457                 "wasm":    {},
1458                 "riscv64": {},
1459         }
1460 )
1461
1462 // wantedAsmOpcode is a single asmcheck check
1463 type wantedAsmOpcode struct {
1464         fileline string         // original source file/line (eg: "/path/foo.go:45")
1465         line     int            // original source line
1466         opcode   *regexp.Regexp // opcode check to be performed on assembly output
1467         negative bool           // true if the check is supposed to fail rather than pass
1468         found    bool           // true if the opcode check matched at least one in the output
1469 }
1470
1471 // A build environment triplet separated by slashes (eg: linux/386/sse2).
1472 // The third field can be empty if the arch does not support variants (eg: "plan9/amd64/")
1473 type buildEnv string
1474
1475 // Environ returns the environment it represents in cmd.Environ() "key=val" format
1476 // For instance, "linux/386/sse2".Environ() returns {"GOOS=linux", "GOARCH=386", "GO386=sse2"}
1477 func (b buildEnv) Environ() []string {
1478         fields := strings.Split(string(b), "/")
1479         if len(fields) != 3 {
1480                 panic("invalid buildEnv string: " + string(b))
1481         }
1482         env := []string{"GOOS=" + fields[0], "GOARCH=" + fields[1]}
1483         if fields[2] != "" {
1484                 env = append(env, archVariants[fields[1]][0]+"="+fields[2])
1485         }
1486         return env
1487 }
1488
1489 // asmChecks represents all the asmcheck checks present in a test file
1490 // The outer map key is the build triplet in which the checks must be performed.
1491 // The inner map key represent the source file line ("filename.go:1234") at which the
1492 // checks must be performed.
1493 type asmChecks map[buildEnv]map[string][]wantedAsmOpcode
1494
1495 // Envs returns all the buildEnv in which at least one check is present
1496 func (a asmChecks) Envs() []buildEnv {
1497         var envs []buildEnv
1498         for e := range a {
1499                 envs = append(envs, e)
1500         }
1501         sort.Slice(envs, func(i, j int) bool {
1502                 return string(envs[i]) < string(envs[j])
1503         })
1504         return envs
1505 }
1506
1507 func (t test) wantedAsmOpcodes(fn string) asmChecks {
1508         ops := make(asmChecks)
1509
1510         comment := ""
1511         src, err := os.ReadFile(fn)
1512         if err != nil {
1513                 t.Fatal(err)
1514         }
1515         for i, line := range strings.Split(string(src), "\n") {
1516                 matches := rxAsmComment.FindStringSubmatch(line)
1517                 code, cmt := matches[1], matches[2]
1518
1519                 // Keep comments pending in the comment variable until
1520                 // we find a line that contains some code.
1521                 comment += " " + cmt
1522                 if code == "" {
1523                         continue
1524                 }
1525
1526                 // Parse and extract any architecture check from comments,
1527                 // made by one architecture name and multiple checks.
1528                 lnum := fn + ":" + strconv.Itoa(i+1)
1529                 for _, ac := range rxAsmPlatform.FindAllStringSubmatch(comment, -1) {
1530                         archspec, allchecks := ac[1:4], ac[4]
1531
1532                         var arch, subarch, os string
1533                         switch {
1534                         case archspec[2] != "": // 3 components: "linux/386/sse2"
1535                                 os, arch, subarch = archspec[0], archspec[1][1:], archspec[2][1:]
1536                         case archspec[1] != "": // 2 components: "386/sse2"
1537                                 os, arch, subarch = "linux", archspec[0], archspec[1][1:]
1538                         default: // 1 component: "386"
1539                                 os, arch, subarch = "linux", archspec[0], ""
1540                                 if arch == "wasm" {
1541                                         os = "js"
1542                                 }
1543                         }
1544
1545                         if _, ok := archVariants[arch]; !ok {
1546                                 t.Fatalf("%s:%d: unsupported architecture: %v", t.goFileName(), i+1, arch)
1547                         }
1548
1549                         // Create the build environments corresponding the above specifiers
1550                         envs := make([]buildEnv, 0, 4)
1551                         arches := []string{arch}
1552                         // ppc64x is a pseudo-arch, generate tests for both endian variants.
1553                         if arch == "ppc64x" {
1554                                 arches = []string{"ppc64", "ppc64le"}
1555                         }
1556                         for _, arch := range arches {
1557                                 if subarch != "" {
1558                                         envs = append(envs, buildEnv(os+"/"+arch+"/"+subarch))
1559                                 } else {
1560                                         subarchs := archVariants[arch]
1561                                         if len(subarchs) == 0 {
1562                                                 envs = append(envs, buildEnv(os+"/"+arch+"/"))
1563                                         } else {
1564                                                 for _, sa := range archVariants[arch][1:] {
1565                                                         envs = append(envs, buildEnv(os+"/"+arch+"/"+sa))
1566                                                 }
1567                                         }
1568                                 }
1569                         }
1570
1571                         for _, m := range rxAsmCheck.FindAllString(allchecks, -1) {
1572                                 negative := false
1573                                 if m[0] == '-' {
1574                                         negative = true
1575                                         m = m[1:]
1576                                 }
1577
1578                                 rxsrc, err := strconv.Unquote(m)
1579                                 if err != nil {
1580                                         t.Fatalf("%s:%d: error unquoting string: %v", t.goFileName(), i+1, err)
1581                                 }
1582
1583                                 // Compile the checks as regular expressions. Notice that we
1584                                 // consider checks as matching from the beginning of the actual
1585                                 // assembler source (that is, what is left on each line of the
1586                                 // compile -S output after we strip file/line info) to avoid
1587                                 // trivial bugs such as "ADD" matching "FADD". This
1588                                 // doesn't remove genericity: it's still possible to write
1589                                 // something like "F?ADD", but we make common cases simpler
1590                                 // to get right.
1591                                 oprx, err := regexp.Compile("^" + rxsrc)
1592                                 if err != nil {
1593                                         t.Fatalf("%s:%d: %v", t.goFileName(), i+1, err)
1594                                 }
1595
1596                                 for _, env := range envs {
1597                                         if ops[env] == nil {
1598                                                 ops[env] = make(map[string][]wantedAsmOpcode)
1599                                         }
1600                                         ops[env][lnum] = append(ops[env][lnum], wantedAsmOpcode{
1601                                                 negative: negative,
1602                                                 fileline: lnum,
1603                                                 line:     i + 1,
1604                                                 opcode:   oprx,
1605                                         })
1606                                 }
1607                         }
1608                 }
1609                 comment = ""
1610         }
1611
1612         return ops
1613 }
1614
1615 func (t test) asmCheck(outStr string, fn string, env buildEnv, fullops map[string][]wantedAsmOpcode) error {
1616         // The assembly output contains the concatenated dump of multiple functions.
1617         // the first line of each function begins at column 0, while the rest is
1618         // indented by a tabulation. These data structures help us index the
1619         // output by function.
1620         functionMarkers := make([]int, 1)
1621         lineFuncMap := make(map[string]int)
1622
1623         lines := strings.Split(outStr, "\n")
1624         rxLine := regexp.MustCompile(fmt.Sprintf(`\((%s:\d+)\)\s+(.*)`, regexp.QuoteMeta(fn)))
1625
1626         for nl, line := range lines {
1627                 // Check if this line begins a function
1628                 if len(line) > 0 && line[0] != '\t' {
1629                         functionMarkers = append(functionMarkers, nl)
1630                 }
1631
1632                 // Search if this line contains a assembly opcode (which is prefixed by the
1633                 // original source file/line in parenthesis)
1634                 matches := rxLine.FindStringSubmatch(line)
1635                 if len(matches) == 0 {
1636                         continue
1637                 }
1638                 srcFileLine, asm := matches[1], matches[2]
1639
1640                 // Associate the original file/line information to the current
1641                 // function in the output; it will be useful to dump it in case
1642                 // of error.
1643                 lineFuncMap[srcFileLine] = len(functionMarkers) - 1
1644
1645                 // If there are opcode checks associated to this source file/line,
1646                 // run the checks.
1647                 if ops, found := fullops[srcFileLine]; found {
1648                         for i := range ops {
1649                                 if !ops[i].found && ops[i].opcode.FindString(asm) != "" {
1650                                         ops[i].found = true
1651                                 }
1652                         }
1653                 }
1654         }
1655         functionMarkers = append(functionMarkers, len(lines))
1656
1657         var failed []wantedAsmOpcode
1658         for _, ops := range fullops {
1659                 for _, o := range ops {
1660                         // There's a failure if a negative match was found,
1661                         // or a positive match was not found.
1662                         if o.negative == o.found {
1663                                 failed = append(failed, o)
1664                         }
1665                 }
1666         }
1667         if len(failed) == 0 {
1668                 return nil
1669         }
1670
1671         // At least one asmcheck failed; report them.
1672         lastFunction := -1
1673         var errbuf bytes.Buffer
1674         fmt.Fprintln(&errbuf)
1675         sort.Slice(failed, func(i, j int) bool { return failed[i].line < failed[j].line })
1676         for _, o := range failed {
1677                 // Dump the function in which this opcode check was supposed to
1678                 // pass but failed.
1679                 funcIdx := lineFuncMap[o.fileline]
1680                 if funcIdx != 0 && funcIdx != lastFunction {
1681                         funcLines := lines[functionMarkers[funcIdx]:functionMarkers[funcIdx+1]]
1682                         t.Log(strings.Join(funcLines, "\n"))
1683                         lastFunction = funcIdx // avoid printing same function twice
1684                 }
1685
1686                 if o.negative {
1687                         fmt.Fprintf(&errbuf, "%s:%d: %s: wrong opcode found: %q\n", t.goFileName(), o.line, env, o.opcode.String())
1688                 } else {
1689                         fmt.Fprintf(&errbuf, "%s:%d: %s: opcode not found: %q\n", t.goFileName(), o.line, env, o.opcode.String())
1690                 }
1691         }
1692         return errors.New(errbuf.String())
1693 }
1694
1695 // defaultRunOutputLimit returns the number of runoutput tests that
1696 // can be executed in parallel.
1697 func defaultRunOutputLimit() int {
1698         const maxArmCPU = 2
1699
1700         cpu := runtime.NumCPU()
1701         if runtime.GOARCH == "arm" && cpu > maxArmCPU {
1702                 cpu = maxArmCPU
1703         }
1704         return cpu
1705 }
1706
1707 func TestShouldTest(t *testing.T) {
1708         if *shard != 0 {
1709                 t.Skipf("nothing to test on shard index %d", *shard)
1710         }
1711
1712         assert := func(ok bool, _ string) {
1713                 t.Helper()
1714                 if !ok {
1715                         t.Error("test case failed")
1716                 }
1717         }
1718         assertNot := func(ok bool, _ string) { t.Helper(); assert(!ok, "") }
1719
1720         // Simple tests.
1721         assert(shouldTest("// +build linux", "linux", "arm"))
1722         assert(shouldTest("// +build !windows", "linux", "arm"))
1723         assertNot(shouldTest("// +build !windows", "windows", "amd64"))
1724
1725         // A file with no build tags will always be tested.
1726         assert(shouldTest("// This is a test.", "os", "arch"))
1727
1728         // Build tags separated by a space are OR-ed together.
1729         assertNot(shouldTest("// +build arm 386", "linux", "amd64"))
1730
1731         // Build tags separated by a comma are AND-ed together.
1732         assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64"))
1733         assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386"))
1734
1735         // Build tags on multiple lines are AND-ed together.
1736         assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64"))
1737         assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64"))
1738
1739         // Test that (!a OR !b) matches anything.
1740         assert(shouldTest("// +build !windows !plan9", "windows", "amd64"))
1741 }
1742
1743 // overlayDir makes a minimal-overhead copy of srcRoot in which new files may be added.
1744 func overlayDir(dstRoot, srcRoot string) error {
1745         dstRoot = filepath.Clean(dstRoot)
1746         if err := os.MkdirAll(dstRoot, 0777); err != nil {
1747                 return err
1748         }
1749
1750         srcRoot, err := filepath.Abs(srcRoot)
1751         if err != nil {
1752                 return err
1753         }
1754
1755         return filepath.WalkDir(srcRoot, func(srcPath string, d fs.DirEntry, err error) error {
1756                 if err != nil || srcPath == srcRoot {
1757                         return err
1758                 }
1759
1760                 suffix := strings.TrimPrefix(srcPath, srcRoot)
1761                 for len(suffix) > 0 && suffix[0] == filepath.Separator {
1762                         suffix = suffix[1:]
1763                 }
1764                 dstPath := filepath.Join(dstRoot, suffix)
1765
1766                 var info fs.FileInfo
1767                 if d.Type()&os.ModeSymlink != 0 {
1768                         info, err = os.Stat(srcPath)
1769                 } else {
1770                         info, err = d.Info()
1771                 }
1772                 if err != nil {
1773                         return err
1774                 }
1775                 perm := info.Mode() & os.ModePerm
1776
1777                 // Always copy directories (don't symlink them).
1778                 // If we add a file in the overlay, we don't want to add it in the original.
1779                 if info.IsDir() {
1780                         return os.MkdirAll(dstPath, perm|0200)
1781                 }
1782
1783                 // If the OS supports symlinks, use them instead of copying bytes.
1784                 if err := os.Symlink(srcPath, dstPath); err == nil {
1785                         return nil
1786                 }
1787
1788                 // Otherwise, copy the bytes.
1789                 src, err := os.Open(srcPath)
1790                 if err != nil {
1791                         return err
1792                 }
1793                 defer src.Close()
1794
1795                 dst, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
1796                 if err != nil {
1797                         return err
1798                 }
1799
1800                 _, err = io.Copy(dst, src)
1801                 if closeErr := dst.Close(); err == nil {
1802                         err = closeErr
1803                 }
1804                 return err
1805         })
1806 }
1807
1808 // The following sets of files are excluded from testing depending on configuration.
1809 // The types2Failures(32Bit) files pass with the 1.17 compiler but don't pass with
1810 // the 1.18 compiler using the new types2 type checker, or pass with sub-optimal
1811 // error(s).
1812
1813 // List of files that the compiler cannot errorcheck with the new typechecker (types2).
1814 var types2Failures = setOf(
1815         "shift1.go",               // types2 reports two new errors which are probably not right
1816         "fixedbugs/issue10700.go", // types2 should give hint about ptr to interface
1817         "fixedbugs/issue18331.go", // missing error about misuse of //go:noescape (irgen needs code from noder)
1818         "fixedbugs/issue18419.go", // types2 reports no field or method member, but should say unexported
1819         "fixedbugs/issue20233.go", // types2 reports two instead of one error (preference: 1.17 compiler)
1820         "fixedbugs/issue20245.go", // types2 reports two instead of one error (preference: 1.17 compiler)
1821         "fixedbugs/issue31053.go", // types2 reports "unknown field" instead of "cannot refer to unexported field"
1822         "fixedbugs/notinheap.go",  // types2 doesn't report errors about conversions that are invalid due to //go:notinheap
1823 )
1824
1825 var types2Failures32Bit = setOf(
1826         "printbig.go",             // large untyped int passed to print (32-bit)
1827         "fixedbugs/bug114.go",     // large untyped int passed to println (32-bit)
1828         "fixedbugs/issue23305.go", // large untyped int passed to println (32-bit)
1829 )
1830
1831 // In all of these cases, the 1.17 compiler reports reasonable errors, but either the
1832 // 1.17 or 1.18 compiler report extra errors, so we can't match correctly on both. We
1833 // now set the patterns to match correctly on all the 1.18 errors.
1834 // This list remains here just as a reference and for comparison - these files all pass.
1835 var _ = setOf(
1836         "import1.go",      // types2 reports extra errors
1837         "initializerr.go", // types2 reports extra error
1838         "typecheck.go",    // types2 reports extra error at function call
1839
1840         "fixedbugs/bug176.go", // types2 reports all errors (pref: types2)
1841         "fixedbugs/bug195.go", // types2 reports slight different errors, and an extra error
1842         "fixedbugs/bug412.go", // types2 produces a follow-on error
1843
1844         "fixedbugs/issue11614.go", // types2 reports an extra error
1845         "fixedbugs/issue17038.go", // types2 doesn't report a follow-on error (pref: types2)
1846         "fixedbugs/issue23732.go", // types2 reports different (but ok) line numbers
1847         "fixedbugs/issue4510.go",  // types2 reports different (but ok) line numbers
1848         "fixedbugs/issue7525b.go", // types2 reports init cycle error on different line - ok otherwise
1849         "fixedbugs/issue7525c.go", // types2 reports init cycle error on different line - ok otherwise
1850         "fixedbugs/issue7525d.go", // types2 reports init cycle error on different line - ok otherwise
1851         "fixedbugs/issue7525e.go", // types2 reports init cycle error on different line - ok otherwise
1852         "fixedbugs/issue7525.go",  // types2 reports init cycle error on different line - ok otherwise
1853 )
1854
1855 func setOf(keys ...string) map[string]bool {
1856         m := make(map[string]bool, len(keys))
1857         for _, key := range keys {
1858                 m[key] = true
1859         }
1860         return m
1861 }
1862
1863 // splitQuoted splits the string s around each instance of one or more consecutive
1864 // white space characters while taking into account quotes and escaping, and
1865 // returns an array of substrings of s or an empty list if s contains only white space.
1866 // Single quotes and double quotes are recognized to prevent splitting within the
1867 // quoted region, and are removed from the resulting substrings. If a quote in s
1868 // isn't closed err will be set and r will have the unclosed argument as the
1869 // last element. The backslash is used for escaping.
1870 //
1871 // For example, the following string:
1872 //
1873 //      a b:"c d" 'e''f'  "g\""
1874 //
1875 // Would be parsed as:
1876 //
1877 //      []string{"a", "b:c d", "ef", `g"`}
1878 //
1879 // [copied from src/go/build/build.go]
1880 func splitQuoted(s string) (r []string, err error) {
1881         var args []string
1882         arg := make([]rune, len(s))
1883         escaped := false
1884         quoted := false
1885         quote := '\x00'
1886         i := 0
1887         for _, rune := range s {
1888                 switch {
1889                 case escaped:
1890                         escaped = false
1891                 case rune == '\\':
1892                         escaped = true
1893                         continue
1894                 case quote != '\x00':
1895                         if rune == quote {
1896                                 quote = '\x00'
1897                                 continue
1898                         }
1899                 case rune == '"' || rune == '\'':
1900                         quoted = true
1901                         quote = rune
1902                         continue
1903                 case unicode.IsSpace(rune):
1904                         if quoted || i > 0 {
1905                                 quoted = false
1906                                 args = append(args, string(arg[:i]))
1907                                 i = 0
1908                         }
1909                         continue
1910                 }
1911                 arg[i] = rune
1912                 i++
1913         }
1914         if quoted || i > 0 {
1915                 args = append(args, string(arg[:i]))
1916         }
1917         if quote != 0 {
1918                 err = errors.New("unclosed quote")
1919         } else if escaped {
1920                 err = errors.New("unfinished escaping")
1921         }
1922         return args, err
1923 }