mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 10:47:27 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			735 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			735 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package terminal
 | |
| 
 | |
| import (
 | |
| 	"flag"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/derekparker/delve/pkg/config"
 | |
| 	"github.com/derekparker/delve/pkg/proc/test"
 | |
| 	"github.com/derekparker/delve/service"
 | |
| 	"github.com/derekparker/delve/service/api"
 | |
| 	"github.com/derekparker/delve/service/rpc2"
 | |
| 	"github.com/derekparker/delve/service/rpccommon"
 | |
| )
 | |
| 
 | |
| var testBackend string
 | |
| 
 | |
| func TestMain(m *testing.M) {
 | |
| 	flag.StringVar(&testBackend, "backend", "", "selects backend")
 | |
| 	flag.Parse()
 | |
| 	if testBackend == "" {
 | |
| 		testBackend = os.Getenv("PROCTEST")
 | |
| 		if testBackend == "" {
 | |
| 			testBackend = "native"
 | |
| 		}
 | |
| 	}
 | |
| 	os.Exit(m.Run())
 | |
| }
 | |
| 
 | |
| type FakeTerminal struct {
 | |
| 	*Term
 | |
| 	t testing.TB
 | |
| }
 | |
| 
 | |
| const logCommandOutput = false
 | |
| 
 | |
