[Python-checkins] r47084 - in sandbox/trunk/pdb: README.txt mconnection.py mpdb.py mthread.py test/test_mconnection.py test/test_mpdb.py
matt.fleming
python-checkins at python.org
Fri Jun 23 21:06:28 CEST 2006
Author: matt.fleming
Date: Fri Jun 23 21:06:28 2006
New Revision: 47084
Modified:
sandbox/trunk/pdb/README.txt
sandbox/trunk/pdb/mconnection.py
sandbox/trunk/pdb/mpdb.py
sandbox/trunk/pdb/mthread.py
sandbox/trunk/pdb/test/test_mconnection.py
sandbox/trunk/pdb/test/test_mpdb.py
Log:
Added test cases, got the beginnings of thread debugging working.
Modified: sandbox/trunk/pdb/README.txt
==============================================================================
--- sandbox/trunk/pdb/README.txt (original)
+++ sandbox/trunk/pdb/README.txt Fri Jun 23 21:06:28 2006
@@ -6,20 +6,42 @@
-=[Abstract]=-
This project is part of a Google Summer of Code 2006 project. Many people
have stated that they would like to see improvements in pdb. This projects
-aims to correct this wish.
+aims to fulfill this wish.
-=[TODO]=-
-* Write more unit tests, test the pdbserver and target commands.
+* Write more unit tests
* Write a signal handler that scripts can import from mpdb that, when
the signal is received, start remote debugging.
* info [target/threads]
set debug threads [on|off]
show debug threads, command needs to be written.
+* Decide on a way to execute commands for a specific thread.
* Implement thread debugging, commands to switch between threads.
-* Lightweight and heavyweight mechanism for setting up threads.
-* Write a proper entry point to mpdb (a rewrite of the main method in mpdb.py).
+ - Because the 'main' thread may exit unexpectedly we need to keep
+ any other threads that we're debugging alive. This may mean that
+ we have to write some code when the main debugger exits to check
+ for any threads being debugged and not exit until all threads have
+ finished (or at least ask if they're sure they wanna leave).
+* Lightweight and heavyweight mechanism for setting up threads
+ - The reason we want a lightweight mechanism is so that we can
+ place mpdb.set_trace() inside a script so that we can debug
+ any threads created with the threading module. It has to be
+ lighweight because the programmer might not want all the features
+ of an MPdb instance, if for example they only care about debugging
+ this one thread instance.
+ - We need a heavyweight mechanism to allow a programmer to inspect
+ and control all threads.
+* Provide a proper top-level methods including, set_trace(), post_mortem(),
+ run(), remote_sighandler() (for allowing a signal to start
+ remote debugging)
* Extend mconnection FIFO's
* mconnection should use the exceptions that have been introduced and mpdb
should check for these exceptions being raised.
* Write documentation
+ - Debugger commands
+ - Debugger model/architecture:
+ - Debugging outside a process
+ - Debugging remotely
+ - Debugging threads
+
Modified: sandbox/trunk/pdb/mconnection.py
==============================================================================
--- sandbox/trunk/pdb/mconnection.py (original)
+++ sandbox/trunk/pdb/mconnection.py Fri Jun 23 21:06:28 2006
@@ -7,9 +7,10 @@
### Exceptions
class ConnectionFailed(Exception): pass
class DroppedConnection(Exception): pass
-class ReadOnClose(Exception): pass
+class ReadError(Exception): pass
+class WriteError(Exception): pass
-class MConnectionServerInterface(object):
+class MConnectionInterface(object):
""" This is an abstract class that specifies the interface a server
connection class must implement. If a target is given, we'll
set up a connection on that target
@@ -35,40 +36,17 @@
"""
raise NotImplementedError, NotImplementedMessage
-class MConnectionClientInterface(object):
- """ This is the interface that a client connection should implement.
- """
- def connect(self, target):
- """ This method is called to connect to a target. """
- raise NotImplementedError, NotImplementedMessage
-
- def disconnect(self):
- """ This method use to disconnect a target."""
- raise NotImplementedError, NotImplementedMessage
-
- def readline(self, bufsize):
- """ This method reads a line of data of maxium length 'bufisze'
- from a connected target.
- """
- raise NotImplementedError, NotImplementedMessage
-
- def write(self, msg):
- """ This method is called write 'msg' to the connected target.
- """
- raise NotImplementedError, NotImplementedMessage
-
-
### This might go in a different file
# Not, serial protocol does not require the distinction between server and
# client.
-class MConnectionSerial(MConnectionServerInterface):
+class MConnectionSerial(MConnectionInterface):
""" This connection class that allows a connection to a
target via a serial line.
"""
def __init__(self):
- MConnectionServerInterface.__init__(self)
+ MConnectionInterface.__init__(self)
self.input = None
self.output = None
@@ -94,27 +72,33 @@
def readline(self, bufsize=2048):
- line = self.input.readline(bufsize)
+ try:
+ line = self.input.readline(bufsize)
+ except IOError, e:
+ raise ReadError, e[1]
return line
def write(self, msg):
if msg[-1] is not '\n':
msg += '\n'
- self.output.write(msg)
- self.output.flush()
+ try:
+ self.output.write(msg)
+ self.output.flush()
+ except IOError, e:
+ raise WriteError, e[1]
### This might go in a different file
import socket
-class MConnectionServerTCP(MConnectionServerInterface):
+class MConnectionServerTCP(MConnectionInterface):
"""This is an implementation of a server class that uses the TCP
protocol as its means of communication.
"""
def __init__(self):
self.listening = False
self._sock = self.output = self.input = None
- MConnectionServerInterface.__init__(self)
+ MConnectionInterface.__init__(self)
def connect(self, addr):
"""Set to allow a connection from a client. 'addr' specifies
@@ -137,8 +121,7 @@
self.input = self.output
def disconnect(self):
- # These two should either _both_ be None, or neither should be None
- if self.output is None and self._sock is None:
+ if self.output is None or self._sock is None:
return
self.output.close()
self._sock.close()
@@ -146,20 +129,26 @@
self.listening = False
def readline(self, bufsize=2048):
- line = self.input.recv(bufsize)
+ try:
+ line = self.input.recv(bufsize)
+ except socket.error, e:
+ raise ReadError, e[1]
if line[-1].isalpha(): line += '\n'
return line
def write(self, msg):
- self.output.sendall(msg)
+ try:
+ self.output.sendall(msg)
+ except socket.error, e:
+ raise WriteError, e[1]
-class MConnectionClientTCP(MConnectionClientInterface):
+class MConnectionClientTCP(MConnectionInterface):
""" A class that allows a connection to be made from a debugger
to a server via TCP.
"""
def __init__(self):
""" Specify the address to connection to. """
- MConnectionClientInterface.__init__(self)
+ MConnectionInterface.__init__(self)
self._sock = self.output = self.input = None
def connect(self, addr):
@@ -171,13 +160,22 @@
self.host = h
self.port = int(p)
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self._sock.connect((self.host, self.port))
+ try:
+ self._sock.connect((self.host, self.port))
+ except socket.error, e:
+ raise ConnectionFailed, e[1]
def write(self, msg):
- self._sock.sendall(msg)
+ try:
+ self._sock.sendall(msg)
+ except socket.error, e:
+ raise WriteError, e[1]
def readline(self, bufsize=2048):
- line = self._sock.recv(bufsize)
+ try:
+ line = self._sock.recv(bufsize)
+ except socket.error, e:
+ raise ReadError, e[1]
return line
def disconnect(self):
Modified: sandbox/trunk/pdb/mpdb.py
==============================================================================
--- sandbox/trunk/pdb/mpdb.py (original)
+++ sandbox/trunk/pdb/mpdb.py Fri Jun 23 21:06:28 2006
@@ -20,8 +20,11 @@
import socket
import sys
import time
+import threading
import traceback
+__all__ = ["MPdb", "pdbserver", "target"]
+
line_prefix = '\n-> '
class MPdb(pydb.Pdb):
@@ -47,6 +50,9 @@
self.target = 'local' # local connections by default
self.connection = None
self.debug_thread = False
+ self.waiter = threading.Event()
+ self.tracers = []
+ self.threads = []
def _rebind_input(self, new_input):
""" This method rebinds the debugger's input to the object specified
@@ -117,28 +123,39 @@
if not self.debug_thread:
self.msg('Thread debugging is not on.')
return
- try:
- import threading
- except ImportError:
- self.msg('Thread debugging not available.')
- self.msg(threading.enumerate())
+ # XXX We need to remove old threads once the script has finished
+ # and currently, we don't.
+ self.msg(self.threads)
return
if arg == 'debug':
- try:
- import threading
- import mthread
- except ImportError:
- self.msg('Thread debugging not available.')
# We do not continue with the main thread for as long as there
# is another thread running. (Will change later so you can choose
# between threads).
- ev = threading.Event()
- mthread.set_event(ev)
- threading.settrace(mthread.trace_dispatch)
+ threading.settrace(self.thread_trace_dispatch)
self.msg('Thread debugging on.')
self.debug_thread = True
return
+ def thread_trace_dispatch(self, frame, event, arg):
+ """ Create an MTracer object so trace the thread. """
+ # This method is called when a thread is being created with the
+ # threading module. The _MainThread is no longer of primary concern,
+ # this new thread is.
+ try:
+ from mthread import MTracer
+ except ImportError:
+ self.msg('Could not import mthread.MTracer')
+ sys.settrace(None)
+ return # Thread not being traced
+
+ self.threads.append(threading.currentThread())
+ self.msg('New thread: %s' % self.threads[-1].getName())
+ m = MTracer(self.stdout)
+ self.tracers.append(m)
+ sys.settrace(m.trace_dispatch)
+
+
+
# Debugger commands
def do_attach(self, addr):
""" Attach to a process or file outside of Pdb.
@@ -289,16 +306,39 @@
self._rebind_input(self.connection)
self._rebind_output(self.connection)
-# This is a mess. It's only here so that I can test other features of the
-# debugger whilst I'm writing them. It will be removed at some point.
-def main(options):
- opts = options[0]
- args = options[1]
- if args:
- mainpyfile = args[0]
- if not os.path.exists(mainpyfile):
- print 'Error:', mainpyfile, 'does not exist'
+def pdbserver(protocol, address, filename):
+ """ This method sets up a pdbserver debugger that allows debuggers
+ to connect to 'address' using 'protocol'. The argument 'filename'
+ is the name of the file that is being debugged.
+ """
+ pass
+
+
+def target(protocol, address):
+ """ Connect to a pdbserver at 'address' using 'protocol'. """
+ pass
+
+
+def main():
+ """ Main entry point to this module. """
+ opts, args = parse_opts()
+ if not opts.scriptname:
+ if not args[0]:
+ print 'Error: mpdb.py must be called with a script name!'
+ sys.exit(1)
+ else:
+ mainpyfile = args[0]
+ if not os.path.exists(mainpyfile):
+ print 'Error:', mainpyfile, 'does not exist'
+ sys.exit(1)
+ if opts.remote:
+ if not opts.protocol:
+ print 'Protocol must be specified for remote debugging'
sys.exit(1)
+ if not opts.debugger:
+ pdbserver(opts.protocol, opts.address, mainpyfile)
+ else:
+ target(opts.protocol, opts.address)
mpdb = MPdb()
while 1:
try:
@@ -328,14 +368,18 @@
parser = OptionParser()
parser.add_option("-s", "--script", dest="scriptname",
help="The script to debug")
- parser.add_option("-l", "--local-debugee", dest="local_debugee",
+ parser.add_option("-l", "--local-debugee", dest="local",
action="store_true",
help="This script is to be debugged locally, from " + \
"another process")
- parser.add_option("-r", "--remote-debugee", dest="remote_debugee",
+ parser.add_option("-p", "--protocol", dest="protocol",
+ help="The protocol to use for remote communication")
+ parser.add_option("-r", "--remote-debugee", dest="remote",
action="store_true",
help="This script is to be debugged by a remote " + \
"debugger")
+ parser.add_option("-a", "--address", dest="address",
+ help="The protocol-specific address of this debugger")
parser.add_option("-d", "--debugger", dest="debugger",
action="store_true",
help="Invoke the debugger.")
@@ -344,6 +388,6 @@
return (options,args)
if __name__ == '__main__':
- main(parse_opts())
+ main()
Modified: sandbox/trunk/pdb/mthread.py
==============================================================================
--- sandbox/trunk/pdb/mthread.py (original)
+++ sandbox/trunk/pdb/mthread.py Fri Jun 23 21:06:28 2006
@@ -2,30 +2,29 @@
import sys
import threading
-import time
-
-# Globals that are private to this file
-_threads = []
-_stop = False
-g_event = None
class MTracer(object):
""" A class to trace a thread. """
- def __init__(self, event):
+ def __init__(self, stdout=None):
self.thread = threading.currentThread()
-
+ # No other thread can be debugged whilst this is set
+ # (including the MainThread)
+ if stdout is None:
+ stdout = sys.stdout
+ self.out = stdout
+
def trace_dispatch(self, frame, event, arg):
if event == 'line':
- print '*** line'
+ print >> self.out, self.thread.getName(),'*** line'
return self.trace_dispatch
if event == 'call':
- print '*** call'
+ print >> self.out, self.thread.getName(), '*** call'
return self.trace_dispatch
if event == 'return':
- print '*** return'
+ print >> self.out, self.thread.getName(), '*** return'
return self.trace_dispatch
if event == 'exception':
- print '*** exception'
+ print >> self.out, '*** exception'
return self.trace_dispatch
if event == 'c_call':
print '*** c_call'
@@ -39,20 +38,8 @@
print 'bdb.Bdb.dispatch: unknown debugging event:', repr(event)
return self.trace_dispatch
-def trace_dispatch(frame, event, arg):
- """ Create a new MTracer object for a thread and set that thread's
- tracing function to the MTracer objects trace method.
- """
- m = MTracer(g_event)
- global _threads
- _threads.append(threading.currentThread())
-
- sys.settrace(m.trace_dispatch)
-def set_event(e):
- global g_event
- g_event = e
-
+
Modified: sandbox/trunk/pdb/test/test_mconnection.py
==============================================================================
--- sandbox/trunk/pdb/test/test_mconnection.py (original)
+++ sandbox/trunk/pdb/test/test_mconnection.py Fri Jun 23 21:06:28 2006
@@ -13,7 +13,7 @@
from socket import gaierror
# Global vars
-__addr__ = 'localhost:8000'
+__addr__ = 'localhost:8002'
MAXTRIES = 100
TESTFN = 'device'
@@ -78,6 +78,15 @@
s = MConnectionServerTCP()
self.assertRaises(ConnectionFailed, s.connect, __addr__)
+ def testInvalidServerAddress(self):
+ """(tcp) Connect to an invalid hostname. """
+ addr = 'fff.209320909xcmnm2iu3-=0-0-z.,x.,091209:2990'
+ self.assertRaises(ConnectionFailed, self.server.connect, addr)
+
+ def testConnectionRefused(self):
+ """(tcp) Test connection refused error. """
+ self.assertRaises(ConnectionFailed, self.client.connect, __addr__)
+
def tearDown(self):
self.server.disconnect()
self.client.disconnect()
Modified: sandbox/trunk/pdb/test/test_mpdb.py
==============================================================================
--- sandbox/trunk/pdb/test/test_mpdb.py (original)
+++ sandbox/trunk/pdb/test/test_mpdb.py Fri Jun 23 21:06:28 2006
@@ -8,7 +8,7 @@
from test import test_support
# Global vars
-__addr__ = 'localhost:8000'
+__addr__ = 'localhost:8002'
script = ""
g_server = None
g_client = None
More information about the Python-checkins
mailing list