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 testscript 9 10import ( 11 "bytes" 12 "context" 13 "flag" 14 "fmt" 15 "io/ioutil" 16 "os" 17 "os/exec" 18 "path/filepath" 19 "regexp" 20 "runtime" 21 "strings" 22 "sync/atomic" 23 "testing" 24 "time" 25 26 "github.com/rogpeppe/go-internal/imports" 27 "github.com/rogpeppe/go-internal/internal/os/execpath" 28 "github.com/rogpeppe/go-internal/par" 29 "github.com/rogpeppe/go-internal/testenv" 30 "github.com/rogpeppe/go-internal/txtar" 31) 32 33var execCache par.Cache 34 35// If -testwork is specified, the test prints the name of the temp directory 36// and does not remove it when done, so that a programmer can 37// poke at the test file tree afterward. 38var testWork = flag.Bool("testwork", false, "") 39 40// Env holds the environment to use at the start of a test script invocation. 41type Env struct { 42 // WorkDir holds the path to the root directory of the 43 // extracted files. 44 WorkDir string 45 // Vars holds the initial set environment variables that will be passed to the 46 // testscript commands. 47 Vars []string 48 // Cd holds the initial current working directory. 49 Cd string 50 // Values holds a map of arbitrary values for use by custom 51 // testscript commands. This enables Setup to pass arbitrary 52 // values (not just strings) through to custom commands. 53 Values map[interface{}]interface{} 54 55 ts *TestScript 56} 57 58// Value returns a value from Env.Values, or nil if no 59// value was set by Setup. 60func (ts *TestScript) Value(key interface{}) interface{} { 61 return ts.values[key] 62} 63 64// Defer arranges for f to be called at the end 65// of the test. If Defer is called multiple times, the 66// defers are executed in reverse order (similar 67// to Go's defer statement) 68func (e *Env) Defer(f func()) { 69 e.ts.Defer(f) 70} 71 72// Getenv retrieves the value of the environment variable named by the key. It 73// returns the value, which will be empty if the variable is not present. 74func (e *Env) Getenv(key string) string { 75 key = envvarname(key) 76 for i := len(e.Vars) - 1; i >= 0; i-- { 77 if pair := strings.SplitN(e.Vars[i], "=", 2); len(pair) == 2 && envvarname(pair[0]) == key { 78 return pair[1] 79 } 80 } 81 return "" 82} 83 84// Setenv sets the value of the environment variable named by the key. It 85// panics if key is invalid. 86func (e *Env) Setenv(key, value string) { 87 if key == "" || strings.IndexByte(key, '=') != -1 { 88 panic(fmt.Errorf("invalid environment variable key %q", key)) 89 } 90 e.Vars = append(e.Vars, key+"="+value) 91} 92 93// T returns the t argument passed to the current test by the T.Run method. 94// Note that if the tests were started by calling Run, 95// the returned value will implement testing.TB. 96// Note that, despite that, the underlying value will not be of type 97// *testing.T because *testing.T does not implement T. 98// 99// If Cleanup is called on the returned value, the function will run 100// after any functions passed to Env.Defer. 101func (e *Env) T() T { 102 return e.ts.t 103} 104 105// Params holds parameters for a call to Run. 106type Params struct { 107 // Dir holds the name of the directory holding the scripts. 108 // All files in the directory with a .txt suffix will be considered 109 // as test scripts. By default the current directory is used. 110 // Dir is interpreted relative to the current test directory. 111 Dir string 112 113 // Setup is called, if not nil, to complete any setup required 114 // for a test. The WorkDir and Vars fields will have already 115 // been initialized and all the files extracted into WorkDir, 116 // and Cd will be the same as WorkDir. 117 // The Setup function may modify Vars and Cd as it wishes. 118 Setup func(*Env) error 119 120 // Condition is called, if not nil, to determine whether a particular 121 // condition is true. It's called only for conditions not in the 122 // standard set, and may be nil. 123 Condition func(cond string) (bool, error) 124 125 // Cmds holds a map of commands available to the script. 126 // It will only be consulted for commands not part of the standard set. 127 Cmds map[string]func(ts *TestScript, neg bool, args []string) 128 129 // TestWork specifies that working directories should be 130 // left intact for later inspection. 131 TestWork bool 132 133 // WorkdirRoot specifies the directory within which scripts' work 134 // directories will be created. Setting WorkdirRoot implies TestWork=true. 135 // If empty, the work directories will be created inside 136 // $GOTMPDIR/go-test-script*, where $GOTMPDIR defaults to os.TempDir(). 137 WorkdirRoot string 138 139 // IgnoreMissedCoverage specifies that if coverage information 140 // is being generated (with the -test.coverprofile flag) and a subcommand 141 // function passed to RunMain fails to generate coverage information 142 // (for example because the function invoked os.Exit), then the 143 // error will be ignored. 144 IgnoreMissedCoverage bool 145 146 // UpdateScripts specifies that if a `cmp` command fails and its second 147 // argument refers to a file inside the testscript file, the command will 148 // succeed and the testscript file will be updated to reflect the actual 149 // content (which could be stdout, stderr or a real file). 150 // 151 // The content will be quoted with txtar.Quote if needed; 152 // a manual change will be needed if it is not unquoted in the 153 // script. 154 UpdateScripts bool 155} 156 157// RunDir runs the tests in the given directory. All files in dir with a ".txt" 158// are considered to be test files. 159func Run(t *testing.T, p Params) { 160 RunT(tshim{t}, p) 161} 162 163// T holds all the methods of the *testing.T type that 164// are used by testscript. 165type T interface { 166 Skip(...interface{}) 167 Fatal(...interface{}) 168 Parallel() 169 Log(...interface{}) 170 FailNow() 171 Run(string, func(T)) 172 // Verbose is usually implemented by the testing package 173 // directly rather than on the *testing.T type. 174 Verbose() bool 175} 176 177type tshim struct { 178 *testing.T 179} 180 181func (t tshim) Run(name string, f func(T)) { 182 t.T.Run(name, func(t *testing.T) { 183 f(tshim{t}) 184 }) 185} 186 187func (t tshim) Verbose() bool { 188 return testing.Verbose() 189} 190 191// RunT is like Run but uses an interface type instead of the concrete *testing.T 192// type to make it possible to use testscript functionality outside of go test. 193func RunT(t T, p Params) { 194 glob := filepath.Join(p.Dir, "*.txt") 195 files, err := filepath.Glob(glob) 196 if err != nil { 197 t.Fatal(err) 198 } 199 if len(files) == 0 { 200 t.Fatal(fmt.Sprintf("no scripts found matching glob: %v", glob)) 201 } 202 testTempDir := p.WorkdirRoot 203 if testTempDir == "" { 204 testTempDir, err = ioutil.TempDir(os.Getenv("GOTMPDIR"), "go-test-script") 205 if err != nil { 206 t.Fatal(err) 207 } 208 } else { 209 p.TestWork = true 210 } 211 // The temp dir returned by ioutil.TempDir might be a sym linked dir (default 212 // behaviour in macOS). That could mess up matching that includes $WORK if, 213 // for example, an external program outputs resolved paths. Evaluating the 214 // dir here will ensure consistency. 215 testTempDir, err = filepath.EvalSymlinks(testTempDir) 216 if err != nil { 217 t.Fatal(err) 218 } 219 refCount := int32(len(files)) 220 for _, file := range files { 221 file := file 222 name := strings.TrimSuffix(filepath.Base(file), ".txt") 223 t.Run(name, func(t T) { 224 t.Parallel() 225 ts := &TestScript{ 226 t: t, 227 testTempDir: testTempDir, 228 name: name, 229 file: file, 230 params: p, 231 ctxt: context.Background(), 232 deferred: func() {}, 233 scriptFiles: make(map[string]string), 234 scriptUpdates: make(map[string]string), 235 } 236 defer func() { 237 if p.TestWork || *testWork { 238 return 239 } 240 removeAll(ts.workdir) 241 if atomic.AddInt32(&refCount, -1) == 0 { 242 // This is the last subtest to finish. Remove the 243 // parent directory too. 244 os.Remove(testTempDir) 245 } 246 }() 247 ts.run() 248 }) 249 } 250} 251 252// A TestScript holds execution state for a single test script. 253type TestScript struct { 254 params Params 255 t T 256 testTempDir string 257 workdir string // temporary work dir ($WORK) 258 log bytes.Buffer // test execution log (printed at end of test) 259 mark int // offset of next log truncation 260 cd string // current directory during test execution; initially $WORK/gopath/src 261 name string // short name of test ("foo") 262 file string // full file name ("testdata/script/foo.txt") 263 lineno int // line number currently executing 264 line string // line currently executing 265 env []string // environment list (for os/exec) 266 envMap map[string]string // environment mapping (matches env; on Windows keys are lowercase) 267 values map[interface{}]interface{} // values for custom commands 268 stdin string // standard input to next 'go' command; set by 'stdin' command. 269 stdout string // standard output from last 'go' command; for 'stdout' command 270 stderr string // standard error from last 'go' command; for 'stderr' command 271 stopped bool // test wants to stop early 272 start time.Time // time phase started 273 background []backgroundCmd // backgrounded 'exec' and 'go' commands 274 deferred func() // deferred cleanup actions. 275 archive *txtar.Archive // the testscript being run. 276 scriptFiles map[string]string // files stored in the txtar archive (absolute paths -> path in script) 277 scriptUpdates map[string]string // updates to testscript files via UpdateScripts. 278 279 ctxt context.Context // per TestScript context 280} 281 282type backgroundCmd struct { 283 cmd *exec.Cmd 284 wait <-chan struct{} 285 neg bool // if true, cmd should fail 286} 287 288// setup sets up the test execution temporary directory and environment. 289// It returns the comment section of the txtar archive. 290func (ts *TestScript) setup() string { 291 ts.workdir = filepath.Join(ts.testTempDir, "script-"+ts.name) 292 293 // Establish a temporary directory in workdir, but use a prefix that ensures 294 // this directory will not be walked when resolving the ./... pattern from 295 // workdir. This is important because when resolving a ./... pattern, cmd/go 296 // (which is used by go/packages) creates temporary build files and 297 // directories. This can, and does, therefore interfere with the ./... 298 // pattern when used from workdir and can lead to race conditions within 299 // cmd/go as it walks directories to match the ./... pattern. 300 tmpDir := filepath.Join(ts.workdir, ".tmp") 301 302 ts.Check(os.MkdirAll(tmpDir, 0777)) 303 env := &Env{ 304 Vars: []string{ 305 "WORK=" + ts.workdir, // must be first for ts.abbrev 306 "PATH=" + os.Getenv("PATH"), 307 homeEnvName() + "=/no-home", 308 tempEnvName() + "=" + tmpDir, 309 "devnull=" + os.DevNull, 310 "/=" + string(os.PathSeparator), 311 ":=" + string(os.PathListSeparator), 312 "$=$", 313 314 // If we are collecting coverage profiles for merging into the main one, 315 // ensure the environment variable is forwarded to sub-processes. 316 "TESTSCRIPT_COVER_DIR=" + os.Getenv("TESTSCRIPT_COVER_DIR"), 317 }, 318 WorkDir: ts.workdir, 319 Values: make(map[interface{}]interface{}), 320 Cd: ts.workdir, 321 ts: ts, 322 } 323 // Must preserve SYSTEMROOT on Windows: https://github.com/golang/go/issues/25513 et al 324 if runtime.GOOS == "windows" { 325 env.Vars = append(env.Vars, 326 "SYSTEMROOT="+os.Getenv("SYSTEMROOT"), 327 "exe=.exe", 328 ) 329 } else { 330 env.Vars = append(env.Vars, 331 "exe=", 332 ) 333 } 334 ts.cd = env.Cd 335 // Unpack archive. 336 a, err := txtar.ParseFile(ts.file) 337 ts.Check(err) 338 ts.archive = a 339 for _, f := range a.Files { 340 name := ts.MkAbs(ts.expand(f.Name)) 341 ts.scriptFiles[name] = f.Name 342 ts.Check(os.MkdirAll(filepath.Dir(name), 0777)) 343 ts.Check(ioutil.WriteFile(name, f.Data, 0666)) 344 } 345 // Run any user-defined setup. 346 if ts.params.Setup != nil { 347 ts.Check(ts.params.Setup(env)) 348 } 349 ts.cd = env.Cd 350 ts.env = env.Vars 351 ts.values = env.Values 352 353 ts.envMap = make(map[string]string) 354 for _, kv := range ts.env { 355 if i := strings.Index(kv, "="); i >= 0 { 356 ts.envMap[envvarname(kv[:i])] = kv[i+1:] 357 } 358 } 359 return string(a.Comment) 360} 361 362// run runs the test script. 363func (ts *TestScript) run() { 364 // Truncate log at end of last phase marker, 365 // discarding details of successful phase. 366 rewind := func() { 367 if !ts.t.Verbose() { 368 ts.log.Truncate(ts.mark) 369 } 370 } 371 372 // Insert elapsed time for phase at end of phase marker 373 markTime := func() { 374 if ts.mark > 0 && !ts.start.IsZero() { 375 afterMark := append([]byte{}, ts.log.Bytes()[ts.mark:]...) 376 ts.log.Truncate(ts.mark - 1) // cut \n and afterMark 377 fmt.Fprintf(&ts.log, " (%.3fs)\n", time.Since(ts.start).Seconds()) 378 ts.log.Write(afterMark) 379 } 380 ts.start = time.Time{} 381 } 382 383 defer func() { 384 // On a normal exit from the test loop, background processes are cleaned up 385 // before we print PASS. If we return early (e.g., due to a test failure), 386 // don't print anything about the processes that were still running. 387 for _, bg := range ts.background { 388 interruptProcess(bg.cmd.Process) 389 } 390 for _, bg := range ts.background { 391 <-bg.wait 392 } 393 ts.background = nil 394 395 markTime() 396 // Flush testScript log to testing.T log. 397 ts.t.Log("\n" + ts.abbrev(ts.log.String())) 398 }() 399 defer func() { 400 ts.deferred() 401 }() 402 script := ts.setup() 403 404 // With -v or -testwork, start log with full environment. 405 if *testWork || ts.t.Verbose() { 406 // Display environment. 407 ts.cmdEnv(false, nil) 408 fmt.Fprintf(&ts.log, "\n") 409 ts.mark = ts.log.Len() 410 } 411 defer ts.applyScriptUpdates() 412 413 // Run script. 414 // See testdata/script/README for documentation of script form. 415Script: 416 for script != "" { 417 // Extract next line. 418 ts.lineno++ 419 var line string 420 if i := strings.Index(script, "\n"); i >= 0 { 421 line, script = script[:i], script[i+1:] 422 } else { 423 line, script = script, "" 424 } 425 426 // # is a comment indicating the start of new phase. 427 if strings.HasPrefix(line, "#") { 428 // If there was a previous phase, it succeeded, 429 // so rewind the log to delete its details (unless -v is in use). 430 // If nothing has happened at all since the mark, 431 // rewinding is a no-op and adding elapsed time 432 // for doing nothing is meaningless, so don't. 433 if ts.log.Len() > ts.mark { 434 rewind() 435 markTime() 436 } 437 // Print phase heading and mark start of phase output. 438 fmt.Fprintf(&ts.log, "%s\n", line) 439 ts.mark = ts.log.Len() 440 ts.start = time.Now() 441 continue 442 } 443 444 // Parse input line. Ignore blanks entirely. 445 args := ts.parse(line) 446 if len(args) == 0 { 447 continue 448 } 449 450 // Echo command to log. 451 fmt.Fprintf(&ts.log, "> %s\n", line) 452 453 // Command prefix [cond] means only run this command if cond is satisfied. 454 for strings.HasPrefix(args[0], "[") && strings.HasSuffix(args[0], "]") { 455 cond := args[0] 456 cond = cond[1 : len(cond)-1] 457 cond = strings.TrimSpace(cond) 458 args = args[1:] 459 if len(args) == 0 { 460 ts.Fatalf("missing command after condition") 461 } 462 want := true 463 if strings.HasPrefix(cond, "!") { 464 want = false 465 cond = strings.TrimSpace(cond[1:]) 466 } 467 ok, err := ts.condition(cond) 468 if err != nil { 469 ts.Fatalf("bad condition %q: %v", cond, err) 470 } 471 if ok != want { 472 // Don't run rest of line. 473 continue Script 474 } 475 } 476 477 // Command prefix ! means negate the expectations about this command: 478 // go command should fail, match should not be found, etc. 479 neg := false 480 if args[0] == "!" { 481 neg = true 482 args = args[1:] 483 if len(args) == 0 { 484 ts.Fatalf("! on line by itself") 485 } 486 } 487 488 // Run command. 489 cmd := scriptCmds[args[0]] 490 if cmd == nil { 491 cmd = ts.params.Cmds[args[0]] 492 } 493 if cmd == nil { 494 ts.Fatalf("unknown command %q", args[0]) 495 } 496 cmd(ts, neg, args[1:]) 497 498 // Command can ask script to stop early. 499 if ts.stopped { 500 // Break instead of returning, so that we check the status of any 501 // background processes and print PASS. 502 break 503 } 504 } 505 506 for _, bg := range ts.background { 507 interruptProcess(bg.cmd.Process) 508 } 509 ts.cmdWait(false, nil) 510 511 // Final phase ended. 512 rewind() 513 markTime() 514 if !ts.stopped { 515 fmt.Fprintf(&ts.log, "PASS\n") 516 } 517} 518 519func (ts *TestScript) applyScriptUpdates() { 520 if len(ts.scriptUpdates) == 0 { 521 return 522 } 523 for name, content := range ts.scriptUpdates { 524 found := false 525 for i := range ts.archive.Files { 526 f := &ts.archive.Files[i] 527 if f.Name != name { 528 continue 529 } 530 data := []byte(content) 531 if txtar.NeedsQuote(data) { 532 data1, err := txtar.Quote(data) 533 if err != nil { 534 ts.t.Fatal(fmt.Sprintf("cannot update script file %q: %v", f.Name, err)) 535 continue 536 } 537 data = data1 538 } 539 f.Data = data 540 found = true 541 } 542 // Sanity check. 543 if !found { 544 panic("script update file not found") 545 } 546 } 547 if err := ioutil.WriteFile(ts.file, txtar.Format(ts.archive), 0666); err != nil { 548 ts.t.Fatal("cannot update script: ", err) 549 } 550 ts.Logf("%s updated", ts.file) 551} 552 553// condition reports whether the given condition is satisfied. 554func (ts *TestScript) condition(cond string) (bool, error) { 555 switch cond { 556 case "short": 557 return testing.Short(), nil 558 case "net": 559 return testenv.HasExternalNetwork(), nil 560 case "link": 561 return testenv.HasLink(), nil 562 case "symlink": 563 return testenv.HasSymlink(), nil 564 case runtime.GOOS, runtime.GOARCH: 565 return true, nil 566 default: 567 if imports.KnownArch[cond] || imports.KnownOS[cond] { 568 return false, nil 569 } 570 if strings.HasPrefix(cond, "exec:") { 571 prog := cond[len("exec:"):] 572 ok := execCache.Do(prog, func() interface{} { 573 _, err := execpath.Look(prog, ts.Getenv) 574 return err == nil 575 }).(bool) 576 return ok, nil 577 } 578 if ts.params.Condition != nil { 579 return ts.params.Condition(cond) 580 } 581 ts.Fatalf("unknown condition %q", cond) 582 panic("unreachable") 583 } 584} 585 586// Helpers for command implementations. 587 588// abbrev abbreviates the actual work directory in the string s to the literal string "$WORK". 589func (ts *TestScript) abbrev(s string) string { 590 s = strings.Replace(s, ts.workdir, "$WORK", -1) 591 if *testWork || ts.params.TestWork { 592 // Expose actual $WORK value in environment dump on first line of work script, 593 // so that the user can find out what directory -testwork left behind. 594 s = "WORK=" + ts.workdir + "\n" + strings.TrimPrefix(s, "WORK=$WORK\n") 595 } 596 return s 597} 598 599// Defer arranges for f to be called at the end 600// of the test. If Defer is called multiple times, the 601// defers are executed in reverse order (similar 602// to Go's defer statement) 603func (ts *TestScript) Defer(f func()) { 604 old := ts.deferred 605 ts.deferred = func() { 606 defer old() 607 f() 608 } 609} 610 611// Check calls ts.Fatalf if err != nil. 612func (ts *TestScript) Check(err error) { 613 if err != nil { 614 ts.Fatalf("%v", err) 615 } 616} 617 618// Logf appends the given formatted message to the test log transcript. 619func (ts *TestScript) Logf(format string, args ...interface{}) { 620 format = strings.TrimSuffix(format, "\n") 621 fmt.Fprintf(&ts.log, format, args...) 622 ts.log.WriteByte('\n') 623} 624 625// exec runs the given command line (an actual subprocess, not simulated) 626// in ts.cd with environment ts.env and then returns collected standard output and standard error. 627func (ts *TestScript) exec(command string, args ...string) (stdout, stderr string, err error) { 628 cmd, err := ts.buildExecCmd(command, args...) 629 if err != nil { 630 return "", "", err 631 } 632 cmd.Dir = ts.cd 633 cmd.Env = append(ts.env, "PWD="+ts.cd) 634 cmd.Stdin = strings.NewReader(ts.stdin) 635 var stdoutBuf, stderrBuf strings.Builder 636 cmd.Stdout = &stdoutBuf 637 cmd.Stderr = &stderrBuf 638 if err = cmd.Start(); err == nil { 639 err = ctxWait(ts.ctxt, cmd) 640 } 641 ts.stdin = "" 642 return stdoutBuf.String(), stderrBuf.String(), err 643} 644 645// execBackground starts the given command line (an actual subprocess, not simulated) 646// in ts.cd with environment ts.env. 647func (ts *TestScript) execBackground(command string, args ...string) (*exec.Cmd, error) { 648 cmd, err := ts.buildExecCmd(command, args...) 649 if err != nil { 650 return nil, err 651 } 652 cmd.Dir = ts.cd 653 cmd.Env = append(ts.env, "PWD="+ts.cd) 654 var stdoutBuf, stderrBuf strings.Builder 655 cmd.Stdin = strings.NewReader(ts.stdin) 656 cmd.Stdout = &stdoutBuf 657 cmd.Stderr = &stderrBuf 658 ts.stdin = "" 659 return cmd, cmd.Start() 660} 661 662func (ts *TestScript) buildExecCmd(command string, args ...string) (*exec.Cmd, error) { 663 if filepath.Base(command) == command { 664 if lp, err := execpath.Look(command, ts.Getenv); err != nil { 665 return nil, err 666 } else { 667 command = lp 668 } 669 } 670 return exec.Command(command, args...), nil 671} 672 673// BackgroundCmds returns a slice containing all the commands that have 674// been started in the background since the most recent wait command, or 675// the start of the script if wait has not been called. 676func (ts *TestScript) BackgroundCmds() []*exec.Cmd { 677 cmds := make([]*exec.Cmd, len(ts.background)) 678 for i, b := range ts.background { 679 cmds[i] = b.cmd 680 } 681 return cmds 682} 683 684// ctxWait is like cmd.Wait, but terminates cmd with os.Interrupt if ctx becomes done. 685// 686// This differs from exec.CommandContext in that it prefers os.Interrupt over os.Kill. 687// (See https://golang.org/issue/21135.) 688func ctxWait(ctx context.Context, cmd *exec.Cmd) error { 689 errc := make(chan error, 1) 690 go func() { errc <- cmd.Wait() }() 691 692 select { 693 case err := <-errc: 694 return err 695 case <-ctx.Done(): 696 interruptProcess(cmd.Process) 697 return <-errc 698 } 699} 700 701// interruptProcess sends os.Interrupt to p if supported, or os.Kill otherwise. 702func interruptProcess(p *os.Process) { 703 if err := p.Signal(os.Interrupt); err != nil { 704 // Per https://golang.org/pkg/os/#Signal, “Interrupt is not implemented on 705 // Windows; using it with os.Process.Signal will return an error.” 706 // Fall back to Kill instead. 707 p.Kill() 708 } 709} 710 711// Exec runs the given command and saves its stdout and stderr so 712// they can be inspected by subsequent script commands. 713func (ts *TestScript) Exec(command string, args ...string) error { 714 var err error 715 ts.stdout, ts.stderr, err = ts.exec(command, args...) 716 if ts.stdout != "" { 717 ts.Logf("[stdout]\n%s", ts.stdout) 718 } 719 if ts.stderr != "" { 720 ts.Logf("[stderr]\n%s", ts.stderr) 721 } 722 return err 723} 724 725// expand applies environment variable expansion to the string s. 726func (ts *TestScript) expand(s string) string { 727 return os.Expand(s, func(key string) string { 728 if key1 := strings.TrimSuffix(key, "@R"); len(key1) != len(key) { 729 return regexp.QuoteMeta(ts.Getenv(key1)) 730 } 731 return ts.Getenv(key) 732 }) 733} 734 735// fatalf aborts the test with the given failure message. 736func (ts *TestScript) Fatalf(format string, args ...interface{}) { 737 fmt.Fprintf(&ts.log, "FAIL: %s:%d: %s\n", ts.file, ts.lineno, fmt.Sprintf(format, args...)) 738 ts.t.FailNow() 739} 740 741// MkAbs interprets file relative to the test script's current directory 742// and returns the corresponding absolute path. 743func (ts *TestScript) MkAbs(file string) string { 744 if filepath.IsAbs(file) { 745 return file 746 } 747 return filepath.Join(ts.cd, file) 748} 749 750// ReadFile returns the contents of the file with the 751// given name, intepreted relative to the test script's 752// current directory. It interprets "stdout" and "stderr" to 753// mean the standard output or standard error from 754// the most recent exec or wait command respectively. 755// 756// If the file cannot be read, the script fails. 757func (ts *TestScript) ReadFile(file string) string { 758 switch file { 759 case "stdout": 760 return ts.stdout 761 case "stderr": 762 return ts.stderr 763 default: 764 file = ts.MkAbs(file) 765 data, err := ioutil.ReadFile(file) 766 ts.Check(err) 767 return string(data) 768 } 769} 770 771// Setenv sets the value of the environment variable named by the key. 772func (ts *TestScript) Setenv(key, value string) { 773 ts.env = append(ts.env, key+"="+value) 774 ts.envMap[envvarname(key)] = value 775} 776 777// Getenv gets the value of the environment variable named by the key. 778func (ts *TestScript) Getenv(key string) string { 779 return ts.envMap[envvarname(key)] 780} 781 782// parse parses a single line as a list of space-separated arguments 783// subject to environment variable expansion (but not resplitting). 784// Single quotes around text disable splitting and expansion. 785// To embed a single quote, double it: 'Don''t communicate by sharing memory.' 786func (ts *TestScript) parse(line string) []string { 787 ts.line = line 788 789 var ( 790 args []string 791 arg string // text of current arg so far (need to add line[start:i]) 792 start = -1 // if >= 0, position where current arg text chunk starts 793 quoted = false // currently processing quoted text 794 ) 795 for i := 0; ; i++ { 796 if !quoted && (i >= len(line) || line[i] == ' ' || line[i] == '\t' || line[i] == '\r' || line[i] == '#') { 797 // Found arg-separating space. 798 if start >= 0 { 799 arg += ts.expand(line[start:i]) 800 args = append(args, arg) 801 start = -1 802 arg = "" 803 } 804 if i >= len(line) || line[i] == '#' { 805 break 806 } 807 continue 808 } 809 if i >= len(line) { 810 ts.Fatalf("unterminated quoted argument") 811 } 812 if line[i] == '\'' { 813 if !quoted { 814 // starting a quoted chunk 815 if start >= 0 { 816 arg += ts.expand(line[start:i]) 817 } 818 start = i + 1 819 quoted = true 820 continue 821 } 822 // 'foo''bar' means foo'bar, like in rc shell and Pascal. 823 if i+1 < len(line) && line[i+1] == '\'' { 824 arg += line[start:i] 825 start = i + 1 826 i++ // skip over second ' before next iteration 827 continue 828 } 829 // ending a quoted chunk 830 arg += line[start:i] 831 start = i + 1 832 quoted = false 833 continue 834 } 835 // found character worth saving; make sure we're saving 836 if start < 0 { 837 start = i 838 } 839 } 840 return args 841} 842 843func removeAll(dir string) error { 844 // module cache has 0444 directories; 845 // make them writable in order to remove content. 846 filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 847 if err != nil { 848 return nil // ignore errors walking in file system 849 } 850 if info.IsDir() { 851 os.Chmod(path, 0777) 852 } 853 return nil 854 }) 855 return os.RemoveAll(dir) 856} 857 858func homeEnvName() string { 859 switch runtime.GOOS { 860 case "windows": 861 return "USERPROFILE" 862 case "plan9": 863 return "home" 864 default: 865 return "HOME" 866 } 867} 868 869func tempEnvName() string { 870 switch runtime.GOOS { 871 case "windows": 872 return "TMP" 873 case "plan9": 874 return "TMPDIR" // actually plan 9 doesn't have one at all but this is fine 875 default: 876 return "TMPDIR" 877 } 878} 879