| func (ft *FakeTerminal) Exec(cmdstr string) (outstr string, err error) {
 | |
| 	outfh, err := ioutil.TempFile("", "cmdtestout")
 | |
| 	if err != nil {
 | |
| 		ft.t.Fatalf("could not create temporary file: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	stdout, stderr, termstdout := os.Stdout, os.Stderr, ft.Term.stdout
 | |
| 	os.Stdout, os.Stderr, ft.Term.stdout = outfh, outfh, outfh
 | |
| 	defer func() {
 | |
| 		os.Stdout, os.Stderr, ft.Term.stdout = stdout, stderr, termstdout
 | |
| 		outfh.Close()
 | |
| 		outbs, err1 := ioutil.ReadFile(outfh.Name())
 | |
| 		if err1 != nil {
 | |
| 			ft.t.Fatalf("could not read temporary output file: %v", err)
 | |
| 		}
 | |
| 		outstr = string(outbs)
 | |
| 		if logCommandOutput {
 | |
| 			ft.t.Logf("command %q -> %q", cmdstr, outstr)
 | |
| 		}
 | |
| 		os.Remove(outfh.Name())
 | |
| 	}()
 | |
| 	err = ft.cmds.Call(cmdstr, ft.Term)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (ft *FakeTerminal) MustExec(cmdstr string) string {
 | |
| 	outstr, err := ft.Exec(cmdstr)
 | |
| 	if err != nil {
 | |
| 		ft.t.Fatalf("Error executing <%s>: %v", cmdstr, err)
 | |
| 	}
 | |
| 	return outstr
 | |
| }
 | |
| 
 | |
| func (ft *FakeTerminal) AssertExec(cmdstr, tgt string) {
 | |
| 	out := ft.MustExec(cmdstr)
 | |
| 	if out != tgt {
 | |
| 		ft.t.Fatalf("Error executing %q, expected %q got %q", cmdstr, tgt, out)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (ft *FakeTerminal) AssertExecError(cmdstr, tgterr string) {
 | |
| 	_, err := ft.Exec(cmdstr)
 | |
| 	if err == nil {
 | |
| 		ft.t.Fatalf("Expected error executing %q", cmdstr)
 | |
| 	}
 | |
| 	if err.Error() != tgterr {
 | |
| 		ft.t.Fatalf("Expected error %q executing %q, got error %q", tgterr, cmdstr, err.Error())
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) {
 | |
| 	if testBackend == "rr" {
 | |
| 		test.MustHaveRecordingAllowed(t)
 | |
| 	}
 | |
| 	os.Setenv("TERM", "dumb")
 | |
| 	listener, err := net.Listen("tcp", "localhost:0")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("couldn't start listener: %s\n", err)
 | |
| 	}
 | |
| 	defer listener.Close()
 | |
| 	server := rpccommon.NewServer(&service.Config{
 | |
| 		Listener:    listener,
 | |
| 		ProcessArgs: []string{test.BuildFixture(name, 0).Path},
 | |
| 		Backend:     testBackend,
 | |
| 	}, false)
 | |
| 	if err := server.Run(); err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	client := rpc2.NewClient(listener.Addr().String())
 | |
| 	defer func() {
 | |
| 		dir, _ := client.TraceDirectory()
 | |
| 		client.Detach(true)
 | |
| 		if dir != "" {
 | |
| 			test.SafeRemoveAll(dir)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	ft := &FakeTerminal{
 | |
| 		t:    t,
 | |
| 		Term: New(client, &config.Config{}),
 | |
| 	}
 | |
| 	fn(ft)
 | |
| }
 | |
| 
 | |
| func TestCommandDefault(t *testing.T) {
 | |
| 	var (
 | |
| 		cmds = Commands{}
 | |
| 		cmd  = cmds.Find("non-existant-command", noPrefix)
 | |
| 	)
 | |
| 
 | |
| 	err := cmd(nil, callContext{}, "")
 | |
| 	if err == nil {
 | |
| 		t.Fatal("cmd() did not default")
 | |
| 	}
 | |
| 
 | |
| 	if err.Error() != "command not available" {
 | |
| 		t.Fatal("wrong command output")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestCommandReplay(t *testing.T) {
 | |
| 	cmds := DebugCommands(nil)
 | |
| 	cmds.Register("foo", func(t *Term, ctx callContext, args string) error { return fmt.Errorf("registered command") }, "foo command")
 | |
| 	cmd := cmds.Find("foo", noPrefix)
 | |
| 
 | |
| 	err := cmd(nil, callContext{}, "")
 | |
| 	if err.Error() != "registered command" {
 | |
| 		t.Fatal("wrong command output")
 | |
| 	}
 | |
| 
 | |
| 	cmd = cmds.Find("", noPrefix)
 | |
| 	err = cmd(nil, callContext{}, "")
 | |
| 	if err.Error() != "registered command" {
 | |
| 		t.Fatal("wrong command output")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestCommandReplayWithoutPreviousCommand(t *testing.T) {
 | |
| 	var (
 | |
| 		cmds = DebugCommands(nil)
 | |
| 		cmd  = cmds.Find("", noPrefix)
 | |
| 		err  = cmd(nil, callContext{}, "")
 | |
| 	)
 | |
| 
 | |
| 	if err != nil {
 | |
| 		t.Error("Null command not returned", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestCommandThread(t *testing.T) {
 | |
| 	var (
 | |
| 		cmds = DebugCommands(nil)
 | |
| 		cmd  = cmds.Find("thread", noPrefix)
 | |
| 	)
 | |
| 
 | |
| 	err := cmd(nil, callContext{}, "")
 | |
| 	if err == nil {
 | |
| 		t.Fatal("thread terminal command did not default")
 | |
| 	}
 | |
| 
 | |
| 	if err.Error() != "you must specify a thread" {
 | |
| 		t.Fatal("wrong command output: ", err.Error())
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestExecuteFile(t *testing.T) {
 | |
| 	breakCount := 0
 | |
| 	traceCount := 0
 | |
| 	c := &Commands{
 | |
| 		client: nil,
 | |
| 		cmds: []command{
 | |
| 			{aliases: []string{"trace"}, cmdFn: func(t *Term, ctx callContext, args string) error {
 | |
| 				traceCount++
 | |
| 				return nil
 | |
| 			}},
 | |
| 			{aliases: []string{"break"}, cmdFn: func(t *Term, ctx callContext, args string) error {
 | |
| 				breakCount++
 | |
| 				return nil
 | |
| 			}},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	fixturesDir := test.FindFixturesDir()
 | |
| 	err := c.executeFile(nil, filepath.Join(fixturesDir, "bpfile"))
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("executeFile: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if breakCount != 1 || traceCount != 1 {
 | |
| 		t.Fatalf("Wrong counts break: %d trace: %d\n", breakCount, traceCount)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestIssue354(t *testing.T) {
 | |
| 	printStack([]api.Stackframe{}, "", false)
 | |
| 	printStack([]api.Stackframe{{api.Location{PC: 0, File: "irrelevant.go", Line: 10, Function: nil}, nil, nil, 0, 0, ""}}, "", false)
 | |
| }
 | |
| 
 | |
| func TestIssue411(t *testing.T) {
 | |
| 	test.AllowRecording(t)
 | |
| 	withTestTerminal("math", t, func(term *FakeTerminal) {
 | |
| 		term.MustExec("break math.go:8")
 | |
| 		term.MustExec("trace math.go:9")
 | |
| 		term.MustExec("continue")
 | |
| 		out := term.MustExec("next")
 | |
| 		if !strings.HasPrefix(out, "> main.main()") {
 | |
| 			t.Fatalf("Wrong output for next: <%s>", out)
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestScopePrefix(t *testing.T) {
 | |
| 	const goroutinesLinePrefix = "  Goroutine "
 | |
| 	const goroutinesCurLinePrefix = "* Goroutine "
 | |
| 	test.AllowRecording(t)
 | |
| 	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
 | |
| 		term.MustExec("b stacktraceme")
 | |
| 		term.MustExec("continue")
 | |
| 
 | |
| 		goroutinesOut := strings.Split(term.MustExec("goroutines"), "\n")
 | |
| 		agoroutines := []int{}
 | |
| 		nonagoroutines := []int{}
 | |
| 		curgid := -1
 | |
| 
 | |
| 		for _, line := range goroutinesOut {
 | |
| 			iscur := strings.HasPrefix(line, goroutinesCurLinePrefix)
 | |
| 			if !iscur && !strings.HasPrefix(line, goroutinesLinePrefix) {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			dash := strings.Index(line, " - ")
 | |
| 			if dash < 0 {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			gid, err := strconv.Atoi(line[len(goroutinesLinePrefix):dash])
 | |
| 			if err != nil {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			if iscur {
 | |
| 				curgid = gid
 | |
| 			}
 | |
| 
 | |
| 			if idx := strings.Index(line, " main.agoroutine "); idx < 0 {
 | |
| 				nonagoroutines = append(nonagoroutines, gid)
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			agoroutines = append(agoroutines, gid)
 | |
| 		}
 | |
| 
 | |
| 		if len(agoroutines) > 10 {
 | |
| 			t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d found): %q", len(agoroutines), goroutinesOut)
 | |
| 		}
 | |
| 
 | |
| 		if len(agoroutines) < 10 {
 | |
| 			extraAgoroutines := 0
 | |
| 			for _, gid := range nonagoroutines {
 | |
| 				stackOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d stack", gid)), "\n")
 | |
| 				for _, line := range stackOut {
 | |
| 					if strings.HasSuffix(line, " main.agoroutine") {
 | |
| 						extraAgoroutines++
 | |
| 						break
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			if len(agoroutines)+extraAgoroutines < 10 {
 | |
| 				t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d+%d found): %q", len(agoroutines), extraAgoroutines, goroutinesOut)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if curgid < 0 {
 | |
| 			t.Fatalf("Could not find current goroutine in output of goroutines: %q", goroutinesOut)
 | |
| 		}
 | |
| 
 | |
| 		seen := make([]bool, 10)
 | |
| 		for _, gid := range agoroutines {
 | |
| 			stackOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d stack", gid)), "\n")
 | |
| 			fid := -1
 | |
| 			for _, line := range stackOut {
 | |
| 				space := strings.Index(line, " ")
 | |
| 				if space < 0 {
 | |
| 					continue
 | |
| 				}
 | |
| 				curfid, err := strconv.Atoi(line[:space])
 | |
| 				if err != nil {
 | |
| 					continue
 | |
| 				}
 | |
| 
 | |
| 				if idx := strings.Index(line, " main.agoroutine"); idx >= 0 {
 | |
| 					fid = curfid
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 			if fid < 0 {
 | |
| 				t.Fatalf("Could not find frame for goroutine %d: %v", gid, stackOut)
 | |
| 			}
 | |
| 			term.AssertExec(fmt.Sprintf("goroutine     %d    frame     %d     locals", gid, fid), "(no locals)\n")
 | |
| 			argsOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d frame %d args", gid, fid)), "\n")
 | |
| 			if len(argsOut) != 4 || argsOut[3] != "" {
 | |
| 				t.Fatalf("Wrong number of arguments in goroutine %d frame %d: %v", gid, fid, argsOut)
 | |
| 			}
 | |
| 			out := term.MustExec(fmt.Sprintf("goroutine %d frame %d p i", gid, fid))
 | |
| 			ival, err := strconv.Atoi(out[:len(out)-1])
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("could not parse value %q of i for goroutine %d frame %d: %v", out, gid, fid, err)
 | |
| 			}
 | |
| 			seen[ival] = true
 | |
| 		}
 | |
| 
 | |
| 		for i := range seen {
 | |
| 			if !seen[i] {
 | |
| 				t.Fatalf("goroutine %d not found", i)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		term.MustExec("c")
 | |
| 
 | |
| 		term.AssertExecError("frame", "not enough arguments")
 | |
| 		term.AssertExecError("frame 1", "not enough arguments")
 | |
| 		term.AssertExecError("frame 1 goroutines", "command not available")
 | |
| 		term.AssertExecError("frame 1 goroutine", "no command passed to goroutine")
 | |
| 		term.AssertExecError(fmt.Sprintf("frame 1 goroutine %d", curgid), "no command passed to goroutine")
 | |
| 		term.AssertExecError(fmt.Sprintf("goroutine %d frame 10 locals", curgid), fmt.Sprintf("Frame 10 does not exist in goroutine %d", curgid))
 | |
| 		term.AssertExecError("goroutine 9000 locals", "Unknown goroutine 9000")
 | |
| 
 | |
| 		term.AssertExecError("print n", "could not find symbol value for n")
 | |
| 		term.AssertExec("frame 1 print n", "3\n")
 | |
| 		term.AssertExec("frame 2 print n", "2\n")
 | |
| 		term.AssertExec("frame 3 print n", "1\n")
 | |
| 		term.AssertExec("frame 4 print n", "0\n")
 | |
| 		term.AssertExecError("frame 5 print n", "could not find symbol value for n")
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestOnPrefix(t *testing.T) {
 | |
| 	const prefix = "\ti: "
 | |
| 	test.AllowRecording(t)
 | |
| 	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
 | |
| 		term.MustExec("b agobp main.agoroutine")
 | |
| 		term.MustExec("on agobp print i")
 | |
| 
 | |
| 		seen := make([]bool, 10)
 | |
| 
 | |
| 		for {
 | |
| 			outstr, err := term.Exec("continue")
 | |
| 			if err != nil {
 | |
| 				if !strings.Contains(err.Error(), "exited") {
 | |
| 					t.Fatalf("Unexpected error executing 'continue': %v", err)
 | |
| 				}
 | |
| 				break
 | |
| 			}
 | |
| 			out := strings.Split(outstr, "\n")
 | |
| 
 | |
| 			for i := range out {
 | |
| 				if !strings.HasPrefix(out[i], "\ti: ") {
 | |
| 					continue
 | |
| 				}
 | |
| 				id, err := strconv.Atoi(out[i][len(prefix):])
 | |
| 				if err != nil {
 | |
| 					continue
 | |
| 				}
 | |
| 				if seen[id] {
 | |
| 					t.Fatalf("Goroutine %d seen twice\n", id)
 | |
| 				}
 | |
| 				seen[id] = true
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		for i := range seen {
 | |
| 			if !seen[i] {
 | |
| 				t.Fatalf("Goroutine %d not seen\n", i)
 | |
| 			}
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestNoVars(t *testing.T) {
 | |
| 	test.AllowRecording(t)
 | |
| 	withTestTerminal("locationsUpperCase", t, func(term *FakeTerminal) {
 | |
| 		term.MustExec("b main.main")
 | |
| 		term.MustExec("continue")
 | |
| 		term.AssertExec("args", "(no args)\n")
 | |
| 		term.AssertExec("locals", "(no locals)\n")
 | |
| 		term.AssertExec("vars filterThatMatchesNothing", "(no vars)\n")
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestOnPrefixLocals(t *testing.T) {
 | |
| 	const prefix = "\ti: "
 | |
| 	test.AllowRecording(t)
 | |
| 	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
 | |
| 		term.MustExec("b agobp main.agoroutine")
 | |
| 		term.MustExec("on agobp args -v")
 | |
| 
 | |
| 		seen := make([]bool, 10)
 | |
| 
 | |
| 		for {
 | |
| 			outstr, err := term.Exec("continue")
 | |
| 			if err != nil {
 | |
| 				if !strings.Contains(err.Error(), "exited") {
 | |
| 					t.Fatalf("Unexpected error executing 'continue': %v", err)
 | |
| 				}
 | |
| 				break
 | |
| 			}
 | |
| 			out := strings.Split(outstr, "\n")
 | |
| 
 | |
| 			for i := range out {
 | |
| 				if !strings.HasPrefix(out[i], "\ti: ") {
 | |
| 					continue
 | |
| 				}
 | |
| 				id, err := strconv.Atoi(out[i][len(prefix):])
 | |
| 				if err != nil {
 | |
| 					continue
 | |
| 				}
 | |
| 				if seen[id] {
 | |
| 					t.Fatalf("Goroutine %d seen twice\n", id)
 | |
| 				}
 | |
| 				seen[id] = true
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		for i := range seen {
 | |
| 			if !seen[i] {
 | |
| 				t.Fatalf("Goroutine %d not seen\n", i)
 | |
| 			}
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func countOccourences(s string, needle string) int {
 | |
| 	count := 0
 | |
| 	for {
 | |
| 		idx := strings.Index(s, needle)
 | |
| 		if idx < 0 {
 | |
| 			break
 | |
| 		}
 | |
| 		count++
 | |
| 		s = s[idx+len(needle):]
 | |
| 	}
 | |
| 	return count
 | |
| }
 | |
| 
 | |
| func TestIssue387(t *testing.T) {
 | |
| 	// a breakpoint triggering during a 'next' operation will interrupt it
 | |
| 	test.AllowRecording(t)
 | |
| 	withTestTerminal("issue387", t, func(term *FakeTerminal) {
 | |
| 		breakpointHitCount := 0
 | |
| 		term.MustExec("break dostuff")
 | |
| 		for {
 | |
| 			outstr, err := term.Exec("continue")
 | |
| 			breakpointHitCount += countOccourences(outstr, "issue387.go:8")
 | |
| 			t.Log(outstr)
 | |
| 			if err != nil {
 | |
| 				if !strings.Contains(err.Error(), "exited") {
 | |
| 					t.Fatalf("Unexpected error executing 'continue': %v", err)
 | |
| 				}
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			pos := 9
 | |
| 
 | |
| 			for {
 | |
| 				outstr = term.MustExec("next")
 | |
| 				breakpointHitCount += countOccourences(outstr, "issue387.go:8")
 | |
| 				t.Log(outstr)
 | |
| 				if countOccourences(outstr, fmt.Sprintf("issue387.go:%d", pos)) == 0 {
 | |
| 					t.Fatalf("did not continue to expected position %d", pos)
 | |
| 				}
 | |
| 				pos++
 | |
| 				if pos >= 11 {
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if breakpointHitCount != 10 {
 | |
| 			t.Fatalf("Breakpoint hit wrong number of times, expected 10 got %d", breakpointHitCount)
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func listIsAt(t *testing.T, term *FakeTerminal, listcmd string, cur, start, end int) {
 | |
| 	outstr := term.MustExec(listcmd)
 | |
| 	lines := strings.Split(outstr, "\n")
 | |
| 
 | |
| 	t.Logf("%q: %q", listcmd, outstr)
 | |
| 
 | |
| 	if !strings.Contains(lines[0], fmt.Sprintf(":%d", cur)) {
 | |
| 		t.Fatalf("Could not find current line number in first output line: %q", lines[0])
 | |
| 	}
 | |
| 
 | |
| 	re := regexp.MustCompile(`(=>)?\s+(\d+):`)
 | |
| 
 | |
| 	outStart, outEnd := 0, 0
 | |
| 
 | |
| 	for _, line := range lines[1:] {
 | |
| 		if line == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 		v := re.FindStringSubmatch(line)
 | |
| 		if len(v) != 3 {
 | |
| 			continue
 | |
| 		}
 | |
| 		curline, _ := strconv.Atoi(v[2])
 | |
| 		if v[1] == "=>" {
 | |
| 			if cur != curline {
 | |
| 				t.Fatalf("Wrong current line, got %d expected %d", curline, cur)
 | |
| 			}
 | |
| 		}
 | |
| 		if outStart == 0 {
 | |
| 			outStart = curline
 | |
| 		}
 | |
| 		outEnd = curline
 | |
| 	}
 | |
| 
 | |
| 	if start != -1 || end != -1 {
 | |
| 		if outStart != start || outEnd != end {
 | |
| 			t.Fatalf("Wrong output range, got %d:%d expected %d:%d", outStart, outEnd, start, end)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestListCmd(t *testing.T) {
 | |
| 	withTestTerminal("testvariables", t, func(term *FakeTerminal) {
 | |
| 		term.MustExec("continue")
 | |
| 		term.MustExec("continue")
 | |
| 		listIsAt(t, term, "list", 24, 19, 29)
 | |
| 		listIsAt(t, term, "list 69", 69, 64, 70)
 | |
| 		listIsAt(t, term, "frame 1 list", 62, 57, 67)
 | |
| 		listIsAt(t, term, "frame 1 list 69", 69, 64, 70)
 | |
| 		_, err := term.Exec("frame 50 list")
 | |
| 		if err == nil {
 | |
| 			t.Fatalf("Expected error requesting 50th frame")
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestReverseContinue(t *testing.T) {
 | |
| 	test.AllowRecording(t)
 | |
| 	if testBackend != "rr" {
 | |
| 		return
 | |
| 	}
 | |
| 	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
 | |
| 		term.MustExec("break main.main")
 | |
| 		term.MustExec("break main.sayhi")
 | |
| 		listIsAt(t, term, "continue", 16, -1, -1)
 | |
| 		listIsAt(t, term, "continue", 12, -1, -1)
 | |
| 		listIsAt(t, term, "rewind", 16, -1, -1)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestCheckpoints(t *testing.T) {
 | |
| 	test.AllowRecording(t)
 | |
| 	if testBackend != "rr" {
 | |
| 		return
 | |
| 	}
 | |
| 	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
 | |
| 		term.MustExec("break main.main")
 | |
| 		listIsAt(t, term, "continue", 16, -1, -1)
 | |
| 		term.MustExec("checkpoint")
 | |
| 		term.MustExec("checkpoints")
 | |
| 		listIsAt(t, term, "next", 17, -1, -1)
 | |
| 		listIsAt(t, term, "next", 18, -1, -1)
 | |
| 		listIsAt(t, term, "restart c1", 16, -1, -1)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestRestart(t *testing.T) {
 | |
| 	withTestTerminal("restartargs", t, func(term *FakeTerminal) {
 | |
| 		term.MustExec("break main.printArgs")
 | |
| 		term.MustExec("continue")
 | |
| 		if out := term.MustExec("print main.args"); !strings.Contains(out, ", []") {
 | |
| 			t.Fatalf("wrong args: %q", out)
 | |
| 		}
 | |
| 		// Reset the arg list
 | |
| 		term.MustExec("restart hello")
 | |
| 		term.MustExec("continue")
 | |
| 		if out := term.MustExec("print main.args"); !strings.Contains(out, ", [\"hello\"]") {
 | |
| 			t.Fatalf("wrong args: %q ", out)
 | |
| 		}
 | |
| 		// Restart w/o arg should retain the current args.
 | |
| 		term.MustExec("restart")
 | |
| 		term.MustExec("continue")
 | |
| 		if out := term.MustExec("print main.args"); !strings.Contains(out, ", [\"hello\"]") {
 | |
| 			t.Fatalf("wrong args: %q ", out)
 | |
| 		}
 | |
| 		// Empty arg list
 | |
| 		term.MustExec("restart -noargs")
 | |
| 		term.MustExec("continue")
 | |
| 		if out := term.MustExec("print main.args"); !strings.Contains(out, ", []") {
 | |
| 			t.Fatalf("wrong args: %q ", out)
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestIssue827(t *testing.T) {
 | |
| 	// switching goroutines when the current thread isn't running any goroutine
 | |
| 	// causes nil pointer dereference.
 | |
| 	withTestTerminal("notify-v2", t, func(term *FakeTerminal) {
 | |
| 		go func() {
 | |
| 			time.Sleep(1 * time.Second)
 | |
| 			http.Get("http://127.0.0.1:8888/test")
 | |
| 			time.Sleep(1 * time.Second)
 | |
| 			term.client.Halt()
 | |
| 		}()
 | |
| 		term.MustExec("continue")
 | |
| 		term.MustExec("goroutine 1")
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func findCmdName(c *Commands, cmdstr string, prefix cmdPrefix) string {
 | |
| 	for _, v := range c.cmds {
 | |
| 		if v.match(cmdstr) {
 | |
| 			if prefix != noPrefix && v.allowedPrefixes&prefix == 0 {
 | |
| 				continue
 | |
| 			}
 | |
| 			return v.aliases[0]
 | |
| 		}
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| func TestConfig(t *testing.T) {
 | |
| 	var term Term
 | |
| 	term.conf = &config.Config{}
 | |
| 	term.cmds = DebugCommands(nil)
 | |
| 
 | |
| 	err := configureCmd(&term, callContext{}, "nonexistent-parameter 10")
 | |
| 	if err == nil {
 | |
| 		t.Fatalf("expected error executing configureCmd(nonexistent-parameter)")
 | |
| 	}
 | |
| 
 | |
| 	err = configureCmd(&term, callContext{}, "max-string-len 10")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("error executing configureCmd(max-string-len): %v", err)
 | |
| 	}
 | |
| 	if term.conf.MaxStringLen == nil {
 | |
| 		t.Fatalf("expected MaxStringLen 10, got nil")
 | |
| 	}
 | |
| 	if *term.conf.MaxStringLen != 10 {
 | |
| 		t.Fatalf("expected MaxStringLen 10, got: %d", *term.conf.MaxStringLen)
 | |
| 	}
 | |
| 
 | |
| 	err = configureCmd(&term, callContext{}, "substitute-path a b")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("error executing configureCmd(substitute-path a b): %v", err)
 | |
| 	}
 | |
| 	if len(term.conf.SubstitutePath) != 1 || (term.conf.SubstitutePath[0] != config.SubstitutePathRule{"a", "b"}) {
 | |
| 		t.Fatalf("unexpected SubstitutePathRules after insert %v", term.conf.SubstitutePath)
 | |
| 	}
 | |
| 
 | |
| 	err = configureCmd(&term, callContext{}, "substitute-path a")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("error executing configureCmd(substitute-path a): %v", err)
 | |
| 	}
 | |
| 	if len(term.conf.SubstitutePath) != 0 {
 | |
| 		t.Fatalf("unexpected SubstitutePathRules after delete %v", term.conf.SubstitutePath)
 | |
| 	}
 | |
| 
 | |
| 	err = configureCmd(&term, callContext{}, "alias print blah")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("error executing configureCmd(alias print blah): %v", err)
 | |
| 	}
 | |
| 	if len(term.conf.Aliases["print"]) != 1 {
 | |
| 		t.Fatalf("aliases not changed after configure command %v", term.conf.Aliases)
 | |
| 	}
 | |
| 	if findCmdName(term.cmds, "blah", noPrefix) != "print" {
 | |
| 		t.Fatalf("new alias not found")
 | |
| 	}
 | |
| 
 | |
| 	err = configureCmd(&term, callContext{}, "alias blah")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("error executing configureCmd(alias blah): %v", err)
 | |
| 	}
 | |
| 	if len(term.conf.Aliases["print"]) != 0 {
 | |
| 		t.Fatalf("alias not removed after configure command %v", term.conf.Aliases)
 | |
| 	}
 | |
| 	if findCmdName(term.cmds, "blah", noPrefix) != "" {
 | |
| 		t.Fatalf("new alias found after delete")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestDisassembleAutogenerated(t *testing.T) {
 | |
| 	// Executing the 'disassemble' command on autogenerated code should work correctly
 | |
| 	withTestTerminal("math", t, func(term *FakeTerminal) {
 | |
| 		term.MustExec("break main.init")
 | |
| 		term.MustExec("continue")
 | |
| 		out := term.MustExec("disassemble")
 | |
| 		if !strings.Contains(out, "TEXT main.init(SB) ") {
 | |
| 			t.Fatalf("output of disassemble wasn't for the main.init function %q", out)
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestIssue1090(t *testing.T) {
 | |
| 	// Exit while executing 'next' should report the "Process exited" error
 | |
| 	// message instead of crashing.
 | |
| 	withTestTerminal("math", t, func(term *FakeTerminal) {
 | |
| 		term.MustExec("break main.main")
 | |
| 		term.MustExec("continue")
 | |
| 		for {
 | |
| 			_, err := term.Exec("next")
 | |
| 			if err != nil && strings.Contains(err.Error(), " has exited with status ") {
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	})
 | |
| }
 | 
