[Python-checkins] r46895 - in sandbox/trunk/pdb: mconnection.py mpdb.py test/tcptest.py

matt.fleming python-checkins at python.org
Mon Jun 12 20:38:37 CEST 2006


Author: matt.fleming
Date: Mon Jun 12 20:38:36 2006
New Revision: 46895

Modified:
   sandbox/trunk/pdb/mconnection.py
   sandbox/trunk/pdb/mpdb.py
   sandbox/trunk/pdb/test/tcptest.py
Log:
Patched with some changes from Rocky (my mentor), the pdbserver command (think gdbserver) now works better and allows a mpdb 'client' to connect and play with the session. Also changed the interface for the connection classes to work with lines instead of bytes.


Modified: sandbox/trunk/pdb/mconnection.py
==============================================================================
--- sandbox/trunk/pdb/mconnection.py	(original)
+++ sandbox/trunk/pdb/mconnection.py	Mon Jun 12 20:38:36 2006
@@ -7,31 +7,21 @@
 
 class MServerConnectionInterface(object):
     """ This is an abstract class that specifies the interface a server
-    connection class must implement.
+    connection class must implement. If a target is given, we'll
+    set up a connection on that target
     """
-    def accept(self, console, addr):
-	""" This method is called when a connection from a debugger
-	is accepted by this server.
-	"""
-	raise NotImplementedError, NotImplementedMessage
+    def connect(self, target):
+        """Use this to set the target. It can also be specified
+        on the initialization."""
+        raise NotImplementedError, NotImplementedMessage
 
     def disconnect(self):
-	""" This method is called to disconnect	any debuggers that
-        are connected and to stop accepting any more connections from
-        debuggers.
-	"""
+	""" This method is called to disconnect	connections."""
 	raise NotImplementedError, NotImplementedMessage
 
-    def listen(self):
-	""" This method is called when a server is initially
-	configured to listen for connections from debuggers. 
-	"""
-	raise NotImplementedError, NotImplementedMessage
-
-    def setup(self):
-        """ This is the only method that is called by the debugger.
-        It must handle setting up a connection to listen on and accept
-        a debugger connection.
+    def readline(self):
+        """ This method reads a line of data of maximum length 'bufsize'
+        from the connected debugger.
         """
         raise NotImplementedError, NotImplementedMessage
 
@@ -41,158 +31,135 @@
         """
         raise NotImplementedError, NotImplementedMessage
 
-    def read(self, bufsize=1024):
-        """ This method is reads a maximum of 'bufsize' bytes from a
-        connected debugger.
-        """
-        raise NotImplementedError, NotImplementedMessage
-
 class MClientConnectionInterface(object):
     """ This is the interface that a client connection should implement.
     """
-    def setup(self):
-        """ This method is called by a debugger to connect to a server. """
+    def connect(self, target):
+        """ This method is called to connect to a target. """
         raise NotImplementedError, NotImplementedMessage
 
-
-    def write(self, msg):
-        """ This method is called by a debugger to write 'msg' to the
-        server it is connected to.
-        """
+    def disconnect(self):
+        """ This method use to disconnect a target."""
         raise NotImplementedError, NotImplementedMessage
-
-
-    def read(self, bufsize=1024):
-        """ This method reads a maximum of 'bufsize' bytes from the connection
-        to the server.
+    
+    def readline(self, bufsize):
+        """ This method reads a line of data of maxium length 'bufisze'
+        from a connected target.
         """
         raise NotImplementedError, NotImplementedMessage
     
-    def disconnect(self):
-        """ This method is called by a debugger to disconnect from a
-        server.
+    def write(self, msg):
+        """ This method is called write 'msg' to the connected target.
         """
         raise NotImplementedError, NotImplementedMessage
-    
+
+
+### This might go in a different file
 class MServerConnectionSerial(MServerConnectionInterface):
