1// Copyright 2018 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// Script-driven tests.
6// See testdata/script/README for an overview.
7
8package main_test
9
10import (
11	"bytes"
12	"context"
13	"errors"
14	"fmt"
15	"go/build"
16	"internal/testenv"
17	"io/fs"
18	"os"
19	"os/exec"
20	"path/filepath"
21	"regexp"
22	"runtime"
23	"strconv"
24	"strings"
25	"sync"
26	"testing"
27	"time"
28
29	"cmd/go/internal/cfg"
30	"cmd/go/internal/imports"
31	"cmd/go/internal/par"
32	"cmd/go/internal/robustio"
33	"cmd/go/internal/txtar"
34	"cmd/go/internal/work"
35	"cmd/internal/objabi"
36	"cmd/internal/sys"
37)
38
39// TestScript runs the tests in testdata/script/*.txt.
40func TestScript(t *testing.T) {
41	testenv.MustHaveGoBuild(t)
42	testenv.SkipIfShortAndSlow(t)
43
44	files, err := filepath.Glob("testdata/script/*.txt")
45	if err != nil {
46		t.Fatal(err)
47	}
48	for _, file := range files {
49		file := file
50		name := strings.TrimSuffix(filepath.Base(file), ".txt")
51		t.Run(name, func(t *testing.T) {
52			t.Parallel()
53			ts := &testScript{t: t, name: name, file: file}
54			ts.setup()
55			if !*testWork {
56				defer removeAll(ts.workdir)
57			}
58			ts.run()
59		})
60	}
61}
62
63// A testScript holds execution state for a single test script.
64type testScript struct {
65	t          *testing.T
66	workdir    string            // temporary work dir ($WORK)
67	log        bytes.Buffer      // test execution log (printed at end of test)
68	mark       int               // offset of next log truncation
69	cd         string            // current directory during test execution; initially $WORK/gopath/src
70	name       string            // short name of test ("foo")
71	file       string            // full file name ("testdata/script/foo.txt")
72	lineno     int               // line number currently executing
73	line       string            // line currently executing
74	env        []string          // environment list (for os/exec)
75	envMap     map[string]string // environment mapping (matches env)
76	stdout     string            // standard output from last 'go' command; for 'stdout' command
77	stderr     string            // standard error from last 'go' command; for 'stderr' command
78	stopped    bool              // test wants to stop early
79	start      time.Time         // time phase started
80	background []*backgroundCmd  // backgrounded 'exec' and 'go' commands
81}
82
83type backgroundCmd struct {
84	want           simpleStatus
85	args           []string
86	cancel         context.CancelFunc
87	done           <-chan struct{}
88	err            error
89	stdout, stderr strings.Builder
90}
91
92type simpleStatus string
93
94const (
95	success          simpleStatus = ""
96	failure          simpleStatus = "!"
97	successOrFailure simpleStatus = "?"
98)
99
100var extraEnvKeys = []string{
101	"SYSTEMROOT",         // must be preserved on Windows to find DLLs; golang.org/issue/25210
102	"WINDIR",             // must be preserved on Windows to be able to run PowerShell command; golang.org/issue/30711
103	"LD_LIBRARY_PATH",    // must be preserved on Unix systems to find shared libraries
104	"CC",                 // don't lose user settings when invoking cgo
105	"GO_TESTING_GOTOOLS", // for gccgo testing
106	"GCCGO",              // for gccgo testing
107	"GCCGOTOOLDIR",       // for gccgo testing
108}
109
110// setup sets up the test execution temporary directory and environment.
111func (ts *testScript) setup() {
112	StartProxy()
113	ts.workdir = filepath.Join(testTmpDir, "script-"+ts.name)
114	ts.check(os.MkdirAll(filepath.Join(ts.workdir, "tmp"), 0777))
115	ts.check(os.MkdirAll(filepath.Join(ts.workdir, "gopath/src"), 0777))
116	ts.cd = filepath.Join(ts.workdir, "gopath/src")
117	ts.env = []string{
118		"WORK=" + ts.workdir, // must be first for ts.abbrev
119		"PATH=" + testBin + string(filepath.ListSeparator) + os.Getenv("PATH"),
120		homeEnvName() + "=/no-home",
121		"CCACHE_DISABLE=1", // ccache breaks with non-existent HOME
122		"GOARCH=" + runtime.GOARCH,
123		"GOCACHE=" + testGOCACHE,
124		"GODEBUG=" + os.Getenv("GODEBUG"),
125		"GOEXE=" + cfg.ExeSuffix,
126		"GOEXPSTRING=" + objabi.Expstring()[2:],
127		"GOOS=" + runtime.GOOS,
128		"GOPATH=" + filepath.Join(ts.workdir, "gopath"),
129		"GOPROXY=" + proxyURL,
130		"GOPRIVATE=",
131		"GOROOT=" + testGOROOT,
132		"GOROOT_FINAL=" + os.Getenv("GOROOT_FINAL"), // causes spurious rebuilds and breaks the "stale" built-in if not propagated
133		"TESTGO_GOROOT=" + testGOROOT,
134		"GOSUMDB=" + testSumDBVerifierKey,
135		"GONOPROXY=",
136		"GONOSUMDB=",
137		"GOVCS=*:all",
138		"PWD=" + ts.cd,
139		tempEnvName() + "=" + filepath.Join(ts.workdir, "tmp"),
140		"devnull=" + os.DevNull,
141		"goversion=" + goVersion(ts),
142		":=" + string(os.PathListSeparator),
143	}
144	if !testenv.HasExternalNetwork() {
145		ts.env = append(ts.env, "TESTGONETWORK=panic", "TESTGOVCS=panic")
146	}
147
148	if runtime.GOOS == "plan9" {
149		ts.env = append(ts.env, "path="+testBin+string(filepath.ListSeparator)+os.Getenv("path"))
150	}
151
152	for _, key := range extraEnvKeys {
153		if val := os.Getenv(key); val != "" {
154			ts.env = append(ts.env, key+"="+val)
155		}
156	}
157
158	ts.envMap = make(map[string]string)
159	for _, kv := range ts.env {
160		if i := strings.Index(kv, "="); i >= 0 {
161			ts.envMap[kv[:i]] = kv[i+1:]
162		}
163	}
164}
165
166// goVersion returns the current Go version.
167func goVersion(ts *testScript) string {
168	tags := build.Default.ReleaseTags
169	version := tags[len(tags)-1]
170	if !regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`).MatchString(version) {
171		ts.fatalf("invalid go version %q", version)
172	}
173	return version[2:]
174}
175
176var execCache par.Cache
177
178// run runs the test script.
179func (ts *testScript) run() {
180	// Truncate log at end of last phase marker,
181	// discarding details of successful phase.
182	rewind := func() {
183		if !testing.Verbose() {
184			ts.log.Truncate(ts.mark)
185		}
186	}
187
188	// Insert elapsed time for phase at end of phase marker
189	markTime := func() {
190		if ts.mark > 0 && !ts.start.IsZero() {
191			afterMark := append([]byte{}, ts.log.Bytes()[ts.mark:]...)
192			ts.log.Truncate(ts.mark - 1) // cut \n and afterMark
193			fmt.Fprintf(&ts.log, " (%.3fs)\n", time.Since(ts.start).Seconds())
194			ts.log.Write(afterMark)
195		}
196		ts.start = time.Time{}
197	}
198
199	defer func() {
200		// On a normal exit from the test loop, background processes are cleaned up
201		// before we print PASS. If we return early (e.g., due to a test failure),
202		// don't print anything about the processes that were still running.
203		for _, bg := range ts.background {
204			bg.cancel()
205		}
206		for _, bg := range ts.background {
207			<-bg.done
208		}
209		ts.background = nil
210
211		markTime()
212		// Flush testScript log to testing.T log.
213		ts.t.Log("\n" + ts.abbrev(ts.log.String()))
214	}()
215
216	// Unpack archive.
217	a, err := txtar.ParseFile(ts.file)
218	ts.check(err)
219	for _, f := range a.Files {
220		name := ts.mkabs(ts.expand(f.Name, false))
221		ts.check(os.MkdirAll(filepath.Dir(name), 0777))
222		ts.check(os.WriteFile(name, f.Data, 0666))
223	}
224
225	// With -v or -testwork, start log with full environment.
226	if *testWork || testing.Verbose() {
227		// Display environment.
228		ts.cmdEnv(success, nil)
229		fmt.Fprintf(&ts.log, "\n")
230		ts.mark = ts.log.Len()
231	}
232
233	// Run script.
234	// See testdata/script/README for documentation of script form.
235	script := string(a.Comment)
236Script:
237	for script != "" {
238		// Extract next line.
239		ts.lineno++
240		var line string
241		if i := strings.Index(script, "\n"); i >= 0 {
242			line, script = script[:i], script[i+1:]
243		} else {
244			line, script = script, ""
245		}
246
247		// # is a comment indicating the start of new phase.
248		if strings.HasPrefix(line, "#") {
249			// If there was a previous phase, it succeeded,
250			// so rewind the log to delete its details (unless -v is in use).
251			// If nothing has happened at all since the mark,
252			// rewinding is a no-op and adding elapsed time
253			// for doing nothing is meaningless, so don't.
254			if ts.log.Len() > ts.mark {
255				rewind()
256				markTime()
257			}
258			// Print phase heading and mark start of phase output.
259			fmt.Fprintf(&ts.log, "%s\n", line)
260			ts.mark = ts.log.Len()
261			ts.start = time.Now()
262			continue
263		}
264
265		// Parse input line. Ignore blanks entirely.
266		parsed := ts.parse(line)
267		if parsed.name == "" {
268			if parsed.want != "" || len(parsed.conds) > 0 {
269				ts.fatalf("missing command")
270			}
271			continue
272		}
273
274		// Echo command to log.
275		fmt.Fprintf(&ts.log, "> %s\n", line)
276
277		for _, cond := range parsed.conds {
278			// Known conds are: $GOOS, $GOARCH, runtime.Compiler, and 'short' (for testing.Short).
279			//
280			// NOTE: If you make changes here, update testdata/script/README too!
281			//
282			ok := false
283			switch cond.tag {
284			case runtime.GOOS, runtime.GOARCH, runtime.Compiler:
285				ok = true
286			case "short":
287				ok = testing.Short()
288			case "cgo":
289				ok = canCgo
290			case "msan":
291				ok = canMSan
292			case "race":
293				ok = canRace
294			case "net":
295				ok = testenv.HasExternalNetwork()
296			case "link":
297				ok = testenv.HasLink()
298			case "root":
299				ok = os.Geteuid() == 0
300			case "symlink":
301				ok = testenv.HasSymlink()
302			case "case-sensitive":
303				ok = isCaseSensitive(ts.t)
304			default:
305				if strings.HasPrefix(cond.tag, "exec:") {
306					prog := cond.tag[len("exec:"):]
307					ok = execCache.Do(prog, func() interface{} {
308						if runtime.GOOS == "plan9" && prog == "git" {
309							// The Git command is usually not the real Git on Plan 9.
310							// See https://golang.org/issues/29640.
311							return false
312						}
313						_, err := exec.LookPath(prog)
314						return err == nil
315					}).(bool)
316					break
317				}
318				if strings.HasPrefix(cond.tag, "GODEBUG:") {
319					value := strings.TrimPrefix(cond.tag, "GODEBUG:")
320					parts := strings.Split(os.Getenv("GODEBUG"), ",")
321					for _, p := range parts {
322						if strings.TrimSpace(p) == value {
323							ok = true
324							break
325						}
326					}
327					break
328				}
329				if strings.HasPrefix(cond.tag, "buildmode:") {
330					value := strings.TrimPrefix(cond.tag, "buildmode:")
331					ok = sys.BuildModeSupported(runtime.Compiler, value, runtime.GOOS, runtime.GOARCH)
332					break
333				}
334				if !imports.KnownArch[cond.tag] && !imports.KnownOS[cond.tag] && cond.tag != "gc" && cond.tag != "gccgo" {
335					ts.fatalf("unknown condition %q", cond.tag)
336				}
337			}
338			if ok != cond.want {
339				// Don't run rest of line.
340				continue Script
341			}
342		}
343
344		// Run command.
345		cmd := scriptCmds[parsed.name]
346		if cmd == nil {
347			ts.fatalf("unknown command %q", parsed.name)
348		}
349		cmd(ts, parsed.want, parsed.args)
350
351		// Command can ask script to stop early.
352		if ts.stopped {
353			// Break instead of returning, so that we check the status of any
354			// background processes and print PASS.
355			break
356		}
357	}
358
359	for _, bg := range ts.background {
360		bg.cancel()
361	}
362	ts.cmdWait(success, nil)
363
364	// Final phase ended.
365	rewind()
366	markTime()
367	if !ts.stopped {
368		fmt.Fprintf(&ts.log, "PASS\n")
369	}
370}
371
372var (
373	onceCaseSensitive sync.Once
374	caseSensitive     bool
375)
376
377func isCaseSensitive(t *testing.T) bool {
378	onceCaseSensitive.Do(func() {
379		tmpdir, err := os.MkdirTemp("", "case-sensitive")
380		if err != nil {
381			t.Fatal("failed to create directory to determine case-sensitivity:", err)
382		}
383		defer os.RemoveAll(tmpdir)
384
385		fcap := filepath.Join(tmpdir, "FILE")
386		if err := os.WriteFile(fcap, []byte{}, 0644); err != nil {
387			t.Fatal("error writing file to determine case-sensitivity:", err)
388		}
389
390		flow := filepath.Join(tmpdir, "file")
391		_, err = os.ReadFile(flow)
392		switch {
393		case err == nil:
394			caseSensitive = false
395			return
396		case os.IsNotExist(err):
397			caseSensitive = true
398			return
399		default:
400			t.Fatal("unexpected error reading file when determining case-sensitivity:", err)
401		}
402	})
403
404	return caseSensitive
405}
406
407// scriptCmds are the script command implementations.
408// Keep list and the implementations below sorted by name.
409//
410// NOTE: If you make changes here, update testdata/script/README too!
411//
412var scriptCmds = map[string]func(*testScript, simpleStatus, []string){
413	"addcrlf": (*testScript).cmdAddcrlf,
414	"cc":      (*testScript).cmdCc,
415	"cd":      (*testScript).cmdCd,
416	"chmod":   (*testScript).cmdChmod,
417	"cmp":     (*testScript).cmdCmp,
418	"cmpenv":  (*testScript).cmdCmpenv,
419	"cp":      (*testScript).cmdCp,
420	"env":     (*testScript).cmdEnv,
421	"exec":    (*testScript).cmdExec,
422	"exists":  (*testScript).cmdExists,
423	"go":      (*testScript).cmdGo,
424	"grep":    (*testScript).cmdGrep,
425	"mkdir":   (*testScript).cmdMkdir,
426	"rm":      (*testScript).cmdRm,
427	"skip":    (*testScript).cmdSkip,
428	"stale":   (*testScript).cmdStale,
429	"stderr":  (*testScript).cmdStderr,
430	"stdout":  (*testScript).cmdStdout,
431	"stop":    (*testScript).cmdStop,
432	"symlink": (*testScript).cmdSymlink,
433	"wait":    (*testScript).cmdWait,
434}
435
436// When expanding shell variables for these commands, we apply regexp quoting to
437// expanded strings within the first argument.
438var regexpCmd = map[string]bool{
439	"grep":   true,
440	"stderr": true,
441	"stdout": true,
442}
443
444// addcrlf adds CRLF line endings to the named files.
445func (ts *testScript) cmdAddcrlf(want simpleStatus, args []string) {
446	if len(args) == 0 {
447		ts.fatalf("usage: addcrlf file...")
448	}
449
450	for _, file := range args {
451		file = ts.mkabs(file)
452		data, err := os.ReadFile(file)
453		ts.check(err)
454		ts.check(os.WriteFile(file, bytes.ReplaceAll(data, []byte("\n"), []byte("\r\n")), 0666))
455	}
456}
457
458// cc runs the C compiler along with platform specific options.
459func (ts *testScript) cmdCc(want simpleStatus, args []string) {
460	if len(args) < 1 || (len(args) == 1 && args[0] == "&") {
461		ts.fatalf("usage: cc args... [&]")
462	}
463
464	var b work.Builder
465	b.Init()
466	ts.cmdExec(want, append(b.GccCmd(".", ""), args...))
467	robustio.RemoveAll(b.WorkDir)
468}
469
470// cd changes to a different directory.
471func (ts *testScript) cmdCd(want simpleStatus, args []string) {
472	if want != success {
473		ts.fatalf("unsupported: %v cd", want)
474	}
475	if len(args) != 1 {
476		ts.fatalf("usage: cd dir")
477	}
478
479	dir := args[0]
480	if !filepath.IsAbs(dir) {
481		dir = filepath.Join(ts.cd, dir)
482	}
483	info, err := os.Stat(dir)
484	if os.IsNotExist(err) {
485		ts.fatalf("directory %s does not exist", dir)
486	}
487	ts.check(err)
488	if !info.IsDir() {
489		ts.fatalf("%s is not a directory", dir)
490	}
491	ts.cd = dir
492	ts.envMap["PWD"] = dir
493	fmt.Fprintf(&ts.log, "%s\n", ts.cd)
494}
495
496// chmod changes permissions for a file or directory.
497func (ts *testScript) cmdChmod(want simpleStatus, args []string) {
498	if want != success {
499		ts.fatalf("unsupported: %v chmod", want)
500	}
501	if len(args) < 2 {
502		ts.fatalf("usage: chmod perm paths...")
503	}
504	perm, err := strconv.ParseUint(args[0], 0, 32)
505	if err != nil || perm&uint64(fs.ModePerm) != perm {
506		ts.fatalf("invalid mode: %s", args[0])
507	}
508	for _, arg := range args[1:] {
509		path := arg
510		if !filepath.IsAbs(path) {
511			path = filepath.Join(ts.cd, arg)
512		}
513		err := os.Chmod(path, fs.FileMode(perm))
514		ts.check(err)
515	}
516}
517
518// cmp compares two files.
519func (ts *testScript) cmdCmp(want simpleStatus, args []string) {
520	if want != success {
521		// It would be strange to say "this file can have any content except this precise byte sequence".
522		ts.fatalf("unsupported: %v cmp", want)
523	}
524	quiet := false
525	if len(args) > 0 && args[0] == "-q" {
526		quiet = true
527		args = args[1:]
528	}
529	if len(args) != 2 {
530		ts.fatalf("usage: cmp file1 file2")
531	}
532	ts.doCmdCmp(args, false, quiet)
533}
534
535// cmpenv compares two files with environment variable substitution.
536func (ts *testScript) cmdCmpenv(want simpleStatus, args []string) {
537	if want != success {
538		ts.fatalf("unsupported: %v cmpenv", want)
539	}
540	quiet := false
541	if len(args) > 0 && args[0] == "-q" {
542		quiet = true
543		args = args[1:]
544	}
545	if len(args) != 2 {
546		ts.fatalf("usage: cmpenv file1 file2")
547	}
548	ts.doCmdCmp(args, true, quiet)
549}
550
551func (ts *testScript) doCmdCmp(args []string, env, quiet bool) {
552	name1, name2 := args[0], args[1]
553	var text1, text2 string
554	if name1 == "stdout" {
555		text1 = ts.stdout
556	} else if name1 == "stderr" {
557		text1 = ts.stderr
558	} else {
559		data, err := os.ReadFile(ts.mkabs(name1))
560		ts.check(err)
561		text1 = string(data)
562	}
563
564	data, err := os.ReadFile(ts.mkabs(name2))
565	ts.check(err)
566	text2 = string(data)
567
568	if env {
569		text1 = ts.expand(text1, false)
570		text2 = ts.expand(text2, false)
571	}
572
573	if text1 == text2 {
574		return
575	}
576
577	if !quiet {
578		fmt.Fprintf(&ts.log, "[diff -%s +%s]\n%s\n", name1, name2, diff(text1, text2))
579	}
580	ts.fatalf("%s and %s differ", name1, name2)
581}
582
583// cp copies files, maybe eventually directories.
584func (ts *testScript) cmdCp(want simpleStatus, args []string) {
585	if len(args) < 2 {
586		ts.fatalf("usage: cp src... dst")
587	}
588
589	dst := ts.mkabs(args[len(args)-1])
590	info, err := os.Stat(dst)
591	dstDir := err == nil && info.IsDir()
592	if len(args) > 2 && !dstDir {
593		ts.fatalf("cp: destination %s is not a directory", dst)
594	}
595
596	for _, arg := range args[:len(args)-1] {
597		var (
598			src  string
599			data []byte
600			mode fs.FileMode
601		)
602		switch arg {
603		case "stdout":
604			src = arg
605			data = []byte(ts.stdout)
606			mode = 0666
607		case "stderr":
608			src = arg
609			data = []byte(ts.stderr)
610			mode = 0666
611		default:
612			src = ts.mkabs(arg)
613			info, err := os.Stat(src)
614			ts.check(err)
615			mode = info.Mode() & 0777
616			data, err = os.ReadFile(src)
617			ts.check(err)
618		}
619		targ := dst
620		if dstDir {
621			targ = filepath.Join(dst, filepath.Base(src))
622		}
623		err := os.WriteFile(targ, data, mode)
624		switch want {
625		case failure:
626			if err == nil {
627				ts.fatalf("unexpected command success")
628			}
629		case success:
630			ts.check(err)
631		}
632	}
633}
634
635// env displays or adds to the environment.
636func (ts *testScript) cmdEnv(want simpleStatus, args []string) {
637	if want != success {
638		ts.fatalf("unsupported: %v env", want)
639	}
640
641	conv := func(s string) string { return s }
642	if len(args) > 0 && args[0] == "-r" {
643		conv = regexp.QuoteMeta
644		args = args[1:]
645	}
646
647	var out strings.Builder
648	if len(args) == 0 {
649		printed := make(map[string]bool) // env list can have duplicates; only print effective value (from envMap) once
650		for _, kv := range ts.env {
651			k := kv[:strings.Index(kv, "=")]
652			if !printed[k] {
653				fmt.Fprintf(&out, "%s=%s\n", k, ts.envMap[k])
654			}
655		}
656	} else {
657		for _, env := range args {
658			i := strings.Index(env, "=")
659			if i < 0 {
660				// Display value instead of setting it.
661				fmt.Fprintf(&out, "%s=%s\n", env, ts.envMap[env])
662				continue
663			}
664			key, val := env[:i], conv(env[i+1:])
665			ts.env = append(ts.env, key+"="+val)
666			ts.envMap[key] = val
667		}
668	}
669	if out.Len() > 0 || len(args) > 0 {
670		ts.stdout = out.String()
671		ts.log.WriteString(out.String())
672	}
673}
674
675// exec runs the given command.
676func (ts *testScript) cmdExec(want simpleStatus, args []string) {
677	if len(args) < 1 || (len(args) == 1 && args[0] == "&") {
678		ts.fatalf("usage: exec program [args...] [&]")
679	}
680
681	background := false
682	if len(args) > 0 && args[len(args)-1] == "&" {
683		background = true
684		args = args[:len(args)-1]
685	}
686
687	bg, err := ts.startBackground(want, args[0], args[1:]...)
688	if err != nil {
689		ts.fatalf("unexpected error starting command: %v", err)
690	}
691	if background {
692		ts.stdout, ts.stderr = "", ""
693		ts.background = append(ts.background, bg)
694		return
695	}
696
697	<-bg.done
698	ts.stdout = bg.stdout.String()
699	ts.stderr = bg.stderr.String()
700	if ts.stdout != "" {
701		fmt.Fprintf(&ts.log, "[stdout]\n%s", ts.stdout)
702	}
703	if ts.stderr != "" {
704		fmt.Fprintf(&ts.log, "[stderr]\n%s", ts.stderr)
705	}
706	if bg.err != nil {
707		fmt.Fprintf(&ts.log, "[%v]\n", bg.err)
708	}
709	ts.checkCmd(bg)
710}
711
712// exists checks that the list of files exists.
713func (ts *testScript) cmdExists(want simpleStatus, args []string) {
714	if want == successOrFailure {
715		ts.fatalf("unsupported: %v exists", want)
716	}
717	var readonly, exec bool
718loop:
719	for len(args) > 0 {
720		switch args[0] {
721		case "-readonly":
722			readonly = true
723			args = args[1:]
724		case "-exec":
725			exec = true
726			args = args[1:]
727		default:
728			break loop
729		}
730	}
731	if len(args) == 0 {
732		ts.fatalf("usage: exists [-readonly] [-exec] file...")
733	}
734
735	for _, file := range args {
736		file = ts.mkabs(file)
737		info, err := os.Stat(file)
738		if err == nil && want == failure {
739			what := "file"
740			if info.IsDir() {
741				what = "directory"
742			}
743			ts.fatalf("%s %s unexpectedly exists", what, file)
744		}
745		if err != nil && want == success {
746			ts.fatalf("%s does not exist", file)
747		}
748		if err == nil && want == success && readonly && info.Mode()&0222 != 0 {
749			ts.fatalf("%s exists but is writable", file)
750		}
751		if err == nil && want == success && exec && runtime.GOOS != "windows" && info.Mode()&0111 == 0 {
752			ts.fatalf("%s exists but is not executable", file)
753		}
754	}
755}
756
757// go runs the go command.
758func (ts *testScript) cmdGo(want simpleStatus, args []string) {
759	ts.cmdExec(want, append([]string{testGo}, args...))
760}
761
762// mkdir creates directories.
763func (ts *testScript) cmdMkdir(want simpleStatus, args []string) {
764	if want != success {
765		ts.fatalf("unsupported: %v mkdir", want)
766	}
767	if len(args) < 1 {
768		ts.fatalf("usage: mkdir dir...")
769	}
770	for _, arg := range args {
771		ts.check(os.MkdirAll(ts.mkabs(arg), 0777))
772	}
773}
774
775// rm removes files or directories.
776func (ts *testScript) cmdRm(want simpleStatus, args []string) {
777	if want != success {
778		ts.fatalf("unsupported: %v rm", want)
779	}
780	if len(args) < 1 {
781		ts.fatalf("usage: rm file...")
782	}
783	for _, arg := range args {
784		file := ts.mkabs(arg)
785		removeAll(file)                    // does chmod and then attempts rm
786		ts.check(robustio.RemoveAll(file)) // report error
787	}
788}
789
790// skip marks the test skipped.
791func (ts *testScript) cmdSkip(want simpleStatus, args []string) {
792	if len(args) > 1 {
793		ts.fatalf("usage: skip [msg]")
794	}
795	if want != success {
796		ts.fatalf("unsupported: %v skip", want)
797	}
798
799	// Before we mark the test as skipped, shut down any background processes and
800	// make sure they have returned the correct status.
801	for _, bg := range ts.background {
802		bg.cancel()
803	}
804	ts.cmdWait(success, nil)
805
806	if len(args) == 1 {
807		ts.t.Skip(args[0])
808	}
809	ts.t.Skip()
810}
811
812// stale checks that the named build targets are stale.
813func (ts *testScript) cmdStale(want simpleStatus, args []string) {
814	if len(args) == 0 {
815		ts.fatalf("usage: stale target...")
816	}
817	tmpl := "{{if .Error}}{{.ImportPath}}: {{.Error.Err}}{{else}}"
818	switch want {
819	case failure:
820		tmpl += "{{if .Stale}}{{.ImportPath}} is unexpectedly stale{{end}}"
821	case success:
822		tmpl += "{{if not .Stale}}{{.ImportPath}} is unexpectedly NOT stale{{end}}"
823	default:
824		ts.fatalf("unsupported: %v stale", want)
825	}
826	tmpl += "{{end}}"
827	goArgs := append([]string{"list", "-e", "-f=" + tmpl}, args...)
828	stdout, stderr, err := ts.exec(testGo, goArgs...)
829	if err != nil {
830		ts.fatalf("go list: %v\n%s%s", err, stdout, stderr)
831	}
832	if stdout != "" {
833		ts.fatalf("%s", stdout)
834	}
835}
836
837// stdout checks that the last go command standard output matches a regexp.
838func (ts *testScript) cmdStdout(want simpleStatus, args []string) {
839	scriptMatch(ts, want, args, ts.stdout, "stdout")
840}
841
842// stderr checks that the last go command standard output matches a regexp.
843func (ts *testScript) cmdStderr(want simpleStatus, args []string) {
844	scriptMatch(ts, want, args, ts.stderr, "stderr")
845}
846
847// grep checks that file content matches a regexp.
848// Like stdout/stderr and unlike Unix grep, it accepts Go regexp syntax.
849func (ts *testScript) cmdGrep(want simpleStatus, args []string) {
850	scriptMatch(ts, want, args, "", "grep")
851}
852
853// scriptMatch implements both stdout and stderr.
854func scriptMatch(ts *testScript, want simpleStatus, args []string, text, name string) {
855	if want == successOrFailure {
856		ts.fatalf("unsupported: %v %s", want, name)
857	}
858
859	n := 0
860	if len(args) >= 1 && strings.HasPrefix(args[0], "-count=") {
861		if want == failure {
862			ts.fatalf("cannot use -count= with negated match")
863		}
864		var err error
865		n, err = strconv.Atoi(args[0][len("-count="):])
866		if err != nil {
867			ts.fatalf("bad -count=: %v", err)
868		}
869		if n < 1 {
870			ts.fatalf("bad -count=: must be at least 1")
871		}
872		args = args[1:]
873	}
874	quiet := false
875	if len(args) >= 1 && args[0] == "-q" {
876		quiet = true
877		args = args[1:]
878	}
879
880	extraUsage := ""
881	wantArgs := 1
882	if name == "grep" {
883		extraUsage = " file"
884		wantArgs = 2
885	}
886	if len(args) != wantArgs {
887		ts.fatalf("usage: %s [-count=N] 'pattern'%s", name, extraUsage)
888	}
889
890	pattern := `(?m)` + args[0]
891	re, err := regexp.Compile(pattern)
892	if err != nil {
893		ts.fatalf("regexp.Compile(%q): %v", pattern, err)
894	}
895
896	isGrep := name == "grep"
897	if isGrep {
898		name = args[1] // for error messages
899		data, err := os.ReadFile(ts.mkabs(args[1]))
900		ts.check(err)
901		text = string(data)
902	}
903
904	// Matching against workdir would be misleading.
905	text = strings.ReplaceAll(text, ts.workdir, "$WORK")
906
907	switch want {
908	case failure:
909		if re.MatchString(text) {
910			if isGrep && !quiet {
911				fmt.Fprintf(&ts.log, "[%s]\n%s\n", name, text)
912			}
913			ts.fatalf("unexpected match for %#q found in %s: %s", pattern, name, re.FindString(text))
914		}
915
916	case success:
917		if !re.MatchString(text) {
918			if isGrep && !quiet {
919				fmt.Fprintf(&ts.log, "[%s]\n%s\n", name, text)
920			}
921			ts.fatalf("no match for %#q found in %s", pattern, name)
922		}
923		if n > 0 {
924			count := len(re.FindAllString(text, -1))
925			if count != n {
926				if isGrep && !quiet {
927					fmt.Fprintf(&ts.log, "[%s]\n%s\n", name, text)
928				}
929				ts.fatalf("have %d matches for %#q, want %d", count, pattern, n)
930			}
931		}
932	}
933}
934
935// stop stops execution of the test (marking it passed).
936func (ts *testScript) cmdStop(want simpleStatus, args []string) {
937	if want != success {
938		ts.fatalf("unsupported: %v stop", want)
939	}
940	if len(args) > 1 {
941		ts.fatalf("usage: stop [msg]")
942	}
943	if len(args) == 1 {
944		fmt.Fprintf(&ts.log, "stop: %s\n", args[0])
945	} else {
946		fmt.Fprintf(&ts.log, "stop\n")
947	}
948	ts.stopped = true
949}
950
951// symlink creates a symbolic link.
952func (ts *testScript) cmdSymlink(want simpleStatus, args []string) {
953	if want != success {
954		ts.fatalf("unsupported: %v symlink", want)
955	}
956	if len(args) != 3 || args[1] != "->" {
957		ts.fatalf("usage: symlink file -> target")
958	}
959	// Note that the link target args[2] is not interpreted with mkabs:
960	// it will be interpreted relative to the directory file is in.
961	ts.check(os.Symlink(args[2], ts.mkabs(args[0])))
962}
963
964// wait waits for background commands to exit, setting stderr and stdout to their result.
965func (ts *testScript) cmdWait(want simpleStatus, args []string) {
966	if want != success {
967		ts.fatalf("unsupported: %v wait", want)
968	}
969	if len(args) > 0 {
970		ts.fatalf("usage: wait")
971	}
972
973	var stdouts, stderrs []string
974	for _, bg := range ts.background {
975		<-bg.done
976
977		args := append([]string{filepath.Base(bg.args[0])}, bg.args[1:]...)
978		fmt.Fprintf(&ts.log, "[background] %s: %v\n", strings.Join(args, " "), bg.err)
979
980		cmdStdout := bg.stdout.String()
981		if cmdStdout != "" {
982			fmt.Fprintf(&ts.log, "[stdout]\n%s", cmdStdout)
983			stdouts = append(stdouts, cmdStdout)
984		}
985
986		cmdStderr := bg.stderr.String()
987		if cmdStderr != "" {
988			fmt.Fprintf(&ts.log, "[stderr]\n%s", cmdStderr)
989			stderrs = append(stderrs, cmdStderr)
990		}
991
992		ts.checkCmd(bg)
993	}
994
995	ts.stdout = strings.Join(stdouts, "")
996	ts.stderr = strings.Join(stderrs, "")
997	ts.background = nil
998}
999
1000// Helpers for command implementations.
1001
1002// abbrev abbreviates the actual work directory in the string s to the literal string "$WORK".
1003func (ts *testScript) abbrev(s string) string {
1004	s = strings.ReplaceAll(s, ts.workdir, "$WORK")
1005	if *testWork {
1006		// Expose actual $WORK value in environment dump on first line of work script,
1007		// so that the user can find out what directory -testwork left behind.
1008		s = "WORK=" + ts.workdir + "\n" + strings.TrimPrefix(s, "WORK=$WORK\n")
1009	}
1010	return s
1011}
1012
1013// check calls ts.fatalf if err != nil.
1014func (ts *testScript) check(err error) {
1015	if err != nil {
1016		ts.fatalf("%v", err)
1017	}
1018}
1019
1020func (ts *testScript) checkCmd(bg *backgroundCmd) {
1021	select {
1022	case <-bg.done:
1023	default:
1024		panic("checkCmd called when not done")
1025	}
1026
1027	if bg.err == nil {
1028		if bg.want == failure {
1029			ts.fatalf("unexpected command success")
1030		}
1031		return
1032	}
1033
1034	if errors.Is(bg.err, context.DeadlineExceeded) {
1035		ts.fatalf("test timed out while running command")
1036	}
1037
1038	if errors.Is(bg.err, context.Canceled) {
1039		// The process was still running at the end of the test.
1040		// The test must not depend on its exit status.
1041		if bg.want != successOrFailure {
1042			ts.fatalf("unexpected background command remaining at test end")
1043		}
1044		return
1045	}
1046
1047	if bg.want == success {
1048		ts.fatalf("unexpected command failure")
1049	}
1050}
1051
1052// exec runs the given command line (an actual subprocess, not simulated)
1053// in ts.cd with environment ts.env and then returns collected standard output and standard error.
1054func (ts *testScript) exec(command string, args ...string) (stdout, stderr string, err error) {
1055	bg, err := ts.startBackground(success, command, args...)
1056	if err != nil {
1057		return "", "", err
1058	}
1059	<-bg.done
1060	return bg.stdout.String(), bg.stderr.String(), bg.err
1061}
1062
1063// startBackground starts the given command line (an actual subprocess, not simulated)
1064// in ts.cd with environment ts.env.
1065func (ts *testScript) startBackground(want simpleStatus, command string, args ...string) (*backgroundCmd, error) {
1066	done := make(chan struct{})
1067	bg := &backgroundCmd{
1068		want:   want,
1069		args:   append([]string{command}, args...),
1070		done:   done,
1071		cancel: func() {},
1072	}
1073
1074	ctx := context.Background()
1075	gracePeriod := 100 * time.Millisecond
1076	if deadline, ok := ts.t.Deadline(); ok {
1077		timeout := time.Until(deadline)
1078		// If time allows, increase the termination grace period to 5% of the
1079		// remaining time.
1080		if gp := timeout / 20; gp > gracePeriod {
1081			gracePeriod = gp
1082		}
1083
1084		// Send the first termination signal with two grace periods remaining.
1085		// If it still hasn't finished after the first period has elapsed,
1086		// we'll escalate to os.Kill with a second period remaining until the
1087		// test deadline..
1088		timeout -= 2 * gracePeriod
1089
1090		if timeout <= 0 {
1091			// The test has less than the grace period remaining. There is no point in
1092			// even starting the command, because it will be terminated immediately.
1093			// Save the expense of starting it in the first place.
1094			bg.err = context.DeadlineExceeded
1095			close(done)
1096			return bg, nil
1097		}
1098
1099		ctx, bg.cancel = context.WithTimeout(ctx, timeout)
1100	}
1101
1102	cmd := exec.Command(command, args...)
1103	cmd.Dir = ts.cd
1104	cmd.Env = append(ts.env, "PWD="+ts.cd)
1105	cmd.Stdout = &bg.stdout
1106	cmd.Stderr = &bg.stderr
1107	if err := cmd.Start(); err != nil {
1108		bg.cancel()
1109		return nil, err
1110	}
1111
1112	go func() {
1113		bg.err = waitOrStop(ctx, cmd, stopSignal(), gracePeriod)
1114		close(done)
1115	}()
1116	return bg, nil
1117}
1118
1119// stopSignal returns the appropriate signal to use to request that a process
1120// stop execution.
1121func stopSignal() os.Signal {
1122	if runtime.GOOS == "windows" {
1123		// Per https://golang.org/pkg/os/#Signal, “Interrupt is not implemented on
1124		// Windows; using it with os.Process.Signal will return an error.”
1125		// Fall back to Kill instead.
1126		return os.Kill
1127	}
1128	return os.Interrupt
1129}
1130
1131// waitOrStop waits for the already-started command cmd by calling its Wait method.
1132//
1133// If cmd does not return before ctx is done, waitOrStop sends it the given interrupt signal.
1134// If killDelay is positive, waitOrStop waits that additional period for Wait to return before sending os.Kill.
1135//
1136// This function is copied from the one added to x/playground/internal in
1137// http://golang.org/cl/228438.
1138func waitOrStop(ctx context.Context, cmd *exec.Cmd, interrupt os.Signal, killDelay time.Duration) error {
1139	if cmd.Process == nil {
1140		panic("waitOrStop called with a nil cmd.Process — missing Start call?")
1141	}
1142	if interrupt == nil {
1143		panic("waitOrStop requires a non-nil interrupt signal")
1144	}
1145
1146	errc := make(chan error)
1147	go func() {
1148		select {
1149		case errc <- nil:
1150			return
1151		case <-ctx.Done():
1152		}
1153
1154		err := cmd.Process.Signal(interrupt)
1155		if err == nil {
1156			err = ctx.Err() // Report ctx.Err() as the reason we interrupted.
1157		} else if err.Error() == "os: process already finished" {
1158			errc <- nil
1159			return
1160		}
1161
1162		if killDelay > 0 {
1163			timer := time.NewTimer(killDelay)
1164			select {
1165			// Report ctx.Err() as the reason we interrupted the process...
1166			case errc <- ctx.Err():
1167				timer.Stop()
1168				return
1169			// ...but after killDelay has elapsed, fall back to a stronger signal.
1170			case <-timer.C:
1171			}
1172
1173			// Wait still hasn't returned.
1174			// Kill the process harder to make sure that it exits.
1175			//
1176			// Ignore any error: if cmd.Process has already terminated, we still
1177			// want to send ctx.Err() (or the error from the Interrupt call)
1178			// to properly attribute the signal that may have terminated it.
1179			_ = cmd.Process.Kill()
1180		}
1181
1182		errc <- err
1183	}()
1184
1185	waitErr := cmd.Wait()
1186	if interruptErr := <-errc; interruptErr != nil {
1187		return interruptErr
1188	}
1189	return waitErr
1190}
1191
1192// expand applies environment variable expansion to the string s.
1193func (ts *testScript) expand(s string, inRegexp bool) string {
1194	return os.Expand(s, func(key string) string {
1195		e := ts.envMap[key]
1196		if inRegexp {
1197			// Replace workdir with $WORK, since we have done the same substitution in
1198			// the text we're about to compare against.
1199			e = strings.ReplaceAll(e, ts.workdir, "$WORK")
1200
1201			// Quote to literal strings: we want paths like C:\work\go1.4 to remain
1202			// paths rather than regular expressions.
1203			e = regexp.QuoteMeta(e)
1204		}
1205		return e
1206	})
1207}
1208
1209// fatalf aborts the test with the given failure message.
1210func (ts *testScript) fatalf(format string, args ...interface{}) {
1211	fmt.Fprintf(&ts.log, "FAIL: %s:%d: %s\n", ts.file, ts.lineno, fmt.Sprintf(format, args...))
1212	ts.t.FailNow()
1213}
1214
1215// mkabs interprets file relative to the test script's current directory
1216// and returns the corresponding absolute path.
1217func (ts *testScript) mkabs(file string) string {
1218	if filepath.IsAbs(file) {
1219		return file
1220	}
1221	return filepath.Join(ts.cd, file)
1222}
1223
1224// A condition guards execution of a command.
1225type condition struct {
1226	want bool
1227	tag  string
1228}
1229
1230// A command is a complete command parsed from a script.
1231type command struct {
1232	want  simpleStatus
1233	conds []condition // all must be satisfied
1234	name  string      // the name of the command; must be non-empty
1235	args  []string    // shell-expanded arguments following name
1236}
1237
1238// parse parses a single line as a list of space-separated arguments
1239// subject to environment variable expansion (but not resplitting).
1240// Single quotes around text disable splitting and expansion.
1241// To embed a single quote, double it: 'Don''t communicate by sharing memory.'
1242func (ts *testScript) parse(line string) command {
1243	ts.line = line
1244
1245	var (
1246		cmd      command
1247		arg      string  // text of current arg so far (need to add line[start:i])
1248		start    = -1    // if >= 0, position where current arg text chunk starts
1249		quoted   = false // currently processing quoted text
1250		isRegexp = false // currently processing unquoted regular expression
1251	)
1252
1253	flushArg := func() {
1254		defer func() {
1255			arg = ""
1256			start = -1
1257		}()
1258
1259		if cmd.name != "" {
1260			cmd.args = append(cmd.args, arg)
1261			// Commands take only one regexp argument (after the optional flags),
1262			// so no subsequent args are regexps. Liberally assume an argument that
1263			// starts with a '-' is a flag.
1264			if len(arg) == 0 || arg[0] != '-' {
1265				isRegexp = false
1266			}
1267			return
1268		}
1269
1270		// Command prefix ! means negate the expectations about this command:
1271		// go command should fail, match should not be found, etc.
1272		// Prefix ? means allow either success or failure.
1273		switch want := simpleStatus(arg); want {
1274		case failure, successOrFailure:
1275			if cmd.want != "" {
1276				ts.fatalf("duplicated '!' or '?' token")
1277			}
1278			cmd.want = want
1279			return
1280		}
1281
1282		// Command prefix [cond] means only run this command if cond is satisfied.
1283		if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") {
1284			want := true
1285			arg = strings.TrimSpace(arg[1 : len(arg)-1])
1286			if strings.HasPrefix(arg, "!") {
1287				want = false
1288				arg = strings.TrimSpace(arg[1:])
1289			}
1290			if arg == "" {
1291				ts.fatalf("empty condition")
1292			}
1293			cmd.conds = append(cmd.conds, condition{want: want, tag: arg})
1294			return
1295		}
1296
1297		cmd.name = arg
1298		isRegexp = regexpCmd[cmd.name]
1299	}
1300
1301	for i := 0; ; i++ {
1302		if !quoted && (i >= len(line) || line[i] == ' ' || line[i] == '\t' || line[i] == '\r' || line[i] == '#') {
1303			// Found arg-separating space.
1304			if start >= 0 {
1305				arg += ts.expand(line[start:i], isRegexp)
1306				flushArg()
1307			}
1308			if i >= len(line) || line[i] == '#' {
1309				break
1310			}
1311			continue
1312		}
1313		if i >= len(line) {
1314			ts.fatalf("unterminated quoted argument")
1315		}
1316		if line[i] == '\'' {
1317			if !quoted {
1318				// starting a quoted chunk
1319				if start >= 0 {
1320					arg += ts.expand(line[start:i], isRegexp)
1321				}
1322				start = i + 1
1323				quoted = true
1324				continue
1325			}
1326			// 'foo''bar' means foo'bar, like in rc shell and Pascal.
1327			if i+1 < len(line) && line[i+1] == '\'' {
1328				arg += line[start:i]
1329				start = i + 1
1330				i++ // skip over second ' before next iteration
1331				continue
1332			}
1333			// ending a quoted chunk
1334			arg += line[start:i]
1335			start = i + 1
1336			quoted = false
1337			continue
1338		}
1339		// found character worth saving; make sure we're saving
1340		if start < 0 {
1341			start = i
1342		}
1343	}
1344	return cmd
1345}
1346
1347// diff returns a formatted diff of the two texts,
1348// showing the entire text and the minimum line-level
1349// additions and removals to turn text1 into text2.
1350// (That is, lines only in text1 appear with a leading -,
1351// and lines only in text2 appear with a leading +.)
1352func diff(text1, text2 string) string {
1353	if text1 != "" && !strings.HasSuffix(text1, "\n") {
1354		text1 += "(missing final newline)"
1355	}
1356	lines1 := strings.Split(text1, "\n")
1357	lines1 = lines1[:len(lines1)-1] // remove empty string after final line
1358	if text2 != "" && !strings.HasSuffix(text2, "\n") {
1359		text2 += "(missing final newline)"
1360	}
1361	lines2 := strings.Split(text2, "\n")
1362	lines2 = lines2[:len(lines2)-1] // remove empty string after final line
1363
1364	// Naive dynamic programming algorithm for edit distance.
1365	// https://en.wikipedia.org/wiki/Wagner–Fischer_algorithm
1366	// dist[i][j] = edit distance between lines1[:len(lines1)-i] and lines2[:len(lines2)-j]
1367	// (The reversed indices make following the minimum cost path
1368	// visit lines in the same order as in the text.)
1369	dist := make([][]int, len(lines1)+1)
1370	for i := range dist {
1371		dist[i] = make([]int, len(lines2)+1)
1372		if i == 0 {
1373			for j := range dist[0] {
1374				dist[0][j] = j
1375			}
1376			continue
1377		}
1378		for j := range dist[i] {
1379			if j == 0 {
1380				dist[i][0] = i
1381				continue
1382			}
1383			cost := dist[i][j-1] + 1
1384			if cost > dist[i-1][j]+1 {
1385				cost = dist[i-1][j] + 1
1386			}
1387			if lines1[len(lines1)-i] == lines2[len(lines2)-j] {
1388				if cost > dist[i-1][j-1] {
1389					cost = dist[i-1][j-1]
1390				}
1391			}
1392			dist[i][j] = cost
1393		}
1394	}
1395
1396	var buf strings.Builder
1397	i, j := len(lines1), len(lines2)
1398	for i > 0 || j > 0 {
1399		cost := dist[i][j]
1400		if i > 0 && j > 0 && cost == dist[i-1][j-1] && lines1[len(lines1)-i] == lines2[len(lines2)-j] {
1401			fmt.Fprintf(&buf, " %s\n", lines1[len(lines1)-i])
1402			i--
1403			j--
1404		} else if i > 0 && cost == dist[i-1][j]+1 {
1405			fmt.Fprintf(&buf, "-%s\n", lines1[len(lines1)-i])
1406			i--
1407		} else {
1408			fmt.Fprintf(&buf, "+%s\n", lines2[len(lines2)-j])
1409			j--
1410		}
1411	}
1412	return buf.String()
1413}
1414
1415var diffTests = []struct {
1416	text1 string
1417	text2 string
1418	diff  string
1419}{
1420	{"a b c", "a b d e f", "a b -c +d +e +f"},
1421	{"", "a b c", "+a +b +c"},
1422	{"a b c", "", "-a -b -c"},
1423	{"a b c", "d e f", "-a -b -c +d +e +f"},
1424	{"a b c d e f", "a b d e f", "a b -c d e f"},
1425	{"a b c e f", "a b c d e f", "a b c +d e f"},
1426}
1427
1428func TestDiff(t *testing.T) {
1429	t.Parallel()
1430
1431	for _, tt := range diffTests {
1432		// Turn spaces into \n.
1433		text1 := strings.ReplaceAll(tt.text1, " ", "\n")
1434		if text1 != "" {
1435			text1 += "\n"
1436		}
1437		text2 := strings.ReplaceAll(tt.text2, " ", "\n")
1438		if text2 != "" {
1439			text2 += "\n"
1440		}
1441		out := diff(text1, text2)
1442		// Cut final \n, cut spaces, turn remaining \n into spaces.
1443		out = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSuffix(out, "\n"), " ", ""), "\n", " ")
1444		if out != tt.diff {
1445			t.Errorf("diff(%q, %q) = %q, want %q", text1, text2, out, tt.diff)
1446		}
1447	}
1448}
1449