github.com/undoio/delve@v1.9.0/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/undoio/delve/pkg/config" 21 "github.com/undoio/delve/pkg/goversion" 22 "github.com/undoio/delve/pkg/logflags" 23 "github.com/undoio/delve/pkg/proc/test" 24 "github.com/undoio/delve/service" 25 "github.com/undoio/delve/service/api" 26 "github.com/undoio/delve/service/debugger" 27 "github.com/undoio/delve/service/rpc2" 28 "github.com/undoio/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" || testBackend == "undo" { 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 test.AllowRecording(t) 295 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 296 term.Exec("continue") 297 status, err := term.handleExit() 298 if err != nil { 299 t.Fatal(err) 300 } 301 if status != 0 { 302 t.Fatalf("incorrect exit status, expected 0, got %d", status) 303 } 304 }) 305 } 306 307 func TestScopePrefix(t *testing.T) { 308 const goroutinesLinePrefix = " Goroutine " 309 const goroutinesCurLinePrefix = "* Goroutine " 310 test.AllowRecording(t) 311 312 lenient := 0 313 if runtime.GOOS == "windows" { 314 lenient = 1 315 } 316 317 withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { 318 term.MustExec("b stacktraceme") 319 term.MustExec("continue") 320 321 goroutinesOut := strings.Split(term.MustExec("goroutines"), "\n") 322 agoroutines := []int{} 323 nonagoroutines := []int{} 324 curgid := -1 325 326 for _, line := range goroutinesOut { 327 iscur := strings.HasPrefix(line, goroutinesCurLinePrefix) 328 if !iscur && !strings.HasPrefix(line, goroutinesLinePrefix) { 329 continue 330 } 331 332 dash := strings.Index(line, " - ") 333 if dash < 0 { 334 continue 335 } 336 337 gid, err := strconv.Atoi(line[len(goroutinesLinePrefix):dash]) 338 if err != nil { 339 continue 340 } 341 342 if iscur { 343 curgid = gid 344 } 345 346 if idx := strings.Index(line, " main.agoroutine "); idx < 0 { 347 nonagoroutines = append(nonagoroutines, gid) 348 continue 349 } 350 351 agoroutines = append(agoroutines, gid) 352 } 353 354 if len(agoroutines) > 10 { 355 t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d found): %q", len(agoroutines), goroutinesOut) 356 } 357 358 if len(agoroutines) < 10 { 359 extraAgoroutines := 0 360 for _, gid := range nonagoroutines { 361 stackOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d stack", gid)), "\n") 362 for _, line := range stackOut { 363 if strings.HasSuffix(line, " main.agoroutine") { 364 extraAgoroutines++ 365 break 366 } 367 } 368 } 369 if len(agoroutines)+extraAgoroutines < 10-lenient { 370 t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d+%d found): %q", len(agoroutines), extraAgoroutines, goroutinesOut) 371 } 372 } 373 374 if curgid < 0 { 375 t.Fatalf("Could not find current goroutine in output of goroutines: %q", goroutinesOut) 376 } 377 378 seen := make([]bool, 10) 379 for _, gid := range agoroutines { 380 stackOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d stack", gid)), "\n") 381 fid := -1 382 for _, line := range stackOut { 383 line = strings.TrimLeft(line, " ") 384 space := strings.Index(line, " ") 385 if space < 0 { 386 continue 387 } 388 curfid, err := strconv.Atoi(line[:space]) 389 if err != nil { 390 continue 391 } 392 393 if idx := strings.Index(line, " main.agoroutine"); idx >= 0 { 394 fid = curfid 395 break 396 } 397 } 398 if fid < 0 { 399 t.Fatalf("Could not find frame for goroutine %d: %q", gid, stackOut) 400 } 401 term.AssertExec(fmt.Sprintf("goroutine %d frame %d locals", gid, fid), "(no locals)\n") 402 argsOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d frame %d args", gid, fid)), "\n") 403 if len(argsOut) != 4 || argsOut[3] != "" { 404 t.Fatalf("Wrong number of arguments in goroutine %d frame %d: %v", gid, fid, argsOut) 405 } 406 out := term.MustExec(fmt.Sprintf("goroutine %d frame %d p i", gid, fid)) 407 ival, err := strconv.Atoi(out[:len(out)-1]) 408 if err != nil { 409 t.Fatalf("could not parse value %q of i for goroutine %d frame %d: %v", out, gid, fid, err) 410 } 411 seen[ival] = true 412 } 413 414 for i := range seen { 415 if !seen[i] { 416 if lenient > 0 { 417 lenient-- 418 } else { 419 t.Fatalf("goroutine %d not found", i) 420 } 421 } 422 } 423 424 term.MustExec("c") 425 426 term.AssertExecError("frame", "not enough arguments") 427 term.AssertExecError(fmt.Sprintf("goroutine %d frame 10 locals", curgid), fmt.Sprintf("Frame 10 does not exist in goroutine %d", curgid)) 428 term.AssertExecError("goroutine 9000 locals", "unknown goroutine 9000") 429 430 term.AssertExecError("print n", "could not find symbol value for n") 431 term.AssertExec("frame 1 print n", "3\n") 432 term.AssertExec("frame 2 print n", "2\n") 433 term.AssertExec("frame 3 print n", "1\n") 434 term.AssertExec("frame 4 print n", "0\n") 435 term.AssertExecError("frame 5 print n", "could not find symbol value for n") 436 437 term.MustExec("frame 2") 438 term.AssertExec("print n", "2\n") 439 term.MustExec("frame 4") 440 term.AssertExec("print n", "0\n") 441 term.MustExec("down") 442 term.AssertExec("print n", "1\n") 443 term.MustExec("down 2") 444 term.AssertExec("print n", "3\n") 445 term.AssertExecError("down 2", "Invalid frame -1") 446 term.AssertExec("print n", "3\n") 447 term.MustExec("up 2") 448 term.AssertExec("print n", "1\n") 449 term.AssertExecError("up 100", "Invalid frame 103") 450 term.AssertExec("print n", "1\n") 451 452 term.MustExec("step") 453 term.AssertExecError("print n", "could not find symbol value for n") 454 term.MustExec("frame 2") 455 term.AssertExec("print n", "2\n") 456 }) 457 } 458 459 func TestOnPrefix(t *testing.T) { 460 if runtime.GOOS == "freebsd" { 461 t.Skip("test is not valid on FreeBSD") 462 } 463 const prefix = "\ti: " 464 test.AllowRecording(t) 465 lenient := false 466 if runtime.GOOS == "windows" { 467 lenient = true 468 } 469 withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { 470 term.MustExec("b agobp main.agoroutine") 471 term.MustExec("on agobp print i") 472 473 seen := make([]bool, 10) 474 475 for { 476 outstr, err := term.Exec("continue") 477 if err != nil { 478 if !strings.Contains(err.Error(), "exited") { 479 t.Fatalf("Unexpected error executing 'continue': %v", err) 480 } 481 break 482 } 483 out := strings.Split(outstr, "\n") 484 485 for i := range out { 486 if !strings.HasPrefix(out[i], prefix) { 487 continue 488 } 489 id, err := strconv.Atoi(out[i][len(prefix):]) 490 if err != nil { 491 continue 492 } 493 if seen[id] { 494 t.Fatalf("Goroutine %d seen twice\n", id) 495 } 496 seen[id] = true 497 } 498 } 499 500 for i := range seen { 501 if !seen[i] { 502 if lenient { 503 lenient = false 504 } else { 505 t.Fatalf("Goroutine %d not seen\n", i) 506 } 507 } 508 } 509 }) 510 } 511 512 func TestNoVars(t *testing.T) { 513 test.AllowRecording(t) 514 withTestTerminal("locationsUpperCase", t, func(term *FakeTerminal) { 515 term.MustExec("b main.main") 516 term.MustExec("continue") 517 term.AssertExec("args", "(no args)\n") 518 term.AssertExec("locals", "(no locals)\n") 519 term.AssertExec("vars filterThatMatchesNothing", "(no vars)\n") 520 }) 521 } 522 523 func TestOnPrefixLocals(t *testing.T) { 524 if runtime.GOOS == "freebsd" { 525 t.Skip("test is not valid on FreeBSD") 526 } 527 const prefix = "\ti: " 528 test.AllowRecording(t) 529 withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { 530 term.MustExec("b agobp main.agoroutine") 531 term.MustExec("on agobp args -v") 532 533 seen := make([]bool, 10) 534 535 for { 536 outstr, err := term.Exec("continue") 537 if err != nil { 538 if !strings.Contains(err.Error(), "exited") { 539 t.Fatalf("Unexpected error executing 'continue': %v", err) 540 } 541 break 542 } 543 out := strings.Split(outstr, "\n") 544 545 for i := range out { 546 if !strings.HasPrefix(out[i], prefix) { 547 continue 548 } 549 id, err := strconv.Atoi(out[i][len(prefix):]) 550 if err != nil { 551 continue 552 } 553 if seen[id] { 554 t.Fatalf("Goroutine %d seen twice\n", id) 555 } 556 seen[id] = true 557 } 558 } 559 560 for i := range seen { 561 if !seen[i] { 562 t.Fatalf("Goroutine %d not seen\n", i) 563 } 564 } 565 }) 566 } 567 568 func countOccurrences(s, needle string) int { 569 count := 0 570 for { 571 idx := strings.Index(s, needle) 572 if idx < 0 { 573 break 574 } 575 count++ 576 s = s[idx+len(needle):] 577 } 578 return count 579 } 580 581 func listIsAt(t *testing.T, term *FakeTerminal, listcmd string, cur, start, end int) { 582 t.Helper() 583 outstr := term.MustExec(listcmd) 584 lines := strings.Split(outstr, "\n") 585 586 t.Logf("%q: %q", listcmd, outstr) 587 588 if cur >= 0 && !strings.Contains(lines[0], fmt.Sprintf(":%d", cur)) { 589 t.Fatalf("Could not find current line number in first output line: %q", lines[0]) 590 } 591 592 re := regexp.MustCompile(`(=>)?\s+(\d+):`) 593 594 outStart, outEnd := 0, 0 595 596 for _, line := range lines[1:] { 597 if line == "" { 598 continue 599 } 600 v := re.FindStringSubmatch(line) 601 if len(v) != 3 { 602 continue 603 } 604 curline, _ := strconv.Atoi(v[2]) 605 if v[1] == "=>" { 606 if cur != curline { 607 t.Fatalf("Wrong current line, got %d expected %d", curline, cur) 608 } 609 } 610 if outStart == 0 { 611 outStart = curline 612 } 613 outEnd = curline 614 } 615 616 if start != -1 || end != -1 { 617 if outStart != start || outEnd != end { 618 t.Fatalf("Wrong output range, got %d:%d expected %d:%d", outStart, outEnd, start, end) 619 } 620 } 621 } 622 623 func TestListCmd(t *testing.T) { 624 withTestTerminal("testvariables", t, func(term *FakeTerminal) { 625 term.MustExec("continue") 626 term.MustExec("continue") 627 listIsAt(t, term, "list", 27, 22, 32) 628 listIsAt(t, term, "list 69", 69, 64, 74) 629 listIsAt(t, term, "frame 1 list", 66, 61, 71) 630 listIsAt(t, term, "frame 1 list 69", 69, 64, 74) 631 _, err := term.Exec("frame 50 list") 632 if err == nil { 633 t.Fatalf("Expected error requesting 50th frame") 634 } 635 listIsAt(t, term, "list testvariables.go:1", -1, 1, 6) 636 listIsAt(t, term, "list testvariables.go:10000", -1, 0, 0) 637 }) 638 } 639 640 func TestReverseContinue(t *testing.T) { 641 test.AllowRecording(t) 642 if testBackend != "rr" && testBackend != "undo" { 643 return 644 } 645 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 646 term.MustExec("break main.main") 647 term.MustExec("break main.sayhi") 648 listIsAt(t, term, "continue", 16, -1, -1) 649 listIsAt(t, term, "continue", 12, -1, -1) 650 listIsAt(t, term, "rewind", 16, -1, -1) 651 }) 652 } 653 654 func TestCheckpoints(t *testing.T) { 655 test.AllowRecording(t) 656 if testBackend != "rr" && testBackend != "undo" { 657 return 658 } 659 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 660 term.MustExec("break main.main") 661 listIsAt(t, term, "continue", 16, -1, -1) 662 term.MustExec("checkpoint") 663 term.MustExec("checkpoints") 664 listIsAt(t, term, "next", 17, -1, -1) 665 listIsAt(t, term, "next", 18, -1, -1) 666 term.MustExec("restart c1") 667 term.MustExec("goroutine 1") 668 listIsAt(t, term, "list", 16, -1, -1) 669 }) 670 } 671 672 func TestNextWithCount(t *testing.T) { 673 test.AllowRecording(t) 674 withTestTerminal("nextcond", t, func(term *FakeTerminal) { 675 term.MustExec("break main.main") 676 listIsAt(t, term, "continue", 8, -1, -1) 677 listIsAt(t, term, "next 2", 10, -1, -1) 678 }) 679 } 680 681 func TestRestart(t *testing.T) { 682 withTestTerminal("restartargs", t, func(term *FakeTerminal) { 683 term.MustExec("break main.printArgs") 684 term.MustExec("continue") 685 if out := term.MustExec("print main.args"); !strings.Contains(out, ", []") { 686 t.Fatalf("wrong args: %q", out) 687 } 688 // Reset the arg list 689 term.MustExec("restart hello") 690 term.MustExec("continue") 691 if out := term.MustExec("print main.args"); !strings.Contains(out, ", [\"hello\"]") { 692 t.Fatalf("wrong args: %q ", out) 693 } 694 // Restart w/o arg should retain the current args. 695 term.MustExec("restart") 696 term.MustExec("continue") 697 if out := term.MustExec("print main.args"); !strings.Contains(out, ", [\"hello\"]") { 698 t.Fatalf("wrong args: %q ", out) 699 } 700 // Empty arg list 701 term.MustExec("restart -noargs") 702 term.MustExec("continue") 703 if out := term.MustExec("print main.args"); !strings.Contains(out, ", []") { 704 t.Fatalf("wrong args: %q ", out) 705 } 706 }) 707 } 708 709 func TestIssue827(t *testing.T) { 710 // switching goroutines when the current thread isn't running any goroutine 711 // causes nil pointer dereference. 712 withTestTerminal("notify-v2", t, func(term *FakeTerminal) { 713 go func() { 714 time.Sleep(1 * time.Second) 715 http.Get("http://127.0.0.1:8888/test") 716 time.Sleep(1 * time.Second) 717 term.client.Halt() 718 }() 719 term.MustExec("continue") 720 term.MustExec("goroutine 1") 721 }) 722 } 723 724 func findCmdName(c *Commands, cmdstr string, prefix cmdPrefix) string { 725 for _, v := range c.cmds { 726 if v.match(cmdstr) { 727 if prefix != noPrefix && v.allowedPrefixes&prefix == 0 { 728 continue 729 } 730 return v.aliases[0] 731 } 732 } 733 return "" 734 } 735 736 func TestConfig(t *testing.T) { 737 var term Term 738 term.conf = &config.Config{} 739 term.cmds = DebugCommands(nil) 740 741 err := configureCmd(&term, callContext{}, "nonexistent-parameter 10") 742 if err == nil { 743 t.Fatalf("expected error executing configureCmd(nonexistent-parameter)") 744 } 745 746 err = configureCmd(&term, callContext{}, "max-string-len 10") 747 if err != nil { 748 t.Fatalf("error executing configureCmd(max-string-len): %v", err) 749 } 750 if term.conf.MaxStringLen == nil { 751 t.Fatalf("expected MaxStringLen 10, got nil") 752 } 753 if *term.conf.MaxStringLen != 10 { 754 t.Fatalf("expected MaxStringLen 10, got: %d", *term.conf.MaxStringLen) 755 } 756 err = configureCmd(&term, callContext{}, "show-location-expr true") 757 if err != nil { 758 t.Fatalf("error executing configureCmd(show-location-expr true)") 759 } 760 if term.conf.ShowLocationExpr != true { 761 t.Fatalf("expected ShowLocationExpr true, got false") 762 } 763 err = configureCmd(&term, callContext{}, "max-variable-recurse 4") 764 if err != nil { 765 t.Fatalf("error executing configureCmd(max-variable-recurse): %v", err) 766 } 767 if term.conf.MaxVariableRecurse == nil { 768 t.Fatalf("expected MaxVariableRecurse 4, got nil") 769 } 770 if *term.conf.MaxVariableRecurse != 4 { 771 t.Fatalf("expected MaxVariableRecurse 4, got: %d", *term.conf.MaxVariableRecurse) 772 } 773 774 err = configureCmd(&term, callContext{}, "substitute-path a b") 775 if err != nil { 776 t.Fatalf("error executing configureCmd(substitute-path a b): %v", err) 777 } 778 if len(term.conf.SubstitutePath) != 1 || (term.conf.SubstitutePath[0] != config.SubstitutePathRule{From: "a", To: "b"}) { 779 t.Fatalf("unexpected SubstitutePathRules after insert %v", term.conf.SubstitutePath) 780 } 781 782 err = configureCmd(&term, callContext{}, "substitute-path a") 783 if err != nil { 784 t.Fatalf("error executing configureCmd(substitute-path a): %v", err) 785 } 786 if len(term.conf.SubstitutePath) != 0 { 787 t.Fatalf("unexpected SubstitutePathRules after delete %v", term.conf.SubstitutePath) 788 } 789 790 err = configureCmd(&term, callContext{}, "alias print blah") 791 if err != nil { 792 t.Fatalf("error executing configureCmd(alias print blah): %v", err) 793 } 794 if len(term.conf.Aliases["print"]) != 1 { 795 t.Fatalf("aliases not changed after configure command %v", term.conf.Aliases) 796 } 797 if findCmdName(term.cmds, "blah", noPrefix) != "print" { 798 t.Fatalf("new alias not found") 799 } 800 801 err = configureCmd(&term, callContext{}, "alias blah") 802 if err != nil { 803 t.Fatalf("error executing configureCmd(alias blah): %v", err) 804 } 805 if len(term.conf.Aliases["print"]) != 0 { 806 t.Fatalf("alias not removed after configure command %v", term.conf.Aliases) 807 } 808 if findCmdName(term.cmds, "blah", noPrefix) != "" { 809 t.Fatalf("new alias found after delete") 810 } 811 } 812 813 func TestIssue1090(t *testing.T) { 814 // Exit while executing 'next' should report the "Process exited" error 815 // message instead of crashing. 816 test.AllowRecording(t) 817 withTestTerminal("math", t, func(term *FakeTerminal) { 818 term.MustExec("break main.main") 819 term.MustExec("continue") 820 for { 821 _, err := term.Exec("next") 822 if err != nil && strings.Contains(err.Error(), " has exited with status ") { 823 break 824 } 825 } 826 }) 827 } 828 829 func TestPrintContextParkedGoroutine(t *testing.T) { 830 test.AllowRecording(t) 831 withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { 832 term.MustExec("break stacktraceme") 833 term.MustExec("continue") 834 835 // pick a goroutine that isn't running on a thread 836 gid := "" 837 gout := strings.Split(term.MustExec("goroutines"), "\n") 838 t.Logf("goroutines -> %q", gout) 839 for _, gline := range gout { 840 if !strings.Contains(gline, "thread ") && strings.Contains(gline, "agoroutine") { 841 if dash := strings.Index(gline, " - "); dash > 0 { 842 gid = gline[len(" Goroutine "):dash] 843 break 844 } 845 } 846 } 847 848 t.Logf("picked %q", gid) 849 term.MustExec(fmt.Sprintf("goroutine %s", gid)) 850 851 frameout := strings.Split(term.MustExec("frame 0"), "\n") 852 t.Logf("frame 0 -> %q", frameout) 853 if strings.Contains(frameout[0], "stacktraceme") { 854 t.Fatal("bad output for `frame 0` command on a parked goorutine") 855 } 856 857 listout := strings.Split(term.MustExec("list"), "\n") 858 t.Logf("list -> %q", listout) 859 if strings.Contains(listout[0], "stacktraceme") { 860 t.Fatal("bad output for list command on a parked goroutine") 861 } 862 }) 863 } 864 865 func TestStepOutReturn(t *testing.T) { 866 ver, _ := goversion.Parse(runtime.Version()) 867 if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) { 868 t.Skip("return variables aren't marked on 1.9 or earlier") 869 } 870 test.AllowRecording(t) 871 withTestTerminal("stepoutret", t, func(term *FakeTerminal) { 872 term.MustExec("break main.stepout") 873 term.MustExec("continue") 874 out := term.MustExec("stepout") 875 t.Logf("output: %q", out) 876 if !strings.Contains(out, "num: ") || !strings.Contains(out, "str: ") { 877 t.Fatal("could not find parameter") 878 } 879 }) 880 } 881 882 func TestOptimizationCheck(t *testing.T) { 883 test.AllowRecording(t) 884 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 885 term.MustExec("break main.main") 886 out := term.MustExec("continue") 887 t.Logf("output %q", out) 888 if strings.Contains(out, optimizedFunctionWarning) { 889 t.Fatal("optimized function warning") 890 } 891 }) 892 893 if goversion.VersionAfterOrEqual(runtime.Version(), 1, 10) { 894 withTestTerminalBuildFlags("continuetestprog", t, test.EnableOptimization|test.EnableInlining, func(term *FakeTerminal) { 895 term.MustExec("break main.main") 896 out := term.MustExec("continue") 897 t.Logf("output %q", out) 898 if !strings.Contains(out, optimizedFunctionWarning) { 899 t.Fatal("optimized function warning missing") 900 } 901 }) 902 } 903 } 904 905 func TestTruncateStacktrace(t *testing.T) { 906 const stacktraceTruncatedMessage = "(truncated)" 907 test.AllowRecording(t) 908 withTestTerminal("stacktraceprog", t, func(term *FakeTerminal) { 909 term.MustExec("break main.stacktraceme") 910 term.MustExec("continue") 911 out1 := term.MustExec("stack") 912 t.Logf("untruncated output %q", out1) 913 if strings.Contains(out1, stacktraceTruncatedMessage) { 914 t.Fatalf("stacktrace was truncated") 915 } 916 out2 := term.MustExec("stack 1") 917 t.Logf("truncated output %q", out2) 918 if !strings.Contains(out2, stacktraceTruncatedMessage) { 919 t.Fatalf("stacktrace was not truncated") 920 } 921 }) 922 } 923 924 func TestIssue1493(t *testing.T) { 925 // The 'regs' command without the '-a' option should only return 926 // general purpose registers. 927 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 928 r := term.MustExec("regs") 929 nr := len(strings.Split(r, "\n")) 930 t.Logf("regs: %s", r) 931 ra := term.MustExec("regs -a") 932 nra := len(strings.Split(ra, "\n")) 933 t.Logf("regs -a: %s", ra) 934 if nr > nra/2+1 { 935 t.Fatalf("'regs' returned too many registers (%d) compared to 'regs -a' (%d)", nr, nra) 936 } 937 }) 938 } 939 940 func findStarFile(name string) string { 941 return filepath.Join(test.FindFixturesDir(), name+".star") 942 } 943 944 func TestIssue1598(t *testing.T) { 945 test.MustSupportFunctionCalls(t, testBackend) 946 withTestTerminal("issue1598", t, func(term *FakeTerminal) { 947 term.MustExec("break issue1598.go:5") 948 term.MustExec("continue") 949 term.MustExec("config max-string-len 500") 950 r := term.MustExec("call x()") 951 t.Logf("result %q", r) 952 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") { 953 t.Fatalf("wrong value returned") 954 } 955 }) 956 } 957 958 func TestExamineMemoryCmd(t *testing.T) { 959 withTestTerminal("examinememory", t, func(term *FakeTerminal) { 960 term.MustExec("break examinememory.go:19") 961 term.MustExec("break examinememory.go:24") 962 term.MustExec("continue") 963 964 addressStr := strings.TrimSpace(term.MustExec("p bspUintptr")) 965 address, err := strconv.ParseInt(addressStr, 0, 64) 966 if err != nil { 967 t.Fatalf("could convert %s into int64, err %s", addressStr, err) 968 } 969 970 res := term.MustExec("examinemem -count 52 -fmt hex " + addressStr) 971 t.Logf("the result of examining memory \n%s", res) 972 // check first line 973 firstLine := fmt.Sprintf("%#x: 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11", address) 974 if !strings.Contains(res, firstLine) { 975 t.Fatalf("expected first line: %s", firstLine) 976 } 977 978 // check last line 979 lastLine := fmt.Sprintf("%#x: 0x3a 0x3b 0x3c 0x00", address+6*8) 980 if !strings.Contains(res, lastLine) { 981 t.Fatalf("expected last line: %s", lastLine) 982 } 983 984 // second examining memory 985 term.MustExec("continue") 986 res = term.MustExec("x -count 52 -fmt bin " + addressStr) 987 t.Logf("the second result of examining memory result \n%s", res) 988 989 // check first line 990 firstLine = fmt.Sprintf("%#x: 11111111 00001011 00001100 00001101", address) 991 if !strings.Contains(res, firstLine) { 992 t.Fatalf("expected first line: %s", firstLine) 993 } 994 995 // third examining memory: -x addr 996 res = term.MustExec("examinemem -x " + addressStr) 997 t.Logf("the third result of examining memory result \n%s", res) 998 firstLine = fmt.Sprintf("%#x: 0xff", address) 999 if !strings.Contains(res, firstLine) { 1000 t.Fatalf("expected first line: %s", firstLine) 1001 } 1002 1003 // fourth examining memory: -x addr + offset 1004 res = term.MustExec("examinemem -x " + addressStr + " + 8") 1005 t.Logf("the fourth result of examining memory result \n%s", res) 1006 firstLine = fmt.Sprintf("%#x: 0x12", address+8) 1007 if !strings.Contains(res, firstLine) { 1008 t.Fatalf("expected first line: %s", firstLine) 1009 } 1010 // fifth examining memory: -x &var 1011 res = term.MustExec("examinemem -x &bs[0]") 1012 t.Logf("the fifth result of examining memory result \n%s", res) 1013 firstLine = fmt.Sprintf("%#x: 0xff", address) 1014 if !strings.Contains(res, firstLine) { 1015 t.Fatalf("expected first line: %s", firstLine) 1016 } 1017 1018 // sixth examining memory: -fmt and double spaces 1019 res = term.MustExec("examinemem -fmt hex -x &bs[0]") 1020 t.Logf("the sixth result of examining memory result \n%s", res) 1021 firstLine = fmt.Sprintf("%#x: 0xff", address) 1022 if !strings.Contains(res, firstLine) { 1023 t.Fatalf("expected first line: %s", firstLine) 1024 } 1025 }) 1026 1027 withTestTerminal("testvariables2", t, func(term *FakeTerminal) { 1028 tests := []struct { 1029 Expr string 1030 Want int 1031 }{ 1032 {Expr: "&i1", Want: 1}, 1033 {Expr: "&i2", Want: 2}, 1034 {Expr: "p1", Want: 1}, 1035 {Expr: "*pp1", Want: 1}, 1036 {Expr: "&str1[1]", Want: '1'}, 1037 {Expr: "c1.pb", Want: 1}, 1038 {Expr: "&c1.pb.a", Want: 1}, 1039 {Expr: "&c1.pb.a.A", Want: 1}, 1040 {Expr: "&c1.pb.a.B", Want: 2}, 1041 } 1042 term.MustExec("continue") 1043 for _, test := range tests { 1044 res := term.MustExec("examinemem -fmt dec -x " + test.Expr) 1045 // strip addr from output, e.g. "0xc0000160b8: 023" -> "023" 1046 res = strings.TrimSpace(strings.Split(res, ":")[1]) 1047 got, err := strconv.Atoi(res) 1048 if err != nil { 1049 t.Fatalf("expr=%q err=%s", test.Expr, err) 1050 } else if got != test.Want { 1051 t.Errorf("expr=%q got=%d want=%d", test.Expr, got, test.Want) 1052 } 1053 } 1054 }) 1055 } 1056 1057 func TestPrintOnTracepoint(t *testing.T) { 1058 test.AllowRecording(t) 1059 withTestTerminal("increment", t, func(term *FakeTerminal) { 1060 term.MustExec("trace main.Increment") 1061 term.MustExec("on 1 print y+1") 1062 out, _ := term.Exec("continue") 1063 if !strings.Contains(out, "y+1: 4") || !strings.Contains(out, "y+1: 2") || !strings.Contains(out, "y+1: 1") { 1064 t.Errorf("output did not contain breakpoint information: %q", out) 1065 } 1066 }) 1067 } 1068 1069 func TestPrintCastToInterface(t *testing.T) { 1070 withTestTerminal("testvariables2", t, func(term *FakeTerminal) { 1071 term.MustExec("continue") 1072 out := term.MustExec(`p (*"interface {}")(uintptr(&iface2))`) 1073 t.Logf("%q", out) 1074 }) 1075 } 1076 1077 func TestParseNewArgv(t *testing.T) { 1078 testCases := []struct { 1079 in string 1080 tgtargs string 1081 tgtredir string 1082 tgterr string 1083 }{ 1084 {"-noargs", "", " | | ", ""}, 1085 {"-noargs arg1", "", "", "too many arguments to restart"}, 1086 {"arg1 arg2", "arg1 | arg2", " | | ", ""}, 1087 {"arg1 arg2 <input.txt", "arg1 | arg2", "input.txt | | ", ""}, 1088 {"arg1 arg2 < input.txt", "arg1 | arg2", "input.txt | | ", ""}, 1089 {"<input.txt", "", "input.txt | | ", ""}, 1090 {"< input.txt", "", "input.txt | | ", ""}, 1091 {"arg1 < input.txt > output.txt 2> error.txt", "arg1", "input.txt | output.txt | error.txt", ""}, 1092 {"< input.txt > output.txt 2> error.txt", "", "input.txt | output.txt | error.txt", ""}, 1093 {"arg1 <input.txt >output.txt 2>error.txt", "arg1", "input.txt | output.txt | error.txt", ""}, 1094 {"<input.txt >output.txt 2>error.txt", "", "input.txt | output.txt | error.txt", ""}, 1095 {"<input.txt <input2.txt", "", "", "redirect error: stdin redirected twice"}, 1096 } 1097 1098 for _, tc := range testCases { 1099 resetArgs, newArgv, newRedirects, err := parseNewArgv(tc.in) 1100 t.Logf("%q -> %q %q %v\n", tc.in, newArgv, newRedirects, err) 1101 if tc.tgterr != "" { 1102 if err == nil { 1103 t.Errorf("Expected error %q, got no error", tc.tgterr) 1104 } else if errstr := err.Error(); errstr != tc.tgterr { 1105 t.Errorf("Expected error %q, got error %q", tc.tgterr, errstr) 1106 } 1107 } else { 1108 if !resetArgs { 1109 t.Errorf("parse error, resetArgs is false") 1110 continue 1111 } 1112 argvstr := strings.Join(newArgv, " | ") 1113 if argvstr != tc.tgtargs { 1114 t.Errorf("Expected new arguments %q, got %q", tc.tgtargs, argvstr) 1115 } 1116 redirstr := strings.Join(newRedirects[:], " | ") 1117 if redirstr != tc.tgtredir { 1118 t.Errorf("Expected new redirects %q, got %q", tc.tgtredir, redirstr) 1119 } 1120 } 1121 } 1122 } 1123 1124 func TestContinueUntil(t *testing.T) { 1125 test.AllowRecording(t) 1126 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 1127 if runtime.GOARCH != "386" { 1128 listIsAt(t, term, "continue main.main", 16, -1, -1) 1129 } else { 1130 listIsAt(t, term, "continue main.main", 17, -1, -1) 1131 } 1132 listIsAt(t, term, "continue main.sayhi", 12, -1, -1) 1133 }) 1134 } 1135 1136 func TestContinueUntilExistingBreakpoint(t *testing.T) { 1137 test.AllowRecording(t) 1138 withTestTerminal("continuetestprog", t, func(term *FakeTerminal) { 1139 term.MustExec("break main.main") 1140 if runtime.GOARCH != "386" { 1141 listIsAt(t, term, "continue main.main", 16, -1, -1) 1142 } else { 1143 listIsAt(t, term, "continue main.main", 17, -1, -1) 1144 } 1145 listIsAt(t, term, "continue main.sayhi", 12, -1, -1) 1146 }) 1147 } 1148 1149 func TestPrintFormat(t *testing.T) { 1150 withTestTerminal("testvariables2", t, func(term *FakeTerminal) { 1151 term.MustExec("continue") 1152 out := term.MustExec("print %#x m2[1].B") 1153 if !strings.Contains(out, "0xb\n") { 1154 t.Fatalf("output did not contain '0xb': %q", out) 1155 } 1156 }) 1157 } 1158 1159 func TestHitCondBreakpoint(t *testing.T) { 1160 test.AllowRecording(t) 1161 withTestTerminal("break", t, func(term *FakeTerminal) { 1162 term.MustExec("break bp1 main.main:4") 1163 term.MustExec("condition -hitcount bp1 > 2") 1164 listIsAt(t, term, "continue", 7, -1, -1) 1165 out := term.MustExec("print i") 1166 t.Logf("%q", out) 1167 if !strings.Contains(out, "3\n") { 1168 t.Fatalf("wrong value of i") 1169 } 1170 }) 1171 } 1172 1173 func TestClearCondBreakpoint(t *testing.T) { 1174 withTestTerminal("break", t, func(term *FakeTerminal) { 1175 term.MustExec("break main.main:4") 1176 term.MustExec("condition 1 i%3==2") 1177 listIsAt(t, term, "continue", 7, -1, -1) 1178 out := term.MustExec("print i") 1179 t.Logf("%q", out) 1180 if !strings.Contains(out, "2\n") { 1181 t.Fatalf("wrong value of i") 1182 } 1183 term.MustExec("condition -clear 1") 1184 listIsAt(t, term, "continue", 7, -1, -1) 1185 out = term.MustExec("print i") 1186 t.Logf("%q", out) 1187 if !strings.Contains(out, "3\n") { 1188 t.Fatalf("wrong value of i") 1189 } 1190 }) 1191 } 1192 1193 func TestBreakpointEditing(t *testing.T) { 1194 term := &FakeTerminal{ 1195 t: t, 1196 Term: New(nil, &config.Config{}), 1197 } 1198 _ = term 1199 1200 var testCases = []struct { 1201 inBp *api.Breakpoint 1202 inBpStr string 1203 edit string 1204 outBp *api.Breakpoint 1205 }{ 1206 { // tracepoint -> breakpoint 1207 &api.Breakpoint{Tracepoint: true}, 1208 "trace", 1209 "", 1210 &api.Breakpoint{}}, 1211 { // breakpoint -> tracepoint 1212 &api.Breakpoint{Variables: []string{"a"}}, 1213 "print a", 1214 "print a\ntrace", 1215 &api.Breakpoint{Tracepoint: true, Variables: []string{"a"}}}, 1216 { // add print var 1217 &api.Breakpoint{Variables: []string{"a"}}, 1218 "print a", 1219 "print b\nprint a\n", 1220 &api.Breakpoint{Variables: []string{"b", "a"}}}, 1221 { // add goroutine flag 1222 &api.Breakpoint{}, 1223 "", 1224 "goroutine", 1225 &api.Breakpoint{Goroutine: true}}, 1226 { // remove goroutine flag 1227 &api.Breakpoint{Goroutine: true}, 1228 "goroutine", 1229 "", 1230 &api.Breakpoint{}}, 1231 { // add stack directive 1232 &api.Breakpoint{}, 1233 "", 1234 "stack 10", 1235 &api.Breakpoint{Stacktrace: 10}}, 1236 { // remove stack directive 1237 &api.Breakpoint{Stacktrace: 20}, 1238 "stack 20", 1239 "print a", 1240 &api.Breakpoint{Variables: []string{"a"}}}, 1241 { // add condition 1242 &api.Breakpoint{Variables: []string{"a"}}, 1243 "print a", 1244 "print a\ncond a < b", 1245 &api.Breakpoint{Variables: []string{"a"}, Cond: "a < b"}}, 1246 { // remove condition 1247 &api.Breakpoint{Cond: "a < b"}, 1248 "cond a < b", 1249 "", 1250 &api.Breakpoint{}}, 1251 { // change condition 1252 &api.Breakpoint{Cond: "a < b"}, 1253 "cond a < b", 1254 "cond a < 5", 1255 &api.Breakpoint{Cond: "a < 5"}}, 1256 { // change hitcount condition 1257 &api.Breakpoint{HitCond: "% 2"}, 1258 "cond -hitcount % 2", 1259 "cond -hitcount = 2", 1260 &api.Breakpoint{HitCond: "= 2"}}, 1261 } 1262 1263 for _, tc := range testCases { 1264 bp := *tc.inBp 1265 bpStr := strings.Join(formatBreakpointAttrs("", &bp, true), "\n") 1266 if bpStr != tc.inBpStr { 1267 t.Errorf("Expected %q got %q for:\n%#v", tc.inBpStr, bpStr, tc.inBp) 1268 } 1269 ctx := callContext{Prefix: onPrefix, Scope: api.EvalScope{GoroutineID: -1, Frame: 0, DeferredCall: 0}, Breakpoint: &bp} 1270 err := term.cmds.parseBreakpointAttrs(nil, ctx, strings.NewReader(tc.edit)) 1271 if err != nil { 1272 t.Errorf("Unexpected error during edit %q", tc.edit) 1273 } 1274 if !reflect.DeepEqual(bp, *tc.outBp) { 1275 t.Errorf("mismatch after edit\nexpected: %#v\ngot: %#v", tc.outBp, bp) 1276 } 1277 } 1278 } 1279 1280 func TestTranscript(t *testing.T) { 1281 withTestTerminal("math", t, func(term *FakeTerminal) { 1282 term.MustExec("break main.main") 1283 out := term.MustExec("continue") 1284 if !strings.HasPrefix(out, "> main.main()") { 1285 t.Fatalf("Wrong output for next: <%s>", out) 1286 } 1287 fh, err := ioutil.TempFile("", "test-transcript-*") 1288 if err != nil { 1289 t.Fatalf("TempFile: %v", err) 1290 } 1291 name := fh.Name() 1292 fh.Close() 1293 t.Logf("output to %q", name) 1294 1295 slurp := func() string { 1296 b, err := ioutil.ReadFile(name) 1297 if err != nil { 1298 t.Fatalf("could not read transcript file: %v", err) 1299 } 1300 return string(b) 1301 } 1302 1303 term.MustExec(fmt.Sprintf("transcript %s", name)) 1304 out = term.MustExec("list") 1305 //term.MustExec("transcript -off") 1306 if out != slurp() { 1307 t.Logf("output of list %s", out) 1308 t.Logf("contents of transcript: %s", slurp()) 1309 t.Errorf("transcript and command out differ") 1310 } 1311 1312 term.MustExec(fmt.Sprintf("transcript -t -x %s", name)) 1313 out = term.MustExec(`print "hello"`) 1314 if out != "" { 1315 t.Errorf("output of print is %q but should have been suppressed by transcript", out) 1316 } 1317 if slurp() != "\"hello\"\n" { 1318 t.Errorf("wrong contents of transcript: %q", slurp()) 1319 } 1320 1321 os.Remove(name) 1322 }) 1323 }