diff --git a/Documentation/usage/dlv.md b/Documentation/usage/dlv.md index 0fcce4c2..affa3e9b 100644 --- a/Documentation/usage/dlv.md +++ b/Documentation/usage/dlv.md @@ -17,6 +17,7 @@ The goal of this tool is to provide a simple yet powerful interface for debuggin ``` --accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate + --api-version=1: Selects API version when headless --build-flags="": Build flags, to be passed to the compiler. --headless[=false]: Run debug server only, in headless mode. --init="": Init file, executed by the terminal client. @@ -34,4 +35,4 @@ The goal of this tool is to provide a simple yet powerful interface for debuggin * [dlv trace](dlv_trace.md) - Compile and begin tracing program. * [dlv version](dlv_version.md) - Prints version. -###### Auto generated by spf13/cobra on 19-Feb-2016 +###### Auto generated by spf13/cobra on 11-Apr-2016 diff --git a/Documentation/usage/dlv_attach.md b/Documentation/usage/dlv_attach.md index fb7c76bf..5958a829 100644 --- a/Documentation/usage/dlv_attach.md +++ b/Documentation/usage/dlv_attach.md @@ -15,6 +15,7 @@ dlv attach pid ``` --accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate + --api-version=1: Selects API version when headless --build-flags="": Build flags, to be passed to the compiler. --headless[=false]: Run debug server only, in headless mode. --init="": Init file, executed by the terminal client. @@ -25,4 +26,4 @@ dlv attach pid ### SEE ALSO * [dlv](dlv.md) - Delve is a debugger for the Go programming language. -###### Auto generated by spf13/cobra on 19-Feb-2016 +###### Auto generated by spf13/cobra on 11-Apr-2016 diff --git a/Documentation/usage/dlv_connect.md b/Documentation/usage/dlv_connect.md index 3aea3c32..24de0543 100644 --- a/Documentation/usage/dlv_connect.md +++ b/Documentation/usage/dlv_connect.md @@ -15,6 +15,7 @@ dlv connect addr ``` --accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate + --api-version=1: Selects API version when headless --build-flags="": Build flags, to be passed to the compiler. --headless[=false]: Run debug server only, in headless mode. --init="": Init file, executed by the terminal client. @@ -25,4 +26,4 @@ dlv connect addr ### SEE ALSO * [dlv](dlv.md) - Delve is a debugger for the Go programming language. -###### Auto generated by spf13/cobra on 19-Feb-2016 +###### Auto generated by spf13/cobra on 11-Apr-2016 diff --git a/Documentation/usage/dlv_debug.md b/Documentation/usage/dlv_debug.md index 3ab12978..94f00b54 100644 --- a/Documentation/usage/dlv_debug.md +++ b/Documentation/usage/dlv_debug.md @@ -16,6 +16,7 @@ dlv debug [package] ``` --accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate + --api-version=1: Selects API version when headless --build-flags="": Build flags, to be passed to the compiler. --headless[=false]: Run debug server only, in headless mode. --init="": Init file, executed by the terminal client. @@ -26,4 +27,4 @@ dlv debug [package] ### SEE ALSO * [dlv](dlv.md) - Delve is a debugger for the Go programming language. -###### Auto generated by spf13/cobra on 19-Feb-2016 +###### Auto generated by spf13/cobra on 11-Apr-2016 diff --git a/Documentation/usage/dlv_exec.md b/Documentation/usage/dlv_exec.md index 2ab0ecca..6e70c5ff 100644 --- a/Documentation/usage/dlv_exec.md +++ b/Documentation/usage/dlv_exec.md @@ -15,6 +15,7 @@ dlv exec [./path/to/binary] ``` --accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate + --api-version=1: Selects API version when headless --build-flags="": Build flags, to be passed to the compiler. --headless[=false]: Run debug server only, in headless mode. --init="": Init file, executed by the terminal client. @@ -25,4 +26,4 @@ dlv exec [./path/to/binary] ### SEE ALSO * [dlv](dlv.md) - Delve is a debugger for the Go programming language. -###### Auto generated by spf13/cobra on 19-Feb-2016 +###### Auto generated by spf13/cobra on 11-Apr-2016 diff --git a/Documentation/usage/dlv_run.md b/Documentation/usage/dlv_run.md index 78bd9799..305206e8 100644 --- a/Documentation/usage/dlv_run.md +++ b/Documentation/usage/dlv_run.md @@ -15,6 +15,7 @@ dlv run ``` --accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate + --api-version=1: Selects API version when headless --build-flags="": Build flags, to be passed to the compiler. --headless[=false]: Run debug server only, in headless mode. --init="": Init file, executed by the terminal client. @@ -25,4 +26,4 @@ dlv run ### SEE ALSO * [dlv](dlv.md) - Delve is a debugger for the Go programming language. -###### Auto generated by spf13/cobra on 19-Feb-2016 +###### Auto generated by spf13/cobra on 11-Apr-2016 diff --git a/Documentation/usage/dlv_test.md b/Documentation/usage/dlv_test.md index 789d543e..ff0dc064 100644 --- a/Documentation/usage/dlv_test.md +++ b/Documentation/usage/dlv_test.md @@ -15,6 +15,7 @@ dlv test [package] ``` --accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate + --api-version=1: Selects API version when headless --build-flags="": Build flags, to be passed to the compiler. --headless[=false]: Run debug server only, in headless mode. --init="": Init file, executed by the terminal client. @@ -25,4 +26,4 @@ dlv test [package] ### SEE ALSO * [dlv](dlv.md) - Delve is a debugger for the Go programming language. -###### Auto generated by spf13/cobra on 19-Feb-2016 +###### Auto generated by spf13/cobra on 11-Apr-2016 diff --git a/Documentation/usage/dlv_trace.md b/Documentation/usage/dlv_trace.md index 20c87b3a..d4d2e321 100644 --- a/Documentation/usage/dlv_trace.md +++ b/Documentation/usage/dlv_trace.md @@ -22,6 +22,7 @@ dlv trace [package] regexp ``` --accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate + --api-version=1: Selects API version when headless --build-flags="": Build flags, to be passed to the compiler. --headless[=false]: Run debug server only, in headless mode. --init="": Init file, executed by the terminal client. @@ -32,4 +33,4 @@ dlv trace [package] regexp ### SEE ALSO * [dlv](dlv.md) - Delve is a debugger for the Go programming language. -###### Auto generated by spf13/cobra on 19-Feb-2016 +###### Auto generated by spf13/cobra on 11-Apr-2016 diff --git a/Documentation/usage/dlv_version.md b/Documentation/usage/dlv_version.md index dc22d273..2f6c7795 100644 --- a/Documentation/usage/dlv_version.md +++ b/Documentation/usage/dlv_version.md @@ -15,6 +15,7 @@ dlv version ``` --accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate + --api-version=1: Selects API version when headless --build-flags="": Build flags, to be passed to the compiler. --headless[=false]: Run debug server only, in headless mode. --init="": Init file, executed by the terminal client. @@ -25,4 +26,4 @@ dlv version ### SEE ALSO * [dlv](dlv.md) - Delve is a debugger for the Go programming language. -###### Auto generated by spf13/cobra on 19-Feb-2016 +###### Auto generated by spf13/cobra on 11-Apr-2016 diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index a7826c60..0c81c542 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -15,7 +15,8 @@ import ( "github.com/derekparker/delve/config" "github.com/derekparker/delve/service" "github.com/derekparker/delve/service/api" - "github.com/derekparker/delve/service/rpc" + "github.com/derekparker/delve/service/rpc1" + "github.com/derekparker/delve/service/rpc2" "github.com/derekparker/delve/terminal" "github.com/derekparker/delve/version" "github.com/spf13/cobra" @@ -26,6 +27,8 @@ var ( Log bool // Headless is whether to run without terminal. Headless bool + // ApiVersion is the requested API version while running headless + ApiVersion int // AcceptMulti allows multiple clients to connect to the same server AcceptMulti bool // Addr is the debugging server listen address. @@ -78,6 +81,7 @@ func New() *cobra.Command { RootCommand.PersistentFlags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.") RootCommand.PersistentFlags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.") RootCommand.PersistentFlags().BoolVarP(&AcceptMulti, "accept-multiclient", "", false, "Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate") + RootCommand.PersistentFlags().IntVar(&ApiVersion, "api-version", 1, "Selects API version when headless") RootCommand.PersistentFlags().StringVar(&InitFile, "init", "", "Init file, executed by the terminal client.") RootCommand.PersistentFlags().StringVar(&BuildFlags, "build-flags", buildFlagsDefault, "Build flags, to be passed to the compiler.") @@ -239,7 +243,7 @@ func traceCmd(cmd *cobra.Command, args []string) { defer listener.Close() // Create and start a debug server - server := rpc.NewServer(&service.Config{ + server := rpc2.NewServer(&service.Config{ Listener: listener, ProcessArgs: processArgs, AttachPid: traceAttachPid, @@ -248,7 +252,7 @@ func traceCmd(cmd *cobra.Command, args []string) { fmt.Fprintln(os.Stderr, err) return 1 } - client := rpc.NewClient(listener.Addr().String()) + client := rpc2.NewClient(listener.Addr().String()) funcs, err := client.ListFunctions(regexp) if err != nil { fmt.Fprintln(os.Stderr, err) @@ -322,7 +326,7 @@ func splitArgs(cmd *cobra.Command, args []string) ([]string, []string) { func connect(addr string, conf *config.Config) int { // Create and start a terminal - attach to running instance var client service.Client - client = rpc.NewClient(addr) + client = rpc2.NewClient(addr) term := terminal.New(client, conf) status, err := term.Run() if err != nil { @@ -344,13 +348,37 @@ func execute(attachPid int, processArgs []string, conf *config.Config) int { fmt.Fprintf(os.Stderr, "Warning: init file ignored\n") } + var server interface { + Run() error + Stop(bool) error + } + + + if !Headless { + ApiVersion = 2 + } + // Create and start a debugger server - server := rpc.NewServer(&service.Config{ - Listener: listener, - ProcessArgs: processArgs, - AttachPid: attachPid, - AcceptMulti: AcceptMulti, - }, Log) + switch ApiVersion { + case 1: + server = rpc1.NewServer(&service.Config{ + Listener: listener, + ProcessArgs: processArgs, + AttachPid: attachPid, + AcceptMulti: AcceptMulti, + }, Log) + case 2: + server = rpc2.NewServer(&service.Config{ + Listener: listener, + ProcessArgs: processArgs, + AttachPid: attachPid, + AcceptMulti: AcceptMulti, + }, Log) + default: + fmt.Println("Unknown API version %d", ApiVersion) + return 1 + } + if err := server.Run(); err != nil { fmt.Fprintln(os.Stderr, err) return 1 @@ -365,7 +393,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config) int { } else { // Create and start a terminal var client service.Client - client = rpc.NewClient(listener.Addr().String()) + client = rpc2.NewClient(listener.Addr().String()) term := terminal.New(client, conf) term.InitFile = InitFile status, err = term.Run() diff --git a/service/client.go b/service/client.go index 6d1ab136..4e26c18a 100644 --- a/service/client.go +++ b/service/client.go @@ -59,8 +59,6 @@ type Client interface { ListPackageVariables(filter string) ([]api.Variable, error) // EvalVariable returns a variable in the context of the current thread. EvalVariable(scope api.EvalScope, symbol string) (*api.Variable, error) - // ListPackageVariablesFor lists all package variables in the context of a thread. - ListPackageVariablesFor(threadID int, filter string) ([]api.Variable, error) // SetVariable sets the value of a variable SetVariable(scope api.EvalScope, symbol, value string) error diff --git a/service/rpc/client.go b/service/rpc1/client.go similarity index 99% rename from service/rpc/client.go rename to service/rpc1/client.go index 295ef44f..0bd71ca6 100644 --- a/service/rpc/client.go +++ b/service/rpc1/client.go @@ -1,4 +1,4 @@ -package rpc +package rpc1 import ( "fmt" diff --git a/service/rpc1/readme.txtr b/service/rpc1/readme.txtr new file mode 100644 index 00000000..c25192d8 --- /dev/null +++ b/service/rpc1/readme.txtr @@ -0,0 +1,5 @@ +This package implements version 1 of Delve's API and is only +kept here for backwards compatibility. Client.go is the old +client code used by Delve's frontend (delve/cmd/dlv), it is +only preserved here for the backwards compatibility tests in +service/test/integration1_test.go. diff --git a/service/rpc/server.go b/service/rpc1/server.go similarity index 99% rename from service/rpc/server.go rename to service/rpc1/server.go index 72012943..ffc8e8ac 100644 --- a/service/rpc/server.go +++ b/service/rpc1/server.go @@ -1,4 +1,4 @@ -package rpc +package rpc1 import ( "errors" @@ -35,6 +35,7 @@ func NewServer(config *service.Config, logEnabled bool) *ServerImpl { if !logEnabled { log.SetOutput(ioutil.Discard) } + log.Printf("Using API v1") return &ServerImpl{ &RPCServer{ diff --git a/service/rpc2/client.go b/service/rpc2/client.go new file mode 100644 index 00000000..9d79e7e2 --- /dev/null +++ b/service/rpc2/client.go @@ -0,0 +1,290 @@ +package rpc2 + +import ( + "fmt" + "log" + "net/rpc" + "net/rpc/jsonrpc" + + "github.com/derekparker/delve/service" + "github.com/derekparker/delve/service/api" +) + +// Client is a RPC service.Client. +type RPCClient struct { + addr string + processPid int + client *rpc.Client +} + +// Ensure the implementation satisfies the interface. +var _ service.Client = &RPCClient{} + +// NewClient creates a new RPCClient. +func NewClient(addr string) *RPCClient { + client, err := jsonrpc.Dial("tcp", addr) + if err != nil { + log.Fatal("dialing:", err) + } + return &RPCClient{ + addr: addr, + client: client, + } +} + +func (c *RPCClient) ProcessPid() int { + out := new(ProcessPidOut) + c.call("ProcessPid", ProcessPidIn{}, out) + return out.Pid +} + +func (c *RPCClient) Detach(kill bool) error { + out := new(DetachOut) + return c.call("Detach", DetachIn{kill}, out) +} + +func (c *RPCClient) Restart() error { + out := new(RestartOut) + return c.call("Restart", RestartIn{}, out) +} + +func (c *RPCClient) GetState() (*api.DebuggerState, error) { + var out StateOut + err := c.call("State", StateIn{}, &out) + return out.State, err +} + +func (c *RPCClient) Continue() <-chan *api.DebuggerState { + ch := make(chan *api.DebuggerState) + go func() { + for { + out := new(CommandOut) + err := c.call("Command", &api.DebuggerCommand{Name: api.Continue}, &out) + state := out.State + if err != nil { + state.Err = err + } + if state.Exited { + // Error types apparantly cannot be marshalled by Go correctly. Must reset error here. + state.Err = fmt.Errorf("Process %d has exited with status %d", c.ProcessPid(), state.ExitStatus) + } + ch <- &state + if err != nil || state.Exited { + close(ch) + return + } + + isbreakpoint := false + istracepoint := true + for i := range state.Threads { + if state.Threads[i].Breakpoint != nil { + isbreakpoint = true + istracepoint = istracepoint && state.Threads[i].Breakpoint.Tracepoint + } + } + + if !isbreakpoint || !istracepoint { + close(ch) + return + } + } + }() + return ch +} + +func (c *RPCClient) Next() (*api.DebuggerState, error) { + var out CommandOut + err := c.call("Command", api.DebuggerCommand{Name: api.Next}, &out) + return &out.State, err +} + +func (c *RPCClient) Step() (*api.DebuggerState, error) { + var out CommandOut + err := c.call("Command", api.DebuggerCommand{Name: api.Step}, &out) + return &out.State, err +} + +func (c *RPCClient) StepInstruction() (*api.DebuggerState, error) { + var out CommandOut + err := c.call("Command", api.DebuggerCommand{Name: api.StepInstruction}, &out) + return &out.State, err +} + +func (c *RPCClient) SwitchThread(threadID int) (*api.DebuggerState, error) { + var out CommandOut + cmd := api.DebuggerCommand{ + Name: api.SwitchThread, + ThreadID: threadID, + } + err := c.call("Command", cmd, &out) + return &out.State, err +} + +func (c *RPCClient) SwitchGoroutine(goroutineID int) (*api.DebuggerState, error) { + var out CommandOut + cmd := api.DebuggerCommand{ + Name: api.SwitchGoroutine, + GoroutineID: goroutineID, + } + err := c.call("Command", cmd, &out) + return &out.State, err +} + +func (c *RPCClient) Halt() (*api.DebuggerState, error) { + var out CommandOut + err := c.call("Command", api.DebuggerCommand{Name: api.Halt}, &out) + return &out.State, err +} + +func (c *RPCClient) GetBreakpoint(id int) (*api.Breakpoint, error) { + var out GetBreakpointOut + err := c.call("GetBreakpoint", GetBreakpointIn{id, ""}, &out) + return &out.Breakpoint, err +} + +func (c *RPCClient) GetBreakpointByName(name string) (*api.Breakpoint, error) { + var out GetBreakpointOut + err := c.call("GetBreakpoint", GetBreakpointIn{0, name}, &out) + return &out.Breakpoint, err +} + +func (c *RPCClient) CreateBreakpoint(breakPoint *api.Breakpoint) (*api.Breakpoint, error) { + var out CreateBreakpointOut + err := c.call("CreateBreakpoint", CreateBreakpointIn{*breakPoint}, &out) + return &out.Breakpoint, err +} + +func (c *RPCClient) ListBreakpoints() ([]*api.Breakpoint, error) { + var out ListBreakpointsOut + err := c.call("ListBreakpoints", ListBreakpointsIn{}, &out) + return out.Breakpoints, err +} + +func (c *RPCClient) ClearBreakpoint(id int) (*api.Breakpoint, error) { + var out ClearBreakpointOut + err := c.call("ClearBreakpoint", ClearBreakpointIn{id, ""}, &out) + return out.Breakpoint, err +} + +func (c *RPCClient) ClearBreakpointByName(name string) (*api.Breakpoint, error) { + var out ClearBreakpointOut + err := c.call("ClearBreakpoint", ClearBreakpointIn{0, name}, &out) + return out.Breakpoint, err +} + +func (c *RPCClient) AmendBreakpoint(bp *api.Breakpoint) error { + out := new(AmendBreakpointOut) + err := c.call("AmendBreakpoint", AmendBreakpointIn{*bp}, out) + return err +} + +func (c *RPCClient) ListThreads() ([]*api.Thread, error) { + var out ListThreadsOut + err := c.call("ListThreads", ListThreadsIn{}, &out) + return out.Threads, err +} + +func (c *RPCClient) GetThread(id int) (*api.Thread, error) { + var out GetThreadOut + err := c.call("GetThread", GetThreadIn{id}, &out) + return out.Thread, err +} + +func (c *RPCClient) EvalVariable(scope api.EvalScope, expr string) (*api.Variable, error) { + var out EvalOut + err := c.call("Eval", EvalIn{scope, expr}, &out) + return out.Variable, err +} + +func (c *RPCClient) SetVariable(scope api.EvalScope, symbol, value string) error { + out := new(SetOut) + return c.call("Set", SetIn{scope, symbol, value}, out) +} + +func (c *RPCClient) ListSources(filter string) ([]string, error) { + sources := new(ListSourcesOut) + err := c.call("ListSources", ListSourcesIn{filter}, sources) + return sources.Sources, err +} + +func (c *RPCClient) ListFunctions(filter string) ([]string, error) { + funcs := new(ListFunctionsOut) + err := c.call("ListFunctions", ListFunctionsIn{filter}, funcs) + return funcs.Funcs, err +} + +func (c *RPCClient) ListTypes(filter string) ([]string, error) { + types := new(ListTypesOut) + err := c.call("ListTypes", ListTypesIn{filter}, types) + return types.Types, err +} + +func (c *RPCClient) ListPackageVariables(filter string) ([]api.Variable, error) { + var out ListPackageVarsOut + err := c.call("ListPackageVars", ListPackageVarsIn{filter}, &out) + return out.Variables, err +} + +func (c *RPCClient) ListLocalVariables(scope api.EvalScope) ([]api.Variable, error) { + var out ListLocalVarsOut + err := c.call("ListLocalVars", ListLocalVarsIn{scope}, &out) + return out.Variables, err +} + +func (c *RPCClient) ListRegisters() (string, error) { + out := new(ListRegistersOut) + err := c.call("ListRegisters", ListRegistersIn{}, out) + return out.Registers, err +} + +func (c *RPCClient) ListFunctionArgs(scope api.EvalScope) ([]api.Variable, error) { + var out ListFunctionArgsOut + err := c.call("ListFunctionArgs", ListFunctionArgsIn{scope}, &out) + return out.Args, err +} + +func (c *RPCClient) ListGoroutines() ([]*api.Goroutine, error) { + var out ListGoroutinesOut + err := c.call("ListGoroutines", ListGoroutinesIn{}, &out) + return out.Goroutines, err +} + +func (c *RPCClient) Stacktrace(goroutineId, depth int, full bool) ([]api.Stackframe, error) { + var out StacktraceOut + err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, full}, &out) + return out.Locations, err +} + +func (c *RPCClient) AttachedToExistingProcess() bool { + out := new(AttachedToExistingProcessOut) + c.call("AttachedToExistingProcess", AttachedToExistingProcessIn{}, out) + return out.Answer +} + +func (c *RPCClient) FindLocation(scope api.EvalScope, loc string) ([]api.Location, error) { + var out FindLocationOut + err := c.call("FindLocation", FindLocationIn{scope, loc}, &out) + return out.Locations, err +} + +// Disassemble code between startPC and endPC +func (c *RPCClient) DisassembleRange(scope api.EvalScope, startPC, endPC uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error) { + var out DisassembleOut + err := c.call("Disassemble", DisassembleIn{scope, startPC, endPC, flavour}, &out) + return out.Disassemble, err +} + +// Disassemble function containing pc +func (c *RPCClient) DisassemblePC(scope api.EvalScope, pc uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error) { + var out DisassembleOut + err := c.call("Disassemble", DisassembleIn{scope, pc, 0, flavour}, &out) + return out.Disassemble, err +} + +func (c *RPCClient) url(path string) string { + return fmt.Sprintf("http://%s%s", c.addr, path) +} + +func (c *RPCClient) call(method string, args, reply interface{}) error { + return c.client.Call("RPCServer."+method, args, reply) +} diff --git a/service/rpc2/server.go b/service/rpc2/server.go new file mode 100644 index 00000000..4ee5b65c --- /dev/null +++ b/service/rpc2/server.go @@ -0,0 +1,538 @@ +package rpc2 + +import ( + "errors" + "fmt" + "io/ioutil" + "log" + "net" + grpc "net/rpc" + "net/rpc/jsonrpc" + + "github.com/derekparker/delve/service" + "github.com/derekparker/delve/service/api" + "github.com/derekparker/delve/service/debugger" +) + +type ServerImpl struct { + s *RPCServer +} + +type RPCServer struct { + // config is all the information necessary to start the debugger and server. + config *service.Config + // listener is used to serve HTTP. + listener net.Listener + // stopChan is used to stop the listener goroutine + stopChan chan struct{} + // debugger is a debugger service. + debugger *debugger.Debugger +} + +// NewServer creates a new RPCServer. +func NewServer(config *service.Config, logEnabled bool) *ServerImpl { + log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) + if !logEnabled { + log.SetOutput(ioutil.Discard) + } + + return &ServerImpl{ + &RPCServer{ + config: config, + listener: config.Listener, + stopChan: make(chan struct{}), + }, + } +} + +// Stop detaches from the debugger and waits for it to stop. +func (s *ServerImpl) Stop(kill bool) error { + if s.s.config.AcceptMulti { + close(s.s.stopChan) + s.s.listener.Close() + } + err := s.s.debugger.Detach(kill) + if err != nil { + return err + } + return nil +} + +// Run starts a debugger and exposes it with an HTTP server. The debugger +// itself can be stopped with the `detach` API. Run blocks until the HTTP +// server stops. +func (s *ServerImpl) Run() error { + var err error + // Create and start the debugger + if s.s.debugger, err = debugger.New(&debugger.Config{ + ProcessArgs: s.s.config.ProcessArgs, + AttachPid: s.s.config.AttachPid, + }); err != nil { + return err + } + + rpcs := grpc.NewServer() + rpcs.Register(s.s) + + go func() { + defer s.s.listener.Close() + for { + c, err := s.s.listener.Accept() + if err != nil { + select { + case <-s.s.stopChan: + // We were supposed to exit, do nothing and return + return + default: + panic(err) + } + } + go rpcs.ServeCodec(jsonrpc.NewServerCodec(c)) + if !s.s.config.AcceptMulti { + break + } + } + }() + return nil +} + +func (s *ServerImpl) Restart() error { + return s.s.Restart(RestartIn{}, nil) +} + +type ProcessPidIn struct { +} + +type ProcessPidOut struct { + Pid int +} + +func (s *RPCServer) ProcessPid(arg ProcessPidIn, out *ProcessPidOut) error { + out.Pid = s.debugger.ProcessPid() + return nil +} + +type DetachIn struct { + Kill bool +} + +type DetachOut struct { +} + +func (s *RPCServer) Detach(arg DetachIn, out *DetachOut) error { + return s.debugger.Detach(arg.Kill) +} + +type RestartIn struct { +} + +type RestartOut struct { +} + +func (s *RPCServer) Restart(arg RestartIn, out *RestartOut) error { + if s.config.AttachPid != 0 { + return errors.New("cannot restart process Delve did not create") + } + return s.debugger.Restart() +} + +type StateIn struct { +} + +type StateOut struct { + State *api.DebuggerState +} + +func (s *RPCServer) State(arg StateIn, out *StateOut) error { + st, err := s.debugger.State() + if err != nil { + return err + } + out.State = st + return nil +} + +type CommandOut struct { + State api.DebuggerState +} + +func (s *RPCServer) Command(command api.DebuggerCommand, out *CommandOut) error { + st, err := s.debugger.Command(&command) + if err != nil { + return err + } + out.State = *st + return nil +} + +type GetBreakpointIn struct { + Id int + Name string +} + +type GetBreakpointOut struct { + Breakpoint api.Breakpoint +} + +func (s *RPCServer) GetBreakpoint(arg GetBreakpointIn, out *GetBreakpointOut) error { + var bp *api.Breakpoint + if arg.Name != "" { + bp = s.debugger.FindBreakpointByName(arg.Name) + if bp == nil { + return fmt.Errorf("no breakpoint with name %s", arg.Name) + } + } else { + bp = s.debugger.FindBreakpoint(arg.Id) + if bp == nil { + return fmt.Errorf("no breakpoint with id %d", arg.Id) + } + } + out.Breakpoint = *bp + return nil +} + +type StacktraceIn struct { + Id int + Depth int + Full bool +} + +type StacktraceOut struct { + Locations []api.Stackframe +} + +func (s *RPCServer) Stacktrace(arg StacktraceIn, out *StacktraceOut) error { + locs, err := s.debugger.Stacktrace(arg.Id, arg.Depth, arg.Full) + if err != nil { + return err + } + out.Locations = locs + return nil +} + +type ListBreakpointsIn struct { +} + +type ListBreakpointsOut struct { + Breakpoints []*api.Breakpoint +} + +func (s *RPCServer) ListBreakpoints(arg ListBreakpointsIn, out *ListBreakpointsOut) error { + out.Breakpoints = s.debugger.Breakpoints() + return nil +} + +type CreateBreakpointIn struct { + Breakpoint api.Breakpoint +} + +type CreateBreakpointOut struct { + Breakpoint api.Breakpoint +} + +func (s *RPCServer) CreateBreakpoint(arg CreateBreakpointIn, out *CreateBreakpointOut) error { + createdbp, err := s.debugger.CreateBreakpoint(&arg.Breakpoint) + if err != nil { + return err + } + out.Breakpoint = *createdbp + return nil +} + +type ClearBreakpointIn struct { + Id int + Name string +} + +type ClearBreakpointOut struct { + Breakpoint *api.Breakpoint +} + +func (s *RPCServer) ClearBreakpoint(arg ClearBreakpointIn, out *ClearBreakpointOut) error { + var bp *api.Breakpoint + if arg.Name != "" { + bp = s.debugger.FindBreakpointByName(arg.Name) + if bp == nil { + return fmt.Errorf("no breakpoint with name %s", arg.Name) + } + } else { + bp = s.debugger.FindBreakpoint(arg.Id) + if bp == nil { + return fmt.Errorf("no breakpoint with id %d", arg.Id) + } + } + deleted, err := s.debugger.ClearBreakpoint(bp) + if err != nil { + return err + } + out.Breakpoint = deleted + return nil +} + +type AmendBreakpointIn struct { + Breakpoint api.Breakpoint +} + +type AmendBreakpointOut struct { +} + +func (s *RPCServer) AmendBreakpoint(arg AmendBreakpointIn, out *AmendBreakpointOut) error { + return s.debugger.AmendBreakpoint(&arg.Breakpoint) +} + +type ListThreadsIn struct { +} + +type ListThreadsOut struct { + Threads []*api.Thread +} + +func (s *RPCServer) ListThreads(arg ListThreadsIn, out *ListThreadsOut) (err error) { + out.Threads, err = s.debugger.Threads() + return err +} + +type GetThreadIn struct { + Id int +} + +type GetThreadOut struct { + Thread *api.Thread +} + +func (s *RPCServer) GetThread(arg GetThreadIn, out *GetThreadOut) error { + t, err := s.debugger.FindThread(arg.Id) + if err != nil { + return err + } + if t == nil { + return fmt.Errorf("no thread with id %d", arg.Id) + } + out.Thread = t + return nil +} + +type ListPackageVarsIn struct { + Filter string +} + +type ListPackageVarsOut struct { + Variables []api.Variable +} + +func (s *RPCServer) ListPackageVars(arg ListPackageVarsIn, out *ListPackageVarsOut) error { + state, err := s.debugger.State() + if err != nil { + return err + } + + current := state.CurrentThread + if current == nil { + return fmt.Errorf("no current thread") + } + + vars, err := s.debugger.PackageVariables(current.ID, arg.Filter) + if err != nil { + return err + } + out.Variables = vars + return nil +} + +type ListRegistersIn struct { +} + +type ListRegistersOut struct { + Registers string +} + +func (s *RPCServer) ListRegisters(arg ListRegistersIn, out *ListRegistersOut) error { + state, err := s.debugger.State() + if err != nil { + return err + } + + regs, err := s.debugger.Registers(state.CurrentThread.ID) + if err != nil { + return err + } + out.Registers = regs + return nil +} + +type ListLocalVarsIn struct { + Scope api.EvalScope +} + +type ListLocalVarsOut struct { + Variables []api.Variable +} + +func (s *RPCServer) ListLocalVars(arg ListLocalVarsIn, out *ListLocalVarsOut) error { + vars, err := s.debugger.LocalVariables(arg.Scope) + if err != nil { + return err + } + out.Variables = vars + return nil +} + +type ListFunctionArgsIn struct { + Scope api.EvalScope +} + +type ListFunctionArgsOut struct { + Args []api.Variable +} + +func (s *RPCServer) ListFunctionArgs(arg ListFunctionArgsIn, out *ListFunctionArgsOut) error { + vars, err := s.debugger.FunctionArguments(arg.Scope) + if err != nil { + return err + } + out.Args = vars + return nil +} + +type EvalIn struct { + Scope api.EvalScope + Expr string +} + +type EvalOut struct { + Variable *api.Variable +} + +func (s *RPCServer) Eval(arg EvalIn, out *EvalOut) error { + v, err := s.debugger.EvalVariableInScope(arg.Scope, arg.Expr) + if err != nil { + return err + } + out.Variable = v + return nil +} + +type SetIn struct { + Scope api.EvalScope + Symbol string + Value string +} + +type SetOut struct { +} + +func (s *RPCServer) Set(arg SetIn, out *SetOut) error { + return s.debugger.SetVariableInScope(arg.Scope, arg.Symbol, arg.Value) +} + +type ListSourcesIn struct { + Filter string +} + +type ListSourcesOut struct { + Sources []string +} + +func (s *RPCServer) ListSources(arg ListSourcesIn, out *ListSourcesOut) error { + ss, err := s.debugger.Sources(arg.Filter) + if err != nil { + return err + } + out.Sources = ss + return nil +} + +type ListFunctionsIn struct { + Filter string +} + +type ListFunctionsOut struct { + Funcs []string +} + +func (s *RPCServer) ListFunctions(arg ListFunctionsIn, out *ListFunctionsOut) error { + fns, err := s.debugger.Functions(arg.Filter) + if err != nil { + return err + } + out.Funcs = fns + return nil +} + +type ListTypesIn struct { + Filter string +} + +type ListTypesOut struct { + Types []string +} + +func (s *RPCServer) ListTypes(arg ListTypesIn, out *ListTypesOut) error { + tps, err := s.debugger.Types(arg.Filter) + if err != nil { + return err + } + out.Types = tps + return nil +} + +type ListGoroutinesIn struct { +} + +type ListGoroutinesOut struct { + Goroutines []*api.Goroutine +} + +func (s *RPCServer) ListGoroutines(arg ListGoroutinesIn, out *ListGoroutinesOut) error { + gs, err := s.debugger.Goroutines() + if err != nil { + return err + } + out.Goroutines = gs + return nil +} + +type AttachedToExistingProcessIn struct { +} + +type AttachedToExistingProcessOut struct { + Answer bool +} + +func (c *RPCServer) AttachedToExistingProcess(arg AttachedToExistingProcessIn, out *AttachedToExistingProcessOut) error { + if c.config.AttachPid != 0 { + out.Answer = true + } + return nil +} + +type FindLocationIn struct { + Scope api.EvalScope + Loc string +} + +type FindLocationOut struct { + Locations []api.Location +} + +func (c *RPCServer) FindLocation(arg FindLocationIn, out *FindLocationOut) error { + var err error + out.Locations, err = c.debugger.FindLocation(arg.Scope, arg.Loc) + return err +} + +type DisassembleIn struct { + Scope api.EvalScope + StartPC, EndPC uint64 + Flavour api.AssemblyFlavour +} + +type DisassembleOut struct { + Disassemble api.AsmInstructions +} + +func (c *RPCServer) Disassemble(arg DisassembleIn, out *DisassembleOut) error { + var err error + out.Disassemble, err = c.debugger.Disassemble(arg.Scope, arg.StartPC, arg.EndPC, arg.Flavour) + return err +} diff --git a/service/test/cmd/typecheckrpc.go b/service/test/cmd/typecheckrpc.go new file mode 100644 index 00000000..014379d0 --- /dev/null +++ b/service/test/cmd/typecheckrpc.go @@ -0,0 +1,204 @@ +package main +// This program checks the types of the arguments of calls to +// the API in service/rpc2/client.go (done using rpc2.(*Client).call) +// against the declared types of API methods in srvice/rpc2/server.go + +import ( + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "log" + "os" + "path/filepath" + "strconv" +) + +func findRPCDir() string { + const parent = ".." + RPCDir := "service/rpc2" + for depth := 0; depth < 10; depth++ { + if _, err := os.Stat(RPCDir); err == nil { + break + } + RPCDir = filepath.Join(parent, RPCDir) + } + return RPCDir +} + +func parseFiles(path string) (*token.FileSet, *types.Package, types.Info, *ast.File) { + fset := token.NewFileSet() + files := []*ast.File{} + + for _, name := range []string{"server.go", "client.go"} { + f, err := parser.ParseFile(fset, filepath.Join(path, name), nil, 0) + if err != nil { + log.Fatal(err) + } + files = append(files, f) + } + + info := types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + } + + var conf types.Config + conf.Importer = importer.Default() + pkg, err := conf.Check(path, fset, files, &info) + if err != nil { + log.Fatal(err) + } + + return fset, pkg, info, files[1] +} + +func getMethods(pkg *types.Package, typename string) map[string]*types.Func { + r := make(map[string]*types.Func) + mset := types.NewMethodSet(types.NewPointer(pkg.Scope().Lookup(typename).Type())) + for i := 0; i < mset.Len(); i++ { + fn := mset.At(i).Obj().(*types.Func) + r[fn.Name()] = fn + } + return r +} + +func publicMethodOf(decl ast.Decl, receiver string) *ast.FuncDecl { + fndecl, isfunc := decl.(*ast.FuncDecl) + if !isfunc { + return nil + } + if fndecl.Name.Name[0] >= 'a' && fndecl.Name.Name[0] <= 'z' { + return nil + } + if fndecl.Recv == nil || len(fndecl.Recv.List) != 1 { + return nil + } + starexpr, isstar := fndecl.Recv.List[0].Type.(*ast.StarExpr) + if !isstar { + return nil + } + identexpr, isident := starexpr.X.(*ast.Ident) + if !isident || identexpr.Name != receiver { + return nil + } + if fndecl.Body == nil { + return nil + } + return fndecl +} + +func findCallCall(fndecl *ast.FuncDecl) *ast.CallExpr { + for _, stmt := range fndecl.Body.List { + var x ast.Expr = nil + + switch s := stmt.(type) { + case *ast.AssignStmt: + if len(s.Rhs) == 1 { + x = s.Rhs[0] + } + case *ast.ReturnStmt: + if len(s.Results) == 1 { + x = s.Results[0] + } + case *ast.ExprStmt: + x = s.X + } + + callx, iscall := x.(*ast.CallExpr) + if !iscall { + continue + } + fun, issel := callx.Fun.(*ast.SelectorExpr) + if !issel || fun.Sel.Name != "call" { + continue + } + return callx + } + return nil +} + +func qf(*types.Package) string { + return "" +} + +func main() { + RPCDir := findRPCDir() + + fset, pkg, info, clientAst := parseFiles(RPCDir) + + serverMethods := getMethods(pkg, "RPCServer") + _ = serverMethods + errcount := 0 + + for _, decl := range clientAst.Decls { + fndecl := publicMethodOf(decl, "RPCClient") + if fndecl == nil { + continue + } + + if fndecl.Name.Name == "Continue" { + // complex function, skip check + continue + } + + callx := findCallCall(fndecl) + + if callx == nil { + log.Printf("%s: could not find RPC call", fset.Position(fndecl.Pos())) + errcount++ + continue + } + + if len(callx.Args) != 3 { + log.Printf("%s: wrong number of arguments for RPC call", fset.Position(callx.Pos())) + errcount++ + continue + } + + arg0, arg0islit := callx.Args[0].(*ast.BasicLit) + arg1 := callx.Args[1] + arg2 := callx.Args[2] + if !arg0islit || arg0.Kind != token.STRING { + continue + } + name, _ := strconv.Unquote(arg0.Value) + serverMethod := serverMethods[name] + if serverMethod == nil { + log.Printf("%s: could not find RPC method %q", fset.Position(callx.Pos()), name) + errcount++ + continue + } + + params := serverMethod.Type().(*types.Signature).Params() + + if a, e := info.TypeOf(arg1), params.At(0).Type(); !types.AssignableTo(a, e) { + log.Printf("%s: wrong type of first argument %s, expected %s", fset.Position(callx.Pos()), types.TypeString(a, qf), types.TypeString(e, qf)) + errcount++ + continue + } + + if a, e := info.TypeOf(arg2), params.At(1).Type(); !types.AssignableTo(a, e) { + log.Printf("%s: wrong type of second argument %s, expected %s", fset.Position(callx.Pos()), types.TypeString(a, qf), types.TypeString(e, qf)) + errcount++ + continue + } + + if clit, ok := arg1.(*ast.CompositeLit); ok { + typ := params.At(0).Type() + st := typ.Underlying().(*types.Struct) + if len(clit.Elts) != st.NumFields() && types.TypeString(typ, qf) != "DebuggerCommand" { + log.Printf("%s: wrong number of fields in first argument's literal %d, expected %d", fset.Position(callx.Pos()), len(clit.Elts), st.NumFields()) + errcount++ + continue + } + } + } + + if errcount > 0 { + log.Printf("%d errors", errcount) + os.Exit(1) + } +} diff --git a/service/test/common_test.go b/service/test/common_test.go new file mode 100644 index 00000000..f980dd5a --- /dev/null +++ b/service/test/common_test.go @@ -0,0 +1,100 @@ +package servicetest + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/derekparker/delve/service" + "github.com/derekparker/delve/service/api" +) + +func assertNoError(err error, t *testing.T, s string) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + fname := filepath.Base(file) + t.Fatalf("failed assertion at %s:%d: %s - %s\n", fname, line, s, err) + } +} + +func assertError(err error, t *testing.T, s string) { + if err == nil { + _, file, line, _ := runtime.Caller(1) + fname := filepath.Base(file) + t.Fatalf("failed assertion at %s:%d: %s (no error)\n", fname, line, s) + } +} + +func init() { + runtime.GOMAXPROCS(2) +} + +type nextTest struct { + begin, end int +} + +func countBreakpoints(t *testing.T, c service.Client) int { + bps, err := c.ListBreakpoints() + assertNoError(err, t, "ListBreakpoints()") + bpcount := 0 + for _, bp := range bps { + if bp.ID >= 0 { + bpcount++ + } + } + return bpcount +} + +func testProgPath(t *testing.T, name string) string { + fp, err := filepath.Abs(fmt.Sprintf("_fixtures/%s.go", name)) + if err != nil { + t.Fatal(err) + } + if _, err := os.Stat(fp); err != nil { + fp, err = filepath.Abs(fmt.Sprintf("../../_fixtures/%s.go", name)) + if err != nil { + t.Fatal(err) + } + } + return fp +} + +func findLocationHelper(t *testing.T, c service.Client, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 { + locs, err := c.FindLocation(api.EvalScope{-1, 0}, loc) + t.Logf("FindLocation(\"%s\") → %v\n", loc, locs) + + if shouldErr { + if err == nil { + t.Fatalf("Resolving location <%s> didn't return an error: %v", loc, locs) + } + } else { + if err != nil { + t.Fatalf("Error resolving location <%s>: %v", loc, err) + } + } + + if (count >= 0) && (len(locs) != count) { + t.Fatalf("Wrong number of breakpoints returned for location <%s> (got %d, expected %d)", loc, len(locs), count) + } + + if checkAddr != 0 && checkAddr != locs[0].PC { + t.Fatalf("Wrong address returned for location <%s> (got %v, epected %v)", loc, locs[0].PC, checkAddr) + } + + addrs := make([]uint64, len(locs)) + for i := range locs { + addrs[i] = locs[i].PC + } + return addrs +} + +func getCurinstr(d3 api.AsmInstructions) *api.AsmInstruction { + for i := range d3 { + if d3[i].AtPC { + return &d3[i] + } + } + return nil +} diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go new file mode 100644 index 00000000..132ed941 --- /dev/null +++ b/service/test/integration1_test.go @@ -0,0 +1,1077 @@ +package servicetest + +import ( + "fmt" + "math/rand" + "net" + "path/filepath" + "runtime" + "strconv" + "strings" + "testing" + "time" + + protest "github.com/derekparker/delve/proc/test" + + "github.com/derekparker/delve/proc" + "github.com/derekparker/delve/service" + "github.com/derekparker/delve/service/api" + "github.com/derekparker/delve/service/rpc1" +) + +func withTestClient1(name string, t *testing.T, fn func(c service.Client)) { + listener, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatalf("couldn't start listener: %s\n", err) + } + defer listener.Close() + server := rpc1.NewServer(&service.Config{ + Listener: listener, + ProcessArgs: []string{protest.BuildFixture(name).Path}, + }, false) + if err := server.Run(); err != nil { + t.Fatal(err) + } + client := rpc1.NewClient(listener.Addr().String()) + defer func() { + client.Detach(true) + }() + + fn(client) +} + +func Test1RunWithInvalidPath(t *testing.T) { + listener, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatalf("couldn't start listener: %s\n", err) + } + defer listener.Close() + server := rpc1.NewServer(&service.Config{ + Listener: listener, + ProcessArgs: []string{"invalid_path"}, + }, false) + if err := server.Run(); err == nil { + t.Fatal("Expected Run to return error for invalid program path") + } +} + +func Test1Restart_afterExit(t *testing.T) { + withTestClient1("continuetestprog", t, func(c service.Client) { + origPid := c.ProcessPid() + state := <-c.Continue() + if !state.Exited { + t.Fatal("expected initial process to have exited") + } + if err := c.Restart(); err != nil { + t.Fatal(err) + } + if c.ProcessPid() == origPid { + t.Fatal("did not spawn new process, has same PID") + } + state = <-c.Continue() + if !state.Exited { + t.Fatalf("expected restarted process to have exited %v", state) + } + }) +} + +func Test1Restart_breakpointPreservation(t *testing.T) { + withTestClient1("continuetestprog", t, func(c service.Client) { + _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Name: "firstbreakpoint", Tracepoint: true}) + assertNoError(err, t, "CreateBreakpoint()") + stateCh := c.Continue() + + state := <-stateCh + if state.CurrentThread.Breakpoint.Name != "firstbreakpoint" || !state.CurrentThread.Breakpoint.Tracepoint { + t.Fatalf("Wrong breakpoint: %#v\n", state.CurrentThread.Breakpoint) + } + state = <-stateCh + if !state.Exited { + t.Fatal("Did not exit after first tracepoint") + } + + t.Log("Restart") + c.Restart() + stateCh = c.Continue() + state = <-stateCh + if state.CurrentThread.Breakpoint.Name != "firstbreakpoint" || !state.CurrentThread.Breakpoint.Tracepoint { + t.Fatalf("Wrong breakpoint (after restart): %#v\n", state.CurrentThread.Breakpoint) + } + state = <-stateCh + if !state.Exited { + t.Fatal("Did not exit after first tracepoint (after restart)") + } + }) +} + +func Test1Restart_duringStop(t *testing.T) { + withTestClient1("continuetestprog", t, func(c service.Client) { + origPid := c.ProcessPid() + _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1}) + if err != nil { + t.Fatal(err) + } + state := <-c.Continue() + if state.CurrentThread.Breakpoint == nil { + t.Fatal("did not hit breakpoint") + } + if err := c.Restart(); err != nil { + t.Fatal(err) + } + if c.ProcessPid() == origPid { + t.Fatal("did not spawn new process, has same PID") + } + bps, err := c.ListBreakpoints() + if err != nil { + t.Fatal(err) + } + if len(bps) == 0 { + t.Fatal("breakpoints not preserved") + } + }) +} + +func Test1Restart_attachPid(t *testing.T) { + // Assert it does not work and returns error. + // We cannot restart a process we did not spawn. + server := rpc1.NewServer(&service.Config{ + Listener: nil, + AttachPid: 999, + }, false) + if err := server.Restart(); err == nil { + t.Fatal("expected error on restart after attaching to pid but got none") + } +} + +func Test1ClientServer_exit(t *testing.T) { + withTestClient1("continuetestprog", t, func(c service.Client) { + state, err := c.GetState() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if e, a := false, state.Exited; e != a { + t.Fatalf("Expected exited %v, got %v", e, a) + } + state = <-c.Continue() + if state.Err == nil { + t.Fatalf("Error expected after continue") + } + if !state.Exited { + t.Fatalf("Expected exit after continue: %v", state) + } + state, err = c.GetState() + if err == nil { + t.Fatal("Expected error on querying state from exited process") + } + }) +} + +func Test1ClientServer_step(t *testing.T) { + withTestClient1("testprog", t, func(c service.Client) { + _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: 1}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + stateBefore := <-c.Continue() + if stateBefore.Err != nil { + t.Fatalf("Unexpected error: %v", stateBefore.Err) + } + + stateAfter, err := c.Step() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if before, after := stateBefore.CurrentThread.PC, stateAfter.CurrentThread.PC; before >= after { + t.Errorf("Expected %#v to be greater than %#v", before, after) + } + }) +} + +func testnext(testcases []nextTest, initialLocation string, t *testing.T) { + withTestClient1("testnextprog", t, func(c service.Client) { + bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: initialLocation, Line: -1}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + state := <-c.Continue() + if state.Err != nil { + t.Fatalf("Unexpected error: %v", state.Err) + } + + _, err = c.ClearBreakpoint(bp.ID) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + for _, tc := range testcases { + if state.CurrentThread.Line != tc.begin { + t.Fatalf("Program not stopped at correct spot expected %d was %d", tc.begin, state.CurrentThread.Line) + } + + t.Logf("Next for scenario %#v", tc) + state, err = c.Next() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if state.CurrentThread.Line != tc.end { + t.Fatalf("Program did not continue to correct next location expected %d was %d", tc.end, state.CurrentThread.Line) + } + } + }) +} + +func Test1NextGeneral(t *testing.T) { + var testcases []nextTest + + ver, _ := proc.ParseVersionString(runtime.Version()) + + if ver.Major < 0 || ver.AfterOrEqual(proc.GoVersion{1, 7, 0, 0, 0}) { + testcases = []nextTest{ + {17, 19}, + {19, 20}, + {20, 23}, + {23, 24}, + {24, 26}, + {26, 31}, + {31, 23}, + {23, 24}, + {24, 26}, + {26, 31}, + {31, 23}, + {23, 24}, + {24, 26}, + {26, 27}, + {27, 28}, + {28, 34}, + } + } else { + testcases = []nextTest{ + {17, 19}, + {19, 20}, + {20, 23}, + {23, 24}, + {24, 26}, + {26, 31}, + {31, 23}, + {23, 24}, + {24, 26}, + {26, 31}, + {31, 23}, + {23, 24}, + {24, 26}, + {26, 27}, + {27, 34}, + } + } + + testnext(testcases, "main.testnext", t) +} + +func Test1NextFunctionReturn(t *testing.T) { + testcases := []nextTest{ + {13, 14}, + {14, 15}, + {15, 35}, + } + testnext(testcases, "main.helloworld", t) +} + +func Test1ClientServer_breakpointInMainThread(t *testing.T) { + withTestClient1("testprog", t, func(c service.Client) { + bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: 1}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + state := <-c.Continue() + if err != nil { + t.Fatalf("Unexpected error: %v, state: %#v", err, state) + } + + pc := state.CurrentThread.PC + + if pc-1 != bp.Addr && pc != bp.Addr { + f, l := state.CurrentThread.File, state.CurrentThread.Line + t.Fatalf("Break not respected:\nPC:%#v %s:%d\nFN:%#v \n", pc, f, l, bp.Addr) + } + }) +} + +func Test1ClientServer_breakpointInSeparateGoroutine(t *testing.T) { + withTestClient1("testthreads", t, func(c service.Client) { + _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.anotherthread", Line: 1}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + state := <-c.Continue() + if state.Err != nil { + t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state) + } + + f, l := state.CurrentThread.File, state.CurrentThread.Line + if f != "testthreads.go" && l != 9 { + t.Fatal("Program did not hit breakpoint") + } + }) +} + +func Test1ClientServer_breakAtNonexistentPoint(t *testing.T) { + withTestClient1("testprog", t, func(c service.Client) { + _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "nowhere", Line: 1}) + if err == nil { + t.Fatal("Should not be able to break at non existent function") + } + }) +} + +func Test1ClientServer_clearBreakpoint(t *testing.T) { + withTestClient1("testprog", t, func(c service.Client) { + bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sleepytime", Line: 1}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if e, a := 1, countBreakpoints(t, c); e != a { + t.Fatalf("Expected breakpoint count %d, got %d", e, a) + } + + deleted, err := c.ClearBreakpoint(bp.ID) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if deleted.ID != bp.ID { + t.Fatalf("Expected deleted breakpoint ID %v, got %v", bp.ID, deleted.ID) + } + + if e, a := 0, countBreakpoints(t, c); e != a { + t.Fatalf("Expected breakpoint count %d, got %d", e, a) + } + }) +} + +func Test1ClientServer_switchThread(t *testing.T) { + withTestClient1("testnextprog", t, func(c service.Client) { + // With invalid thread id + _, err := c.SwitchThread(-1) + if err == nil { + t.Fatal("Expected error for invalid thread id") + } + + _, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + state := <-c.Continue() + if state.Err != nil { + t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state) + } + + var nt int + ct := state.CurrentThread.ID + threads, err := c.ListThreads() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + for _, th := range threads { + if th.ID != ct { + nt = th.ID + break + } + } + if nt == 0 { + t.Fatal("could not find thread to switch to") + } + // With valid thread id + state, err = c.SwitchThread(nt) + if err != nil { + t.Fatal(err) + } + if state.CurrentThread.ID != nt { + t.Fatal("Did not switch threads") + } + }) +} + +func Test1ClientServer_infoLocals(t *testing.T) { + withTestClient1("testnextprog", t, func(c service.Client) { + fp := testProgPath(t, "testnextprog") + _, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 23}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + state := <-c.Continue() + if state.Err != nil { + t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state) + } + locals, err := c.ListLocalVariables(api.EvalScope{-1, 0}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(locals) != 3 { + t.Fatalf("Expected 3 locals, got %d %#v", len(locals), locals) + } + }) +} + +func Test1ClientServer_infoArgs(t *testing.T) { + withTestClient1("testnextprog", t, func(c service.Client) { + fp := testProgPath(t, "testnextprog") + _, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 47}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + state := <-c.Continue() + if state.Err != nil { + t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state) + } + regs, err := c.ListRegisters() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if regs == "" { + t.Fatal("Expected string showing registers values, got empty string") + } + locals, err := c.ListFunctionArgs(api.EvalScope{-1, 0}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(locals) != 2 { + t.Fatalf("Expected 2 function args, got %d %#v", len(locals), locals) + } + }) +} + +func Test1ClientServer_traceContinue(t *testing.T) { + withTestClient1("integrationprog", t, func(c service.Client) { + fp := testProgPath(t, "integrationprog") + _, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 15, Tracepoint: true, Goroutine: true, Stacktrace: 5, Variables: []string{"i"}}) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + count := 0 + contChan := c.Continue() + for state := range contChan { + if state.CurrentThread != nil && state.CurrentThread.Breakpoint != nil { + count++ + + t.Logf("%v", state) + + bpi := state.CurrentThread.BreakpointInfo + + if bpi.Goroutine == nil { + t.Fatalf("No goroutine information") + } + + if len(bpi.Stacktrace) <= 0 { + t.Fatalf("No stacktrace\n") + } + + if len(bpi.Variables) != 1 { + t.Fatalf("Wrong number of variables returned: %d", len(bpi.Variables)) + } + + if bpi.Variables[0].Name != "i" { + t.Fatalf("Wrong variable returned %s", bpi.Variables[0].Name) + } + + t.Logf("Variable i is %v", bpi.Variables[0]) + + n, err := strconv.Atoi(bpi.Variables[0].Value) + + if err != nil || n != count-1 { + t.Fatalf("Wrong variable value %q (%v %d)", bpi.Variables[0].Value, err, count) + } + } + if state.Exited { + continue + } + t.Logf("%v", state) + if state.Err != nil { + t.Fatalf("Unexpected error during continue: %v\n", state.Err) + } + + } + + if count != 3 { + t.Fatalf("Wrong number of continues hit: %d\n", count) + } + }) +} + +func Test1ClientServer_traceContinue2(t *testing.T) { + withTestClient1("integrationprog", t, func(c service.Client) { + bp1, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Tracepoint: true}) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + bp2, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1, Tracepoint: true}) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + countMain := 0 + countSayhi := 0 + contChan := c.Continue() + for state := range contChan { + if state.CurrentThread != nil && state.CurrentThread.Breakpoint != nil { + switch state.CurrentThread.Breakpoint.ID { + case bp1.ID: + countMain++ + case bp2.ID: + countSayhi++ + } + + t.Logf("%v", state) + } + if state.Exited { + continue + } + if state.Err != nil { + t.Fatalf("Unexpected error during continue: %v\n", state.Err) + } + + } + + if countMain != 1 { + t.Fatalf("Wrong number of continues (main.main) hit: %d\n", countMain) + } + + if countSayhi != 3 { + t.Fatalf("Wrong number of continues (main.sayhi) hit: %d\n", countSayhi) + } + }) +} + +func Test1ClientServer_FindLocations(t *testing.T) { + withTestClient1("locationsprog", t, func(c service.Client) { + someFunctionCallAddr := findLocationHelper(t, c, "locationsprog.go:26", false, 1, 0)[0] + someFunctionLine1 := findLocationHelper(t, c, "locationsprog.go:27", false, 1, 0)[0] + findLocationHelper(t, c, "anotherFunction:1", false, 1, someFunctionLine1) + findLocationHelper(t, c, "main.anotherFunction:1", false, 1, someFunctionLine1) + findLocationHelper(t, c, "anotherFunction", false, 1, someFunctionCallAddr) + findLocationHelper(t, c, "main.anotherFunction", false, 1, someFunctionCallAddr) + findLocationHelper(t, c, fmt.Sprintf("*0x%x", someFunctionCallAddr), false, 1, someFunctionCallAddr) + findLocationHelper(t, c, "sprog.go:26", true, 0, 0) + + findLocationHelper(t, c, "String", true, 0, 0) + findLocationHelper(t, c, "main.String", true, 0, 0) + + someTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:14", false, 1, 0)[0] + otherTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:18", false, 1, 0)[0] + findLocationHelper(t, c, "SomeType.String", false, 1, someTypeStringFuncAddr) + findLocationHelper(t, c, "(*SomeType).String", false, 1, someTypeStringFuncAddr) + findLocationHelper(t, c, "main.SomeType.String", false, 1, someTypeStringFuncAddr) + findLocationHelper(t, c, "main.(*SomeType).String", false, 1, someTypeStringFuncAddr) + + // Issue #275 + readfile := findLocationHelper(t, c, "io/ioutil.ReadFile", false, 1, 0)[0] + + // Issue #296 + findLocationHelper(t, c, "/io/ioutil.ReadFile", false, 1, readfile) + findLocationHelper(t, c, "ioutil.ReadFile", false, 1, readfile) + + stringAddrs := findLocationHelper(t, c, "/^main.*Type.*String$/", false, 2, 0) + + if otherTypeStringFuncAddr != stringAddrs[0] && otherTypeStringFuncAddr != stringAddrs[1] { + t.Fatalf("Wrong locations returned for \"/.*Type.*String/\", got: %v expected: %v and %v\n", stringAddrs, someTypeStringFuncAddr, otherTypeStringFuncAddr) + } + + _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 3, Tracepoint: false}) + if err != nil { + t.Fatalf("CreateBreakpoint(): %v\n", err) + } + + <-c.Continue() + + locationsprog34Addr := findLocationHelper(t, c, "locationsprog.go:34", false, 1, 0)[0] + findLocationHelper(t, c, fmt.Sprintf("%s:34", testProgPath(t, "locationsprog")), false, 1, locationsprog34Addr) + findLocationHelper(t, c, "+1", false, 1, locationsprog34Addr) + findLocationHelper(t, c, "34", false, 1, locationsprog34Addr) + findLocationHelper(t, c, "-1", false, 1, findLocationHelper(t, c, "locationsprog.go:32", false, 1, 0)[0]) + }) + + withTestClient1("testnextdefer", t, func(c service.Client) { + firstMainLine := findLocationHelper(t, c, "testnextdefer.go:5", false, 1, 0)[0] + findLocationHelper(t, c, "main.main", false, 1, firstMainLine) + }) + + withTestClient1("stacktraceprog", t, func(c service.Client) { + stacktracemeAddr := findLocationHelper(t, c, "stacktraceprog.go:4", false, 1, 0)[0] + findLocationHelper(t, c, "main.stacktraceme", false, 1, stacktracemeAddr) + }) + + withTestClient1("locationsUpperCase", t, func(c service.Client) { + // Upper case + findLocationHelper(t, c, "locationsUpperCase.go:6", false, 1, 0) + + // Fully qualified path + path := protest.Fixtures["locationsUpperCase"].Source + findLocationHelper(t, c, path+":6", false, 1, 0) + bp, err := c.CreateBreakpoint(&api.Breakpoint{File: path, Line: 6}) + if err != nil { + t.Fatalf("Could not set breakpoint in %s: %v\n", path, err) + } + c.ClearBreakpoint(bp.ID) + + // Allow `/` or `\` on Windows + if runtime.GOOS == "windows" { + findLocationHelper(t, c, filepath.FromSlash(path)+":6", false, 1, 0) + bp, err = c.CreateBreakpoint(&api.Breakpoint{File: filepath.FromSlash(path), Line: 6}) + if err != nil { + t.Fatalf("Could not set breakpoint in %s: %v\n", filepath.FromSlash(path), err) + } + c.ClearBreakpoint(bp.ID) + } + + // Case-insensitive on Windows, case-sensitive otherwise + shouldWrongCaseBeError := true + numExpectedMatches := 0 + if runtime.GOOS == "windows" { + shouldWrongCaseBeError = false + numExpectedMatches = 1 + } + findLocationHelper(t, c, strings.ToLower(path)+":6", shouldWrongCaseBeError, numExpectedMatches, 0) + bp, err = c.CreateBreakpoint(&api.Breakpoint{File: strings.ToLower(path), Line: 6}) + if (err == nil) == shouldWrongCaseBeError { + t.Fatalf("Could not set breakpoint in %s: %v\n", strings.ToLower(path), err) + } + c.ClearBreakpoint(bp.ID) + }) +} + +func Test1ClientServer_FindLocationsAddr(t *testing.T) { + withTestClient1("locationsprog2", t, func(c service.Client) { + <-c.Continue() + + afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0] + anonfunc := findLocationHelper(t, c, "main.main.func1", false, 1, 0)[0] + + findLocationHelper(t, c, "*fn1", false, 1, afunction) + findLocationHelper(t, c, "*fn3", false, 1, anonfunc) + }) +} + +func Test1ClientServer_EvalVariable(t *testing.T) { + withTestClient1("testvariables", t, func(c service.Client) { + state := <-c.Continue() + + if state.Err != nil { + t.Fatalf("Continue(): %v\n", state.Err) + } + + var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "a1") + assertNoError(err, t, "EvalVariable") + + t.Logf("var1: %s", var1.SinglelineString()) + + if var1.Value != "foofoofoofoofoofoo" { + t.Fatalf("Wrong variable value: %s", var1.Value) + } + }) +} + +func Test1ClientServer_SetVariable(t *testing.T) { + withTestClient1("testvariables", t, func(c service.Client) { + state := <-c.Continue() + + if state.Err != nil { + t.Fatalf("Continue(): %v\n", state.Err) + } + + assertNoError(c.SetVariable(api.EvalScope{-1, 0}, "a2", "8"), t, "SetVariable()") + + a2, err := c.EvalVariable(api.EvalScope{-1, 0}, "a2") + + t.Logf("a2: %v", a2) + + n, err := strconv.Atoi(a2.Value) + + if err != nil && n != 8 { + t.Fatalf("Wrong variable value: %v", a2) + } + }) +} + +func Test1ClientServer_FullStacktrace(t *testing.T) { + withTestClient1("goroutinestackprog", t, func(c service.Client) { + _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1}) + assertNoError(err, t, "CreateBreakpoint()") + state := <-c.Continue() + if state.Err != nil { + t.Fatalf("Continue(): %v\n", state.Err) + } + + gs, err := c.ListGoroutines() + assertNoError(err, t, "GoroutinesInfo()") + found := make([]bool, 10) + for _, g := range gs { + frames, err := c.Stacktrace(g.ID, 10, true) + assertNoError(err, t, fmt.Sprintf("Stacktrace(%d)", g.ID)) + for i, frame := range frames { + if frame.Function == nil { + continue + } + if frame.Function.Name != "main.agoroutine" { + continue + } + t.Logf("frame %d: %v", i, frame) + for _, arg := range frame.Arguments { + if arg.Name != "i" { + continue + } + t.Logf("frame %d, variable i is %v\n", arg) + argn, err := strconv.Atoi(arg.Value) + if err == nil { + found[argn] = true + } + } + } + } + + for i := range found { + if !found[i] { + t.Fatalf("Goroutine %d not found", i) + } + } + + state = <-c.Continue() + if state.Err != nil { + t.Fatalf("Continue(): %v\n", state.Err) + } + + frames, err := c.Stacktrace(-1, 10, true) + assertNoError(err, t, "Stacktrace") + + cur := 3 + for i, frame := range frames { + if i == 0 { + continue + } + t.Logf("frame %d: %v", i, frame) + v := frame.Var("n") + if v == nil { + t.Fatalf("Could not find value of variable n in frame %d", i) + } + vn, err := strconv.Atoi(v.Value) + if err != nil || vn != cur { + t.Fatalf("Expected value %d got %d (error: %v)", cur, vn, err) + } + cur-- + if cur < 0 { + break + } + } + }) +} + +func Test1Issue355(t *testing.T) { + // After the target process has terminated should return an error but not crash + withTestClient1("continuetestprog", t, func(c service.Client) { + bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) + assertNoError(err, t, "CreateBreakpoint()") + ch := c.Continue() + state := <-ch + tid := state.CurrentThread.ID + gid := state.SelectedGoroutine.ID + assertNoError(state.Err, t, "First Continue()") + ch = c.Continue() + state = <-ch + if !state.Exited { + t.Fatalf("Target did not terminate after second continue") + } + + ch = c.Continue() + state = <-ch + assertError(state.Err, t, "Continue()") + + _, err = c.Next() + assertError(err, t, "Next()") + _, err = c.Step() + assertError(err, t, "Step()") + _, err = c.StepInstruction() + assertError(err, t, "StepInstruction()") + _, err = c.SwitchThread(tid) + assertError(err, t, "SwitchThread()") + _, err = c.SwitchGoroutine(gid) + assertError(err, t, "SwitchGoroutine()") + _, err = c.Halt() + assertError(err, t, "Halt()") + _, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: -1}) + assertError(err, t, "CreateBreakpoint()") + _, err = c.ClearBreakpoint(bp.ID) + assertError(err, t, "ClearBreakpoint()") + _, err = c.ListThreads() + assertError(err, t, "ListThreads()") + _, err = c.GetThread(tid) + assertError(err, t, "GetThread()") + assertError(c.SetVariable(api.EvalScope{gid, 0}, "a", "10"), t, "SetVariable()") + _, err = c.ListLocalVariables(api.EvalScope{gid, 0}) + assertError(err, t, "ListLocalVariables()") + _, err = c.ListFunctionArgs(api.EvalScope{gid, 0}) + assertError(err, t, "ListFunctionArgs()") + _, err = c.ListRegisters() + assertError(err, t, "ListRegisters()") + _, err = c.ListGoroutines() + assertError(err, t, "ListGoroutines()") + _, err = c.Stacktrace(gid, 10, false) + assertError(err, t, "Stacktrace()") + _, err = c.FindLocation(api.EvalScope{gid, 0}, "+1") + assertError(err, t, "FindLocation()") + _, err = c.DisassemblePC(api.EvalScope{-1, 0}, 0x40100, api.IntelFlavour) + assertError(err, t, "DisassemblePC()") + }) +} + +func Test1Disasm(t *testing.T) { + // Tests that disassembling by PC, range, and current PC all yeld similar results + // Tests that disassembly by current PC will return a disassembly containing the instruction at PC + // Tests that stepping on a calculated CALL instruction will yield a disassembly that contains the + // effective destination of the CALL instruction + withTestClient1("locationsprog2", t, func(c service.Client) { + ch := c.Continue() + state := <-ch + assertNoError(state.Err, t, "Continue()") + + locs, err := c.FindLocation(api.EvalScope{-1, 0}, "main.main") + assertNoError(err, t, "FindLocation()") + if len(locs) != 1 { + t.Fatalf("wrong number of locations for main.main: %d", len(locs)) + } + d1, err := c.DisassemblePC(api.EvalScope{-1, 0}, locs[0].PC, api.IntelFlavour) + assertNoError(err, t, "DisassemblePC()") + if len(d1) < 2 { + t.Fatalf("wrong size of disassembly: %d", len(d1)) + } + + pcstart := d1[0].Loc.PC + pcend := d1[len(d1)-1].Loc.PC + uint64(len(d1[len(d1)-1].Bytes)) + d2, err := c.DisassembleRange(api.EvalScope{-1, 0}, pcstart, pcend, api.IntelFlavour) + assertNoError(err, t, "DisassembleRange()") + + if len(d1) != len(d2) { + t.Logf("d1: %v", d1) + t.Logf("d2: %v", d2) + t.Fatal("mismatched length between disassemble pc and disassemble range") + } + + d3, err := c.DisassemblePC(api.EvalScope{-1, 0}, state.CurrentThread.PC, api.IntelFlavour) + assertNoError(err, t, "DisassemblePC() - second call") + + if len(d1) != len(d3) { + t.Logf("d1: %v", d1) + t.Logf("d3: %v", d3) + t.Fatal("mismatched length between the two calls of disassemble pc") + } + + // look for static call to afunction() on line 29 + found := false + for i := range d3 { + if d3[i].Loc.Line == 29 && strings.HasPrefix(d3[i].Text, "call") && d3[i].DestLoc != nil && d3[i].DestLoc.Function != nil && d3[i].DestLoc.Function.Name == "main.afunction" { + found = true + break + } + } + if !found { + t.Fatal("Could not find call to main.afunction on line 29") + } + + haspc := false + for i := range d3 { + if d3[i].AtPC { + haspc = true + break + } + } + + if !haspc { + t.Logf("d3: %v", d3) + t.Fatal("PC instruction not found") + } + + startinstr := getCurinstr(d3) + + count := 0 + for { + if count > 20 { + t.Fatal("too many step instructions executed without finding a call instruction") + } + state, err := c.StepInstruction() + assertNoError(err, t, fmt.Sprintf("StepInstruction() %d", count)) + + d3, err = c.DisassemblePC(api.EvalScope{-1, 0}, state.CurrentThread.PC, api.IntelFlavour) + assertNoError(err, t, fmt.Sprintf("StepInstruction() %d", count)) + + curinstr := getCurinstr(d3) + + if curinstr == nil { + t.Fatalf("Could not find current instruction %d", count) + } + + if curinstr.Loc.Line != startinstr.Loc.Line { + t.Fatal("Calling StepInstruction() repeatedly did not find the call instruction") + } + + if strings.HasPrefix(curinstr.Text, "call") { + t.Logf("call: %v", curinstr) + if curinstr.DestLoc == nil || curinstr.DestLoc.Function == nil { + t.Fatalf("Call instruction does not have destination: %v", curinstr) + } + if curinstr.DestLoc.Function.Name != "main.afunction" { + t.Fatalf("Call instruction destination not main.afunction: %v", curinstr) + } + break + } + + count++ + } + }) +} + +func Test1NegativeStackDepthBug(t *testing.T) { + // After the target process has terminated should return an error but not crash + withTestClient1("continuetestprog", t, func(c service.Client) { + _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) + assertNoError(err, t, "CreateBreakpoint()") + ch := c.Continue() + state := <-ch + assertNoError(state.Err, t, "Continue()") + _, err = c.Stacktrace(-1, -2, true) + assertError(err, t, "Stacktrace()") + }) +} + +func Test1ClientServer_CondBreakpoint(t *testing.T) { + withTestClient1("parallel_next", t, func(c service.Client) { + bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1}) + assertNoError(err, t, "CreateBreakpoint()") + bp.Cond = "n == 7" + assertNoError(c.AmendBreakpoint(bp), t, "AmendBreakpoint() 1") + bp, err = c.GetBreakpoint(bp.ID) + assertNoError(err, t, "GetBreakpoint() 1") + bp.Variables = append(bp.Variables, "n") + assertNoError(c.AmendBreakpoint(bp), t, "AmendBreakpoint() 2") + bp, err = c.GetBreakpoint(bp.ID) + assertNoError(err, t, "GetBreakpoint() 2") + if bp.Cond == "" { + t.Fatalf("No condition set on breakpoint %#v", bp) + } + if len(bp.Variables) != 1 { + t.Fatalf("Wrong number of expressions to evaluate on breakpoint %#v", bp) + } + state := <-c.Continue() + assertNoError(state.Err, t, "Continue()") + + nvar, err := c.EvalVariable(api.EvalScope{-1, 0}, "n") + assertNoError(err, t, "EvalVariable()") + + if nvar.SinglelineString() != "7" { + t.Fatalf("Stopped on wrong goroutine %s\n", nvar.Value) + } + }) +} + +func Test1SkipPrologue(t *testing.T) { + withTestClient1("locationsprog2", t, func(c service.Client) { + <-c.Continue() + + afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0] + findLocationHelper(t, c, "*fn1", false, 1, afunction) + findLocationHelper(t, c, "locationsprog2.go:8", false, 1, afunction) + + afunction0 := findLocationHelper(t, c, "main.afunction:0", false, 1, 0)[0] + + if afunction == afunction0 { + t.Fatal("Skip prologue failed") + } + }) +} + +func Test1SkipPrologue2(t *testing.T) { + withTestClient1("callme", t, func(c service.Client) { + callme := findLocationHelper(t, c, "main.callme", false, 1, 0)[0] + callmeZ := findLocationHelper(t, c, "main.callme:0", false, 1, 0)[0] + findLocationHelper(t, c, "callme.go:5", false, 1, callme) + if callme == callmeZ { + t.Fatal("Skip prologue failed") + } + + callme2 := findLocationHelper(t, c, "main.callme2", false, 1, 0)[0] + callme2Z := findLocationHelper(t, c, "main.callme2:0", false, 1, 0)[0] + findLocationHelper(t, c, "callme.go:12", false, 1, callme2) + if callme2 == callme2Z { + t.Fatal("Skip prologue failed") + } + + callme3 := findLocationHelper(t, c, "main.callme3", false, 1, 0)[0] + callme3Z := findLocationHelper(t, c, "main.callme3:0", false, 1, 0)[0] + // callme3 does not have local variables therefore the first line of the function is immediately after the prologue + findLocationHelper(t, c, "callme.go:18", false, 1, callme3Z) + if callme3 == callme3Z { + t.Fatal("Skip prologue failed") + } + }) +} + +func Test1Issue419(t *testing.T) { + // Calling service/rpc.(*Client).Halt could cause a crash because both Halt and Continue simultaneously + // try to read 'runtime.g' and debug/dwarf.Data.Type is not thread safe + withTestClient1("issue419", t, func(c service.Client) { + go func() { + rand.Seed(time.Now().Unix()) + d := time.Duration(rand.Intn(4) + 1) + time.Sleep(d * time.Second) + _, err := c.Halt() + assertNoError(err, t, "RequestManualStop()") + }() + statech := c.Continue() + state := <-statech + assertNoError(state.Err, t, "Continue()") + }) +} + +func Test1TypesCommand(t *testing.T) { + withTestClient1("testvariables2", t, func(c service.Client) { + state := <-c.Continue() + assertNoError(state.Err, t, "Continue()") + types, err := c.ListTypes("") + assertNoError(err, t, "ListTypes()") + + found := false + for i := range types { + if types[i] == "main.astruct" { + found = true + break + } + } + if !found { + t.Fatal("Type astruct not found in ListTypes output") + } + + types, err = c.ListTypes("^main.astruct$") + assertNoError(err, t, "ListTypes(\"main.astruct\")") + if len(types) != 1 { + t.Fatalf("ListTypes(\"^main.astruct$\") did not filter properly, expected 1 got %d: %v", len(types), types) + } + }) +} + +func Test1Issue406(t *testing.T) { + withTestClient1("issue406", t, func(c service.Client) { + locs, err := c.FindLocation(api.EvalScope{-1, 0}, "issue406.go:146") + assertNoError(err, t, "FindLocation()") + _, err = c.CreateBreakpoint(&api.Breakpoint{Addr: locs[0].PC}) + assertNoError(err, t, "CreateBreakpoint()") + ch := c.Continue() + state := <-ch + assertNoError(state.Err, t, "Continue()") + v, err := c.EvalVariable(api.EvalScope{-1, 0}, "cfgtree") + assertNoError(err, t, "EvalVariable()") + vs := v.MultilineString("") + t.Logf("cfgtree formats to: %s\n", vs) + }) +} diff --git a/service/test/integration_test.go b/service/test/integration2_test.go similarity index 86% rename from service/test/integration_test.go rename to service/test/integration2_test.go index fdeb45d7..209d80f8 100644 --- a/service/test/integration_test.go +++ b/service/test/integration2_test.go @@ -4,7 +4,6 @@ import ( "fmt" "math/rand" "net" - "os" "path/filepath" "runtime" "strconv" @@ -17,47 +16,23 @@ import ( "github.com/derekparker/delve/proc" "github.com/derekparker/delve/service" "github.com/derekparker/delve/service/api" - "github.com/derekparker/delve/service/rpc" + "github.com/derekparker/delve/service/rpc2" ) -func init() { - runtime.GOMAXPROCS(2) -} - -func assertNoError(err error, t *testing.T, s string) { - if err != nil { - _, file, line, _ := runtime.Caller(1) - fname := filepath.Base(file) - t.Fatalf("failed assertion at %s:%d: %s - %s\n", fname, line, s, err) - } -} - -func assertError(err error, t *testing.T, s string) { - if err == nil { - _, file, line, _ := runtime.Caller(1) - fname := filepath.Base(file) - t.Fatalf("failed assertion at %s:%d: %s (no error)\n", fname, line, s) - } -} - -func TestMain(m *testing.M) { - os.Exit(protest.RunTestsWithFixtures(m)) -} - -func withTestClient(name string, t *testing.T, fn func(c service.Client)) { +func withTestClient2(name string, t *testing.T, fn func(c service.Client)) { 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{ + server := rpc2.NewServer(&service.Config{ Listener: listener, ProcessArgs: []string{protest.BuildFixture(name).Path}, }, false) if err := server.Run(); err != nil { t.Fatal(err) } - client := rpc.NewClient(listener.Addr().String()) + client := rpc2.NewClient(listener.Addr().String()) defer func() { client.Detach(true) }() @@ -71,7 +46,7 @@ func TestRunWithInvalidPath(t *testing.T) { t.Fatalf("couldn't start listener: %s\n", err) } defer listener.Close() - server := rpc.NewServer(&service.Config{ + server := rpc2.NewServer(&service.Config{ Listener: listener, ProcessArgs: []string{"invalid_path"}, }, false) @@ -81,7 +56,7 @@ func TestRunWithInvalidPath(t *testing.T) { } func TestRestart_afterExit(t *testing.T) { - withTestClient("continuetestprog", t, func(c service.Client) { + withTestClient2("continuetestprog", t, func(c service.Client) { origPid := c.ProcessPid() state := <-c.Continue() if !state.Exited { @@ -101,7 +76,7 @@ func TestRestart_afterExit(t *testing.T) { } func TestRestart_breakpointPreservation(t *testing.T) { - withTestClient("continuetestprog", t, func(c service.Client) { + withTestClient2("continuetestprog", t, func(c service.Client) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Name: "firstbreakpoint", Tracepoint: true}) assertNoError(err, t, "CreateBreakpoint()") stateCh := c.Continue() @@ -130,7 +105,7 @@ func TestRestart_breakpointPreservation(t *testing.T) { } func TestRestart_duringStop(t *testing.T) { - withTestClient("continuetestprog", t, func(c service.Client) { + withTestClient2("continuetestprog", t, func(c service.Client) { origPid := c.ProcessPid() _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1}) if err != nil { @@ -159,7 +134,7 @@ func TestRestart_duringStop(t *testing.T) { func TestRestart_attachPid(t *testing.T) { // Assert it does not work and returns error. // We cannot restart a process we did not spawn. - server := rpc.NewServer(&service.Config{ + server := rpc2.NewServer(&service.Config{ Listener: nil, AttachPid: 999, }, false) @@ -169,7 +144,7 @@ func TestRestart_attachPid(t *testing.T) { } func TestClientServer_exit(t *testing.T) { - withTestClient("continuetestprog", t, func(c service.Client) { + withTestClient2("continuetestprog", t, func(c service.Client) { state, err := c.GetState() if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -192,7 +167,7 @@ func TestClientServer_exit(t *testing.T) { } func TestClientServer_step(t *testing.T) { - withTestClient("testprog", t, func(c service.Client) { + withTestClient2("testprog", t, func(c service.Client) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: 1}) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -214,12 +189,8 @@ func TestClientServer_step(t *testing.T) { }) } -type nextTest struct { - begin, end int -} - -func testnext(testcases []nextTest, initialLocation string, t *testing.T) { - withTestClient("testnextprog", t, func(c service.Client) { +func testnext2(testcases []nextTest, initialLocation string, t *testing.T) { + withTestClient2("testnextprog", t, func(c service.Client) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: initialLocation, Line: -1}) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -310,7 +281,7 @@ func TestNextFunctionReturn(t *testing.T) { } func TestClientServer_breakpointInMainThread(t *testing.T) { - withTestClient("testprog", t, func(c service.Client) { + withTestClient2("testprog", t, func(c service.Client) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: 1}) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -331,7 +302,7 @@ func TestClientServer_breakpointInMainThread(t *testing.T) { } func TestClientServer_breakpointInSeparateGoroutine(t *testing.T) { - withTestClient("testthreads", t, func(c service.Client) { + withTestClient2("testthreads", t, func(c service.Client) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.anotherthread", Line: 1}) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -350,7 +321,7 @@ func TestClientServer_breakpointInSeparateGoroutine(t *testing.T) { } func TestClientServer_breakAtNonexistentPoint(t *testing.T) { - withTestClient("testprog", t, func(c service.Client) { + withTestClient2("testprog", t, func(c service.Client) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "nowhere", Line: 1}) if err == nil { t.Fatal("Should not be able to break at non existent function") @@ -358,20 +329,8 @@ func TestClientServer_breakAtNonexistentPoint(t *testing.T) { }) } -func countBreakpoints(t *testing.T, c service.Client) int { - bps, err := c.ListBreakpoints() - assertNoError(err, t, "ListBreakpoints()") - bpcount := 0 - for _, bp := range bps { - if bp.ID >= 0 { - bpcount++ - } - } - return bpcount -} - func TestClientServer_clearBreakpoint(t *testing.T) { - withTestClient("testprog", t, func(c service.Client) { + withTestClient2("testprog", t, func(c service.Client) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sleepytime", Line: 1}) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -397,7 +356,7 @@ func TestClientServer_clearBreakpoint(t *testing.T) { } func TestClientServer_switchThread(t *testing.T) { - withTestClient("testnextprog", t, func(c service.Client) { + withTestClient2("testnextprog", t, func(c service.Client) { // With invalid thread id _, err := c.SwitchThread(-1) if err == nil { @@ -439,22 +398,8 @@ func TestClientServer_switchThread(t *testing.T) { }) } -func testProgPath(t *testing.T, name string) string { - fp, err := filepath.Abs(fmt.Sprintf("_fixtures/%s.go", name)) - if err != nil { - t.Fatal(err) - } - if _, err := os.Stat(fp); err != nil { - fp, err = filepath.Abs(fmt.Sprintf("../../_fixtures/%s.go", name)) - if err != nil { - t.Fatal(err) - } - } - return fp -} - func TestClientServer_infoLocals(t *testing.T) { - withTestClient("testnextprog", t, func(c service.Client) { + withTestClient2("testnextprog", t, func(c service.Client) { fp := testProgPath(t, "testnextprog") _, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 23}) if err != nil { @@ -475,7 +420,7 @@ func TestClientServer_infoLocals(t *testing.T) { } func TestClientServer_infoArgs(t *testing.T) { - withTestClient("testnextprog", t, func(c service.Client) { + withTestClient2("testnextprog", t, func(c service.Client) { fp := testProgPath(t, "testnextprog") _, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 47}) if err != nil { @@ -503,7 +448,7 @@ func TestClientServer_infoArgs(t *testing.T) { } func TestClientServer_traceContinue(t *testing.T) { - withTestClient("integrationprog", t, func(c service.Client) { + withTestClient2("integrationprog", t, func(c service.Client) { fp := testProgPath(t, "integrationprog") _, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 15, Tracepoint: true, Goroutine: true, Stacktrace: 5, Variables: []string{"i"}}) if err != nil { @@ -560,7 +505,7 @@ func TestClientServer_traceContinue(t *testing.T) { } func TestClientServer_traceContinue2(t *testing.T) { - withTestClient("integrationprog", t, func(c service.Client) { + withTestClient2("integrationprog", t, func(c service.Client) { bp1, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Tracepoint: true}) if err != nil { t.Fatalf("Unexpected error: %v\n", err) @@ -602,37 +547,8 @@ func TestClientServer_traceContinue2(t *testing.T) { }) } -func findLocationHelper(t *testing.T, c service.Client, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 { - locs, err := c.FindLocation(api.EvalScope{-1, 0}, loc) - t.Logf("FindLocation(\"%s\") → %v\n", loc, locs) - - if shouldErr { - if err == nil { - t.Fatalf("Resolving location <%s> didn't return an error: %v", loc, locs) - } - } else { - if err != nil { - t.Fatalf("Error resolving location <%s>: %v", loc, err) - } - } - - if (count >= 0) && (len(locs) != count) { - t.Fatalf("Wrong number of breakpoints returned for location <%s> (got %d, expected %d)", loc, len(locs), count) - } - - if checkAddr != 0 && checkAddr != locs[0].PC { - t.Fatalf("Wrong address returned for location <%s> (got %v, epected %v)", loc, locs[0].PC, checkAddr) - } - - addrs := make([]uint64, len(locs)) - for i := range locs { - addrs[i] = locs[i].PC - } - return addrs -} - func TestClientServer_FindLocations(t *testing.T) { - withTestClient("locationsprog", t, func(c service.Client) { + withTestClient2("locationsprog", t, func(c service.Client) { someFunctionCallAddr := findLocationHelper(t, c, "locationsprog.go:26", false, 1, 0)[0] someFunctionLine1 := findLocationHelper(t, c, "locationsprog.go:27", false, 1, 0)[0] findLocationHelper(t, c, "anotherFunction:1", false, 1, someFunctionLine1) @@ -679,17 +595,17 @@ func TestClientServer_FindLocations(t *testing.T) { findLocationHelper(t, c, "-1", false, 1, findLocationHelper(t, c, "locationsprog.go:32", false, 1, 0)[0]) }) - withTestClient("testnextdefer", t, func(c service.Client) { + withTestClient2("testnextdefer", t, func(c service.Client) { firstMainLine := findLocationHelper(t, c, "testnextdefer.go:5", false, 1, 0)[0] findLocationHelper(t, c, "main.main", false, 1, firstMainLine) }) - withTestClient("stacktraceprog", t, func(c service.Client) { + withTestClient2("stacktraceprog", t, func(c service.Client) { stacktracemeAddr := findLocationHelper(t, c, "stacktraceprog.go:4", false, 1, 0)[0] findLocationHelper(t, c, "main.stacktraceme", false, 1, stacktracemeAddr) }) - withTestClient("locationsUpperCase", t, func(c service.Client) { + withTestClient2("locationsUpperCase", t, func(c service.Client) { // Upper case findLocationHelper(t, c, "locationsUpperCase.go:6", false, 1, 0) @@ -729,7 +645,7 @@ func TestClientServer_FindLocations(t *testing.T) { } func TestClientServer_FindLocationsAddr(t *testing.T) { - withTestClient("locationsprog2", t, func(c service.Client) { + withTestClient2("locationsprog2", t, func(c service.Client) { <-c.Continue() afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0] @@ -741,7 +657,7 @@ func TestClientServer_FindLocationsAddr(t *testing.T) { } func TestClientServer_EvalVariable(t *testing.T) { - withTestClient("testvariables", t, func(c service.Client) { + withTestClient2("testvariables", t, func(c service.Client) { state := <-c.Continue() if state.Err != nil { @@ -760,7 +676,7 @@ func TestClientServer_EvalVariable(t *testing.T) { } func TestClientServer_SetVariable(t *testing.T) { - withTestClient("testvariables", t, func(c service.Client) { + withTestClient2("testvariables", t, func(c service.Client) { state := <-c.Continue() if state.Err != nil { @@ -782,7 +698,7 @@ func TestClientServer_SetVariable(t *testing.T) { } func TestClientServer_FullStacktrace(t *testing.T) { - withTestClient("goroutinestackprog", t, func(c service.Client) { + withTestClient2("goroutinestackprog", t, func(c service.Client) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1}) assertNoError(err, t, "CreateBreakpoint()") state := <-c.Continue() @@ -855,7 +771,7 @@ func TestClientServer_FullStacktrace(t *testing.T) { func TestIssue355(t *testing.T) { // After the target process has terminated should return an error but not crash - withTestClient("continuetestprog", t, func(c service.Client) { + withTestClient2("continuetestprog", t, func(c service.Client) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) assertNoError(err, t, "CreateBreakpoint()") ch := c.Continue() @@ -911,21 +827,12 @@ func TestIssue355(t *testing.T) { }) } -func getCurinstr(d3 api.AsmInstructions) *api.AsmInstruction { - for i := range d3 { - if d3[i].AtPC { - return &d3[i] - } - } - return nil -} - func TestDisasm(t *testing.T) { // Tests that disassembling by PC, range, and current PC all yeld similar results // Tests that disassembly by current PC will return a disassembly containing the instruction at PC // Tests that stepping on a calculated CALL instruction will yield a disassembly that contains the // effective destination of the CALL instruction - withTestClient("locationsprog2", t, func(c service.Client) { + withTestClient2("locationsprog2", t, func(c service.Client) { ch := c.Continue() state := <-ch assertNoError(state.Err, t, "Continue()") @@ -1027,7 +934,7 @@ func TestDisasm(t *testing.T) { func TestNegativeStackDepthBug(t *testing.T) { // After the target process has terminated should return an error but not crash - withTestClient("continuetestprog", t, func(c service.Client) { + withTestClient2("continuetestprog", t, func(c service.Client) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) assertNoError(err, t, "CreateBreakpoint()") ch := c.Continue() @@ -1039,7 +946,7 @@ func TestNegativeStackDepthBug(t *testing.T) { } func TestClientServer_CondBreakpoint(t *testing.T) { - withTestClient("parallel_next", t, func(c service.Client) { + withTestClient2("parallel_next", t, func(c service.Client) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1}) assertNoError(err, t, "CreateBreakpoint()") bp.Cond = "n == 7" @@ -1069,7 +976,7 @@ func TestClientServer_CondBreakpoint(t *testing.T) { } func TestSkipPrologue(t *testing.T) { - withTestClient("locationsprog2", t, func(c service.Client) { + withTestClient2("locationsprog2", t, func(c service.Client) { <-c.Continue() afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0] @@ -1085,7 +992,7 @@ func TestSkipPrologue(t *testing.T) { } func TestSkipPrologue2(t *testing.T) { - withTestClient("callme", t, func(c service.Client) { + withTestClient2("callme", t, func(c service.Client) { callme := findLocationHelper(t, c, "main.callme", false, 1, 0)[0] callmeZ := findLocationHelper(t, c, "main.callme:0", false, 1, 0)[0] findLocationHelper(t, c, "callme.go:5", false, 1, callme) @@ -1113,7 +1020,7 @@ func TestSkipPrologue2(t *testing.T) { func TestIssue419(t *testing.T) { // Calling service/rpc.(*Client).Halt could cause a crash because both Halt and Continue simultaneously // try to read 'runtime.g' and debug/dwarf.Data.Type is not thread safe - withTestClient("issue419", t, func(c service.Client) { + withTestClient2("issue419", t, func(c service.Client) { go func() { rand.Seed(time.Now().Unix()) d := time.Duration(rand.Intn(4) + 1) @@ -1128,7 +1035,7 @@ func TestIssue419(t *testing.T) { } func TestTypesCommand(t *testing.T) { - withTestClient("testvariables2", t, func(c service.Client) { + withTestClient2("testvariables2", t, func(c service.Client) { state := <-c.Continue() assertNoError(state.Err, t, "Continue()") types, err := c.ListTypes("") @@ -1152,3 +1059,19 @@ func TestTypesCommand(t *testing.T) { } }) } + +func TestIssue406(t *testing.T) { + withTestClient2("issue406", t, func(c service.Client) { + locs, err := c.FindLocation(api.EvalScope{-1, 0}, "issue406.go:146") + assertNoError(err, t, "FindLocation()") + _, err = c.CreateBreakpoint(&api.Breakpoint{Addr: locs[0].PC}) + assertNoError(err, t, "CreateBreakpoint()") + ch := c.Continue() + state := <-ch + assertNoError(state.Err, t, "Continue()") + v, err := c.EvalVariable(api.EvalScope{-1, 0}, "cfgtree") + assertNoError(err, t, "EvalVariable()") + vs := v.MultilineString("") + t.Logf("cfgtree formats to: %s\n", vs) + }) +} diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 530a0d6f..695f40ef 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/derekparker/delve/proc" - "github.com/derekparker/delve/service" "github.com/derekparker/delve/service/api" protest "github.com/derekparker/delve/proc/test" @@ -646,19 +645,3 @@ func TestUnsafePointer(t *testing.T) { } }) } - -func TestIssue406(t *testing.T) { - withTestClient("issue406", t, func(c service.Client) { - locs, err := c.FindLocation(api.EvalScope{-1, 0}, "issue406.go:146") - assertNoError(err, t, "FindLocation()") - _, err = c.CreateBreakpoint(&api.Breakpoint{Addr: locs[0].PC}) - assertNoError(err, t, "CreateBreakpoint()") - ch := c.Continue() - state := <-ch - assertNoError(state.Err, t, "Continue()") - v, err := c.EvalVariable(api.EvalScope{-1, 0}, "cfgtree") - assertNoError(err, t, "EvalVariable()") - vs := v.MultilineString("") - t.Logf("cfgtree formats to: %s\n", vs) - }) -} diff --git a/terminal/command_test.go b/terminal/command_test.go index c37b90a1..1ea01fef 100644 --- a/terminal/command_test.go +++ b/terminal/command_test.go @@ -13,7 +13,7 @@ import ( "github.com/derekparker/delve/proc/test" "github.com/derekparker/delve/service" "github.com/derekparker/delve/service/api" - "github.com/derekparker/delve/service/rpc" + "github.com/derekparker/delve/service/rpc2" ) type FakeTerminal struct { @@ -76,14 +76,14 @@ func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) { t.Fatalf("couldn't start listener: %s\n", err) } defer listener.Close() - server := rpc.NewServer(&service.Config{ + server := rpc2.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()) + client := rpc2.NewClient(listener.Addr().String()) defer func() { client.Detach(true) }()