github.com/neilgarb/delve@v1.9.2-nobreaks/pkg/terminal/command_test.go (about) 1 package terminal 2 3 import ( 4 "bytes" 5 "flag" 6 "fmt" 7 "io/ioutil" 8 "net" 9 "net/http" 10 "os" 11 "path/filepath" 12 "reflect" 13 "regexp" 14 "runtime" 15 "strconv" 16 "strings" 17 "testing" 18 "time" 19 20 "github.com/go-delve/delve/pkg/config" 21 "github.com/go-delve/delve/pkg/goversion" 22 "github.com/go-delve/delve/pkg/logflags" 23 "github.com/go-delve/delve/pkg/proc/test" 24 "github.com/go-delve/delve/service" 25 "github.com/go-delve/delve/service/api" 26 "github.com/go-delve/delve/service/debugger" 27 "github.com/go-delve/delve/service/rpc2" 28 "github.com/go-delve/delve/service/rpccommon" 29 ) 30 31 var testBackend, buildMode string 32 33 func TestMain(m *testing.M) { 34 flag.StringVar(&testBackend, "backend", "", "selects backend") 35 flag.StringVar(&buildMode, "test-buildmode", "", "selects build mode") 36 var logConf string 37 flag.StringVar(&logConf, "log", "", "configures logging") 38 flag.Parse() 39 test.DefaultTestBackend(&testBackend) 40 if buildMode != "" && buildMode != "pie" { 41 fmt.Fprintf(os.Stderr, "unknown build mode %q", buildMode) 42 os.Exit(1) 43 } 44 logflags.Setup(logConf != "", logConf, "") 45 os.Exit(test.RunTestsWithFixtures(m)) 46 } 47 48 type FakeTerminal struct { 49 *Term 50 t testing.TB 51 } 52 53 const logCommandOutput = false 54 55 func (ft *FakeTerminal) Exec(cmdstr string) (outstr string, err error) { 56 var buf bytes.Buffer 57 ft.Term.stdout.w = &buf 58 ft.Term.starlarkEnv.Redirect(ft.Term.stdout) 59 err = ft.cmds.Call(cmdstr, ft.Term) 60 outstr = buf.String() 61 if logCommandOutput { 62 ft.t.Logf("command %q -> %q", cmdstr, outstr) 63 } 64 ft.Term.stdout.Flush() 65 return 66 } 67 68 func (ft *FakeTerminal) ExecStarlark(starlarkProgram string) (outstr string, err error) { 69 var buf bytes.Buffer 70 ft.Term.stdout.w = &buf 71 ft.Term.starlarkEnv.Redirect(ft.Term.stdout) 72 _, err = ft.Term.starlarkEnv.Execute("<stdin>", starlarkProgram, "main", nil) 73 outstr = buf.String() 74 if logCommandOutput { 75 ft.t.Logf("command %q -> %q", starlarkProgram, outstr) 76 } 77 ft.Term.stdout.Flush() 78 return 79 } 80 81 func (ft *FakeTerminal) MustExec(cmdstr string) string { 82 outstr, err := ft.Exec(cmdstr) 83 if err != nil { 84 ft.t.Errorf("output of %q: %q", cmdstr, outstr) 85 ft.t.Fatalf("Error executing <%s>: %v", cmdstr, err) 86 } 87 return outstr 88 } 89 90 func (ft *FakeTerminal) MustExecStarlark(starlarkProgram string) string { 91 outstr, err := ft.ExecStarlark(starlarkProgram) 92 if err != nil { 93 ft.t.Errorf("output of %q: %q", starlarkProgram, outstr) 94 ft.t.Fatalf("Error executing <%s>: %v", starlarkProgram, err) 95 } 96 return outstr 97 } 98 99 func (ft *FakeTerminal) AssertExec(cmdstr, tgt string) { 100 out := ft.MustExec(cmdstr) 101 if out != tgt { 102 ft.t.Fatalf("Error executing %q, expected %q got %q", cmdstr, tgt, out) 103 } 104 } 105 106 func (ft *FakeTerminal) AssertExecError(cmdstr, tgterr string) { 107 _, err := ft.Exec(cmdstr) 108 if err == nil { 109 ft.t.Fatalf("Expected error executing %q", cmdstr) 110 } 111 if err.Error() != tgterr { 112 ft.t.Fatalf("Expected error %q executing %q, got error %q", tgterr, cmdstr, err.Error()) 113 } 114 } 115 116 func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) { 117 withTestTerminalBuildFlags(name, t, 0, fn) 118 } 119 120 func withTestTerminalBuildFlags(name string, t testing.TB, buildFlags test.BuildFlags, fn func(*FakeTerminal)) { 121 if testBackend == "rr" { 122 test.MustHaveRecordingAllowed(t) 123 } 124 os.Setenv("TERM", "dumb") 125 listener, err := net.Listen("tcp", "127.0.0.1:0") 126 if err != nil { 127 t.Fatalf("couldn't start listener: %s\n", err) 128 } 129 defer listener.Close() 130 if buildMode == "pie" { 131 buildFlags |= test.BuildModePIE 132 } 133 server := rpccommon.NewServer(&service.Config{ 134 Listener: listener, 135 ProcessArgs: []string{test.BuildFixture(name, buildFlags).Path}, 136 Debugger: debugger.Config{ 137 Backend: testBackend, 138 }, 139 }) 140 if err := server.Run(); err != nil { 141 t.Fatal(err) 142 } 143 client := rpc2.NewClient(listener.Addr().String()) 144 defer func() { 145 client.Detach(true) 146 }() 147 148 ft := &FakeTerminal{ 149 t: t, 150 Term: New(client, &config.Config{}), 151 } 152 fn(ft) 153 } 154 155 func TestCommandDefault(t *testing.T) { 156 var ( 157 cmds = Commands{} 158 cmd = cmds.Find("non-existent-command", noPrefix).cmdFn 159 ) 160 161 err := cmd(nil, callContext{}, "") 162 if err == nil { 163 t.Fatal("cmd() did not default") 164 } 165 166 if err.Error() != "command not available" { 167 t.Fatal("wrong command output") 168 } 169 } 170 171 func TestCommandReplayWithoutPreviousCommand(t *testing.T) { 172 var ( 173 cmds = DebugCommands(nil) 174 cmd = cmds.Find("", noPrefix).cmdFn 175 err = cmd(nil, callContext{}, "") 176 ) 177 178 if err != nil { 179 t.Error("Null command not returned", err) 180 } 181 } 182 183 func TestCommandThread(t *testing.T) { 184 var ( 185 cmds = DebugCommands(nil) 186 cmd = cmds.Find("thread", noPrefix).cmdFn 187 ) 188 189 err := cmd(nil, callContext{}, "") 190 if err == nil { 191 t.Fatal("thread terminal command did not default") 192 } 193 194 if err.Error() != "you must specify a thread" { 195 t.Fatal("wrong command output: ", err.Error()) 196 } 197 } 198 199 func TestExecuteFile(t *testing.T) { 200 breakCount := 0 201 traceCount := 0 202 c := &Commands{ 203 client: nil, 204 cmds: []command{ 205 {aliases: []string{"trace"}, cmdFn: func(t *Term, ctx callContext, args string) error { 206 traceCount++ 207 return nil 208 }}, 209 {aliases: []string{"break"}, cmdFn: func(t *Term, ctx callContext, args string) error { 210 breakCount++ 211 return nil 212 }}, 213 }, 214 } 215 216 fixturesDir := test.FindFixturesDir() 217 err := c.executeFile(nil, filepath.Join(fixturesDir, "bpfile")) 218 if err != nil { 219 t.Fatalf("executeFile: %v", err) 220 } 221 222 if breakCount != 1 || traceCount != 1 { 223 t.Fatalf("Wrong counts break: %d trace: %d\n", breakCount, traceCount) 224 } 225 } 226 227 func TestIssue354(t *testing.T) { 228 printStack(&Term{}, os.Stdout, []api.Stackframe{}, "", false) 229 printStack(&Term{}, os.Stdout, []api.Stackframe{ 230 {Location: api.Location{PC: 0, File: "irrelevant.go", Line: 10, Function: nil}, 231 Bottom: true}}, "", false) 232 } 233 234 func TestIssue411(t *testing.T) { 235 test.AllowRecording(t) 236 withTestTerminal("math", t, func(term *FakeTerminal) { 237 term.MustExec("break _fixtures/math.go:8") 238 term.MustExec("trace _fixtures/math.go:9") 239 term.MustExec("continue") 240 out := term.MustExec("next") 241 if !strings.HasPrefix(out, "> goroutine(1): main.main()") { 242 t.Fatalf("Wrong output for next: <%s>", out) 243 } 244 }) 245 } 246 247 func TestTrace(t *testing.T) { 248 test.AllowRecording(t) 249 withTestTerminal("issue573", t, func(term *FakeTerminal) { 250 term.MustExec("trace foo") 251 out, _ := term.Exec("continue") 252 // The output here is a little strange, but we don't filter stdout vs stderr so it gets jumbled. 253 // Therefore we assert about the call and return values separately. 254 if !strings.Contains(out, "> goroutine(1): main.foo(99, 9801)") { 255 t.Fatalf("Wrong output for tracepoint: %s", out) 256 } 257 if !strings.Contains(out, "=> (9900)") { 258 t.Fatalf("Wrong output for tracepoint return value: %s", out) 259 } 260 }) 261 } 262 263 func TestTraceWithName(t *testing.T) { 264 test.AllowRecording(t) 265 withTestTerminal("issue573", t, func(term *FakeTerminal) { 266 term.MustExec("trace foobar foo") 267 out, _ := term.Exec("continue") 268 // The output here is a little strange, but we don't filter stdout vs stderr so it gets jumbled. 269 // Therefore we assert about the call and return values separately. 270 if !strings.Contains(out, "> goroutine(1): [foobar] main.foo(99, 9801)") { 271 t.Fatalf("Wrong output for tracepoint: %s", out) 272 } 273 if !strings.Contains(out, "=> (9900)") { 274 t.Fatalf("Wrong output for tracepoint return value: %s", out) 275 } 276 }) 277 } 278 279 func TestTraceOnNonFunctionEntry(t *testing.T) { 280 test.AllowRecording(t) 281 withTestTerminal("issue573", t, func(term *FakeTerminal) { 282 term.MustExec("trace foobar issue573.go:19") 283 out, _ := term.Exec("continue") 284 if !strings.Contains(out, "> goroutine(1): [foobar] main.foo(99, 9801)") { 285 t.Fatalf("Wrong output for tracepoint: %s", out) 286 } 287 if strings.Contains(out, "=> (9900)") { 288 t.Fatalf("Tracepoint on non-function locspec should not have return value:\n%s", out) 289 } 290 }) 291 } 292 293 func TestExitStatus(t *testing.T) { 294 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 295 term.Exec("continue") 296 status, err := term.handleExit() 297 if err != nil { 298 t.Fatal(err) 299 } 300 if status != 0 { 301 t.Fatalf("incorrect exit status, expected 0, got %d", status) 302 } 303 }) 304 } 305 306 func TestScopePrefix(t *testing.T) { 307 const goroutinesLinePrefix = " Goroutine " 308 const goroutinesCurLinePrefix = "* Goroutine " 309 test.AllowRecording(t) 310 311 lenient := 0 312 if runtime.GOOS == "windows" { 313 lenient = 1 314 } 315 316 withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { 317 term.MustExec("b stacktraceme") 318 term.MustExec("continue") 319 320 goroutinesOut := strings.Split(term.MustExec("goroutines"), "\n") 321 agoroutines := []int{} 322 nonagoroutines := []int{} 323 curgid := -1 324 325 for _, line := range goroutinesOut { 326 iscur := strings.HasPrefix(line, goroutinesCurLinePrefix) 327 if !iscur && !strings.HasPrefix(line, goroutinesLinePrefix) { 328 continue 329 } 330 331 dash := strings.Index(line, " - ") 332 if dash < 0 { 333 continue 334 } 335 336 gid, err := strconv.Atoi(line[len(goroutinesLinePrefix):dash]) 337 if err != nil { 338 continue 339 } 340 341 if iscur { 342 curgid = gid 343 } 344 345 if idx := strings.Index(line, " main.agoroutine "); idx < 0 { 346 nonagoroutines = append(nonagoroutines, gid) 347 continue 348 } 349 350 agoroutines = append(agoroutines, gid) 351 } 352 353 if len(agoroutines) > 10 { 354 t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d found): %q", len(agoroutines), goroutinesOut) 355 } 356 357 if len(agoroutines) < 10 { 358 extraAgoroutines := 0 359 for _, gid := range nonagoroutines { 360 stackOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d stack", gid)), "\n") 361 for _, line := range stackOut { 362 if strings.HasSuffix(line, " main.agoroutine") { 363 extraAgoroutines++ 364 break 365 } 366 } 367 } 368 if len(agoroutines)+extraAgoroutines < 10-lenient { 369 t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d+%d found): %q", len(agoroutines), extraAgoroutines, goroutinesOut) 370 } 371 } 372 373 if curgid < 0 { 374 t.Fatalf("Could not find current goroutine in output of goroutines: %q", goroutinesOut) 375 } 376 377 seen := make([]bool, 10) 378 for _, gid := range agoroutines { 379 stackOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d stack", gid)), "\n") 380 fid := -1 381 for _, line := range stackOut { 382 line = strings.TrimLeft(line, " ") 383 space := strings.Index(line, " ") 384 if space < 0 { 385 continue 386 } 387 curfid, err := strconv.Atoi(line[:space]) 388 if err != nil { 389 continue 390 } 391 392 if idx := strings.Index(line, " main.agoroutine"); idx >= 0 { 393 fid = curfid 394 break 395 } 396 } 397 if fid < 0 { 398 t.Fatalf("Could not find frame for goroutine %d: %q", gid, stackOut) 399 } 400 term.AssertExec(fmt.Sprintf("goroutine %d frame %d locals", gid, fid), "(no locals)\n") 401 argsOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d frame %d args", gid, fid)), "\n") 402 if len(argsOut) != 4 || argsOut[3] != "" { 403 t.Fatalf("Wrong number of arguments in goroutine %d frame %d: %v", gid, fid, argsOut) 404 } 405 out := term.MustExec(fmt.Sprintf("goroutine %d frame %d p i", gid, fid)) 406 ival, err := strconv.Atoi(out[:len(out)-1]) 407 if err != nil { 408 t.Fatalf("could not parse value %q of i for goroutine %d frame %d: %v", out, gid, fid, err) 409 } 410 seen[ival] = true 411 } 412 413 for i := range seen { 414 if !seen[i] { 415 if lenient > 0 { 416 lenient-- 417 } else { 418 t.Fatalf("goroutine %d not found", i) 419 } 420 } 421 } 422 423 term.MustExec("c") 424 425 term.AssertExecError("frame", "not enough arguments") 426 term.AssertExecError(fmt.Sprintf("goroutine %d frame 10 locals", curgid), fmt.Sprintf("Frame 10 does not exist in goroutine %d", curgid)) 427 term.AssertExecError("goroutine 9000 locals", "unknown goroutine 9000") 428 429 term.AssertExecError("print n", "could not find symbol value for n") 430 term.AssertExec("frame 1 print n", "3\n") 431 term.AssertExec("frame 2 print n", "2\n") 432 term.AssertExec("frame 3 print n", "1\n") 433 term.AssertExec("frame 4 print n", "0\n") 434 term.AssertExecError("frame 5 print n", "could not find symbol value for n") 435 436 term.MustExec("frame 2") 437 term.AssertExec("print n", "2\n") 438 term.MustExec("frame 4") 439 term.AssertExec("print n", "0\n") 440 term.MustExec("down") 441 term.AssertExec("print n", "1\n") 442 term.MustExec("down 2") 443 term.AssertExec("print n", "3\n") 444 term.AssertExecError("down 2", "Invalid frame -1") 445 term.AssertExec("print n", "3\n") 446 term.MustExec("up 2") 447 term.AssertExec("print n", "1\n") 448 term.AssertExecError("up 100", "Invalid frame 103") 449 term.AssertExec("print n", "1\n") 450 451 term.MustExec("step") 452 term.AssertExecError("print n", "could not find symbol value for n") 453 term.MustExec("frame 2") 454 term.AssertExec("print n", "2\n") 455 }) 456 } 457 458 func TestOnPrefix(t *testing.T) { 459 if runtime.GOOS == "freebsd" { 460 t.Skip("test is not valid on FreeBSD") 461 } 462 const prefix = "\ti: " 463 test.AllowRecording(t) 464 lenient := false 465 if runtime.GOOS == "windows" { 466 lenient = true 467 } 468 withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { 469 term.MustExec("b agobp main.agoroutine") 470 term.MustExec("on agobp print i") 471 472 seen := make([]bool, 10) 473 474 for { 475 outstr, err := term.Exec("continue") 476 if err != nil { 477 if !strings.Contains(err.Error(), "exited") { 478 t.Fatalf("Unexpected error executing 'continue': %v", err) 479 } 480 break 481 } 482 out := strings.Split(outstr, "\n") 483 484 for i := range out { 485 if !strings.HasPrefix(out[i], prefix) { 486 continue 487 } 488 id, err := strconv.Atoi(out[i][len(prefix):]) 489 if err != nil { 490 continue 491 } 492 if seen[id] { 493 t.Fatalf("Goroutine %d seen twice\n", id) 494 } 495 seen[id] = true 496 } 497 } 498 499 for i := range seen { 500 if !seen[i] { 501 if lenient { 502 lenient = false 503 } else { 504 t.Fatalf("Goroutine %d not seen\n", i) 505 } 506 } 507 } 508 }) 509 } 510 511 func TestNoVars(t *testing.T) { 512 test.AllowRecording(t) 513 withTestTerminal("locationsUpperCase", t, func(term *FakeTerminal) { 514 term.MustExec("b main.main") 515 term.MustExec("continue") 516 term.AssertExec("args", "(no args)\n") 517 term.AssertExec("locals", "(no locals)\n") 518 term.AssertExec("vars filterThatMatchesNothing", "(no vars)\n") 519 }) 520 } 521 522 func TestOnPrefixLocals(t *testing.T) { 523 if runtime.GOOS == "freebsd" { 524 t.Skip("test is not valid on FreeBSD") 525 } 526 const prefix = "\ti: " 527 test.AllowRecording(t) 528 withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { 529 term.MustExec("b agobp main.agoroutine") 530 term.MustExec("on agobp args -v") 531 532 seen := make([]bool, 10) 533 534 for { 535 outstr, err := term.Exec("continue") 536 if err != nil { 537 if !strings.Contains(err.Error(), "exited") { 538 t.Fatalf("Unexpected error executing 'continue': %v", err) 539 } 540 break 541 } 542 out := strings.Split(outstr, "\n") 543 544 for i := range out { 545 if !strings.HasPrefix(out[i], prefix) { 546 continue 547 } 548 id, err := strconv.Atoi(out[i][len(prefix):]) 549 if err != nil { 550 continue 551 } 552 if seen[id] { 553 t.Fatalf("Goroutine %d seen twice\n", id) 554 } 555 seen[id] = true 556 } 557 } 558 559 for i := range seen { 560 if !seen[i] { 561 t.Fatalf("Goroutine %d not seen\n", i) 562 } 563 } 564 }) 565 } 566 567 func countOccurrences(s, needle string) int { 568 count := 0 569 for { 570 idx := strings.Index(s, needle) 571 if idx < 0 { 572 break 573 } 574 count++ 575 s = s[idx+len(needle):] 576 } 577 return count 578 } 579 580 func listIsAt(t *testing.T, term *FakeTerminal, listcmd string, cur, start, end int) { 581 t.Helper() 582 outstr := term.MustExec(listcmd) 583 lines := strings.Split(outstr, "\n") 584 585 t.Logf("%q: %q", listcmd, outstr) 586 587 if cur >= 0 && !strings.Contains(lines[0], fmt.Sprintf(":%d", cur)) { 588 t.Fatalf("Could not find current line number in first output line: %q", lines[0]) 589 } 590 591 re := regexp.MustCompile(`(=>)?\s+(\d+):`) 592 593 outStart, outEnd := 0, 0 594 595 for _, line := range lines[1:] { 596 if line == "" { 597 continue 598 } 599 v := re.FindStringSubmatch(line) 600 if len(v) != 3 { 601 continue 602 } 603 curline, _ := strconv.Atoi(v[2]) 604 if v[1] == "=>" { 605 if cur != curline { 606 t.Fatalf("Wrong current line, got %d expected %d", curline, cur) 607 } 608 } 609 if outStart == 0 { 610 outStart = curline 611 } 612 outEnd = curline 613 } 614 615 if start != -1 || end != -1 { 616 if outStart != start || outEnd != end { 617 t.Fatalf("Wrong output range, got %d:%d expected %d:%d", outStart, outEnd, start, end) 618 } 619 } 620 } 621 622 func TestListCmd(t *testing.T) { 623 withTestTerminal("testvariables", t, func(term *FakeTerminal) { 624 term.MustExec("continue") 625 term.MustExec("continue") 626 listIsAt(t, term, "list", 27, 22, 32) 627 listIsAt(t, term, "list 69", 69, 64, 74) 628 listIsAt(t, term, "frame 1 list", 66, 61, 71) 629 listIsAt(t, term, "frame 1 list 69", 69, 64, 74) 630 _, err := term.Exec("frame 50 list") 631 if err == nil { 632 t.Fatalf("Expected error requesting 50th frame") 633 } 634 listIsAt(t, term, "list testvariables.go:1", -1, 1, 6) 635 listIsAt(t, term, "list testvariables.go:10000", -1, 0, 0) 636 }) 637 } 638 639 func TestReverseContinue(t *testing.T) { 640 test.AllowRecording(t) 641 if testBackend != "rr" { 642 return 643 } 644 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 645 term.MustExec("break main.main") 646 term.MustExec("break main.sayhi") 647 listIsAt(t, term, "continue", 16, -1, -1) 648 listIsAt(t, term, "continue", 12, -1, -1) 649 listIsAt(t, term, "rewind", 16, -1, -1) 650 }) 651 } 652 653 func TestCheckpoints(t *testing.T) { 654 test.AllowRecording(t) 655 if testBackend != "rr" { 656 return 657 } 658 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 659 term.MustExec("break main.main") 660 listIsAt(t, term, "continue", 16, -1, -1) 661 term.MustExec("checkpoint") 662 term.MustExec("checkpoints") 663 listIsAt(t, term, "next", 17, -1, -1) 664 listIsAt(t, term, "next", 18, -1, -1) 665 term.MustExec("restart c1") 666 term.MustExec("goroutine 1") 667 listIsAt(t, term, "list", 16, -1, -1) 668 }) 669 } 670 671 func TestNextWithCount(t *testing.T) { 672 test.AllowRecording(t) 673 withTestTerminal("nextcond", t, func(term *FakeTerminal) { 674 term.MustExec("break main.main") 675 listIsAt(t, term, "continue", 8, -1, -1) 676 listIsAt(t, term, "next 2", 10, -1, -1) 677 }) 678 } 679 680 func TestRestart(t *testing.T) { 681 withTestTerminal("restartargs", t, func(term *FakeTerminal) { 682 term.MustExec("break main.printArgs") 683 term.MustExec("continue") 684 if out := term.MustExec("print main.args"); !strings.Contains(out, ", []") { 685 t.Fatalf("wrong args: %q", out) 686 } 687 // Reset the arg list 688 term.MustExec("restart hello") 689 term.MustExec("continue") 690 if out := term.MustExec("print main.args"); !strings.Contains(out, ", [\"hello\"]") { 691 t.Fatalf("wrong args: %q ", out) 692 } 693 // Restart w/o arg should retain the current args. 694 term.MustExec("restart") 695 term.MustExec("continue") 696 if out := term.MustExec("print main.args"); !strings.Contains(out, ", [\"hello\"]") { 697 t.Fatalf("wrong args: %q ", out) 698 } 699 // Empty arg list 700 term.MustExec("restart -noargs") 701 term.MustExec("continue") 702 if out := term.MustExec("print main.args"); !strings.Contains(out, ", []") { 703 t.Fatalf("wrong args: %q ", out) 704 } 705 }) 706 } 707 708 func TestIssue827(t *testing.T) { 709 // switching goroutines when the current thread isn't running any goroutine 710 // causes nil pointer dereference. 711 withTestTerminal("notify-v2", t, func(term *FakeTerminal) { 712 go func() { 713 time.Sleep(1 * time.Second) 714 http.Get("http://127.0.0.1:8888/test") 715 time.Sleep(1 * time.Second) 716 term.client.Halt() 717 }() 718 term.MustExec("continue") 719 term.MustExec("goroutine 1") 720 }) 721 } 722 723 func findCmdName(c *Commands, cmdstr string, prefix cmdPrefix) string { 724 for _, v := range c.cmds { 725 if v.match(cmdstr) { 726 if prefix != noPrefix && v.allowedPrefixes&prefix == 0 { 727 continue 728 } 729 return v.aliases[0] 730 } 731 } 732 return "" 733 } 734 735 func TestConfig(t *testing.T) { 736 var term Term 737 term.conf = &config.Config{} 738 term.cmds = DebugCommands(nil) 739 740 err := configureCmd(&term, callContext{}, "nonexistent-parameter 10") 741 if err == nil { 742 t.Fatalf("expected error executing configureCmd(nonexistent-parameter)") 743 } 744 745 err = configureCmd(&term, callContext{}, "max-string-len 10") 746 if err != nil { 747 t.Fatalf("error executing configureCmd(max-string-len): %v", err) 748 } 749 if term.conf.MaxStringLen == nil { 750 t.Fatalf("expected MaxStringLen 10, got nil") 751 } 752 if *term.conf.MaxStringLen != 10 { 753 t.Fatalf("expected MaxStringLen 10, got: %d", *term.conf.MaxStringLen) 754 } 755 err = configureCmd(&term, callContext{}, "show-location-expr true") 756 if err != nil { 757 t.Fatalf("error executing configureCmd(show-location-expr true)") 758 } 759 if term.conf.ShowLocationExpr != true { 760 t.Fatalf("expected ShowLocationExpr true, got false") 761 } 762 err = configureCmd(&term, callContext{}, "max-variable-recurse 4") 763 if err != nil { 764 t.Fatalf("error executing configureCmd(max-variable-recurse): %v", err) 765 } 766 if term.conf.MaxVariableRecurse == nil { 767 t.Fatalf("expected MaxVariableRecurse 4, got nil") 768 } 769 if *term.conf.MaxVariableRecurse != 4 { 770 t.Fatalf("expected MaxVariableRecurse 4, got: %d", *term.conf.MaxVariableRecurse) 771 } 772 773 err = configureCmd(&term, callContext{}, "substitute-path a b") 774 if err != nil { 775 t.Fatalf("error executing configureCmd(substitute-path a b): %v", err) 776 } 777 if len(term.conf.SubstitutePath) != 1 || (term.conf.SubstitutePath[0] != config.SubstitutePathRule{From: "a", To: "b"}) { 778 t.Fatalf("unexpected SubstitutePathRules after insert %v", term.conf.SubstitutePath) 779 } 780 781 err = configureCmd(&term, callContext{}, "substitute-path a") 782 if err != nil { 783 t.Fatalf("error executing configureCmd(substitute-path a): %v", err) 784 } 785 if len(term.conf.SubstitutePath) != 0 { 786 t.Fatalf("unexpected SubstitutePathRules after delete %v", term.conf.SubstitutePath) 787 } 788 789 err = configureCmd(&term, callContext{}, "alias print blah") 790 if err != nil { 791 t.Fatalf("error executing configureCmd(alias print blah): %v", err) 792 } 793 if len(term.conf.Aliases["print"]) != 1 { 794 t.Fatalf("aliases not changed after configure command %v", term.conf.Aliases) 795 } 796 if findCmdName(term.cmds, "blah", noPrefix) != "print" { 797 t.Fatalf("new alias not found") 798 } 799 800 err = configureCmd(&term, callContext{}, "alias blah") 801 if err != nil { 802 t.Fatalf("error executing configureCmd(alias blah): %v", err) 803 } 804 if len(term.conf.Aliases["print"]) != 0 { 805 t.Fatalf("alias not removed after configure command %v", term.conf.Aliases) 806 } 807 if findCmdName(term.cmds, "blah", noPrefix) != "" { 808 t.Fatalf("new alias found after delete") 809 } 810 } 811 812 func TestIssue1090(t *testing.T) { 813 // Exit while executing 'next' should report the "Process exited" error 814 // message instead of crashing. 815 withTestTerminal("math", t, func(term *FakeTerminal) { 816 term.MustExec("break main.main") 817 term.MustExec("continue") 818 for { 819 _, err := term.Exec("next") 820 if err != nil && strings.Contains(err.Error(), " has exited with status ") { 821 break 822 } 823 } 824 }) 825 } 826 827 func TestPrintContextParkedGoroutine(t *testing.T) { 828 withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { 829 term.MustExec("break stacktraceme") 830 term.MustExec("continue") 831 832 // pick a goroutine that isn't running on a thread 833 gid := "" 834 gout := strings.Split(term.MustExec("goroutines"), "\n") 835 t.Logf("goroutines -> %q", gout) 836 for _, gline := range gout { 837 if !strings.Contains(gline, "thread ") && strings.Contains(gline, "agoroutine") { 838 if dash := strings.Index(gline, " - "); dash > 0 { 839 gid = gline[len(" Goroutine "):dash] 840 break 841 } 842 } 843 } 844 845 t.Logf("picked %q", gid) 846 term.MustExec(fmt.Sprintf("goroutine %s", gid)) 847 848 frameout := strings.Split(term.MustExec("frame 0"), "\n") 849 t.Logf("frame 0 -> %q", frameout) 850 if strings.Contains(frameout[0], "stacktraceme") { 851 t.Fatal("bad output for `frame 0` command on a parked goorutine") 852 } 853 854 listout := strings.Split(term.MustExec("list"), "\n") 855 t.Logf("list -> %q", listout) 856 if strings.Contains(listout[0], "stacktraceme") { 857 t.Fatal("bad output for list command on a parked goroutine") 858 } 859 }) 860 } 861 862 func TestStepOutReturn(t *testing.T) { 863 ver, _ := goversion.Parse(runtime.Version()) 864 if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) { 865 t.Skip("return variables aren't marked on 1.9 or earlier") 866 } 867 withTestTerminal("stepoutret", t, func(term *FakeTerminal) { 868 term.MustExec("break main.stepout") 869 term.MustExec("continue") 870 out := term.MustExec("stepout") 871 t.Logf("output: %q", out) 872 if !strings.Contains(out, "num: ") || !strings.Contains(out, "str: ") { 873 t.Fatal("could not find parameter") 874 } 875 }) 876 } 877 878 func TestOptimizationCheck(t *testing.T) { 879 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 880 term.MustExec("break main.main") 881 out := term.MustExec("continue") 882 t.Logf("output %q", out) 883 if strings.Contains(out, optimizedFunctionWarning) { 884 t.Fatal("optimized function warning") 885 } 886 }) 887 888 if goversion.VersionAfterOrEqual(runtime.Version(), 1, 10) { 889 withTestTerminalBuildFlags("continuetestprog", t, test.EnableOptimization|test.EnableInlining, func(term *FakeTerminal) { 890 term.MustExec("break main.main") 891 out := term.MustExec("continue") 892 t.Logf("output %q", out) 893 if !strings.Contains(out, optimizedFunctionWarning) { 894 t.Fatal("optimized function warning missing") 895 } 896 }) 897 } 898 } 899 900 func TestTruncateStacktrace(t *testing.T) { 901 const stacktraceTruncatedMessage = "(truncated)" 902 withTestTerminal("stacktraceprog", t, func(term *FakeTerminal) { 903 term.MustExec("break main.stacktraceme") 904 term.MustExec("continue") 905 out1 := term.MustExec("stack") 906 t.Logf("untruncated output %q", out1) 907 if strings.Contains(out1, stacktraceTruncatedMessage) { 908 t.Fatalf("stacktrace was truncated") 909 } 910 out2 := term.MustExec("stack 1") 911 t.Logf("truncated output %q", out2) 912 if !strings.Contains(out2, stacktraceTruncatedMessage) { 913 t.Fatalf("stacktrace was not truncated") 914 } 915 }) 916 } 917 918 func TestIssue1493(t *testing.T) { 919 // The 'regs' command without the '-a' option should only return 920 // general purpose registers. 921 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 922 r := term.MustExec("regs") 923 nr := len(strings.Split(r, "\n")) 924 t.Logf("regs: %s", r) 925 ra := term.MustExec("regs -a") 926 nra := len(strings.Split(ra, "\n")) 927 t.Logf("regs -a: %s", ra) 928 if nr > nra/2+1 { 929 t.Fatalf("'regs' returned too many registers (%d) compared to 'regs -a' (%d)", nr, nra) 930 } 931 }) 932 } 933 934 func findStarFile(name string) string { 935 return filepath.Join(test.FindFixturesDir(), name+".star") 936 } 937 938 func TestIssue1598(t *testing.T) { 939 test.MustSupportFunctionCalls(t, testBackend) 940 withTestTerminal("issue1598", t, func(term *FakeTerminal) { 941 term.MustExec("break issue1598.go:5") 942 term.MustExec("continue") 943 term.MustExec("config max-string-len 500") 944 r := term.MustExec("call x()") 945 t.Logf("result %q", r) 946 if !strings.Contains(r, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut \\nlabore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut") { 947 t.Fatalf("wrong value returned") 948 } 949 }) 950 } 951 952 func TestExamineMemoryCmd(t *testing.T) { 953 withTestTerminal("examinememory", t, func(term *FakeTerminal) { 954 term.MustExec("break examinememory.go:19") 955 term.MustExec("break examinememory.go:24") 956 term.MustExec("continue") 957 958 addressStr := strings.TrimSpace(term.MustExec("p bspUintptr")) 959 address, err := strconv.ParseInt(addressStr, 0, 64) 960 if err != nil { 961 t.Fatalf("could convert %s into int64, err %s", addressStr, err) 962 } 963 964 res := term.MustExec("examinemem -count 52 -fmt hex " + addressStr) 965 t.Logf("the result of examining memory \n%s", res) 966 // check first line 967 firstLine := fmt.Sprintf("%#x: 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11", address) 968 if !strings.Contains(res, firstLine) { 969 t.Fatalf("expected first line: %s", firstLine) 970 } 971 972 // check last line 973 lastLine := fmt.Sprintf("%#x: 0x3a 0x3b 0x3c 0x00", address+6*8) 974 if !strings.Contains(res, lastLine) { 975 t.Fatalf("expected last line: %s", lastLine) 976 } 977 978 // second examining memory 979 term.MustExec("continue") 980 res = term.MustExec("x -count 52 -fmt bin " + addressStr) 981 t.Logf("the second result of examining memory result \n%s", res) 982 983 // check first line 984 firstLine = fmt.Sprintf("%#x: 11111111 00001011 00001100 00001101", address) 985 if !strings.Contains(res, firstLine) { 986 t.Fatalf("expected first line: %s", firstLine) 987 } 988 989 // third examining memory: -x addr 990 res = term.MustExec("examinemem -x " + addressStr) 991 t.Logf("the third result of examining memory result \n%s", res) 992 firstLine = fmt.Sprintf("%#x: 0xff", address) 993 if !strings.Contains(res, firstLine) { 994 t.Fatalf("expected first line: %s", firstLine) 995 } 996 997 // fourth examining memory: -x addr + offset 998 res = term.MustExec("examinemem -x " + addressStr + " + 8") 999 t.Logf("the fourth result of examining memory result \n%s", res) 1000 firstLine = fmt.Sprintf("%#x: 0x12", address+8) 1001 if !strings.Contains(res, firstLine) { 1002 t.Fatalf("expected first line: %s", firstLine) 1003 } 1004 // fifth examining memory: -x &var 1005 res = term.MustExec("examinemem -x &bs[0]") 1006 t.Logf("the fifth result of examining memory result \n%s", res) 1007 firstLine = fmt.Sprintf("%#x: 0xff", address) 1008 if !strings.Contains(res, firstLine) { 1009 t.Fatalf("expected first line: %s", firstLine) 1010 } 1011 1012 // sixth examining memory: -fmt and double spaces 1013 res = term.MustExec("examinemem -fmt hex -x &bs[0]") 1014 t.Logf("the sixth result of examining memory result \n%s", res) 1015 firstLine = fmt.Sprintf("%#x: 0xff", address) 1016 if !strings.Contains(res, firstLine) { 1017 t.Fatalf("expected first line: %s", firstLine) 1018 } 1019 }) 1020 1021 withTestTerminal("testvariables2", t, func(term *FakeTerminal) { 1022 tests := []struct { 1023 Expr string 1024 Want int 1025 }{ 1026 {Expr: "&i1", Want: 1}, 1027 {Expr: "&i2", Want: 2}, 1028 {Expr: "p1", Want: 1}, 1029 {Expr: "*pp1", Want: 1}, 1030 {Expr: "&str1[1]", Want: '1'}, 1031 {Expr: "c1.pb", Want: 1}, 1032 {Expr: "&c1.pb.a", Want: 1}, 1033 {Expr: "&c1.pb.a.A", Want: 1}, 1034 {Expr: "&c1.pb.a.B", Want: 2}, 1035 } 1036 term.MustExec("continue") 1037 for _, test := range tests { 1038 res := term.MustExec("examinemem -fmt dec -x " + test.Expr) 1039 // strip addr from output, e.g. "0xc0000160b8: 023" -> "023" 1040 res = strings.TrimSpace(strings.Split(res, ":")[1]) 1041 got, err := strconv.Atoi(res) 1042 if err != nil { 1043 t.Fatalf("expr=%q err=%s", test.Expr, err) 1044 } else if got != test.Want { 1045 t.Errorf("expr=%q got=%d want=%d", test.Expr, got, test.Want) 1046 } 1047 } 1048 }) 1049 } 1050 1051 func TestPrintOnTracepoint(t *testing.T) { 1052 withTestTerminal("increment", t, func(term *FakeTerminal) { 1053 term.MustExec("trace main.Increment") 1054 term.MustExec("on 1 print y+1") 1055 out, _ := term.Exec("continue") 1056 if !strings.Contains(out, "y+1: 4") || !strings.Contains(out, "y+1: 2") || !strings.Contains(out, "y+1: 1") { 1057 t.Errorf("output did not contain breakpoint information: %q", out) 1058 } 1059 }) 1060 } 1061 1062 func TestPrintCastToInterface(t *testing.T) { 1063 withTestTerminal("testvariables2", t, func(term *FakeTerminal) { 1064 term.MustExec("continue") 1065 out := term.MustExec(`p (*"interface {}")(uintptr(&iface2))`) 1066 t.Logf("%q", out) 1067 }) 1068 } 1069 1070 func TestParseNewArgv(t *testing.T) { 1071 testCases := []struct { 1072 in string 1073 tgtargs string 1074 tgtredir string 1075 tgterr string 1076 }{ 1077 {"-noargs", "", " | | ", ""}, 1078 {"-noargs arg1", "", "", "too many arguments to restart"}, 1079 {"arg1 arg2", "arg1 | arg2", " | | ", ""}, 1080 {"arg1 arg2 <input.txt", "arg1 | arg2", "input.txt | | ", ""}, 1081 {"arg1 arg2 < input.txt", "arg1 | arg2", "input.txt | | ", ""}, 1082 {"<input.txt", "", "input.txt | | ", ""}, 1083 {"< input.txt", "", "input.txt | | ", ""}, 1084 {"arg1 < input.txt > output.txt 2> error.txt", "arg1", "input.txt | output.txt | error.txt", ""}, 1085 {"< input.txt > output.txt 2> error.txt", "", "input.txt | output.txt | error.txt", ""}, 1086 {"arg1 <input.txt >output.txt 2>error.txt", "arg1", "input.txt | output.txt | error.txt", ""}, 1087 {"<input.txt >output.txt 2>error.txt", "", "input.txt | output.txt | error.txt", ""}, 1088 {"<input.txt <input2.txt", "", "", "redirect error: stdin redirected twice"}, 1089 } 1090 1091 for _, tc := range testCases { 1092 resetArgs, newArgv, newRedirects, err := parseNewArgv(tc.in) 1093 t.Logf("%q -> %q %q %v\n", tc.in, newArgv, newRedirects, err) 1094 if tc.tgterr != "" { 1095 if err == nil { 1096 t.Errorf("Expected error %q, got no error", tc.tgterr) 1097 } else if errstr := err.Error(); errstr != tc.tgterr { 1098 t.Errorf("Expected error %q, got error %q", tc.tgterr, errstr) 1099 } 1100 } else { 1101 if !resetArgs { 1102 t.Errorf("parse error, resetArgs is false") 1103 continue 1104 } 1105 argvstr := strings.Join(newArgv, " | ") 1106 if argvstr != tc.tgtargs { 1107 t.Errorf("Expected new arguments %q, got %q", tc.tgtargs, argvstr) 1108 } 1109 redirstr := strings.Join(newRedirects[:], " | ") 1110 if redirstr != tc.tgtredir { 1111 t.Errorf("Expected new redirects %q, got %q", tc.tgtredir, redirstr) 1112 } 1113 } 1114 } 1115 } 1116 1117 func TestContinueUntil(t *testing.T) { 1118 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 1119 if runtime.GOARCH != "386" { 1120 listIsAt(t, term, "continue main.main", 16, -1, -1) 1121 } else { 1122 listIsAt(t, term, "continue main.main", 17, -1, -1) 1123 } 1124 listIsAt(t, term, "continue main.sayhi", 12, -1, -1) 1125 }) 1126 } 1127 1128 func TestContinueUntilExistingBreakpoint(t *testing.T) { 1129 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 1130 term.MustExec("break main.main") 1131 if runtime.GOARCH != "386" { 1132 listIsAt(t, term, "continue main.main", 16, -1, -1) 1133 } else { 1134 listIsAt(t, term, "continue main.main", 17, -1, -1) 1135 } 1136 listIsAt(t, term, "continue main.sayhi", 12, -1, -1) 1137 }) 1138 } 1139 1140 func TestPrintFormat(t *testing.T) { 1141 withTestTerminal("testvariables2", t, func(term *FakeTerminal) { 1142 term.MustExec("continue") 1143 out := term.MustExec("print %#x m2[1].B") 1144 if !strings.Contains(out, "0xb\n") { 1145 t.Fatalf("output did not contain '0xb': %q", out) 1146 } 1147 }) 1148 } 1149 1150 func TestHitCondBreakpoint(t *testing.T) { 1151 withTestTerminal("break", t, func(term *FakeTerminal) { 1152 term.MustExec("break bp1 main.main:4") 1153 term.MustExec("condition -hitcount bp1 > 2") 1154 listIsAt(t, term, "continue", 7, -1, -1) 1155 out := term.MustExec("print i") 1156 t.Logf("%q", out) 1157 if !strings.Contains(out, "3\n") { 1158 t.Fatalf("wrong value of i") 1159 } 1160 }) 1161 } 1162 1163 func TestClearCondBreakpoint(t *testing.T) { 1164 withTestTerminal("break", t, func(term *FakeTerminal) { 1165 term.MustExec("break main.main:4") 1166 term.MustExec("condition 1 i%3==2") 1167 listIsAt(t, term, "continue", 7, -1, -1) 1168 out := term.MustExec("print i") 1169 t.Logf("%q", out) 1170 if !strings.Contains(out, "2\n") { 1171 t.Fatalf("wrong value of i") 1172 } 1173 term.MustExec("condition -clear 1") 1174 listIsAt(t, term, "continue", 7, -1, -1) 1175 out = term.MustExec("print i") 1176 t.Logf("%q", out) 1177 if !strings.Contains(out, "3\n") { 1178 t.Fatalf("wrong value of i") 1179 } 1180 }) 1181 } 1182 1183 func TestBreakpointEditing(t *testing.T) { 1184 term := &FakeTerminal{ 1185 t: t, 1186 Term: New(nil, &config.Config{}), 1187 } 1188 _ = term 1189 1190 var testCases = []struct { 1191 inBp *api.Breakpoint 1192 inBpStr string 1193 edit string 1194 outBp *api.Breakpoint 1195 }{ 1196 { // tracepoint -> breakpoint 1197 &api.Breakpoint{Tracepoint: true}, 1198 "trace", 1199 "", 1200 &api.Breakpoint{}}, 1201 { // breakpoint -> tracepoint 1202 &api.Breakpoint{Variables: []string{"a"}}, 1203 "print a", 1204 "print a\ntrace", 1205 &api.Breakpoint{Tracepoint: true, Variables: []string{"a"}}}, 1206 { // add print var 1207 &api.Breakpoint{Variables: []string{"a"}}, 1208 "print a", 1209 "print b\nprint a\n", 1210 &api.Breakpoint{Variables: []string{"b", "a"}}}, 1211 { // add goroutine flag 1212 &api.Breakpoint{}, 1213 "", 1214 "goroutine", 1215 &api.Breakpoint{Goroutine: true}}, 1216 { // remove goroutine flag 1217 &api.Breakpoint{Goroutine: true}, 1218 "goroutine", 1219 "", 1220 &api.Breakpoint{}}, 1221 { // add stack directive 1222 &api.Breakpoint{}, 1223 "", 1224 "stack 10", 1225 &api.Breakpoint{Stacktrace: 10}}, 1226 { // remove stack directive 1227 &api.Breakpoint{Stacktrace: 20}, 1228 "stack 20", 1229 "print a", 1230 &api.Breakpoint{Variables: []string{"a"}}}, 1231 { // add condition 1232 &api.Breakpoint{Variables: []string{"a"}}, 1233 "print a", 1234 "print a\ncond a < b", 1235 &api.Breakpoint{Variables: []string{"a"}, Cond: "a < b"}}, 1236 { // remove condition 1237 &api.Breakpoint{Cond: "a < b"}, 1238 "cond a < b", 1239 "", 1240 &api.Breakpoint{}}, 1241 { // change condition 1242 &api.Breakpoint{Cond: "a < b"}, 1243 "cond a < b", 1244 "cond a < 5", 1245 &api.Breakpoint{Cond: "a < 5"}}, 1246 { // change hitcount condition 1247 &api.Breakpoint{HitCond: "% 2"}, 1248 "cond -hitcount % 2", 1249 "cond -hitcount = 2", 1250 &api.Breakpoint{HitCond: "= 2"}}, 1251 } 1252 1253 for _, tc := range testCases { 1254 bp := *tc.inBp 1255 bpStr := strings.Join(formatBreakpointAttrs("", &bp, true), "\n") 1256 if bpStr != tc.inBpStr { 1257 t.Errorf("Expected %q got %q for:\n%#v", tc.inBpStr, bpStr, tc.inBp) 1258 } 1259 ctx := callContext{Prefix: onPrefix, Scope: api.EvalScope{GoroutineID: -1, Frame: 0, DeferredCall: 0}, Breakpoint: &bp} 1260 err := term.cmds.parseBreakpointAttrs(nil, ctx, strings.NewReader(tc.edit)) 1261 if err != nil { 1262 t.Errorf("Unexpected error during edit %q", tc.edit) 1263 } 1264 if !reflect.DeepEqual(bp, *tc.outBp) { 1265 t.Errorf("mismatch after edit\nexpected: %#v\ngot: %#v", tc.outBp, bp) 1266 } 1267 } 1268 } 1269 1270 func TestTranscript(t *testing.T) { 1271 withTestTerminal("math", t, func(term *FakeTerminal) { 1272 term.MustExec("break main.main") 1273 out := term.MustExec("continue") 1274 if !strings.HasPrefix(out, "> main.main()") { 1275 t.Fatalf("Wrong output for next: <%s>", out) 1276 } 1277 fh, err := ioutil.TempFile("", "test-transcript-*") 1278 if err != nil { 1279 t.Fatalf("TempFile: %v", err) 1280 } 1281 name := fh.Name() 1282 fh.Close() 1283 t.Logf("output to %q", name) 1284 1285 slurp := func() string { 1286 b, err := ioutil.ReadFile(name) 1287 if err != nil { 1288 t.Fatalf("could not read transcript file: %v", err) 1289 } 1290 return string(b) 1291 } 1292 1293 term.MustExec(fmt.Sprintf("transcript %s", name)) 1294 out = term.MustExec("list") 1295 //term.MustExec("transcript -off") 1296 if out != slurp() { 1297 t.Logf("output of list %s", out) 1298 t.Logf("contents of transcript: %s", slurp()) 1299 t.Errorf("transcript and command out differ") 1300 } 1301 1302 term.MustExec(fmt.Sprintf("transcript -t -x %s", name)) 1303 out = term.MustExec(`print "hello"`) 1304 if out != "" { 1305 t.Errorf("output of print is %q but should have been suppressed by transcript", out) 1306 } 1307 if slurp() != "\"hello\"\n" { 1308 t.Errorf("wrong contents of transcript: %q", slurp()) 1309 } 1310 1311 os.Remove(name) 1312 }) 1313 }