-    """ This is a server connection class that allows a debugger
-    to connect via a serial line. A serial device must be specified
-    (e.g. /dev/ttyS0, COM1, etc.).
+
+    """ This server connection class that allows a connection to a
+    target via a serial line. 
     """
-    def __init__(self, device):
-        self._dev = device
+
+    def __init__(self):
         MServerConnectionInterface.__init__(self)
+        self.input = None
+        self.output = None
 
-    def setup(self):
+    def connect(self, device):
+        """ Create our fileobject by opening the serial device for
+        this connection. A serial device must be specified,
+        (e.g. /dev/ttyS0, /dev/ttya, COM1, etc.).
+        """
+        self._dev = device
         self.output = open(self._dev, 'w')
         self.input = open(self._dev, 'r')
 
-    def listen(self):
-        pass
+    def disconnect(self):
+        """ Close the serial device. """
+        self.output.close()
+        self.input.close()
 
-    def accept(self, debugger, addr):
-        pass
+    def readline(self, bufsize=2048):
+        line = self.input.recv(bufsize)
+        return line
 
     def write(self, msg):
         self.output.write(msg)
 
-    def read(self, bufsize=1024):
-        return self.input.read()
-
-    def disconnect(self):
-        self.output.close()
-        self.input.close()
+MClientConnectionSerial = MServerConnectionSerial
 
+### This might go in a different file
 class MServerConnectionTCP(MServerConnectionInterface):
-    """ This is an implementation of a server class that uses the TCP
-    protocol as its means of communication. Debuggers wishing to connect
-    to this target must use this syntax for the server command,
-
-    `target tcp hostname:port
-
+    """This is an implementation of a server class that uses the TCP
+    protocol as its means of communication.
     """
-    def __init__(self, addr):
-        """ 'addr' specifies the hostname and port combination of
-        the server.
-        """
+    def __init__(self):
+        self.listening = False
+        self.output = self.input = None
 	MServerConnectionInterface.__init__(self)
+        
+    def connect(self, addr):
+        """Set to allow a connection from a client. 'addr' specifies
+        the hostname and port combination of the server.
+        """
         h,p = addr.split(':')
         self.host = h
         self.port = int(p)
-        
-    def setup(self):
-        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        self._sock.bind((self.host, self.port))
-        self.listen()
-        
-    def listen(self):
-        """ This method is not usually called except by this object's
-        setup() method.
-        """
-        self._sock.listen(1)
-        debugger, addr = self._sock.accept()
-        self.accept(debugger, addr)
-
-    def accept(self, debugger, addr):
-        self.output = debugger
-        self.input = debugger
+        if not self.listening:
+            self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+            self._sock.bind((self.host, self.port))
+            self._sock.listen(1)
+            self.listening = True
+        self.output, addr = self._sock.accept()
+        self.input = self.output
 
-    def write(self, msg):
-        self.output.sendall(msg)
-
-    def read(self, bufsize=1024):
-        return self.input.recv(bufsize)
-    
     def disconnect(self):
         self.output.close()
         self._sock.close()
+        self.listening = False
 
-class MClientConnectionSerial(MClientConnectionInterface):
-    """ A class that allows a connection to be made from a debugger
-    to a server via a serial line. Specify the serial device it is
-    connected to (e.g. /dev/ttya).
-    """
-    def __init__(self, device):
-        """ Specify the serial device. """
-        MClientConnectionInterface.__init__(self)
-        self._dev = device
-        self.input = None
-        self.output = None
-
-    def setup(self):
-        """ Create our fileobject by opening the serial device for
-        this connection.
-        """
-        self.output = open(self._dev, "w")
+    def readline(self, bufsize=2048):
+        line = self.input.recv(bufsize)
+        if line[-1].isalpha(): line += '\n'
+        return line
 
-    def disconnect(self):
-        """ Close the serial device. """
-        self.output.close()
+    def flush(self):
+        pass
+    
+    def write(self, msg):
+        self.output.sendall(msg)
 
 class MClientConnectionTCP(MClientConnectionInterface):
     """ A class that allows a connection to be made from a debugger
-    to a server via TCP. Specify the address of the server
-    (e.g. host:2020).
+    to a server via TCP.
     """
-    def __init__(self, addr):
+    def __init__(self):
         """ Specify the address to connection to. """
         MClientConnectionInterface.__init__(self)
+
+    def connect(self, addr):
+        """Connect to the server. 'input' reads data from the
+        server. 'output' writes data to the server.  Specify the
+        address of the server (e.g. host:2020).  """
+
         h, p = addr.split(':')
         self.host = h
         self.port = int(p)
-
-    def setup(self):
-        """ Connect to the server. 'input' reads data from the
-        server. 'output' writes data to the server.
-        """
         self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self._sock.connect((self.host, self.port))
 
     def write(self, msg):
-        self._sock.send(msg)
+        self._sock.sendall(msg)
+
+    def readline(self, bufsize=2048):
+        line = self._sock.recv(bufsize)
+        return line
+    
 
-    def read(self, bufsize=1024):
-        return self._sock.recv(bufsize)
+    def flush(self):
+        pass
     
     def disconnect(self):
         """ Close the socket to the server. """

Modified: sandbox/trunk/pdb/mpdb.py
==============================================================================
--- sandbox/trunk/pdb/mpdb.py	(original)
+++ sandbox/trunk/pdb/mpdb.py	Mon Jun 12 20:38:36 2006
@@ -43,12 +43,15 @@
         """
         pydb.Pdb.__init__(self, completekey, stdin, stdout)
         self.prompt = '(MPdb)'
+        self.target = 'local'  # local connections by default
+        self.connection = None
 
     def _rebind_input(self, new_input):
 	""" This method rebinds the debugger's input to the object specified
 	by 'new_input'.
 	"""
         self.stdin.flush()
+        self.use_rawinput = False
 	self.stdin = new_input
 
     def  _rebind_output(self, new_output):
@@ -58,9 +61,23 @@
         self.stdout.flush()
         self.stdout = new_output
 
+    def remote_onecmd(self, line):
+        """ All commands in 'line' are sent across this object's connection
+        instance variable.
+        """
+        self.connection.write(line)
+        ret = self.connection.readline()
+        # The output from the command that we've just sent to the server
+        # is returned along with the prompt of that server. So we keep reading
+        # until we find our prompt.
+        while "(MPdb)" not in ret:
+            ret += self.connection.readline()
+        self.msg_nocr(ret)
+        return
+
     def msg_nocr(self, msg, out=None):
-        """Common routine for reporting messages (no carriage return).
-        Derived classed may want to override this to capture output.
+        """Common routine for reporting messages. Derived classes may want
+        to override this to capture output.
         """
         do_print = True
         if self.logging:
@@ -71,7 +88,6 @@
             if out is None:
                 out = self.stdout
             print >> out, msg,
-            out.flush()
 
     # Debugger commands
     def do_attach(self, addr):
@@ -86,7 +102,6 @@
 (see the "directory" command).  You can also use the "file" command
 to specify the program, and to load its symbol table.
 """
- 
     def do_target(self, args):
 	""" Connect to a target machine or process.
 The first argument is the type or protocol of the target machine
@@ -99,19 +114,60 @@
 List of target subcommands:
 
 target serial -- Use a remote computer via a serial line
-target socket -- Use a remote computer via a socket connection
+target tcp -- Use a remote computer via a socket connection
 """
-        cls, addr = args.split(' ')
-        if '.' in cls:
-            base = cls[:cls.rfind('.')]
-            cls = cls[cls.rfind('.')+1:]
-            exec 'from ' + base + ' import ' + cls
+        target, addr = args.split(' ')
+        if self.target == 'remote':
+            self.msg('Already connected to a remote machine.')
+            return
+        if target == 'tcp':
+            # TODO: need to save state of current debug session
+            if self.connection: self.connection.disconnect()
+            try:
+                from mconnection import MClientConnectionTCP
+                self.connection = MClientConnectionTCP()
+            except ImportError:
+                self.msg('Could not import MClientConnectionTCP')
+                return
+        elif target == 'serial':
+            if self.connection: self.connection.disconnect()
+            try:
+                from mconnection import MClientConnectionSerial
+                self.connection = MClientConnectionSerial()
+            except ImportError:
+                self.msg('Could not import MClientConnectionSerial')
+                return
         else:
-            __import__(cls)
-        self.connection = eval(cls+'(addr)')
-        self.connection.setup()
-        # XXX currently this method doesn't do anything
-
+            if '.' in target:
+                if self.connection: self.connection.disconnect()
+                # We dynamically load the class for the connection
+                base = cls[:cls.rfind('.')]
+                cls = cls[cls.rfind('.')+1:]
+                exec 'from ' + base + ' import ' + cls
+            else:
+                try:
+                    __import__(target)
+                except ImportError:
+                    self.msg('Unknown target type')
+                    return
+            self.connection = eval(target+'()')
+        self.connection.connect(addr)
+        # 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.onecmd = self.remote_onecmd
+        self.target = 'remote'
+
+    def do_detach(self, args):
+        """ Detach a process or file previously attached.
+If a process, it is no longer traced, and it continues its execution.  If
+you were debugging a file, the file is closed and Pdb no longer accesses it.
+"""
+        pass
+        
     def do_pdbserver(self, args):
         """ Allow a debugger to connect to this session.
 The first argument is the type or protocol that is used for this connection
@@ -120,24 +176,37 @@
 The next argument is protocol specific arguments (e.g. hostname and
 port number for a TCP connection, or a serial device for a serial line
 connection). The next argument is the filename of the script that is
-being debugged. The rest of the arguments are passed to the script file
-and are optional. For more information on the arguments for a
+being debugged. The rest of the arguments are passed as arguments to the
+script file and are optional. For more information on the arguments for a
 particular protocol, type `help pdbserver ' followed by the protocol name.
 The syntax for this command is,
 
 `pdbserver ConnectionClass comm scriptfile [args ...]'
 
 """
-        cls, comm, scriptfile_and_args = args.split(' ')
-        if '.' in cls:
-            base = cls[:cls.rfind('.')]
-            cls = cls[cls.rfind('.')+1:]
-            exec 'from ' + base + ' import ' + cls
+        target, comm, scriptfile_and_args = args.split(' ')
+        if self.target == 'remote':
+            self.msg('Already connected remotely')
+            return
+        if target == 'tcp':
+            try:
+                from mconnection import MServerConnectionTCP
+                self.connection = MServerConnectionTCP()
+            except ImportError:
+                self.msg('Could not load MServerConnectionTCP class')
+                return
         else:
-            __import__(cls)
-        self.connection = eval(cls+'(comm)')
-        self.connection.setup()
-        self._rebind_output(self.connection.output)
+            if '.' in target:
+                base = target[:target.rfind('.')]
+                target = target[target.rfind('.')+1:]
+                exec 'from ' + base + ' import ' + target
+            else:
+                __import__(target)
+            self.connection = eval(target+'()')
+        self.connection.connect(comm)
+        self.target = 'remote'
+        self._rebind_output(self.connection)
+        self._rebind_input(self.connection)
 
 def main(options):
     opts = options[0]
@@ -170,6 +239,7 @@
                       mainpyfile + " will be restarted"
 
 # Utility functions
+
 # Parse arguments
 def parse_opts():
     parser = OptionParser()

Modified: sandbox/trunk/pdb/test/tcptest.py
==============================================================================
--- sandbox/trunk/pdb/test/tcptest.py	(original)
+++ sandbox/trunk/pdb/test/tcptest.py	Mon Jun 12 20:38:36 2006
@@ -12,30 +12,26 @@
 
 class TestTCPConnections(unittest.TestCase):
     def testClientConnectAndRead(self):
-        # We need to exercise finer control over the server's socket so we're
-        # not using the setup() method.
-        self.client = MClientConnectionTCP(__addr__)
-        self.server = MServerConnectionTCP(__addr__)
-        self.server._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        self.server._sock.bind((self.server.host, self.server.port))
-        self.server._sock.listen(1)
         pid = os.fork()
-        for i in range(30):
-            try:
-                self.client.setup()
-                break
-            except socket.error:
-                pass
-            time.sleep(5)
-        debugger, addr = self.server._sock.accept()
-        self.server.accept(debugger, addr)
-        self.server.write("good")
-        line = self.client.read()
-        self.assertEqual("good", line, "Could not read from server")
-        self.client.disconnect()
-        self.server.disconnect()
+        if pid == 0:
+            # Child
+            self.server = MServerConnectionTCP()
+            self.server.connect(__addr__)
+            self.server.write("good")
+            self.server.disconnect()
+        else:
+            # Parent
+            self.client = MClientConnectionTCP()
+            for i in range(30):
+                try:
+                    self.client.connect(__addr__)
+                    break
+                except socket.error:
+                    pass
+                time.sleep(5)
+            line = self.client.readline()
+            self.assertEqual("good", line, "Could not read from server")
+            self.client.disconnect()
 
-
-        
 if __name__ == '__main__':
     unittest.main()


More information about the Python-checkins mailing list