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