[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