service,terminal: APIv2 plus method to select API version (#460)

New API version with better backwards compatibility plus mechanism to
select the API version that a headless instance should use.

Adds service/test/cmd/typecheckrpc.go to type check the RPC interface.
This commit is contained in:
Alessandro Arzilli
2016-04-18 21:20:20 +02:00
committed by Derek Parker
parent f37a26d525
commit af4798e2a9
22 changed files with 2331 additions and 175 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -1,4 +1,4 @@
package rpc
package rpc1
import (
"fmt"

5
service/rpc1/readme.txtr Normal file
View File

@ -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.

View File

@ -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{

290
service/rpc2/client.go Normal file
View File

@ -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)
}

538
service/rpc2/server.go Normal file
View File

@ -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
}

View File

@ -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)
}
}

100
service/test/common_test.go Normal file
View File

@ -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
}

File diff suppressed because it is too large Load Diff

View File

@ -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)
})
}

View File

@ -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)
})
}

View File

@ -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)
}()