[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

AltStyle によって変換されたページ (->オリジナル) /