...

Source file src/github.com/rogpeppe/go-internal/testscript/testscript.go

Documentation: github.com/rogpeppe/go-internal/testscript

     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 testscript
     9  
    10  import (
    11  	"bytes"
    12  	"context"
    13  	"errors"
    14  	"flag"
    15  	"fmt"
    16  	"go/build"
    17  	"io"
    18  	"io/fs"
    19  	"os"
    20  	"os/exec"
    21  	"path/filepath"
    22  	"regexp"
    23  	"runtime"
    24  	"sort"
    25  	"strings"
    26  	"sync/atomic"
    27  	"syscall"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/rogpeppe/go-internal/imports"
    32  	"github.com/rogpeppe/go-internal/internal/misspell"
    33  	"github.com/rogpeppe/go-internal/internal/os/execpath"
    34  	"github.com/rogpeppe/go-internal/par"
    35  	"github.com/rogpeppe/go-internal/testenv"
    36  	"github.com/rogpeppe/go-internal/testscript/internal/pty"
    37  	"github.com/rogpeppe/go-internal/txtar"
    38  )
    39  
    40  var goVersionRegex = regexp.MustCompile(`^go([1-9][0-9]*)\.([1-9][0-9]*)$`)
    41  
    42  var execCache par.Cache
    43  
    44  // If -testwork is specified, the test prints the name of the temp directory
    45  // and does not remove it when done, so that a programmer can
    46  // poke at the test file tree afterward.
    47  var testWork = flag.Bool("testwork", false, "")
    48  
    49  // timeSince is defined as a variable so that it can be overridden
    50  // for the local testscript tests so that we can test against predictable
    51  // output.
    52  var timeSince = time.Since
    53  
    54  // showVerboseEnv specifies whether the environment should be displayed
    55  // automatically when in verbose mode. This is set to false for the local testscript tests so we
    56  // can test against predictable output.
    57  var showVerboseEnv = true
    58  
    59  // Env holds the environment to use at the start of a test script invocation.
    60  type Env struct {
    61  	// WorkDir holds the path to the root directory of the
    62  	// extracted files.
    63  	WorkDir string
    64  	// Vars holds the initial set environment variables that will be passed to the
    65  	// testscript commands.
    66  	Vars []string
    67  	// Cd holds the initial current working directory.
    68  	Cd string
    69  	// Values holds a map of arbitrary values for use by custom
    70  	// testscript commands. This enables Setup to pass arbitrary
    71  	// values (not just strings) through to custom commands.
    72  	Values map[interface{}]interface{}
    73  
    74  	ts *TestScript
    75  }
    76  
    77  // Value returns a value from Env.Values, or nil if no
    78  // value was set by Setup.
    79  func (ts *TestScript) Value(key interface{}) interface{} {
    80  	return ts.values[key]
    81  }
    82  
    83  // Defer arranges for f to be called at the end
    84  // of the test. If Defer is called multiple times, the
    85  // defers are executed in reverse order (similar
    86  // to Go's defer statement)
    87  func (e *Env) Defer(f func()) {
    88  	e.ts.Defer(f)
    89  }
    90  
    91  // Getenv retrieves the value of the environment variable named by the key. It
    92  // returns the value, which will be empty if the variable is not present.
    93  func (e *Env) Getenv(key string) string {
    94  	key = envvarname(key)
    95  	for i := len(e.Vars) - 1; i >= 0; i-- {
    96  		if pair := strings.SplitN(e.Vars[i], "=", 2); len(pair) == 2 && envvarname(pair[0]) == key {
    97  			return pair[1]
    98  		}
    99  	}
   100  	return ""
   101  }
   102  
   103  func envvarname(k string) string {
   104  	if runtime.GOOS == "windows" {
   105  		return strings.ToLower(k)
   106  	}
   107  	return k
   108  }
   109  
   110  // Setenv sets the value of the environment variable named by the key. It
   111  // panics if key is invalid.
   112  func (e *Env) Setenv(key, value string) {
   113  	if key == "" || strings.IndexByte(key, '=') != -1 {
   114  		panic(fmt.Errorf("invalid environment variable key %q", key))
   115  	}
   116  	e.Vars = append(e.Vars, key+"="+value)
   117  }
   118  
   119  // T returns the t argument passed to the current test by the T.Run method.
   120  // Note that if the tests were started by calling Run,
   121  // the returned value will implement testing.TB.
   122  // Note that, despite that, the underlying value will not be of type
   123  // *testing.T because *testing.T does not implement T.
   124  //
   125  // If Cleanup is called on the returned value, the function will run
   126  // after any functions passed to Env.Defer.
   127  func (e *Env) T() T {
   128  	return e.ts.t
   129  }
   130  
   131  // Params holds parameters for a call to Run.
   132  type Params struct {
   133  	// Dir holds the name of the directory holding the scripts.
   134  	// All files in the directory with a .txtar or .txt suffix will be
   135  	// considered as test scripts. By default the current directory is used.
   136  	// Dir is interpreted relative to the current test directory.
   137  	Dir string
   138  
   139  	// Setup is called, if not nil, to complete any setup required
   140  	// for a test. The WorkDir and Vars fields will have already
   141  	// been initialized and all the files extracted into WorkDir,
   142  	// and Cd will be the same as WorkDir.
   143  	// The Setup function may modify Vars and Cd as it wishes.
   144  	Setup func(*Env) error
   145  
   146  	// Condition is called, if not nil, to determine whether a particular
   147  	// condition is true. It's called only for conditions not in the
   148  	// standard set, and may be nil.
   149  	Condition func(cond string) (bool, error)
   150  
   151  	// Cmds holds a map of commands available to the script.
   152  	// It will only be consulted for commands not part of the standard set.
   153  	Cmds map[string]func(ts *TestScript, neg bool, args []string)
   154  
   155  	// TestWork specifies that working directories should be
   156  	// left intact for later inspection.
   157  	TestWork bool
   158  
   159  	// WorkdirRoot specifies the directory within which scripts' work
   160  	// directories will be created. Setting WorkdirRoot implies TestWork=true.
   161  	// If empty, the work directories will be created inside
   162  	// $GOTMPDIR/go-test-script*, where $GOTMPDIR defaults to os.TempDir().
   163  	WorkdirRoot string
   164  
   165  	// Deprecated: this option is no longer used.
   166  	IgnoreMissedCoverage bool
   167  
   168  	// UpdateScripts specifies that if a `cmp` command fails and its second
   169  	// argument refers to a file inside the testscript file, the command will
   170  	// succeed and the testscript file will be updated to reflect the actual
   171  	// content (which could be stdout, stderr or a real file).
   172  	//
   173  	// The content will be quoted with txtar.Quote if needed;
   174  	// a manual change will be needed if it is not unquoted in the
   175  	// script.
   176  	UpdateScripts bool
   177  
   178  	// RequireExplicitExec requires that commands passed to RunMain must be used
   179  	// in test scripts via `exec cmd` and not simply `cmd`. This can help keep
   180  	// consistency across test scripts as well as keep separate process
   181  	// executions explicit.
   182  	RequireExplicitExec bool
   183  
   184  	// RequireUniqueNames requires that names in the txtar archive are unique.
   185  	// By default, later entries silently overwrite earlier ones.
   186  	RequireUniqueNames bool
   187  
   188  	// ContinueOnError causes a testscript to try to continue in
   189  	// the face of errors. Once an error has occurred, the script
   190  	// will continue as if in verbose mode.
   191  	ContinueOnError bool
   192  
   193  	// Deadline, if not zero, specifies the time at which the test run will have
   194  	// exceeded the timeout. It is equivalent to testing.T's Deadline method,
   195  	// and Run will set it to the method's return value if this field is zero.
   196  	Deadline time.Time
   197  }
   198  
   199  // RunDir runs the tests in the given directory. All files in dir with a ".txt"
   200  // or ".txtar" extension are considered to be test files.
   201  func Run(t *testing.T, p Params) {
   202  	if deadline, ok := t.Deadline(); ok && p.Deadline.IsZero() {
   203  		p.Deadline = deadline
   204  	}
   205  	RunT(tshim{t}, p)
   206  }
   207  
   208  // T holds all the methods of the *testing.T type that
   209  // are used by testscript.
   210  type T interface {
   211  	Skip(...interface{})
   212  	Fatal(...interface{})
   213  	Parallel()
   214  	Log(...interface{})
   215  	FailNow()
   216  	Run(string, func(T))
   217  	// Verbose is usually implemented by the testing package
   218  	// directly rather than on the *testing.T type.
   219  	Verbose() bool
   220  }
   221  
   222  // TFailed holds optional extra methods implemented on T.
   223  // It's defined as a separate type for backward compatibility reasons.
   224  type TFailed interface {
   225  	Failed() bool
   226  }
   227  
   228  type tshim struct {
   229  	*testing.T
   230  }
   231  
   232  func (t tshim) Run(name string, f func(T)) {
   233  	t.T.Run(name, func(t *testing.T) {
   234  		f(tshim{t})
   235  	})
   236  }
   237  
   238  func (t tshim) Verbose() bool {
   239  	return testing.Verbose()
   240  }
   241  
   242  // RunT is like Run but uses an interface type instead of the concrete *testing.T
   243  // type to make it possible to use testscript functionality outside of go test.
   244  func RunT(t T, p Params) {
   245  	entries, err := os.ReadDir(p.Dir)
   246  	if os.IsNotExist(err) {
   247  		// Continue so we give a helpful error on len(files)==0 below.
   248  	} else if err != nil {
   249  		t.Fatal(err)
   250  	}
   251  	var files []string
   252  	for _, entry := range entries {
   253  		name := entry.Name()
   254  		if strings.HasSuffix(name, ".txtar") || strings.HasSuffix(name, ".txt") {
   255  			files = append(files, filepath.Join(p.Dir, name))
   256  		}
   257  	}
   258  
   259  	if len(files) == 0 {
   260  		t.Fatal(fmt.Sprintf("no txtar nor txt scripts found in dir %s", p.Dir))
   261  	}
   262  	testTempDir := p.WorkdirRoot
   263  	if testTempDir == "" {
   264  		testTempDir, err = os.MkdirTemp(os.Getenv("GOTMPDIR"), "go-test-script")
   265  		if err != nil {
   266  			t.Fatal(err)
   267  		}
   268  	} else {
   269  		p.TestWork = true
   270  	}
   271  	// The temp dir returned by os.MkdirTemp might be a sym linked dir (default
   272  	// behaviour in macOS). That could mess up matching that includes $WORK if,
   273  	// for example, an external program outputs resolved paths. Evaluating the
   274  	// dir here will ensure consistency.
   275  	testTempDir, err = filepath.EvalSymlinks(testTempDir)
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  
   280  	var (
   281  		ctx         = context.Background()
   282  		gracePeriod = 100 * time.Millisecond
   283  		cancel      context.CancelFunc
   284  	)
   285  	if !p.Deadline.IsZero() {
   286  		timeout := time.Until(p.Deadline)
   287  
   288  		// If time allows, increase the termination grace period to 5% of the
   289  		// remaining time.
   290  		if gp := timeout / 20; gp > gracePeriod {
   291  			gracePeriod = gp
   292  		}
   293  
   294  		// When we run commands that execute subprocesses, we want to reserve two
   295  		// grace periods to clean up. We will send the first termination signal when
   296  		// the context expires, then wait one grace period for the process to
   297  		// produce whatever useful output it can (such as a stack trace). After the
   298  		// first grace period expires, we'll escalate to os.Kill, leaving the second
   299  		// grace period for the test function to record its output before the test
   300  		// process itself terminates.
   301  		timeout -= 2 * gracePeriod
   302  
   303  		ctx, cancel = context.WithTimeout(ctx, timeout)
   304  		// We don't defer cancel() because RunT returns before the sub-tests,
   305  		// and we don't have access to Cleanup due to the T interface. Instead,
   306  		// we call it after the refCount goes to zero below.
   307  		_ = cancel
   308  	}
   309  
   310  	refCount := int32(len(files))
   311  	for _, file := range files {
   312  		file := file
   313  		name := strings.TrimSuffix(filepath.Base(file), ".txt")
   314  		name = strings.TrimSuffix(name, ".txtar")
   315  		t.Run(name, func(t T) {
   316  			t.Parallel()
   317  			ts := &TestScript{
   318  				t:             t,
   319  				testTempDir:   testTempDir,
   320  				name:          name,
   321  				file:          file,
   322  				params:        p,
   323  				ctxt:          ctx,
   324  				gracePeriod:   gracePeriod,
   325  				deferred:      func() {},
   326  				scriptFiles:   make(map[string]string),
   327  				scriptUpdates: make(map[string]string),
   328  			}
   329  			defer func() {
   330  				if p.TestWork || *testWork {
   331  					return
   332  				}
   333  				removeAll(ts.workdir)
   334  				if atomic.AddInt32(&refCount, -1) == 0 {
   335  					// This is the last subtest to finish. Remove the
   336  					// parent directory too, and cancel the context.
   337  					os.Remove(testTempDir)
   338  					if cancel != nil {
   339  						cancel()
   340  					}
   341  				}
   342  			}()
   343  			ts.run()
   344  		})
   345  	}
   346  }
   347  
   348  // A TestScript holds execution state for a single test script.
   349  type TestScript struct {
   350  	params        Params
   351  	t             T
   352  	testTempDir   string
   353  	workdir       string                      // temporary work dir ($WORK)
   354  	log           bytes.Buffer                // test execution log (printed at end of test)
   355  	mark          int                         // offset of next log truncation
   356  	cd            string                      // current directory during test execution; initially $WORK/gopath/src
   357  	name          string                      // short name of test ("foo")
   358  	file          string                      // full file name ("testdata/script/foo.txt")
   359  	lineno        int                         // line number currently executing
   360  	line          string                      // line currently executing
   361  	env           []string                    // environment list (for os/exec)
   362  	envMap        map[string]string           // environment mapping (matches env; on Windows keys are lowercase)
   363  	values        map[interface{}]interface{} // values for custom commands
   364  	stdin         string                      // standard input to next 'go' command; set by 'stdin' command.
   365  	stdout        string                      // standard output from last 'go' command; for 'stdout' command
   366  	stderr        string                      // standard error from last 'go' command; for 'stderr' command
   367  	ttyin         string                      // terminal input; set by 'ttyin' command
   368  	stdinPty      bool                        // connect pty to standard input; set by 'ttyin -stdin' command
   369  	ttyout        string                      // terminal output; for 'ttyout' command
   370  	stopped       bool                        // test wants to stop early
   371  	start         time.Time                   // time phase started
   372  	background    []backgroundCmd             // backgrounded 'exec' and 'go' commands
   373  	deferred      func()                      // deferred cleanup actions.
   374  	archive       *txtar.Archive              // the testscript being run.
   375  	scriptFiles   map[string]string           // files stored in the txtar archive (absolute paths -> path in script)
   376  	scriptUpdates map[string]string           // updates to testscript files via UpdateScripts.
   377  
   378  	// runningBuiltin indicates if we are running a user-supplied builtin
   379  	// command. These commands are specified via Params.Cmds.
   380  	runningBuiltin bool
   381  
   382  	// builtinStd(out|err) are established if a user-supplied builtin command
   383  	// requests Stdout() or Stderr(). Either both are non-nil, or both are nil.
   384  	// This invariant is maintained by both setBuiltinStd() and
   385  	// clearBuiltinStd().
   386  	builtinStdout *strings.Builder
   387  	builtinStderr *strings.Builder
   388  
   389  	ctxt        context.Context // per TestScript context
   390  	gracePeriod time.Duration   // time between SIGQUIT and SIGKILL
   391  }
   392  
   393  type backgroundCmd struct {
   394  	name string
   395  	cmd  *exec.Cmd
   396  	wait <-chan struct{}
   397  	neg  bool // if true, cmd should fail
   398  }
   399  
   400  func writeFile(name string, data []byte, perm fs.FileMode, excl bool) error {
   401  	oflags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
   402  	if excl {
   403  		oflags |= os.O_EXCL
   404  	}
   405  	f, err := os.OpenFile(name, oflags, perm)
   406  	if err != nil {
   407  		return err
   408  	}
   409  	defer f.Close()
   410  	if _, err := f.Write(data); err != nil {
   411  		return fmt.Errorf("cannot write file contents: %v", err)
   412  	}
   413  	return nil
   414  }
   415  
   416  // Name returns the short name or basename of the test script.
   417  func (ts *TestScript) Name() string { return ts.name }
   418  
   419  // setup sets up the test execution temporary directory and environment.
   420  // It returns the comment section of the txtar archive.
   421  func (ts *TestScript) setup() string {
   422  	defer catchFailNow(func() {
   423  		// There's been a failure in setup; fail immediately regardless
   424  		// of the ContinueOnError flag.
   425  		ts.t.FailNow()
   426  	})
   427  	ts.workdir = filepath.Join(ts.testTempDir, "script-"+ts.name)
   428  
   429  	// Establish a temporary directory in workdir, but use a prefix that ensures
   430  	// this directory will not be walked when resolving the ./... pattern from
   431  	// workdir. This is important because when resolving a ./... pattern, cmd/go
   432  	// (which is used by go/packages) creates temporary build files and
   433  	// directories. This can, and does, therefore interfere with the ./...
   434  	// pattern when used from workdir and can lead to race conditions within
   435  	// cmd/go as it walks directories to match the ./... pattern.
   436  	tmpDir := filepath.Join(ts.workdir, ".tmp")
   437  
   438  	ts.Check(os.MkdirAll(tmpDir, 0o777))
   439  	env := &Env{
   440  		Vars: []string{
   441  			"WORK=" + ts.workdir, // must be first for ts.abbrev
   442  			"PATH=" + os.Getenv("PATH"),
   443  			"GOTRACEBACK=system",
   444  			homeEnvName() + "=/no-home",
   445  			tempEnvName() + "=" + tmpDir,
   446  			"devnull=" + os.DevNull,
   447  			"/=" + string(os.PathSeparator),
   448  			":=" + string(os.PathListSeparator),
   449  			"$=$",
   450  		},
   451  		WorkDir: ts.workdir,
   452  		Values:  make(map[interface{}]interface{}),
   453  		Cd:      ts.workdir,
   454  		ts:      ts,
   455  	}
   456  
   457  	// These env vars affect how a Go program behaves at run-time;
   458  	// If the user or `go test` wrapper set them, we should propagate them
   459  	// so that sub-process commands run via the test binary see them as well.
   460  	for _, name := range []string{
   461  		// If we are collecting coverage profiles, e.g. `go test -coverprofile`.
   462  		"GOCOVERDIR",
   463  		// If the user set GORACE when running a command like `go test -race`,
   464  		// such as GORACE=atexit_sleep_ms=10 to avoid the default 1s sleeps.
   465  		"GORACE",
   466  	} {
   467  		if val := os.Getenv(name); val != "" {
   468  			env.Vars = append(env.Vars, name+"="+val)
   469  		}
   470  	}
   471  	// Must preserve SYSTEMROOT on Windows: https://github.com/golang/go/issues/25513 et al
   472  	if runtime.GOOS == "windows" {
   473  		env.Vars = append(env.Vars,
   474  			"SYSTEMROOT="+os.Getenv("SYSTEMROOT"),
   475  			"exe=.exe",
   476  		)
   477  	} else {
   478  		env.Vars = append(env.Vars,
   479  			"exe=",
   480  		)
   481  	}
   482  	ts.cd = env.Cd
   483  	// Unpack archive.
   484  	a, err := txtar.ParseFile(ts.file)
   485  	ts.Check(err)
   486  	ts.archive = a
   487  	for _, f := range a.Files {
   488  		name := ts.MkAbs(ts.expand(f.Name))
   489  		ts.scriptFiles[name] = f.Name
   490  		ts.Check(os.MkdirAll(filepath.Dir(name), 0o777))
   491  		switch err := writeFile(name, f.Data, 0o666, ts.params.RequireUniqueNames); {
   492  		case ts.params.RequireUniqueNames && errors.Is(err, fs.ErrExist):
   493  			ts.Check(fmt.Errorf("%s would overwrite %s (because RequireUniqueNames is enabled)", f.Name, name))
   494  		default:
   495  			ts.Check(err)
   496  		}
   497  	}
   498  	// Run any user-defined setup.
   499  	if ts.params.Setup != nil {
   500  		ts.Check(ts.params.Setup(env))
   501  	}
   502  	ts.cd = env.Cd
   503  	ts.env = env.Vars
   504  	ts.values = env.Values
   505  
   506  	ts.envMap = make(map[string]string)
   507  	for _, kv := range ts.env {
   508  		if i := strings.Index(kv, "="); i >= 0 {
   509  			ts.envMap[envvarname(kv[:i])] = kv[i+1:]
   510  		}
   511  	}
   512  	return string(a.Comment)
   513  }
   514  
   515  // run runs the test script.
   516  func (ts *TestScript) run() {
   517  	// Truncate log at end of last phase marker,
   518  	// discarding details of successful phase.
   519  	verbose := ts.t.Verbose()
   520  	rewind := func() {
   521  		if !verbose {
   522  			ts.log.Truncate(ts.mark)
   523  		}
   524  	}
   525  
   526  	// Insert elapsed time for phase at end of phase marker
   527  	markTime := func() {
   528  		if ts.mark > 0 && !ts.start.IsZero() {
   529  			afterMark := append([]byte{}, ts.log.Bytes()[ts.mark:]...)
   530  			ts.log.Truncate(ts.mark - 1) // cut \n and afterMark
   531  			fmt.Fprintf(&ts.log, " (%.3fs)\n", timeSince(ts.start).Seconds())
   532  			ts.log.Write(afterMark)
   533  		}
   534  		ts.start = time.Time{}
   535  	}
   536  
   537  	failed := false
   538  	defer func() {
   539  		// On a normal exit from the test loop, background processes are cleaned up
   540  		// before we print PASS. If we return early (e.g., due to a test failure),
   541  		// don't print anything about the processes that were still running.
   542  		for _, bg := range ts.background {
   543  			interruptProcess(bg.cmd.Process)
   544  		}
   545  		if ts.t.Verbose() || failed {
   546  			// In verbose mode or on test failure, we want to see what happened in the background
   547  			// processes too.
   548  			ts.waitBackground(false)
   549  		} else {
   550  			for _, bg := range ts.background {
   551  				<-bg.wait
   552  			}
   553  			ts.background = nil
   554  		}
   555  
   556  		markTime()
   557  		// Flush testScript log to testing.T log.
   558  		ts.t.Log(ts.abbrev(ts.log.String()))
   559  	}()
   560  	defer func() {
   561  		ts.deferred()
   562  	}()
   563  	script := ts.setup()
   564  
   565  	// With -v or -testwork, start log with full environment.
   566  	if *testWork || (showVerboseEnv && ts.t.Verbose()) {
   567  		// Display environment.
   568  		ts.cmdEnv(false, nil)
   569  		fmt.Fprintf(&ts.log, "\n")
   570  		ts.mark = ts.log.Len()
   571  	}
   572  	defer ts.applyScriptUpdates()
   573  
   574  	// Run script.
   575  	// See testdata/script/README for documentation of script form.
   576  	for script != "" {
   577  		// Extract next line.
   578  		ts.lineno++
   579  		var line string
   580  		if i := strings.Index(script, "\n"); i >= 0 {
   581  			line, script = script[:i], script[i+1:]
   582  		} else {
   583  			line, script = script, ""
   584  		}
   585  
   586  		// # is a comment indicating the start of new phase.
   587  		if strings.HasPrefix(line, "#") {
   588  			// If there was a previous phase, it succeeded,
   589  			// so rewind the log to delete its details (unless -v is in use or
   590  			// ContinueOnError was enabled and there was a previous error,
   591  			// causing verbose to be set to true).
   592  			// If nothing has happened at all since the mark,
   593  			// rewinding is a no-op and adding elapsed time
   594  			// for doing nothing is meaningless, so don't.
   595  			if ts.log.Len() > ts.mark {
   596  				rewind()
   597  				markTime()
   598  			}
   599  			// Print phase heading and mark start of phase output.
   600  			fmt.Fprintf(&ts.log, "%s\n", line)
   601  			ts.mark = ts.log.Len()
   602  			ts.start = time.Now()
   603  			continue
   604  		}
   605  
   606  		ok := ts.runLine(line)
   607  		if !ok {
   608  			failed = true
   609  			if ts.params.ContinueOnError {
   610  				verbose = true
   611  			} else {
   612  				ts.t.FailNow()
   613  			}
   614  		}
   615  
   616  		// Command can ask script to stop early.
   617  		if ts.stopped {
   618  			// Break instead of returning, so that we check the status of any
   619  			// background processes and print PASS.
   620  			break
   621  		}
   622  	}
   623  
   624  	for _, bg := range ts.background {
   625  		interruptProcess(bg.cmd.Process)
   626  	}
   627  	ts.cmdWait(false, nil)
   628  
   629  	// If we reached here but we've failed (probably because ContinueOnError
   630  	// was set), don't wipe the log and print "PASS".
   631  	if failed {
   632  		ts.t.FailNow()
   633  	}
   634  
   635  	// Final phase ended.
   636  	rewind()
   637  	markTime()
   638  	if !ts.stopped {
   639  		fmt.Fprintf(&ts.log, "PASS\n")
   640  	}
   641  }
   642  
   643  func (ts *TestScript) runLine(line string) (runOK bool) {
   644  	defer catchFailNow(func() {
   645  		runOK = false
   646  	})
   647  
   648  	// Parse input line. Ignore blanks entirely.
   649  	args := ts.parse(line)
   650  	if len(args) == 0 {
   651  		return true
   652  	}
   653  
   654  	// Echo command to log.
   655  	fmt.Fprintf(&ts.log, "> %s\n", line)
   656  
   657  	// Command prefix [cond] means only run this command if cond is satisfied.
   658  	for strings.HasPrefix(args[0], "[") && strings.HasSuffix(args[0], "]") {
   659  		cond := args[0]
   660  		cond = cond[1 : len(cond)-1]
   661  		cond = strings.TrimSpace(cond)
   662  		args = args[1:]
   663  		if len(args) == 0 {
   664  			ts.Fatalf("missing command after condition")
   665  		}
   666  		want := true
   667  		if strings.HasPrefix(cond, "!") {
   668  			want = false
   669  			cond = strings.TrimSpace(cond[1:])
   670  		}
   671  		ok, err := ts.condition(cond)
   672  		if err != nil {
   673  			ts.Fatalf("bad condition %q: %v", cond, err)
   674  		}
   675  		if ok != want {
   676  			// Don't run rest of line.
   677  			return true
   678  		}
   679  	}
   680  
   681  	// Command prefix ! means negate the expectations about this command:
   682  	// go command should fail, match should not be found, etc.
   683  	neg := false
   684  	if args[0] == "!" {
   685  		neg = true
   686  		args = args[1:]
   687  		if len(args) == 0 {
   688  			ts.Fatalf("! on line by itself")
   689  		}
   690  	}
   691  
   692  	// Run command.
   693  	cmd := scriptCmds[args[0]]
   694  	if cmd == nil {
   695  		cmd = ts.params.Cmds[args[0]]
   696  	}
   697  	if cmd == nil {
   698  		// try to find spelling corrections. We arbitrarily limit the number of
   699  		// corrections, to not be too noisy.
   700  		switch c := ts.cmdSuggestions(args[0]); len(c) {
   701  		case 1:
   702  			ts.Fatalf("unknown command %q (did you mean %q?)", args[0], c[0])
   703  		case 2, 3, 4:
   704  			ts.Fatalf("unknown command %q (did you mean one of %q?)", args[0], c)
   705  		default:
   706  			ts.Fatalf("unknown command %q", args[0])
   707  		}
   708  	}
   709  	ts.callBuiltinCmd(args[0], func() {
   710  		cmd(ts, neg, args[1:])
   711  	})
   712  	return true
   713  }
   714  
   715  func (ts *TestScript) callBuiltinCmd(cmd string, runCmd func()) {
   716  	ts.runningBuiltin = true
   717  	defer func() {
   718  		r := recover()
   719  		ts.runningBuiltin = false
   720  		ts.clearBuiltinStd()
   721  		switch r {
   722  		case nil:
   723  			// we did not panic
   724  		default:
   725  			// re-"throw" the panic
   726  			panic(r)
   727  		}
   728  	}()
   729  	runCmd()
   730  }
   731  
   732  func (ts *TestScript) cmdSuggestions(name string) []string {
   733  	// special case: spell-correct `!cmd` to `! cmd`
   734  	if strings.HasPrefix(name, "!") {
   735  		if _, ok := scriptCmds[name[1:]]; ok {
   736  			return []string{"! " + name[1:]}
   737  		}
   738  		if _, ok := ts.params.Cmds[name[1:]]; ok {
   739  			return []string{"! " + name[1:]}
   740  		}
   741  	}
   742  	var candidates []string
   743  	for c := range scriptCmds {
   744  		if misspell.AlmostEqual(name, c) {
   745  			candidates = append(candidates, c)
   746  		}
   747  	}
   748  	for c := range ts.params.Cmds {
   749  		if misspell.AlmostEqual(name, c) {
   750  			candidates = append(candidates, c)
   751  		}
   752  	}
   753  	if len(candidates) == 0 {
   754  		return nil
   755  	}
   756  	// deduplicate candidates
   757  	// TODO: Use slices.Compact (and maybe slices.Sort) once we can use Go 1.21
   758  	sort.Strings(candidates)
   759  	out := candidates[:1]
   760  	for _, c := range candidates[1:] {
   761  		if out[len(out)-1] == c {
   762  			out = append(out, c)
   763  		}
   764  	}
   765  	return out
   766  }
   767  
   768  func (ts *TestScript) applyScriptUpdates() {
   769  	if len(ts.scriptUpdates) == 0 {
   770  		return
   771  	}
   772  	for name, content := range ts.scriptUpdates {
   773  		found := false
   774  		for i := range ts.archive.Files {
   775  			f := &ts.archive.Files[i]
   776  			if f.Name != name {
   777  				continue
   778  			}
   779  			data := []byte(content)
   780  			if txtar.NeedsQuote(data) {
   781  				data1, err := txtar.Quote(data)
   782  				if err != nil {
   783  					ts.Fatalf("cannot update script file %q: %v", f.Name, err)
   784  					continue
   785  				}
   786  				data = data1
   787  			}
   788  			f.Data = data
   789  			found = true
   790  		}
   791  		// Sanity check.
   792  		if !found {
   793  			panic("script update file not found")
   794  		}
   795  	}
   796  	if err := os.WriteFile(ts.file, txtar.Format(ts.archive), 0o666); err != nil {
   797  		ts.t.Fatal("cannot update script: ", err)
   798  	}
   799  	ts.Logf("%s updated", ts.file)
   800  }
   801  
   802  var failNow = errors.New("fail now!")
   803  
   804  // catchFailNow catches any panic from Fatalf and calls
   805  // f if it did so. It must be called in a defer.
   806  func catchFailNow(f func()) {
   807  	e := recover()
   808  	if e == nil {
   809  		return
   810  	}
   811  	if e != failNow {
   812  		panic(e)
   813  	}
   814  	f()
   815  }
   816  
   817  // condition reports whether the given condition is satisfied.
   818  func (ts *TestScript) condition(cond string) (bool, error) {
   819  	switch {
   820  	case cond == "short":
   821  		return testing.Short(), nil
   822  	case cond == "net":
   823  		return testenv.HasExternalNetwork(), nil
   824  	case cond == "link":
   825  		return testenv.HasLink(), nil
   826  	case cond == "symlink":
   827  		return testenv.HasSymlink(), nil
   828  	case imports.KnownOS[cond]:
   829  		return cond == runtime.GOOS, nil
   830  	case cond == "unix":
   831  		return imports.UnixOS[runtime.GOOS], nil
   832  	case imports.KnownArch[cond]:
   833  		return cond == runtime.GOARCH, nil
   834  	case strings.HasPrefix(cond, "exec:"):
   835  		prog := cond[len("exec:"):]
   836  		ok := execCache.Do(prog, func() interface{} {
   837  			_, err := execpath.Look(prog, ts.Getenv)
   838  			return err == nil
   839  		}).(bool)
   840  		return ok, nil
   841  	case cond == "gc" || cond == "gccgo":
   842  		// TODO this reflects the compiler that the current
   843  		// binary was built with but not necessarily the compiler
   844  		// that will be used.
   845  		return cond == runtime.Compiler, nil
   846  	case goVersionRegex.MatchString(cond):
   847  		for _, v := range build.Default.ReleaseTags {
   848  			if cond == v {
   849  				return true, nil
   850  			}
   851  		}
   852  		return false, nil
   853  	case ts.params.Condition != nil:
   854  		return ts.params.Condition(cond)
   855  	default:
   856  		ts.Fatalf("unknown condition %q", cond)
   857  		panic("unreachable")
   858  	}
   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.Replace(s, ts.workdir, "$WORK", -1)
   866  	if *testWork || ts.params.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  // Defer arranges for f to be called at the end
   875  // of the test. If Defer is called multiple times, the
   876  // defers are executed in reverse order (similar
   877  // to Go's defer statement)
   878  func (ts *TestScript) Defer(f func()) {
   879  	old := ts.deferred
   880  	ts.deferred = func() {
   881  		defer old()
   882  		f()
   883  	}
   884  }
   885  
   886  // Check calls ts.Fatalf if err != nil.
   887  func (ts *TestScript) Check(err error) {
   888  	if err != nil {
   889  		ts.Fatalf("%v", err)
   890  	}
   891  }
   892  
   893  // Stdout returns an io.Writer that can be used by a user-supplied builtin
   894  // command (delcared via Params.Cmds) to write to stdout. If this method is
   895  // called outside of the execution of a user-supplied builtin command, the
   896  // call panics.
   897  func (ts *TestScript) Stdout() io.Writer {
   898  	if !ts.runningBuiltin {
   899  		panic("can only call TestScript.Stdout when running a builtin command")
   900  	}
   901  	ts.setBuiltinStd()
   902  	return ts.builtinStdout
   903  }
   904  
   905  // Stderr returns an io.Writer that can be used by a user-supplied builtin
   906  // command (delcared via Params.Cmds) to write to stderr. If this method is
   907  // called outside of the execution of a user-supplied builtin command, the
   908  // call panics.
   909  func (ts *TestScript) Stderr() io.Writer {
   910  	if !ts.runningBuiltin {
   911  		panic("can only call TestScript.Stderr when running a builtin command")
   912  	}
   913  	ts.setBuiltinStd()
   914  	return ts.builtinStderr
   915  }
   916  
   917  // setBuiltinStd ensures that builtinStdout and builtinStderr are non nil.
   918  func (ts *TestScript) setBuiltinStd() {
   919  	// This method must maintain the invariant that both builtinStdout and
   920  	// builtinStderr are set or neither are set
   921  
   922  	// If both are set, nothing to do
   923  	if ts.builtinStdout != nil && ts.builtinStderr != nil {
   924  		return
   925  	}
   926  	ts.builtinStdout = new(strings.Builder)
   927  	ts.builtinStderr = new(strings.Builder)
   928  }
   929  
   930  // clearBuiltinStd sets ts.stdout and ts.stderr from the builtin command
   931  // buffers, logs both, and resets both builtinStdout and builtinStderr to nil.
   932  func (ts *TestScript) clearBuiltinStd() {
   933  	// This method must maintain the invariant that both builtinStdout and
   934  	// builtinStderr are set or neither are set
   935  
   936  	// If neither set, nothing to do
   937  	if ts.builtinStdout == nil && ts.builtinStderr == nil {
   938  		return
   939  	}
   940  	ts.stdout = ts.builtinStdout.String()
   941  	ts.builtinStdout = nil
   942  	ts.stderr = ts.builtinStderr.String()
   943  	ts.builtinStderr = nil
   944  	ts.logStd()
   945  }
   946  
   947  // Logf appends the given formatted message to the test log transcript.
   948  func (ts *TestScript) Logf(format string, args ...interface{}) {
   949  	format = strings.TrimSuffix(format, "\n")
   950  	fmt.Fprintf(&ts.log, format, args...)
   951  	ts.log.WriteByte('\n')
   952  }
   953  
   954  // exec runs the given command line (an actual subprocess, not simulated)
   955  // in ts.cd with environment ts.env and then returns collected standard output and standard error.
   956  func (ts *TestScript) exec(command string, args ...string) (stdout, stderr string, err error) {
   957  	cmd, err := ts.buildExecCmd(command, args...)
   958  	if err != nil {
   959  		return "", "", err
   960  	}
   961  	cmd.Dir = ts.cd
   962  	cmd.Env = append(ts.env, "PWD="+ts.cd)
   963  	cmd.Stdin = strings.NewReader(ts.stdin)
   964  	var stdoutBuf, stderrBuf strings.Builder
   965  	cmd.Stdout = &stdoutBuf
   966  	cmd.Stderr = &stderrBuf
   967  	if ts.ttyin != "" {
   968  		ctrl, tty, err := pty.Open()
   969  		if err != nil {
   970  			return "", "", err
   971  		}
   972  		doneR, doneW := make(chan struct{}), make(chan struct{})
   973  		var ptyBuf strings.Builder
   974  		go func() {
   975  			io.Copy(ctrl, strings.NewReader(ts.ttyin))
   976  			ctrl.Write([]byte{4 /* EOT */})
   977  			close(doneW)
   978  		}()
   979  		go func() {
   980  			io.Copy(&ptyBuf, ctrl)
   981  			close(doneR)
   982  		}()
   983  		defer func() {
   984  			tty.Close()
   985  			ctrl.Close()
   986  			<-doneR
   987  			<-doneW
   988  			ts.ttyin = ""
   989  			ts.ttyout = ptyBuf.String()
   990  		}()
   991  		pty.SetCtty(cmd, tty)
   992  		if ts.stdinPty {
   993  			cmd.Stdin = tty
   994  		}
   995  	}
   996  	if err = cmd.Start(); err == nil {
   997  		err = waitOrStop(ts.ctxt, cmd, ts.gracePeriod)
   998  	}
   999  	ts.stdin = ""
  1000  	ts.stdinPty = false
  1001  	return stdoutBuf.String(), stderrBuf.String(), err
  1002  }
  1003  
  1004  // execBackground starts the given command line (an actual subprocess, not simulated)
  1005  // in ts.cd with environment ts.env.
  1006  func (ts *TestScript) execBackground(command string, args ...string) (*exec.Cmd, error) {
  1007  	if ts.ttyin != "" {
  1008  		return nil, errors.New("ttyin is not supported by background commands")
  1009  	}
  1010  	cmd, err := ts.buildExecCmd(command, args...)
  1011  	if err != nil {
  1012  		return nil, err
  1013  	}
  1014  	cmd.Dir = ts.cd
  1015  	cmd.Env = append(ts.env, "PWD="+ts.cd)
  1016  	var stdoutBuf, stderrBuf strings.Builder
  1017  	cmd.Stdin = strings.NewReader(ts.stdin)
  1018  	cmd.Stdout = &stdoutBuf
  1019  	cmd.Stderr = &stderrBuf
  1020  	ts.stdin = ""
  1021  	return cmd, cmd.Start()
  1022  }
  1023  
  1024  func (ts *TestScript) buildExecCmd(command string, args ...string) (*exec.Cmd, error) {
  1025  	if filepath.Base(command) == command {
  1026  		if lp, err := execpath.Look(command, ts.Getenv); err != nil {
  1027  			return nil, err
  1028  		} else {
  1029  			command = lp
  1030  		}
  1031  	}
  1032  	return exec.Command(command, args...), nil
  1033  }
  1034  
  1035  // BackgroundCmds returns a slice containing all the commands that have
  1036  // been started in the background since the most recent wait command, or
  1037  // the start of the script if wait has not been called.
  1038  func (ts *TestScript) BackgroundCmds() []*exec.Cmd {
  1039  	cmds := make([]*exec.Cmd, len(ts.background))
  1040  	for i, b := range ts.background {
  1041  		cmds[i] = b.cmd
  1042  	}
  1043  	return cmds
  1044  }
  1045  
  1046  // waitOrStop waits for the already-started command cmd by calling its Wait method.
  1047  //
  1048  // If cmd does not return before ctx is done, waitOrStop sends it an interrupt
  1049  // signal. If killDelay is positive, waitOrStop waits that additional period for
  1050  // Wait to return before sending os.Kill.
  1051  func waitOrStop(ctx context.Context, cmd *exec.Cmd, killDelay time.Duration) error {
  1052  	if cmd.Process == nil {
  1053  		panic("waitOrStop called with a nil cmd.Process — missing Start call?")
  1054  	}
  1055  
  1056  	errc := make(chan error)
  1057  	go func() {
  1058  		select {
  1059  		case errc <- nil:
  1060  			return
  1061  		case <-ctx.Done():
  1062  		}
  1063  
  1064  		var interrupt os.Signal = syscall.SIGQUIT
  1065  		if runtime.GOOS == "windows" {
  1066  			// Per https://golang.org/pkg/os/#Signal, “Interrupt is not implemented on
  1067  			// Windows; using it with os.Process.Signal will return an error.”
  1068  			// Fall back directly to Kill instead.
  1069  			interrupt = os.Kill
  1070  		}
  1071  
  1072  		err := cmd.Process.Signal(interrupt)
  1073  		if err == nil {
  1074  			err = ctx.Err() // Report ctx.Err() as the reason we interrupted.
  1075  		} else if err == os.ErrProcessDone {
  1076  			errc <- nil
  1077  			return
  1078  		}
  1079  
  1080  		if killDelay > 0 {
  1081  			timer := time.NewTimer(killDelay)
  1082  			select {
  1083  			// Report ctx.Err() as the reason we interrupted the process...
  1084  			case errc <- ctx.Err():
  1085  				timer.Stop()
  1086  				return
  1087  			// ...but after killDelay has elapsed, fall back to a stronger signal.
  1088  			case <-timer.C:
  1089  			}
  1090  
  1091  			// Wait still hasn't returned.
  1092  			// Kill the process harder to make sure that it exits.
  1093  			//
  1094  			// Ignore any error: if cmd.Process has already terminated, we still
  1095  			// want to send ctx.Err() (or the error from the Interrupt call)
  1096  			// to properly attribute the signal that may have terminated it.
  1097  			_ = cmd.Process.Kill()
  1098  		}
  1099  
  1100  		errc <- err
  1101  	}()
  1102  
  1103  	waitErr := cmd.Wait()
  1104  	if interruptErr := <-errc; interruptErr != nil {
  1105  		return interruptErr
  1106  	}
  1107  	return waitErr
  1108  }
  1109  
  1110  // interruptProcess sends os.Interrupt to p if supported, or os.Kill otherwise.
  1111  func interruptProcess(p *os.Process) {
  1112  	if err := p.Signal(os.Interrupt); err != nil {
  1113  		// Per https://golang.org/pkg/os/#Signal, “Interrupt is not implemented on
  1114  		// Windows; using it with os.Process.Signal will return an error.”
  1115  		// Fall back to Kill instead.
  1116  		p.Kill()
  1117  	}
  1118  }
  1119  
  1120  // Exec runs the given command and saves its stdout and stderr so
  1121  // they can be inspected by subsequent script commands.
  1122  func (ts *TestScript) Exec(command string, args ...string) error {
  1123  	var err error
  1124  	ts.stdout, ts.stderr, err = ts.exec(command, args...)
  1125  	ts.logStd()
  1126  	return err
  1127  }
  1128  
  1129  // logStd logs the current non-empty values of stdout and stderr.
  1130  func (ts *TestScript) logStd() {
  1131  	if ts.stdout != "" {
  1132  		ts.Logf("[stdout]\n%s", ts.stdout)
  1133  	}
  1134  	if ts.stderr != "" {
  1135  		ts.Logf("[stderr]\n%s", ts.stderr)
  1136  	}
  1137  }
  1138  
  1139  // expand applies environment variable expansion to the string s.
  1140  func (ts *TestScript) expand(s string) string {
  1141  	return os.Expand(s, func(key string) string {
  1142  		if key1 := strings.TrimSuffix(key, "@R"); len(key1) != len(key) {
  1143  			return regexp.QuoteMeta(ts.Getenv(key1))
  1144  		}
  1145  		return ts.Getenv(key)
  1146  	})
  1147  }
  1148  
  1149  // fatalf aborts the test with the given failure message.
  1150  func (ts *TestScript) Fatalf(format string, args ...interface{}) {
  1151  	// In user-supplied builtins, the only way we have of aborting
  1152  	// is via Fatalf. Hence if we are aborting from a user-supplied
  1153  	// builtin, it's important we first log stdout and stderr. If
  1154  	// we are not, the following call is a no-op.
  1155  	ts.clearBuiltinStd()
  1156  
  1157  	fmt.Fprintf(&ts.log, "FAIL: %s:%d: %s\n", ts.file, ts.lineno, fmt.Sprintf(format, args...))
  1158  	// This should be caught by the defer inside the TestScript.runLine method.
  1159  	// We do this rather than calling ts.t.FailNow directly because we want to
  1160  	// be able to continue on error when Params.ContinueOnError is set.
  1161  	panic(failNow)
  1162  }
  1163  
  1164  // MkAbs interprets file relative to the test script's current directory
  1165  // and returns the corresponding absolute path.
  1166  func (ts *TestScript) MkAbs(file string) string {
  1167  	if filepath.IsAbs(file) {
  1168  		return file
  1169  	}
  1170  	return filepath.Join(ts.cd, file)
  1171  }
  1172  
  1173  // ReadFile returns the contents of the file with the
  1174  // given name, intepreted relative to the test script's
  1175  // current directory. It interprets "stdout" and "stderr" to
  1176  // mean the standard output or standard error from
  1177  // the most recent exec or wait command respectively.
  1178  //
  1179  // If the file cannot be read, the script fails.
  1180  func (ts *TestScript) ReadFile(file string) string {
  1181  	switch file {
  1182  	case "stdout":
  1183  		return ts.stdout
  1184  	case "stderr":
  1185  		return ts.stderr
  1186  	case "ttyout":
  1187  		return ts.ttyout
  1188  	default:
  1189  		file = ts.MkAbs(file)
  1190  		data, err := os.ReadFile(file)
  1191  		ts.Check(err)
  1192  		return string(data)
  1193  	}
  1194  }
  1195  
  1196  // Setenv sets the value of the environment variable named by the key.
  1197  func (ts *TestScript) Setenv(key, value string) {
  1198  	ts.env = append(ts.env, key+"="+value)
  1199  	ts.envMap[envvarname(key)] = value
  1200  }
  1201  
  1202  // Getenv gets the value of the environment variable named by the key.
  1203  func (ts *TestScript) Getenv(key string) string {
  1204  	return ts.envMap[envvarname(key)]
  1205  }
  1206  
  1207  // parse parses a single line as a list of space-separated arguments
  1208  // subject to environment variable expansion (but not resplitting).
  1209  // Single quotes around text disable splitting and expansion.
  1210  // To embed a single quote, double it: 'Don”t communicate by sharing memory.'
  1211  func (ts *TestScript) parse(line string) []string {
  1212  	ts.line = line
  1213  
  1214  	var (
  1215  		args   []string
  1216  		arg    string  // text of current arg so far (need to add line[start:i])
  1217  		start  = -1    // if >= 0, position where current arg text chunk starts
  1218  		quoted = false // currently processing quoted text
  1219  	)
  1220  	for i := 0; ; i++ {
  1221  		if !quoted && (i >= len(line) || line[i] == ' ' || line[i] == '\t' || line[i] == '\r' || line[i] == '#') {
  1222  			// Found arg-separating space.
  1223  			if start >= 0 {
  1224  				arg += ts.expand(line[start:i])
  1225  				args = append(args, arg)
  1226  				start = -1
  1227  				arg = ""
  1228  			}
  1229  			if i >= len(line) || line[i] == '#' {
  1230  				break
  1231  			}
  1232  			continue
  1233  		}
  1234  		if i >= len(line) {
  1235  			ts.Fatalf("unterminated quoted argument")
  1236  		}
  1237  		if line[i] == '\'' {
  1238  			if !quoted {
  1239  				// starting a quoted chunk
  1240  				if start >= 0 {
  1241  					arg += ts.expand(line[start:i])
  1242  				}
  1243  				start = i + 1
  1244  				quoted = true
  1245  				continue
  1246  			}
  1247  			// 'foo''bar' means foo'bar, like in rc shell and Pascal.
  1248  			if i+1 < len(line) && line[i+1] == '\'' {
  1249  				arg += line[start:i]
  1250  				start = i + 1
  1251  				i++ // skip over second ' before next iteration
  1252  				continue
  1253  			}
  1254  			// ending a quoted chunk
  1255  			arg += line[start:i]
  1256  			start = i + 1
  1257  			quoted = false
  1258  			continue
  1259  		}
  1260  		// found character worth saving; make sure we're saving
  1261  		if start < 0 {
  1262  			start = i
  1263  		}
  1264  	}
  1265  	return args
  1266  }
  1267  
  1268  func removeAll(dir string) error {
  1269  	// module cache has 0o444 directories;
  1270  	// make them writable in order to remove content.
  1271  	filepath.WalkDir(dir, func(path string, entry fs.DirEntry, err error) error {
  1272  		if err != nil {
  1273  			return nil // ignore errors walking in file system
  1274  		}
  1275  		if entry.IsDir() {
  1276  			os.Chmod(path, 0o777)
  1277  		}
  1278  		return nil
  1279  	})
  1280  	return os.RemoveAll(dir)
  1281  }
  1282  
  1283  func homeEnvName() string {
  1284  	switch runtime.GOOS {
  1285  	case "windows":
  1286  		return "USERPROFILE"
  1287  	case "plan9":
  1288  		return "home"
  1289  	default:
  1290  		return "HOME"
  1291  	}
  1292  }
  1293  
  1294  func tempEnvName() string {
  1295  	switch runtime.GOOS {
  1296  	case "windows":
  1297  		return "TMP"
  1298  	case "plan9":
  1299  		return "TMPDIR" // actually plan 9 doesn't have one at all but this is fine
  1300  	default:
  1301  		return "TMPDIR"
  1302  	}
  1303  }
  1304  

View as plain text