[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