github.com/zxy12/go_duplicate_112_new@v0.0.0-20200807091221-747231827200/src/cmd/go/script_test.go (about)

     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  
     8  package main_test
     9  
    10  import (
    11  	"bytes"
    12  	"context"
    13  	"fmt"
    14  	"go/build"
    15  	"internal/testenv"
    16  	"io/ioutil"
    17  	"os"
    18  	"os/exec"
    19  	"path/filepath"
    20  	"regexp"
    21  	"runtime"
    22  	"strconv"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"cmd/go/internal/imports"
    28  	"cmd/go/internal/par"
    29  	"cmd/go/internal/txtar"
    30  	"cmd/go/internal/work"
    31  )
    32  
    33  // TestScript runs the tests in testdata/script/*.txt.
    34  func TestScript(t *testing.T) {
    35  	testenv.MustHaveGoBuild(t)
    36  	if skipExternal {
    37  		t.Skipf("skipping external tests on %s/%s", runtime.GOOS, runtime.GOARCH)
    38  	}
    39  
    40  	files, err := filepath.Glob("testdata/script/*.txt")
    41  	if err != nil {
    42  		t.Fatal(err)
    43  	}
    44  	for _, file := range files {
    45  		file := file
    46  		name := strings.TrimSuffix(filepath.Base(file), ".txt")
    47  		t.Run(name, func(t *testing.T) {
    48  			t.Parallel()
    49  			ts := &testScript{t: t, name: name, file: file}
    50  			ts.setup()
    51  			if !*testWork {
    52  				defer removeAll(ts.workdir)
    53  			}
    54  			ts.run()
    55  		})
    56  	}
    57  }
    58  
    59  // A testScript holds execution state for a single test script.
    60  type testScript struct {
    61  	t          *testing.T
    62  	workdir    string            // temporary work dir ($WORK)
    63  	log        bytes.Buffer      // test execution log (printed at end of test)
    64  	mark       int               // offset of next log truncation
    65  	cd         string            // current directory during test execution; initially $WORK/gopath/src
    66  	name       string            // short name of test ("foo")
    67  	file       string            // full file name ("testdata/script/foo.txt")
    68  	lineno     int               // line number currently executing
    69  	line       string            // line currently executing
    70  	env        []string          // environment list (for os/exec)
    71  	envMap     map[string]string // environment mapping (matches env)
    72  	stdout     string            // standard output from last 'go' command; for 'stdout' command
    73  	stderr     string            // standard error from last 'go' command; for 'stderr' command
    74  	stopped    bool              // test wants to stop early
    75  	start      time.Time         // time phase started
    76  	background []backgroundCmd   // backgrounded 'exec' and 'go' commands
    77  }
    78  
    79  type backgroundCmd struct {
    80  	cmd  *exec.Cmd
    81  	wait <-chan struct{}
    82  	neg  bool // if true, cmd should fail
    83  }
    84  
    85  var extraEnvKeys = []string{
    86  	"SYSTEMROOT", // must be preserved on Windows to find DLLs; golang.org/issue/25210
    87  }
    88  
    89  // setup sets up the test execution temporary directory and environment.
    90  func (ts *testScript) setup() {
    91  	StartProxy()
    92  	ts.workdir = filepath.Join(testTmpDir, "script-"+ts.name)
    93  	ts.check(os.MkdirAll(filepath.Join(ts.workdir, "tmp"), 0777))
    94  	ts.check(os.MkdirAll(filepath.Join(ts.workdir, "gopath/src"), 0777))
    95  	ts.cd = filepath.Join(ts.workdir, "gopath/src")
    96  	ts.env = []string{
    97  		"WORK=" + ts.workdir, // must be first for ts.abbrev
    98  		"PATH=" + testBin + string(filepath.ListSeparator) + os.Getenv("PATH"),
    99  		homeEnvName() + "=/no-home",
   100  		"CCACHE_DISABLE=1", // ccache breaks with non-existent HOME
   101  		"GOARCH=" + runtime.GOARCH,
   102  		"GOCACHE=" + testGOCACHE,
   103  		"GOOS=" + runtime.GOOS,
   104  		"GOPATH=" + filepath.Join(ts.workdir, "gopath"),
   105  		"GOPROXY=" + proxyURL,
   106  		"GOROOT=" + testGOROOT,
   107  		tempEnvName() + "=" + filepath.Join(ts.workdir, "tmp"),
   108  		"devnull=" + os.DevNull,
   109  		"goversion=" + goVersion(ts),
   110  		":=" + string(os.PathListSeparator),
   111  	}
   112  
   113  	if runtime.GOOS == "plan9" {
   114  		ts.env = append(ts.env, "path="+testBin+string(filepath.ListSeparator)+os.Getenv("path"))
   115  	}
   116  
   117  	if runtime.GOOS == "windows" {
   118  		ts.env = append(ts.env, "exe=.exe")
   119  	} else {
   120  		ts.env = append(ts.env, "exe=")
   121  	}
   122  	for _, key := range extraEnvKeys {
   123  		if val := os.Getenv(key); val != "" {
   124  			ts.env = append(ts.env, key+"="+val)
   125  		}
   126  	}
   127  
   128  	ts.envMap = make(map[string]string)
   129  	for _, kv := range ts.env {
   130  		if i := strings.Index(kv, "="); i >= 0 {
   131  			ts.envMap[kv[:i]] = kv[i+1:]
   132  		}
   133  	}
   134  }
   135  
   136  // goVersion returns the current Go version.
   137  func goVersion(ts *testScript) string {
   138  	tags := build.Default.ReleaseTags
   139  	version := tags[len(tags)-1]
   140  	if !regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`).MatchString(version) {
   141  		ts.fatalf("invalid go version %q", version)
   142  	}
   143  	return version[2:]
   144  }
   145  
   146  var execCache par.Cache
   147  
   148  // run runs the test script.
   149  func (ts *testScript) run() {
   150  	// Truncate log at end of last phase marker,
   151  	// discarding details of successful phase.
   152  	rewind := func() {
   153  		if !testing.Verbose() {
   154  			ts.log.Truncate(ts.mark)
   155  		}
   156  	}
   157  
   158  	// Insert elapsed time for phase at end of phase marker
   159  	markTime := func() {
   160  		if ts.mark > 0 && !ts.start.IsZero() {
   161  			afterMark := append([]byte{}, ts.log.Bytes()[ts.mark:]...)
   162  			ts.log.Truncate(ts.mark - 1) // cut \n and afterMark
   163  			fmt.Fprintf(&ts.log, " (%.3fs)\n", time.Since(ts.start).Seconds())
   164  			ts.log.Write(afterMark)
   165  		}
   166  		ts.start = time.Time{}
   167  	}
   168  
   169  	defer func() {
   170  		// On a normal exit from the test loop, background processes are cleaned up
   171  		// before we print PASS. If we return early (e.g., due to a test failure),
   172  		// don't print anything about the processes that were still running.
   173  		for _, bg := range ts.background {
   174  			interruptProcess(bg.cmd.Process)
   175  		}
   176  		for _, bg := range ts.background {
   177  			<-bg.wait
   178  		}
   179  		ts.background = nil
   180  
   181  		markTime()
   182  		// Flush testScript log to testing.T log.
   183  		ts.t.Log("\n" + ts.abbrev(ts.log.String()))
   184  	}()
   185  
   186  	// Unpack archive.
   187  	a, err := txtar.ParseFile(ts.file)
   188  	ts.check(err)
   189  	for _, f := range a.Files {
   190  		name := ts.mkabs(ts.expand(f.Name))
   191  		ts.check(os.MkdirAll(filepath.Dir(name), 0777))
   192  		ts.check(ioutil.WriteFile(name, f.Data, 0666))
   193  	}
   194  
   195  	// With -v or -testwork, start log with full environment.
   196  	if *testWork || testing.Verbose() {
   197  		// Display environment.
   198  		ts.cmdEnv(false, nil)
   199  		fmt.Fprintf(&ts.log, "\n")
   200  		ts.mark = ts.log.Len()
   201  	}
   202  
   203  	// Run script.
   204  	// See testdata/script/README for documentation of script form.
   205  	script := string(a.Comment)
   206  Script:
   207  	for script != "" {
   208  		// Extract next line.
   209  		ts.lineno++
   210  		var line string
   211  		if i := strings.Index(script, "\n"); i >= 0 {
   212  			line, script = script[:i], script[i+1:]
   213  		} else {
   214  			line, script = script, ""
   215  		}
   216  
   217  		// # is a comment indicating the start of new phase.
   218  		if strings.HasPrefix(line, "#") {
   219  			// If there was a previous phase, it succeeded,
   220  			// so rewind the log to delete its details (unless -v is in use).
   221  			// If nothing has happened at all since the mark,
   222  			// rewinding is a no-op and adding elapsed time
   223  			// for doing nothing is meaningless, so don't.
   224  			if ts.log.Len() > ts.mark {
   225  				rewind()
   226  				markTime()
   227  			}
   228  			// Print phase heading and mark start of phase output.
   229  			fmt.Fprintf(&ts.log, "%s\n", line)
   230  			ts.mark = ts.log.Len()
   231  			ts.start = time.Now()
   232  			continue
   233  		}
   234  
   235  		// Parse input line. Ignore blanks entirely.
   236  		args := ts.parse(line)
   237  		if len(args) == 0 {
   238  			continue
   239  		}
   240  
   241  		// Echo command to log.
   242  		fmt.Fprintf(&ts.log, "> %s\n", line)
   243  
   244  		// Command prefix [cond] means only run this command if cond is satisfied.
   245  		for strings.HasPrefix(args[0], "[") && strings.HasSuffix(args[0], "]") {
   246  			cond := args[0]
   247  			cond = cond[1 : len(cond)-1]
   248  			cond = strings.TrimSpace(cond)
   249  			args = args[1:]
   250  			if len(args) == 0 {
   251  				ts.fatalf("missing command after condition")
   252  			}
   253  			want := true
   254  			if strings.HasPrefix(cond, "!") {
   255  				want = false
   256  				cond = strings.TrimSpace(cond[1:])
   257  			}
   258  			// Known conds are: $GOOS, $GOARCH, runtime.Compiler, and 'short' (for testing.Short).
   259  			//
   260  			// NOTE: If you make changes here, update testdata/script/README too!
   261  			//
   262  			ok := false
   263  			switch cond {
   264  			case runtime.GOOS, runtime.GOARCH, runtime.Compiler:
   265  				ok = true
   266  			case "short":
   267  				ok = testing.Short()
   268  			case "cgo":
   269  				ok = canCgo
   270  			case "msan":
   271  				ok = canMSan
   272  			case "race":
   273  				ok = canRace
   274  			case "net":
   275  				ok = testenv.HasExternalNetwork()
   276  			case "link":
   277  				ok = testenv.HasLink()
   278  			case "root":
   279  				ok = os.Geteuid() == 0
   280  			case "symlink":
   281  				ok = testenv.HasSymlink()
   282  			default:
   283  				if strings.HasPrefix(cond, "exec:") {
   284  					prog := cond[len("exec:"):]
   285  					ok = execCache.Do(prog, func() interface{} {
   286  						if runtime.GOOS == "plan9" && prog == "git" {
   287  							// The Git command is usually not the real Git on Plan 9.
   288  							// See https://golang.org/issues/29640.
   289  							return false
   290  						}
   291  						_, err := exec.LookPath(prog)
   292  						return err == nil
   293  					}).(bool)
   294  					break
   295  				}
   296  				if !imports.KnownArch[cond] && !imports.KnownOS[cond] && cond != "gc" && cond != "gccgo" {
   297  					ts.fatalf("unknown condition %q", cond)
   298  				}
   299  			}
   300  			if ok != want {
   301  				// Don't run rest of line.
   302  				continue Script
   303  			}
   304  		}
   305  
   306  		// Command prefix ! means negate the expectations about this command:
   307  		// go command should fail, match should not be found, etc.
   308  		neg := false
   309  		if args[0] == "!" {
   310  			neg = true
   311  			args = args[1:]
   312  			if len(args) == 0 {
   313  				ts.fatalf("! on line by itself")
   314  			}
   315  		}
   316  
   317  		// Run command.
   318  		cmd := scriptCmds[args[0]]
   319  		if cmd == nil {
   320  			ts.fatalf("unknown command %q", args[0])
   321  		}
   322  		cmd(ts, neg, args[1:])
   323  
   324  		// Command can ask script to stop early.
   325  		if ts.stopped {
   326  			// Break instead of returning, so that we check the status of any
   327  			// background processes and print PASS.
   328  			break
   329  		}
   330  	}
   331  
   332  	for _, bg := range ts.background {
   333  		interruptProcess(bg.cmd.Process)
   334  	}
   335  	ts.cmdWait(false, nil)
   336  
   337  	// Final phase ended.
   338  	rewind()
   339  	markTime()
   340  	if !ts.stopped {
   341  		fmt.Fprintf(&ts.log, "PASS\n")
   342  	}
   343  }
   344  
   345  // scriptCmds are the script command implementations.
   346  // Keep list and the implementations below sorted by name.
   347  //
   348  // NOTE: If you make changes here, update testdata/script/README too!
   349  //
   350  var scriptCmds = map[string]func(*testScript, bool, []string){
   351  	"addcrlf": (*testScript).cmdAddcrlf,
   352  	"cc":      (*testScript).cmdCc,
   353  	"cd":      (*testScript).cmdCd,
   354  	"chmod":   (*testScript).cmdChmod,
   355  	"cmp":     (*testScript).cmdCmp,
   356  	"cmpenv":  (*testScript).cmdCmpenv,
   357  	"cp":      (*testScript).cmdCp,
   358  	"env":     (*testScript).cmdEnv,
   359  	"exec":    (*testScript).cmdExec,
   360  	"exists":  (*testScript).cmdExists,
   361  	"go":      (*testScript).cmdGo,
   362  	"grep":    (*testScript).cmdGrep,
   363  	"mkdir":   (*testScript).cmdMkdir,
   364  	"rm":      (*testScript).cmdRm,
   365  	"skip":    (*testScript).cmdSkip,
   366  	"stale":   (*testScript).cmdStale,
   367  	"stderr":  (*testScript).cmdStderr,
   368  	"stdout":  (*testScript).cmdStdout,
   369  	"stop":    (*testScript).cmdStop,
   370  	"symlink": (*testScript).cmdSymlink,
   371  	"wait":    (*testScript).cmdWait,
   372  }
   373  
   374  // addcrlf adds CRLF line endings to the named files.
   375  func (ts *testScript) cmdAddcrlf(neg bool, args []string) {
   376  	if len(args) == 0 {
   377  		ts.fatalf("usage: addcrlf file...")
   378  	}
   379  
   380  	for _, file := range args {
   381  		file = ts.mkabs(file)
   382  		data, err := ioutil.ReadFile(file)
   383  		ts.check(err)
   384  		ts.check(ioutil.WriteFile(file, bytes.ReplaceAll(data, []byte("\n"), []byte("\r\n")), 0666))
   385  	}
   386  }
   387  
   388  // cc runs the C compiler along with platform specific options.
   389  func (ts *testScript) cmdCc(neg bool, args []string) {
   390  	if len(args) < 1 || (len(args) == 1 && args[0] == "&") {
   391  		ts.fatalf("usage: cc args... [&]")
   392  	}
   393  
   394  	var b work.Builder
   395  	b.Init()
   396  	ts.cmdExec(neg, append(b.GccCmd(".", ""), args...))
   397  }
   398  
   399  // cd changes to a different directory.
   400  func (ts *testScript) cmdCd(neg bool, args []string) {
   401  	if neg {
   402  		ts.fatalf("unsupported: ! cd")
   403  	}
   404  	if len(args) != 1 {
   405  		ts.fatalf("usage: cd dir")
   406  	}
   407  
   408  	dir := args[0]
   409  	if !filepath.IsAbs(dir) {
   410  		dir = filepath.Join(ts.cd, dir)
   411  	}
   412  	info, err := os.Stat(dir)
   413  	if os.IsNotExist(err) {
   414  		ts.fatalf("directory %s does not exist", dir)
   415  	}
   416  	ts.check(err)
   417  	if !info.IsDir() {
   418  		ts.fatalf("%s is not a directory", dir)
   419  	}
   420  	ts.cd = dir
   421  	fmt.Fprintf(&ts.log, "%s\n", ts.cd)
   422  }
   423  
   424  // chmod changes permissions for a file or directory.
   425  func (ts *testScript) cmdChmod(neg bool, args []string) {
   426  	if neg {
   427  		ts.fatalf("unsupported: ! chmod")
   428  	}
   429  	if len(args) < 2 {
   430  		ts.fatalf("usage: chmod perm paths...")
   431  	}
   432  	perm, err := strconv.ParseUint(args[0], 0, 32)
   433  	if err != nil || perm&uint64(os.ModePerm) != perm {
   434  		ts.fatalf("invalid mode: %s", args[0])
   435  	}
   436  	for _, path := range args[1:] {
   437  		err := os.Chmod(path, os.FileMode(perm))
   438  		ts.check(err)
   439  	}
   440  }
   441  
   442  // cmp compares two files.
   443  func (ts *testScript) cmdCmp(neg bool, args []string) {
   444  	if neg {
   445  		// It would be strange to say "this file can have any content except this precise byte sequence".
   446  		ts.fatalf("unsupported: ! cmp")
   447  	}
   448  	if len(args) != 2 {
   449  		ts.fatalf("usage: cmp file1 file2")
   450  	}
   451  	ts.doCmdCmp(args, false)
   452  }
   453  
   454  // cmpenv compares two files with environment variable substitution.
   455  func (ts *testScript) cmdCmpenv(neg bool, args []string) {
   456  	if neg {
   457  		ts.fatalf("unsupported: ! cmpenv")
   458  	}
   459  	if len(args) != 2 {
   460  		ts.fatalf("usage: cmpenv file1 file2")
   461  	}
   462  	ts.doCmdCmp(args, true)
   463  }
   464  
   465  func (ts *testScript) doCmdCmp(args []string, env bool) {
   466  	name1, name2 := args[0], args[1]
   467  	var text1, text2 string
   468  	if name1 == "stdout" {
   469  		text1 = ts.stdout
   470  	} else if name1 == "stderr" {
   471  		text1 = ts.stderr
   472  	} else {
   473  		data, err := ioutil.ReadFile(ts.mkabs(name1))
   474  		ts.check(err)
   475  		text1 = string(data)
   476  	}
   477  
   478  	data, err := ioutil.ReadFile(ts.mkabs(name2))
   479  	ts.check(err)
   480  	text2 = string(data)
   481  
   482  	if env {
   483  		text1 = ts.expand(text1)
   484  		text2 = ts.expand(text2)
   485  	}
   486  
   487  	if text1 == text2 {
   488  		return
   489  	}
   490  
   491  	fmt.Fprintf(&ts.log, "[diff -%s +%s]\n%s\n", name1, name2, diff(text1, text2))
   492  	ts.fatalf("%s and %s differ", name1, name2)
   493  }
   494  
   495  // cp copies files, maybe eventually directories.
   496  func (ts *testScript) cmdCp(neg bool, args []string) {
   497  	if neg {
   498  		ts.fatalf("unsupported: ! cp")
   499  	}
   500  	if len(args) < 2 {
   501  		ts.fatalf("usage: cp src... dst")
   502  	}
   503  
   504  	dst := ts.mkabs(args[len(args)-1])
   505  	info, err := os.Stat(dst)
   506  	dstDir := err == nil && info.IsDir()
   507  	if len(args) > 2 && !dstDir {
   508  		ts.fatalf("cp: destination %s is not a directory", dst)
   509  	}
   510  
   511  	for _, arg := range args[:len(args)-1] {
   512  		src := ts.mkabs(arg)
   513  		info, err := os.Stat(src)
   514  		ts.check(err)
   515  		data, err := ioutil.ReadFile(src)
   516  		ts.check(err)
   517  		targ := dst
   518  		if dstDir {
   519  			targ = filepath.Join(dst, filepath.Base(src))
   520  		}
   521  		ts.check(ioutil.WriteFile(targ, data, info.Mode()&0777))
   522  	}
   523  }
   524  
   525  // env displays or adds to the environment.
   526  func (ts *testScript) cmdEnv(neg bool, args []string) {
   527  	if neg {
   528  		ts.fatalf("unsupported: ! env")
   529  	}
   530  	if len(args) == 0 {
   531  		printed := make(map[string]bool) // env list can have duplicates; only print effective value (from envMap) once
   532  		for _, kv := range ts.env {
   533  			k := kv[:strings.Index(kv, "=")]
   534  			if !printed[k] {
   535  				fmt.Fprintf(&ts.log, "%s=%s\n", k, ts.envMap[k])
   536  			}
   537  		}
   538  		return
   539  	}
   540  	for _, env := range args {
   541  		i := strings.Index(env, "=")
   542  		if i < 0 {
   543  			// Display value instead of setting it.
   544  			fmt.Fprintf(&ts.log, "%s=%s\n", env, ts.envMap[env])
   545  			continue
   546  		}
   547  		ts.env = append(ts.env, env)
   548  		ts.envMap[env[:i]] = env[i+1:]
   549  	}
   550  }
   551  
   552  // exec runs the given command.
   553  func (ts *testScript) cmdExec(neg bool, args []string) {
   554  	if len(args) < 1 || (len(args) == 1 && args[0] == "&") {
   555  		ts.fatalf("usage: exec program [args...] [&]")
   556  	}
   557  
   558  	var err error
   559  	if len(args) > 0 && args[len(args)-1] == "&" {
   560  		var cmd *exec.Cmd
   561  		cmd, err = ts.execBackground(args[0], args[1:len(args)-1]...)
   562  		if err == nil {
   563  			wait := make(chan struct{})
   564  			go func() {
   565  				ctxWait(testCtx, cmd)
   566  				close(wait)
   567  			}()
   568  			ts.background = append(ts.background, backgroundCmd{cmd, wait, neg})
   569  		}
   570  		ts.stdout, ts.stderr = "", ""
   571  	} else {
   572  		ts.stdout, ts.stderr, err = ts.exec(args[0], args[1:]...)
   573  		if ts.stdout != "" {
   574  			fmt.Fprintf(&ts.log, "[stdout]\n%s", ts.stdout)
   575  		}
   576  		if ts.stderr != "" {
   577  			fmt.Fprintf(&ts.log, "[stderr]\n%s", ts.stderr)
   578  		}
   579  		if err == nil && neg {
   580  			ts.fatalf("unexpected command success")
   581  		}
   582  	}
   583  
   584  	if err != nil {
   585  		fmt.Fprintf(&ts.log, "[%v]\n", err)
   586  		if testCtx.Err() != nil {
   587  			ts.fatalf("test timed out while running command")
   588  		} else if !neg {
   589  			ts.fatalf("unexpected command failure")
   590  		}
   591  	}
   592  }
   593  
   594  // exists checks that the list of files exists.
   595  func (ts *testScript) cmdExists(neg bool, args []string) {
   596  	var readonly bool
   597  	if len(args) > 0 && args[0] == "-readonly" {
   598  		readonly = true
   599  		args = args[1:]
   600  	}
   601  	if len(args) == 0 {
   602  		ts.fatalf("usage: exists [-readonly] file...")
   603  	}
   604  
   605  	for _, file := range args {
   606  		file = ts.mkabs(file)
   607  		info, err := os.Stat(file)
   608  		if err == nil && neg {
   609  			what := "file"
   610  			if info.IsDir() {
   611  				what = "directory"
   612  			}
   613  			ts.fatalf("%s %s unexpectedly exists", what, file)
   614  		}
   615  		if err != nil && !neg {
   616  			ts.fatalf("%s does not exist", file)
   617  		}
   618  		if err == nil && !neg && readonly && info.Mode()&0222 != 0 {
   619  			ts.fatalf("%s exists but is writable", file)
   620  		}
   621  	}
   622  }
   623  
   624  // go runs the go command.
   625  func (ts *testScript) cmdGo(neg bool, args []string) {
   626  	ts.cmdExec(neg, append([]string{testGo}, args...))
   627  }
   628  
   629  // mkdir creates directories.
   630  func (ts *testScript) cmdMkdir(neg bool, args []string) {
   631  	if neg {
   632  		ts.fatalf("unsupported: ! mkdir")
   633  	}
   634  	if len(args) < 1 {
   635  		ts.fatalf("usage: mkdir dir...")
   636  	}
   637  	for _, arg := range args {
   638  		ts.check(os.MkdirAll(ts.mkabs(arg), 0777))
   639  	}
   640  }
   641  
   642  // rm removes files or directories.
   643  func (ts *testScript) cmdRm(neg bool, args []string) {
   644  	if neg {
   645  		ts.fatalf("unsupported: ! rm")
   646  	}
   647  	if len(args) < 1 {
   648  		ts.fatalf("usage: rm file...")
   649  	}
   650  	for _, arg := range args {
   651  		file := ts.mkabs(arg)
   652  		removeAll(file)              // does chmod and then attempts rm
   653  		ts.check(os.RemoveAll(file)) // report error
   654  	}
   655  }
   656  
   657  // skip marks the test skipped.
   658  func (ts *testScript) cmdSkip(neg bool, args []string) {
   659  	if len(args) > 1 {
   660  		ts.fatalf("usage: skip [msg]")
   661  	}
   662  	if neg {
   663  		ts.fatalf("unsupported: ! skip")
   664  	}
   665  
   666  	// Before we mark the test as skipped, shut down any background processes and
   667  	// make sure they have returned the correct status.
   668  	for _, bg := range ts.background {
   669  		interruptProcess(bg.cmd.Process)
   670  	}
   671  	ts.cmdWait(false, nil)
   672  
   673  	if len(args) == 1 {
   674  		ts.t.Skip(args[0])
   675  	}
   676  	ts.t.Skip()
   677  }
   678  
   679  // stale checks that the named build targets are stale.
   680  func (ts *testScript) cmdStale(neg bool, args []string) {
   681  	if len(args) == 0 {
   682  		ts.fatalf("usage: stale target...")
   683  	}
   684  	tmpl := "{{if .Error}}{{.ImportPath}}: {{.Error.Err}}{else}}"
   685  	if neg {
   686  		tmpl += "{{if .Stale}}{{.ImportPath}} is unexpectedly stale{{end}}"
   687  	} else {
   688  		tmpl += "{{if not .Stale}}{{.ImportPath}} is unexpectedly NOT stale{{end}}"
   689  	}
   690  	tmpl += "{{end}}"
   691  	goArgs := append([]string{"list", "-e", "-f=" + tmpl}, args...)
   692  	stdout, stderr, err := ts.exec(testGo, goArgs...)
   693  	if err != nil {
   694  		ts.fatalf("go list: %v\n%s%s", err, stdout, stderr)
   695  	}
   696  	if stdout != "" {
   697  		ts.fatalf("%s", stdout)
   698  	}
   699  }
   700  
   701  // stdout checks that the last go command standard output matches a regexp.
   702  func (ts *testScript) cmdStdout(neg bool, args []string) {
   703  	scriptMatch(ts, neg, args, ts.stdout, "stdout")
   704  }
   705  
   706  // stderr checks that the last go command standard output matches a regexp.
   707  func (ts *testScript) cmdStderr(neg bool, args []string) {
   708  	scriptMatch(ts, neg, args, ts.stderr, "stderr")
   709  }
   710  
   711  // grep checks that file content matches a regexp.
   712  // Like stdout/stderr and unlike Unix grep, it accepts Go regexp syntax.
   713  func (ts *testScript) cmdGrep(neg bool, args []string) {
   714  	scriptMatch(ts, neg, args, "", "grep")
   715  }
   716  
   717  // scriptMatch implements both stdout and stderr.
   718  func scriptMatch(ts *testScript, neg bool, args []string, text, name string) {
   719  	n := 0
   720  	if len(args) >= 1 && strings.HasPrefix(args[0], "-count=") {
   721  		if neg {
   722  			ts.fatalf("cannot use -count= with negated match")
   723  		}
   724  		var err error
   725  		n, err = strconv.Atoi(args[0][len("-count="):])
   726  		if err != nil {
   727  			ts.fatalf("bad -count=: %v", err)
   728  		}
   729  		if n < 1 {
   730  			ts.fatalf("bad -count=: must be at least 1")
   731  		}
   732  		args = args[1:]
   733  	}
   734  
   735  	extraUsage := ""
   736  	want := 1
   737  	if name == "grep" {
   738  		extraUsage = " file"
   739  		want = 2
   740  	}
   741  	if len(args) != want {
   742  		ts.fatalf("usage: %s [-count=N] 'pattern'%s", name, extraUsage)
   743  	}
   744  
   745  	pattern := args[0]
   746  	re, err := regexp.Compile(`(?m)` + pattern)
   747  	ts.check(err)
   748  
   749  	isGrep := name == "grep"
   750  	if isGrep {
   751  		name = args[1] // for error messages
   752  		data, err := ioutil.ReadFile(ts.mkabs(args[1]))
   753  		ts.check(err)
   754  		text = string(data)
   755  	}
   756  
   757  	// Matching against workdir would be misleading.
   758  	text = strings.ReplaceAll(text, ts.workdir, "$WORK")
   759  
   760  	if neg {
   761  		if re.MatchString(text) {
   762  			if isGrep {
   763  				fmt.Fprintf(&ts.log, "[%s]\n%s\n", name, text)
   764  			}
   765  			ts.fatalf("unexpected match for %#q found in %s: %s", pattern, name, re.FindString(text))
   766  		}
   767  	} else {
   768  		if !re.MatchString(text) {
   769  			if isGrep {
   770  				fmt.Fprintf(&ts.log, "[%s]\n%s\n", name, text)
   771  			}
   772  			ts.fatalf("no match for %#q found in %s", pattern, name)
   773  		}
   774  		if n > 0 {
   775  			count := len(re.FindAllString(text, -1))
   776  			if count != n {
   777  				if isGrep {
   778  					fmt.Fprintf(&ts.log, "[%s]\n%s\n", name, text)
   779  				}
   780  				ts.fatalf("have %d matches for %#q, want %d", count, pattern, n)
   781  			}
   782  		}
   783  	}
   784  }
   785  
   786  // stop stops execution of the test (marking it passed).
   787  func (ts *testScript) cmdStop(neg bool, args []string) {
   788  	if neg {
   789  		ts.fatalf("unsupported: ! stop")
   790  	}
   791  	if len(args) > 1 {
   792  		ts.fatalf("usage: stop [msg]")
   793  	}
   794  	if len(args) == 1 {
   795  		fmt.Fprintf(&ts.log, "stop: %s\n", args[0])
   796  	} else {
   797  		fmt.Fprintf(&ts.log, "stop\n")
   798  	}
   799  	ts.stopped = true
   800  }
   801  
   802  // symlink creates a symbolic link.
   803  func (ts *testScript) cmdSymlink(neg bool, args []string) {
   804  	if neg {
   805  		ts.fatalf("unsupported: ! symlink")
   806  	}
   807  	if len(args) != 3 || args[1] != "->" {
   808  		ts.fatalf("usage: symlink file -> target")
   809  	}
   810  	// Note that the link target args[2] is not interpreted with mkabs:
   811  	// it will be interpreted relative to the directory file is in.
   812  	ts.check(os.Symlink(args[2], ts.mkabs(args[0])))
   813  }
   814  
   815  // wait waits for background commands to exit, setting stderr and stdout to their result.
   816  func (ts *testScript) cmdWait(neg bool, args []string) {
   817  	if neg {
   818  		ts.fatalf("unsupported: ! wait")
   819  	}
   820  	if len(args) > 0 {
   821  		ts.fatalf("usage: wait")
   822  	}
   823  
   824  	var stdouts, stderrs []string
   825  	for _, bg := range ts.background {
   826  		<-bg.wait
   827  
   828  		args := append([]string{filepath.Base(bg.cmd.Args[0])}, bg.cmd.Args[1:]...)
   829  		fmt.Fprintf(&ts.log, "[background] %s: %v\n", strings.Join(args, " "), bg.cmd.ProcessState)
   830  
   831  		cmdStdout := bg.cmd.Stdout.(*strings.Builder).String()
   832  		if cmdStdout != "" {
   833  			fmt.Fprintf(&ts.log, "[stdout]\n%s", cmdStdout)
   834  			stdouts = append(stdouts, cmdStdout)
   835  		}
   836  
   837  		cmdStderr := bg.cmd.Stderr.(*strings.Builder).String()
   838  		if cmdStderr != "" {
   839  			fmt.Fprintf(&ts.log, "[stderr]\n%s", cmdStderr)
   840  			stderrs = append(stderrs, cmdStderr)
   841  		}
   842  
   843  		if bg.cmd.ProcessState.Success() {
   844  			if bg.neg {
   845  				ts.fatalf("unexpected command success")
   846  			}
   847  		} else {
   848  			if testCtx.Err() != nil {
   849  				ts.fatalf("test timed out while running command")
   850  			} else if !bg.neg {
   851  				ts.fatalf("unexpected command failure")
   852  			}
   853  		}
   854  	}
   855  
   856  	ts.stdout = strings.Join(stdouts, "")
   857  	ts.stderr = strings.Join(stderrs, "")
   858  	ts.background = nil
   859  }
   860  
   861  // Helpers for command implementations.
   862  
   863  // abbrev abbreviates the actual work directory in the string s to the literal string "$WORK".
   864  func (ts *testScript) abbrev(s string) string {
   865  	s = strings.ReplaceAll(s, ts.workdir, "$WORK")
   866  	if *testWork {
   867  		// Expose actual $WORK value in environment dump on first line of work script,
   868  		// so that the user can find out what directory -testwork left behind.
   869  		s = "WORK=" + ts.workdir + "\n" + strings.TrimPrefix(s, "WORK=$WORK\n")
   870  	}
   871  	return s
   872  }
   873  
   874  // check calls ts.fatalf if err != nil.
   875  func (ts *testScript) check(err error) {
   876  	if err != nil {
   877  		ts.fatalf("%v", err)
   878  	}
   879  }
   880  
   881  // exec runs the given command line (an actual subprocess, not simulated)
   882  // in ts.cd with environment ts.env and then returns collected standard output and standard error.
   883  func (ts *testScript) exec(command string, args ...string) (stdout, stderr string, err error) {
   884  	cmd := exec.Command(command, args...)
   885  	cmd.Dir = ts.cd
   886  	cmd.Env = append(ts.env, "PWD="+ts.cd)
   887  	var stdoutBuf, stderrBuf strings.Builder
   888  	cmd.Stdout = &stdoutBuf
   889  	cmd.Stderr = &stderrBuf
   890  	if err = cmd.Start(); err == nil {
   891  		err = ctxWait(testCtx, cmd)
   892  	}
   893  	return stdoutBuf.String(), stderrBuf.String(), err
   894  }
   895  
   896  // execBackground starts the given command line (an actual subprocess, not simulated)
   897  // in ts.cd with environment ts.env.
   898  func (ts *testScript) execBackground(command string, args ...string) (*exec.Cmd, error) {
   899  	cmd := exec.Command(command, args...)
   900  	cmd.Dir = ts.cd
   901  	cmd.Env = append(ts.env, "PWD="+ts.cd)
   902  	var stdoutBuf, stderrBuf strings.Builder
   903  	cmd.Stdout = &stdoutBuf
   904  	cmd.Stderr = &stderrBuf
   905  	return cmd, cmd.Start()
   906  }
   907  
   908  // ctxWait is like cmd.Wait, but terminates cmd with os.Interrupt if ctx becomes done.
   909  //
   910  // This differs from exec.CommandContext in that it prefers os.Interrupt over os.Kill.
   911  // (See https://golang.org/issue/21135.)
   912  func ctxWait(ctx context.Context, cmd *exec.Cmd) error {
   913  	errc := make(chan error, 1)
   914  	go func() { errc <- cmd.Wait() }()
   915  
   916  	select {
   917  	case err := <-errc:
   918  		return err
   919  	case <-ctx.Done():
   920  		interruptProcess(cmd.Process)
   921  		return <-errc
   922  	}
   923  }
   924  
   925  // interruptProcess sends os.Interrupt to p if supported, or os.Kill otherwise.
   926  func interruptProcess(p *os.Process) {
   927  	if err := p.Signal(os.Interrupt); err != nil {
   928  		// Per https://golang.org/pkg/os/#Signal, “Interrupt is not implemented on
   929  		// Windows; using it with os.Process.Signal will return an error.”
   930  		// Fall back to Kill instead.
   931  		p.Kill()
   932  	}
   933  }
   934  
   935  // expand applies environment variable expansion to the string s.
   936  func (ts *testScript) expand(s string) string {
   937  	return os.Expand(s, func(key string) string { return ts.envMap[key] })
   938  }
   939  
   940  // fatalf aborts the test with the given failure message.
   941  func (ts *testScript) fatalf(format string, args ...interface{}) {
   942  	fmt.Fprintf(&ts.log, "FAIL: %s:%d: %s\n", ts.file, ts.lineno, fmt.Sprintf(format, args...))
   943  	ts.t.FailNow()
   944  }
   945  
   946  // mkabs interprets file relative to the test script's current directory
   947  // and returns the corresponding absolute path.
   948  func (ts *testScript) mkabs(file string) string {
   949  	if filepath.IsAbs(file) {
   950  		return file
   951  	}
   952  	return filepath.Join(ts.cd, file)
   953  }
   954  
   955  // parse parses a single line as a list of space-separated arguments
   956  // subject to environment variable expansion (but not resplitting).
   957  // Single quotes around text disable splitting and expansion.
   958  // To embed a single quote, double it: 'Don''t communicate by sharing memory.'
   959  func (ts *testScript) parse(line string) []string {
   960  	ts.line = line
   961  
   962  	var (
   963  		args   []string
   964  		arg    string  // text of current arg so far (need to add line[start:i])
   965  		start  = -1    // if >= 0, position where current arg text chunk starts
   966  		quoted = false // currently processing quoted text
   967  	)
   968  	for i := 0; ; i++ {
   969  		if !quoted && (i >= len(line) || line[i] == ' ' || line[i] == '\t' || line[i] == '\r' || line[i] == '#') {
   970  			// Found arg-separating space.
   971  			if start >= 0 {
   972  				arg += ts.expand(line[start:i])
   973  				args = append(args, arg)
   974  				start = -1
   975  				arg = ""
   976  			}
   977  			if i >= len(line) || line[i] == '#' {
   978  				break
   979  			}
   980  			continue
   981  		}
   982  		if i >= len(line) {
   983  			ts.fatalf("unterminated quoted argument")
   984  		}
   985  		if line[i] == '\'' {
   986  			if !quoted {
   987  				// starting a quoted chunk
   988  				if start >= 0 {
   989  					arg += ts.expand(line[start:i])
   990  				}
   991  				start = i + 1
   992  				quoted = true
   993  				continue
   994  			}
   995  			// 'foo''bar' means foo'bar, like in rc shell and Pascal.
   996  			if i+1 < len(line) && line[i+1] == '\'' {
   997  				arg += line[start:i]
   998  				start = i + 1
   999  				i++ // skip over second ' before next iteration
  1000  				continue
  1001  			}
  1002  			// ending a quoted chunk
  1003  			arg += line[start:i]
  1004  			start = i + 1
  1005  			quoted = false
  1006  			continue
  1007  		}
  1008  		// found character worth saving; make sure we're saving
  1009  		if start < 0 {
  1010  			start = i
  1011  		}
  1012  	}
  1013  	return args
  1014  }
  1015  
  1016  // diff returns a formatted diff of the two texts,
  1017  // showing the entire text and the minimum line-level
  1018  // additions and removals to turn text1 into text2.
  1019  // (That is, lines only in text1 appear with a leading -,
  1020  // and lines only in text2 appear with a leading +.)
  1021  func diff(text1, text2 string) string {
  1022  	if text1 != "" && !strings.HasSuffix(text1, "\n") {
  1023  		text1 += "(missing final newline)"
  1024  	}
  1025  	lines1 := strings.Split(text1, "\n")
  1026  	lines1 = lines1[:len(lines1)-1] // remove empty string after final line
  1027  	if text2 != "" && !strings.HasSuffix(text2, "\n") {
  1028  		text2 += "(missing final newline)"
  1029  	}
  1030  	lines2 := strings.Split(text2, "\n")
  1031  	lines2 = lines2[:len(lines2)-1] // remove empty string after final line
  1032  
  1033  	// Naive dynamic programming algorithm for edit distance.
  1034  	// https://en.wikipedia.org/wiki/Wagner–Fischer_algorithm
  1035  	// dist[i][j] = edit distance between lines1[:len(lines1)-i] and lines2[:len(lines2)-j]
  1036  	// (The reversed indices make following the minimum cost path
  1037  	// visit lines in the same order as in the text.)
  1038  	dist := make([][]int, len(lines1)+1)
  1039  	for i := range dist {
  1040  		dist[i] = make([]int, len(lines2)+1)
  1041  		if i == 0 {
  1042  			for j := range dist[0] {
  1043  				dist[0][j] = j
  1044  			}
  1045  			continue
  1046  		}
  1047  		for j := range dist[i] {
  1048  			if j == 0 {
  1049  				dist[i][0] = i
  1050  				continue
  1051  			}
  1052  			cost := dist[i][j-1] + 1
  1053  			if cost > dist[i-1][j]+1 {
  1054  				cost = dist[i-1][j] + 1
  1055  			}
  1056  			if lines1[len(lines1)-i] == lines2[len(lines2)-j] {
  1057  				if cost > dist[i-1][j-1] {
  1058  					cost = dist[i-1][j-1]
  1059  				}
  1060  			}
  1061  			dist[i][j] = cost
  1062  		}
  1063  	}
  1064  
  1065  	var buf strings.Builder
  1066  	i, j := len(lines1), len(lines2)
  1067  	for i > 0 || j > 0 {
  1068  		cost := dist[i][j]
  1069  		if i > 0 && j > 0 && cost == dist[i-1][j-1] && lines1[len(lines1)-i] == lines2[len(lines2)-j] {
  1070  			fmt.Fprintf(&buf, " %s\n", lines1[len(lines1)-i])
  1071  			i--
  1072  			j--
  1073  		} else if i > 0 && cost == dist[i-1][j]+1 {
  1074  			fmt.Fprintf(&buf, "-%s\n", lines1[len(lines1)-i])
  1075  			i--
  1076  		} else {
  1077  			fmt.Fprintf(&buf, "+%s\n", lines2[len(lines2)-j])
  1078  			j--
  1079  		}
  1080  	}
  1081  	return buf.String()
  1082  }
  1083  
  1084  var diffTests = []struct {
  1085  	text1 string
  1086  	text2 string
  1087  	diff  string
  1088  }{
  1089  	{"a b c", "a b d e f", "a b -c +d +e +f"},
  1090  	{"", "a b c", "+a +b +c"},
  1091  	{"a b c", "", "-a -b -c"},
  1092  	{"a b c", "d e f", "-a -b -c +d +e +f"},
  1093  	{"a b c d e f", "a b d e f", "a b -c d e f"},
  1094  	{"a b c e f", "a b c d e f", "a b c +d e f"},
  1095  }
  1096  
  1097  func TestDiff(t *testing.T) {
  1098  	for _, tt := range diffTests {
  1099  		// Turn spaces into \n.
  1100  		text1 := strings.ReplaceAll(tt.text1, " ", "\n")
  1101  		if text1 != "" {
  1102  			text1 += "\n"
  1103  		}
  1104  		text2 := strings.ReplaceAll(tt.text2, " ", "\n")
  1105  		if text2 != "" {
  1106  			text2 += "\n"
  1107  		}
  1108  		out := diff(text1, text2)
  1109  		// Cut final \n, cut spaces, turn remaining \n into spaces.
  1110  		out = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSuffix(out, "\n"), " ", ""), "\n", " ")
  1111  		if out != tt.diff {
  1112  			t.Errorf("diff(%q, %q) = %q, want %q", text1, text2, out, tt.diff)
  1113  		}
  1114  	}
  1115  }