[Python-checkins] r47082 - in sandbox/trunk/pdb: README.txt doc mconnection.py mpdb.py mthread.py test/test_mconnection.py test/test_mpdb.py

matt.fleming python-checkins at python.org
Fri Jun 23 12:59:58 CEST 2006


Author: matt.fleming
Date: Fri Jun 23 12:59:57 2006
New Revision: 47082

Added:
   sandbox/trunk/pdb/doc/
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:
Introduce protocol-ignorant exceptions for mconnection which makes it eaiser
for mpdb to look for the same exceptions regardless of which protocol is being
used for remote debugging. Add a doc directory for documentation which I'll 
start to fill in soon. Update the README.txt to reflect dicussion via e-mail
my mentor, regarding what tasks are to be completed. Fix some test cases
and documentat _why_ we need to have each connection class implement a flush
method.


Modified: sandbox/trunk/pdb/README.txt
==============================================================================
--- sandbox/trunk/pdb/README.txt	(original)
+++ sandbox/trunk/pdb/README.txt	Fri Jun 23 12:59:57 2006
@@ -12,5 +12,14 @@
 * Write more unit tests, test the pdbserver and target commands.
 * Write a signal handler that scripts can import from mpdb that, when
  the signal is received, start remote debugging.
-* info target,threads command needs to be written.
+* info [target/threads]
+  set debug threads [on|off]
+  show debug threads, command needs to be written.
+* 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).
+* Extend mconnection FIFO's
+* mconnection should use the exceptions that have been introduced and mpdb
+  should check for these exceptions being raised.
+* Write documentation
 

Modified: sandbox/trunk/pdb/mconnection.py
==============================================================================
--- sandbox/trunk/pdb/mconnection.py	(original)
+++ sandbox/trunk/pdb/mconnection.py	Fri Jun 23 12:59:57 2006
@@ -5,11 +5,11 @@
 NotImplementedMessage = "This method must be overriden in a subclass"
 
 ### Exceptions
-class ConnectionRefused(Exception): pass
+class ConnectionFailed(Exception): pass
 class DroppedConnection(Exception): pass
 class ReadOnClose(Exception): pass
 
