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 }