Skip to content

Commit

Permalink
Changed imports to make the debugger a package outside the debugger_p…
Browse files Browse the repository at this point in the history
…lugin. Rework on the CodeExecutor to improve testeability. Added RPCDebugAdapterClient for clients to connect to the RPCDebugAdapter.
  • Loading branch information
jsargiot committed Feb 17, 2013
1 parent 290ae5c commit 905058d
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 18 deletions.
8 changes: 4 additions & 4 deletions debugger_plugin/core/ndb.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import time
import weakref

import debugger_plugin.core.process
import process

# Debugger internal data
_IGNORE_FILES = ['threading.py', 'process.py', 'ndb.py', 'serialize.py'
Expand Down Expand Up @@ -328,7 +328,7 @@ def run(self):
threading.settrace(self.trace_dispatch)
sys.settrace(self.trace_dispatch)
# Execute file
debugger_plugin.core.process.CodeExecutor(self.s_file).run()
process.CodeExecutor(self.s_file).run()
# UnSet tracing...
threading.settrace(None)
sys.settrace(None)
Expand Down Expand Up @@ -424,8 +424,8 @@ def is_breakpoint(self, filename, line):
# Create debugger object
dbg = Debugger(sys.argv[0])
# Start communication interface (RPC) adapter
import debugger_plugin.core.rpc_adapter
rpcadapter = debugger_plugin.core.rpc_adapter.RPCDebuggerAdapter(dbg)
import rpc_adapter
rpcadapter = rpc_adapter.RPCDebuggerAdapter(dbg)
rpcadapter.start()
# Start debugger
dbg.run()
Expand Down
27 changes: 16 additions & 11 deletions debugger_plugin/core/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,36 @@ class CodeExecutor:
file directly in the current execution.
"""

def __init__(self, filename):
def __init__(self, filename = None, string = None):
"""Create a new CodeExecutor for the specified filename."""
self.filename = filename
self._name = "<string>"
self._code = 'pass'
if filename:
# Read source from file
with open(filename, 'r') as fd:
self._code = fd.read() + "\n"
self._name = filename
elif string:
self._code = string

def run(self, glob = None, loc = None):
"""Run the code using globals and locals."""
"""
Run the code using globals and locals. If no load_file or load_string
calls were made, a "pass" is executed.
"""
# Define basic globals if they were not specified
if glob is None:
glob = {
'__name__': '__main__',
'__doc__': None,
'__file__': self.filename,
'__file__': self._name,
'__builtins__': __builtin__,
}
# If not locals were specified, use globals
if loc is None:
loc = glob
# Read source from file
_fd = open(self.filename, 'r')
try:
s_code = _fd.read() + "\n"
finally:
_fd.close()
# Compile and execute code
c_code = compile(source=s_code, filename=self.filename, mode='exec')
c_code = compile(source=self._code, filename=self._name, mode='exec')
exec c_code in glob, loc


Expand Down
156 changes: 153 additions & 3 deletions debugger_plugin/core/rpc_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
import logging
from SimpleXMLRPCServer import SimpleXMLRPCServer
import threading
import xmlrpclib

import debugger_plugin.core.serialize
import serialize


class RPCDebuggerAdapter(threading.Thread, SimpleXMLRPCServer):
Expand Down Expand Up @@ -125,7 +126,7 @@ def export_evaluate(self, t_id, e_str):
"""
t_obj = self._debugger.get_thread(t_id)
result = t_obj.evaluate(e_str)
return debugger_plugin.core.serialize.serialize(e_str, e_str, result)
return serialize.serialize(e_str, e_str, result)

def export_execute(self, t_id, e_str):
"""
Expand All @@ -134,7 +135,7 @@ def export_execute(self, t_id, e_str):
"""
t_obj = self._debugger.get_thread(t_id)
result = t_obj.execute(e_str)
return debugger_plugin.core.serialize.serialize(e_str, e_str, result)
return serialize.serialize(e_str, e_str, result)

def export_list_threads(self):
"""List the running threads."""
Expand All @@ -149,3 +150,152 @@ def export_list_threads(self):
def export_get_messages(self):
"""Retrieve the list of unread messages of the debugger."""
return self._debugger.get_messages()



class RPCDebuggerAdapterClient:
"""
Threads safe class to control a Debugger using the RPCDebuggerAdapter.
A RPCDebuggerAdapterClient object is used to control a RPCDebuggerAdapter
thru RPC calls over the network.
By default, the client will try to connect to localhost.
+-------------+ +------------+ +------------+
| RPCClient |+------------->| RPCAdapter |--------->| Debugger |
+-------------+ (RPC) +------------+ +------------+
"""

lock = threading.Lock()

def __init__(self, host="localhost", port=8765):
"""Creates a new DebuggerMaster to handle a DebuggerSlave."""
self.host = host
self.port = port
self.remote = None

def __safe_call(self, func, *args):
"""
Executes an RPC call to a non-threaded RPC server securely. This
method uses a thread lock to ensure one call at a time.
"""
if self.remote is None:
return

self.lock.acquire()
try:
return func(*args)
except socket.error:
raise DebuggerConnectionError("No connection could be made.")
finally:
self.lock.release()

def connect(self, retries=1):
"""
Connects to the remote end to start the debugging session. Returns True
if connection is successful.
"""
conn_str = "http://{0}:{1}".format(self.host, self.port)
self.remote = xmlrpclib.Server(conn_str)
while retries > 0:
if self.is_alive():
return True
retries = retries - 1
return False

def disconnect(self):
"""
Disconnect from the remote end. Always return True
"""
self.remote = None
return True

def is_alive(self):
"""
Check connectivity to the remote debugger. Try to make a RPC call,
return False if connection was not successful.
"""
try:
self.__safe_call(self.remote.ping)
return True
except DebuggerConnectionError:
pass
except Exception:
pass
return False

def start(self):
"""Start remote debugger execution of code."""
return self.__safe_call(self.remote.start)

def stop(self):
"""Stop debugger session and exit current execution."""
return self.__safe_call(self.remote.stop)

def resume(self, t_id):
"""Resume the execution of the specified debug thread."""
return self.__safe_call(self.remote.resume, t_id)

def resume_all(self):
"""Resume the execution of all debug threads."""
return self.__safe_call(self.remote.resume_all)

def step_over(self, t_id):
"""
Stop execution of the specified debug thread on the next line of the
same file or the parent context.
"""
return self.__safe_call(self.remote.step_over, t_id)

def step_into(self, t_id):
"""
Stop execution of the specifed debug thread in the next instruction.
"""
return self.__safe_call(self.remote.step_into, t_id)

def step_out(self, t_id):
"""
Resume execution of the specified debug thread until a return statement
(implicit or explicit) is found.
"""
return self.__safe_call(self.remote.step_out, t_id)

def get_stack(self, t_id):
"""Return the list of files in the stack for the specifed thread."""
return self.__safe_call(self.remote.get_stack, t_id)

def set_breakpoint(self, filename, line):
"""Set a breakpoint in the specifed file and line."""
return self.__safe_call(self.remote.set_breakpoint, filename, line)

def evaluate(self, t_id, e_str):
"""
Evaluate the expression within the context of the specified debug
thread. Since eval only evaluates expressions, a call to this method
with an assignment will fail.
For a deep understanding of the inner working of this method, see:
http://docs.python.org/2/library/functions.html#eval.
"""
return self.__safe_call(self.remote.evaluate, t_id, e_str)

def execute(self, t_id, e_str):
"""
Execute an expression within the context of the specified debug thread.
For a deep understanding of the inner working of this method, see:
http://docs.python.org/2/reference/simple_stmts.html#exec.
"""
return self.__safe_call(self.remote.execute, t_id, e_str)

def list_threads(self):
"""Return the list of active threads on the remote debugger."""
return self.__safe_call(self.remote.list_threads)

def get_messages(self):
"""Return the list of available messages on the remote debugger."""
return self.__safe_call(self.remote.get_messages)


0 comments on commit 905058d

Please sign in to comment.