-class MServerConnectionInterface(object):
+class MConnectionServerInterface(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,7 +35,7 @@
         """
         raise NotImplementedError, NotImplementedMessage
 
-class MClientConnectionInterface(object):
+class MConnectionClientInterface(object):
     """ This is the interface that a client connection should implement.
     """
     def connect(self, target):
@@ -59,14 +59,14 @@
 
 
 ### This might go in a different file
-class MServerConnectionSerial(MServerConnectionInterface):
+class MConnectionServerSerial(MConnectionServerInterface):
 
     """ This server connection class that allows a connection to a
     target via a serial line. 
     """
 
     def __init__(self):
-        MServerConnectionInterface.__init__(self)
+        MConnectionServerInterface.__init__(self)
         self.input = None
         self.output = None
 
@@ -100,19 +100,19 @@
         self.output.write(msg)
         self.output.flush()
 
-MClientConnectionSerial = MServerConnectionSerial
+MConnectionClientSerial = MConnectionServerSerial
 
 ### This might go in a different file
 import socket
 
-class MServerConnectionTCP(MServerConnectionInterface):
+class MConnectionServerTCP(MConnectionServerInterface):
     """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
-	MServerConnectionInterface.__init__(self)
+	MConnectionServerInterface.__init__(self)
         
     def connect(self, addr):
         """Set to allow a connection from a client. 'addr' specifies
@@ -126,8 +126,9 @@
             self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
             try:
                 self._sock.bind((self.host, self.port))
-            except socket.error:
-                raise ConnectionRefused
+            except socket.error, e:
+                # Use e[1] as a more detailed error message
+                raise ConnectionFailed, e[1]
             self._sock.listen(1)
             self.listening = True
         self.output, addr = self._sock.accept()
@@ -147,19 +148,16 @@
         if line[-1].isalpha(): line += '\n'
         return line
 
-    def flush(self):
-        pass
-    
     def write(self, msg):
         self.output.sendall(msg)
 
-class MClientConnectionTCP(MClientConnectionInterface):
+class MConnectionClientTCP(MConnectionClientInterface):
     """ 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. """
-        MClientConnectionInterface.__init__(self)
+        MConnectionClientInterface.__init__(self)
         self._sock = self.output = self.input = None
 
     def connect(self, addr):
@@ -180,9 +178,6 @@
         line = self._sock.recv(bufsize)
         return line
 
-    def flush(self):
-        pass
-    
     def disconnect(self):
         """ Close the socket to the server. """
         # We shouldn't bail if we haven't been connected yet

Modified: sandbox/trunk/pdb/mpdb.py
==============================================================================
--- sandbox/trunk/pdb/mpdb.py	(original)
+++ sandbox/trunk/pdb/mpdb.py	Fri Jun 23 12:59:57 2006
@@ -19,6 +19,7 @@
 import pydb
 import socket
 import sys
+import time
 import traceback
 
 line_prefix = '\n-> '
@@ -45,6 +46,7 @@
         self.prompt = '(MPdb)'
         self.target = 'local'  # local connections by default
         self.connection = None
+        self.debug_thread = False
 
     def _rebind_input(self, new_input):
         """ This method rebinds the debugger's input to the object specified
@@ -59,6 +61,13 @@
         """
         self.stdout.flush()
         self.stdout = new_output
+        if not hasattr(self.stdout, 'flush'):
+            # Add a dummy flush method because cmdloop() in cmd.py
+            # uses this code:
+            #    self.stdout.write(self.prompt)
+            #    self.stdout.flush()
+            #    line = self.readline()
+            self.stdout.flush = lambda: None
 
     def remote_onecmd(self, line):
         """ All commands in 'line' are sent across this object's connection
@@ -100,6 +109,36 @@
         else:
             pydb.Pdb.do_info(self, arg)
 
+    def do_thread(self, arg):
+        """ Enable thread debugging. """
+        # XXX Rocky, how are we subclassing info/set commands? This will do
+        # for now.
+        if arg == 'info':
+            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())
+            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)
+            self.msg('Thread debugging on.')
+            self.debug_thread = True
+            return
+
     # Debugger commands
     def do_attach(self, addr):
         """ Attach to a process or file outside of Pdb.
@@ -139,21 +178,23 @@
             # TODO: need to save state of current debug session
             if self.connection: self.connection.disconnect()
             try:
-                from mconnection import MClientConnectionTCP
+                from mconnection import (MConnectionClientTCP,
+                                         ConnectionFailed)
                 # Matt - Where are the connection parameters? 
-                self.connection = MClientConnectionTCP()
+                self.connection = MConnectionClientTCP()
                     
             except ImportError:
-                self.msg('Could not import MClientConnectionTCP')
+                self.msg('Could not import MConnectionClientTCP')
                 return
         elif target == 'serial':
             # Matt - Where are the connection parameters? 
             if self.connection: self.connection.disconnect()
             try:
-                from mconnection import MClientConnectionSerial
-                self.connection = MClientConnectionSerial()
+                from mconnection import (MConnectionClientSerial,
+                                         ConnectionFailed)
+                self.connection = MConnectionClientSerial()
             except ImportError:
-                self.msg('Could not import MClientConnectionSerial')
+                self.msg('Could not import MConnectionClientSerial')
                 return
         else:
             if '.' in target:
@@ -161,7 +202,7 @@
                 # We dynamically load the class for the connection
                 base = target[:target.rfind('.')]
                 cls = target[target.rfind('.')+1:]
-                exec 'from ' + base + ' import ' + cls
+                exec 'from ' + base + ' import (' + cls + ', ConnectionFailed)'
             else:
                 try:
                     __import__(target)
@@ -171,17 +212,15 @@
             self.connection = eval(target+'()')
         try: 
             self.connection.connect(addr)
-        except socket.error:
-            # Matt: I couldn't figure out what the right
-            # exception name was to use that getts the error message.
-            self.msg("Failed to connect to %s" % addr)
+        except ConnectionFailed, err:
+            self.msg("Failed to connect to %s: (%s)" % (addr, err))
             return
         # This interpreter no longer interprets commands but sends
         # them straight across this object's connection to a server.
         self.prompt = "" # Get our prompt from the server now
         line = self.connection.readline()
         self.msg_nocr(line)
-        self.msg = self.connection.write
+        self._rebind_output(self.connection)
         self.onecmd = self.remote_onecmd
         self.target = 'remote'
 
@@ -218,23 +257,29 @@
             return
         if target == 'tcp':
             try:
-                from mconnection import MServerConnectionTCP
-                self.connection = MServerConnectionTCP()
+                from mconnection import (MConnectionServerTCP,
+                                         ConnectionFailed)
+                self.connection = MConnectionServerTCP()
             except ImportError:
-                self.msg('Could not load MServerConnectionTCP class')
+                self.msg('Could not load MConnectionServerTCP class')
                 return
         else:
             if '.' in target:
                 base = target[:target.rfind('.')]
                 target = target[target.rfind('.')+1:]
-                exec 'from ' + base + ' import ' + target
+                exec 'from ' + base + ' import (' + target + \
+                     ', ConnectionFailed)'
             else:
                 __import__(target)
             self.connection = eval(target+'()')
-        self.connection.connect(comm)
+        try:
+            self.connection.connect(comm)
+        except ConnectionFailed, err:
+            self.msg("Failed to connect to %s: (%s)" % (addr, err))
+            return
         self.target = 'remote'
-        self._rebind_output(self.connection)
         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.
@@ -252,7 +297,7 @@
             mpdb._runscript(mainpyfile)
             if mpdb._user_requested_quit:
                 break
-            self.msg("The program finished and will be restarted")
+            mpdb.msg("The program finished and will be restarted")
         except SystemExit:
             # In most cases SystemExit does not warrant a post-mortem session.
             mpdb.msg("The program exited via sys.exit(). " + \

Modified: sandbox/trunk/pdb/mthread.py
==============================================================================
--- sandbox/trunk/pdb/mthread.py	(original)
+++ sandbox/trunk/pdb/mthread.py	Fri Jun 23 12:59:57 2006
@@ -1,97 +1,61 @@
 """ This file contains all code for allowing the debugging of threads. """
 
 import sys
-import thread
 import threading
 import time
-from mpdb import MPdb
 
-# Globals
-STOP = False
-t_dict = {}
-t_current = None
-
-class MyThread(threading.Thread):
-    def run(self):
-        while True:
-            if STOP: break
-            x = 2
+# Globals that are private to this file
+_threads = []
+_stop = False
+g_event = None
 
-class MTracer(MPdb):
+class MTracer(object):
     """ A class to trace a thread. """
-    def __init__(self):
-        MPdb.__init__(self)
-        self.quitting = False
-        self.botframe = None
-        self.stopframe = None
-        self.returnframe = None
-        self.thread = threading.currentThread().getName()
+    def __init__(self, event):
+        self.thread = threading.currentThread()
 
     def trace_dispatch(self, frame, event, arg):
-        try:
-            if not t_dict[self.thread]:
-                return
-        except KeyError:
-            return
-        if self.quitting:
-            return # None
         if event == 'line':
-            return self.dispatch_line(frame)
+            print '*** line'
+            return self.trace_dispatch
         if event == 'call':
-            return self.dispatch_call(frame, arg)
+            print '*** call'
+            return self.trace_dispatch
         if event == 'return':
-            return self.dispatch_return(frame, arg)
+            print '*** return'
+            return self.trace_dispatch
         if event == 'exception':
-            return self.dispatch_exception(frame, arg)
+            print '*** exception'
+            return self.trace_dispatch
         if event == 'c_call':
+            print '*** c_call'
             return self.trace_dispatch
         if event == 'c_exception':
+            print '*** c_exception'
             return self.trace_dispatch
         if event == 'c_return':
+            print '*** c_return'
             return self.trace_dispatch
         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())
     
-    # Override Pdb methods
+    sys.settrace(m.trace_dispatch)
 
-    def interaction(self, frame, traceback):
-        print "Thread[%s]: %s" % (self.thread, frame.f_code)
+def set_event(e):
+    global g_event
+    g_event = e
     
-def trace_dispatch(self, frame, somethinelse):
-    """ Create a new MTracer object for a thread and set that threads
-    tracing function to the MTracer objects trace method. Put this thread
-    into the global dict of threads that we can trace.
-    """
-    global t_current
-    m = MTracer()
 
-    # This is supposed to simulate the idea of the first thread becomes
-    # the current thread we're debugging. In mpdb the user will have to
-    # manually change the currently debugged thread with a command.
-    # This thread is not necessarily the first 'created' in a script, but
-    # the first to be 'run'.
-    if t_current is None:
-        global t_dict
-        t_current = m.thread
-        t_dict[m.thread] = True
-    sys.settrace(m.trace_dispatch)
 
-def threads():
-    # Set the globla tracing function
-    threading.settrace(trace_dispatch)
-
-    global STOP, t_dict
-
-    t_list = []
-    # Create 3 threads
-    for i in range(3):
-        t = MyThread()
-        t.start()
-        t_list.append(t)
+    
 
-    STOP = True
+        
 
-if __name__ == '__main__':
-    threads()
-    

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 12:59:57 2006
@@ -18,9 +18,9 @@
 TESTFN = 'device'
 
 sys.path.append("..")
-from mconnection import (MServerConnectionTCP, MClientConnectionTCP,
-                         MServerConnectionSerial, MClientConnectionSerial,
-                         ConnectionRefused)
+from mconnection import (MConnectionServerTCP, MConnectionClientTCP,
+                         MConnectionServerSerial, MConnectionClientSerial,
+                         ConnectionFailed)
 
 # Try to connect the client to addr either until we've tried MAXTRIES
 # times or until it succeeds.
@@ -35,8 +35,8 @@
 
 class TestTCPConnections(unittest.TestCase):
     def setUp(self):
-        self.server = MServerConnectionTCP()
-        self.client = MClientConnectionTCP()
+        self.server = MConnectionServerTCP()
+        self.client = MConnectionClientTCP()
 
     def testClientConnectToServer(self):
         """(tcp) Connect client to server. """
@@ -57,7 +57,7 @@
 
     def testDisconnectDisconnected(self):
         """(tcp) Disconnect a disconnected session. """
-        s = MServerConnectionTCP()
+        s = MConnectionServerTCP()
         s.disconnect()
         s.disconnect()
 
@@ -70,14 +70,14 @@
         line = self.server.readline()
         self.assertEquals('good\n', line, 'Could not read first line.')
 
-    def testConnectionRefused(self):
-        """(tcp) Test refused connection. """
+    def testErrorAddressAlreadyInUse(self):
+        """(tcp) Test address already in use error. """
         thread.start_new_thread(repeatedConnect, (self.client, __addr__))
         self.server.connect(__addr__)
 
         # Set up second server on same port
-        s = MServerConnectionTCP()
-        self.assertRaises(ConnectionRefused, s.connect, __addr__)
+        s = MConnectionServerTCP()
+        self.assertRaises(ConnectionFailed, s.connect, __addr__)
 
     def tearDown(self):
         self.server.disconnect()
@@ -88,8 +88,8 @@
     on *nix systems is just files anyway.
     """
     def setUp(self):
-        self.server = MServerConnectionSerial()
-        self.client = MClientConnectionSerial()
+        self.server = MConnectionServerSerial()
+        self.client = MConnectionClientSerial()
         fd = open(TESTFN, "wr+")
         fd.close()
         self.server.connect(TESTFN)

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 12:59:57 2006
@@ -18,7 +18,6 @@
 
 sys.path.append("..")
 from mpdb import MPdb
-from mconnection import MServerConnectionTCP, MClientConnectionTCP
 
 def doTargetConnect(cmds=None):
     global g_client


More information about the Python-checkins mailing list