mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-04 14:36:47 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			350 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			350 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package terminal
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"net"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"github.com/derekparker/delve/proc/test"
 | 
						|
	"github.com/derekparker/delve/service"
 | 
						|
	"github.com/derekparker/delve/service/api"
 | 
						|
	"github.com/derekparker/delve/service/rpc"
 | 
						|
)
 | 
						|
 | 
						|
type FakeTerminal struct {
 | 
						|
	*Term
 | 
						|
	t testing.TB
 | 
						|
}
 | 
						|
 | 
						|
func (ft *FakeTerminal) Exec(cmdstr string) (outstr string, err error) {
 | 
						|
	cmdstr, args := parseCommand(cmdstr)
 | 
						|
 | 
						|
	outfh, err := ioutil.TempFile("", "cmdtestout")
 | 
						|
	if err != nil {
 | 
						|
		ft.t.Fatalf("could not create temporary file: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	stdout, stderr := os.Stdout, os.Stderr
 | 
						|
	os.Stdout, os.Stderr = outfh, outfh
 | 
						|
	defer func() {
 | 
						|
		os.Stdout, os.Stderr = stdout, stderr
 | 
						|
		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)
 | 
						|
		os.Remove(outfh.Name())
 | 
						|
	}()
 | 
						|
	err = ft.cmds.Call(cmdstr, args, 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)) {
 | 
						|
	listener, err := net.Listen("tcp", "localhost:0")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("couldn't start listener: %s\n", err)
 | 
						|
	}
 | 
						|
	defer listener.Close()
 | 
						|
	server := rpc.NewServer(&service.Config{
 | 
						|
		Listener:    listener,
 | 
						|
		ProcessArgs: []string{test.BuildFixture(name).Path},
 | 
						|
	}, false)
 | 
						|
	if err := server.Run(); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	client := rpc.NewClient(listener.Addr().String())
 | 
						|
	defer func() {
 | 
						|
		client.Detach(true)
 | 
						|
	}()
 | 
						|
 | 
						|
	ft := &FakeTerminal{
 | 
						|
		t:    t,
 | 
						|
		Term: New(client, nil),
 | 
						|
	}
 | 
						|
	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{}, "")
 | 
						|
	printStack([]api.Stackframe{{api.Location{PC: 0, File: "irrelevant.go", Line: 10, Function: nil}, nil, nil}}, "")
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue411(t *testing.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 "
 | 
						|
	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
 | 
						|
		term.MustExec("b stacktraceme")
 | 
						|
		term.MustExec("continue")
 | 
						|
 | 
						|
		goroutinesOut := strings.Split(term.MustExec("goroutines"), "\n")
 | 
						|
		agoroutines := []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 {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			agoroutines = append(agoroutines, gid)
 | 
						|
		}
 | 
						|
 | 
						|
		if len(agoroutines) != 10 {
 | 
						|
			t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine: %q", 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), "")
 | 
						|
			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: "
 | 
						|
	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.Index(err.Error(), "exited") < 0 {
 | 
						|
					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)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 |