[Python-checkins] r50824 - in sandbox/trunk/pdb: Doc/lib/libmpdb.tex mpdb.py test/Makefile test/files/proc.py test/support.py test/test_mconnection.py test/test_mpdb.py test/test_mthread.py test/test_process.py

matt.fleming python-checkins at python.org
Tue Jul 25 19:11:14 CEST 2006


Author: matt.fleming
Date: Tue Jul 25 19:11:12 2006
New Revision: 50824

Added:
   sandbox/trunk/pdb/test/support.py
   sandbox/trunk/pdb/test/test_process.py
Modified:
   sandbox/trunk/pdb/Doc/lib/libmpdb.tex
   sandbox/trunk/pdb/mpdb.py
   sandbox/trunk/pdb/test/Makefile
   sandbox/trunk/pdb/test/files/proc.py
   sandbox/trunk/pdb/test/test_mconnection.py
   sandbox/trunk/pdb/test/test_mpdb.py
   sandbox/trunk/pdb/test/test_mthread.py
Log:
Change the way we do tests, instead of including docstrings let the name
of the class and test be the description. Also move some common code into
a 'support' file. Documentation update and unit tests for debugging already
running programs. Also added an '-e' option to mpdb to allow executing
commands specified on the cmdline.


Modified: sandbox/trunk/pdb/Doc/lib/libmpdb.tex
==============================================================================
--- sandbox/trunk/pdb/Doc/lib/libmpdb.tex	(original)
+++ sandbox/trunk/pdb/Doc/lib/libmpdb.tex	Tue Jul 25 19:11:12 2006
@@ -1149,11 +1149,44 @@
 this session all commands come from the client and are executed on the 
 pdbserver.
 
-\section{External Process Debugging}
+\section{Debugging an already running process}
 \label{proc-debug}
 This section describes how \module{mpdb} debugs processes that are external
-to the process in which \module{mpdb} is being run.
+to the process in which \module{mpdb} is being run. A user can call the
+\code{mpdb.process_debugging} function inside their code to enable a debugger
+running as a separate process to debug their program. The way this works
+is that \module{mpdb} sets up a signal handler for a specified signal to
+\module{mpdb}'s signal handler. If there was a signal handler for this signal
+previously, it is saved. When this signal handler is traps the signal it
+starts a \code{pdbserver} that a debugger can connect to. Once the debugger
+has 'detached' from the \code{pdbserver}, \module{mpdb}'s signal handler
+is removed and the old signal handler (if any) is restored.
+
+\begin{funcdesc}{process_debugging}{sig, protocol, addr}
+This function enables debugging by another process by setting the signal
+handler for signal \code{sig} to \module{mpdb}'s signal handler. When a client
+wishes to connect to this process, it uses the \code{attach} command which
+sends \module{mpdb}'s \code{debug_signal} to a process. It is up to the user
+of \module{mpdb} to ensure that the programming they are debugging and the 
+signal sent during the \code{attach} command are the same, \ref{command::set:}.
+When the signal is received by \module{mpdb}'s signal handler a \code{pdbserver}
+is started on \code{addr} using protoocol \code{protocol}.
+\code{sig}, \code{protocol} and \code{addr} are all optional arguments that
+specify the signal to use for debugging, the protocol to use for \code{pdbserver}
+and the add for the \code{pdbserver}, respectively. 
+
+
+This function returns a string which is the address used for the 
+\code{pdbserver}. If an address is not specified it defaults to
+a path consisting of the system's temporary
+directory is used as returned by \code{tempfile.gettempdir()}, and a filename
+made up from the process' pid and the word 'mpdb', i.e. 
+\code{'/tmp/22987mpdb'}. If \code{protocol} is not specified a FIFO is used.
 
+The default debug signal is \code{SIGUSR1}.
+\end{funcdesc}
+
+\subsection{Example}
 If a program wishes to allow debugging from another process it must import
 and call the \code{process_debugging} function from the \module{mpdb} module.
 This function sets up a signal handler for \module{mpdb}'s debugging signal
@@ -1168,7 +1201,9 @@
 \end{verbatim}
 
 From the debugger console a user must issue the 'attach' command, the
-\code{target_addr} variable must be set \ref{command::set}.
+\code{target_addr} variable must be set \ref{command::set}. The user can
+also select a signal to send to the process by calling the 
+\code{set debug-signal} command, \ref{command::set}.
 
 \begin{verbatim}
 (MPdb) set target [protcol] [address]
@@ -1181,4 +1216,3 @@
 specify the hostname, as it can only be 'localhost'.
 
 
-

Modified: sandbox/trunk/pdb/mpdb.py
==============================================================================
--- sandbox/trunk/pdb/mpdb.py	(original)
+++ sandbox/trunk/pdb/mpdb.py	Tue Jul 25 19:11:12 2006
@@ -54,10 +54,13 @@
         self.lastcmd = ''
         self.connection = None
         self.debugger_name = 'mpdb'
-        self._show_cmds.append('target-address')
-        self._show_cmds.sort()
-        self._info_cmds.append('target')
-        self._info_cmds.sort()
+
+        self.setcmds.add('debug-signal', self.set_debug_signal)
+        self.setcmds.add('target-address', self.set_target_address)
+        self.showcmds.add('debug-signal', self.show_debug_signal)
+        self.showcmds.add('target-address', self.show_target_address)
+        self.infocmds.add('target', self.info_target)
+
         self.target_addr = ""    # target address used by 'attach'
         self.debug_signal = None # The signal used by 'attach'
 
@@ -92,6 +95,9 @@
             self.onecmd = lambda x: pydb.Pdb.onecmd(self, x)
             self.do_rquit(None)
             return
+        if 'detach'.startswith(line):
+            self.connection.write('rdetach')
+            self.do_detach(None)
         self.connection.write(line)
         ret = self.connection.readline()
         if ret == '':
@@ -139,63 +145,41 @@
             self.prompt = self.local_prompt
             self.onecmd = lambda x: pydb.Pdb.onecmd(self, x)
 
-    def do_info(self, arg):
-        """Extends pydb do_info() to give info about the Mpdb extensions."""
-        if not arg:
-            pydb.Pdb.do_info(self, arg)
-            return
-
-        args = arg.split()
-        if 'target'.startswith(args[0]) and len(args[0]) > 2:
-            self.msg("target is %s" % self.target)
-        else:
-            pydb.Pdb.do_info(self, arg)
-
-    def info_helper(self, cmd, label=False):
-        """Extends pydb info_helper() to give info about a single Mpdb
-        info extension."""
-        if label:
-            self.msg_nocr("info %s --" % cmd)
-        if 'target'.startswith(cmd):
-            self.msg("Names of targets and files being debugged")
-        else:
-            pydb.Pdb.info_helper(self, cmd)
-
     def help_mpdb(self, *arg):
         help()
 
-    def do_set(self, arg):
-        """ Extends pydb.do_set() to allow setting of mpdb extensions.
+    def set_debug_signal(self, args):
+        """Set the signal sent to a process to trigger debugging."""
+        try:
+            exec 'from signal import %s' % args[1]
+        except ImportError:
+            self.errmsg('Invalid signal')
+            return
+        self.debug_signal = args[1]
+        self.msg('debug-signal set to: %s' % self.debug_signal)
+
+    def set_target_address(self, args):
+        """Set the address of a target."""
+        self.target_addr = "".join(["%s " % a for a in args[1:]])
+        self.target_addr = self.target_addr.strip()
+        self.msg('target address set to %s' % self.target_addr)
+
+    def show_debug_signal(self, arg):
+        """Show the signal currently used for triggering debugging
+        of an already running process.
         """
-        if not arg:
-            pydb.Pdb.do_set(self, arg)
+        if not self.debug_signal:
+            self.msg('debug-signal not set.')
             return
+        self.msg('debug-signal is %s' % self.debug_signal)
 
-        args = arg.split()
-        if 'debug-signal'.startswith(args[0]):
-            self.debug_signal = args[1]
-            self.msg('debug-signal set to: %s' % self.debug_signal)
-        elif 'target-address'.startswith(args[0]):
-            self.target_addr = "".join(["%s " % a for a in args[1:]])
-            self.target_addr = self.target_addr.strip()
-            self.msg('target address set to: %s' % self.target_addr)
-
-    def do_show(self, arg):
-        """Extends pydb.do_show() to show Mpdb extension settings. """
-        if not arg:
-            pydb.Pdb.do_show(self, arg)
-            return
+    def show_target_address(self, arg):
+        """Show the address of the current target."""
+        self.msg('target-address is %s.' % self.target_addr.__repr__())
 
-        args = arg.split()
-        if 'debug-signal'.startswith(args[0]):
-            if not self.debug_signal:
-                self.msg('debug-signal is not set.')
-            else:
-                self.msg('debug-signal is %s.' % self.debug_signal)
-        elif 'target-address'.startswith(args[0]):
-            self.msg('target address is %s.' % self.target_addr.__repr__())
-        else:
-            pydb.Pdb.do_show(self, arg)
+    def info_target(self, args):
+        """Display information about the current target."""
+        self.msg('target is %s' % self.target)
 
     # Debugger commands
     def do_attach(self, addr):
@@ -222,21 +206,17 @@
         if not self.debug_signal:
             from signal import SIGUSR1
             self.debug_signal = SIGUSR1
-        else:
-            # Because self.debug_signal may be a string
-            self.debug_signal = eval(self.debug_signal)
         try:
             os.kill(pid, self.debug_signal)
         except OSError, err:
             self.errmsg(err)
             return
 
-        # Will remove this
-        time.sleep(3.0)
-
-        # At the moment by default we'll use named pipes for communication
+        # XXX this still needs removing
+        time.sleep(1.0)
+        
         self.do_target(self.target_addr)
-
+            
     def do_target(self, args):
         """ Connect to a target machine or process.
 The first argument is the type or protocol of the target machine
@@ -328,7 +308,7 @@
 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
+        raise KeyboardInterrupt
         
     def do_pdbserver(self, args):
         """ Allow a debugger to connect to this session.
@@ -409,6 +389,7 @@
         self._rebind_input(self.orig_stdin)
         self._disconnect()
         self.target = 'local'
+        sys.settrace(None)
         self.do_quit(None)
 
     def do_restart(self, arg):
@@ -427,6 +408,17 @@
         else:
             self.msg("Re exec'ing\n\t%s" % self._sys_argv)
         os.execvp(self._sys_argv[0], self._sys_argv)
+        
+    def do_rdetach(self, arg):
+        """ The rdetach command is performed on the pdbserver, it cleans
+        things up when the client has detached from this process.
+        Control returns to the file being debugged and execution of that
+        file continues.
+        """
+        self._rebind_input(self.orig_stdin)
+        self._rebind_output(self.orig_stdout)
+
+        self.cmdqueue.append('continue')  # Continue execution
 
 def pdbserver(addr, m):
     """ This method sets up a pdbserver debugger that allows debuggers
@@ -472,7 +464,6 @@
 
 def thread_debugging(m):
     """ Setup this debugger to handle threaded applications."""
-    sys.path.append(os.path.dirname(m._sys_argv[1]))
     import mthread
     mthread.init(m)
     while True:
@@ -492,7 +483,8 @@
     """ Allow debugging of other processes. This routine should
     be imported and called near the top of the program file.
     It sets up signal handlers that are used to create a pdbserver
-    that a debugging client can attach to.
+    that a debugging client can attach to. The address of the pdbserver
+    is returned a string.
 
     The optional argument 'sig', specifies which signal will be
     used for running process debugging. If 'sig' is not specified
@@ -521,13 +513,12 @@
         proto = 'mconnection.MConnectionServerFIFO'
         
     if addr is not None:
-        pdbserver_addr = addr
+        pdbserver_addr = proto + " " + addr
     else:
-        # XXX I don't think a successful symlink attack can be made here,
-        # because pdbserver bails if the file for a FIFO already exists.
-        tmp = os.tempnam(None,'mpdb') # use 'mpdb' as a prefix
+        from tempfile import gettempdir
+        tmp = gettempdir() + "/" + str(os.getpid()) + "mpdb"
         pdbserver_addr = proto + " " + tmp
-    print pdbserver_addr
+    return pdbserver_addr
 
 def signal_handler(signum, frame):
     """ This signal handler replaces the programs signal handler
@@ -545,13 +536,10 @@
     del frame.f_globals['mpdb']
 
     m.do_pdbserver(pdbserver_addr)
+    m.set_trace(frame)
 
-    try:
-        m.set_trace(m.curframe)
-    finally:
-        m.do_quit(None)
-        import signal
-        signal.signal(signum, old_handler)
+    import signal
+    signal.signal(signum, old_handler)
     
 
 def main():
@@ -571,7 +559,9 @@
                       + " 'protocol address scriptname'."),
         make_option("-d", "--debug-thread", action="store_true",
                       help="Turn on thread debugging."),
-        make_option("--pid", dest="pid", help="Attach to running process PID.")
+        make_option("--pid", dest="pid", help="Attach to running process PID."),
+        make_option("-e", "--exec", dest="commands",
+                    help="Specify commands to execute.")
         ]
         
     opts = process_options(mpdb, "mpdb", os.path.basename(sys.argv[0])
@@ -594,6 +584,10 @@
         # module search path.
         sys.path[0] = mpdb.main_dirname = os.path.dirname(mpdb.mainpyfile)
 
+    if opts.commands:
+        cmds = opts.commands.split(',')
+        mpdb.cmdqueue = cmds
+        
     if opts.target:
         target(opts.target, opts, mpdb)
         sys.exit()

Modified: sandbox/trunk/pdb/test/Makefile
==============================================================================
--- sandbox/trunk/pdb/test/Makefile	(original)
+++ sandbox/trunk/pdb/test/Makefile	Tue Jul 25 19:11:12 2006
@@ -8,10 +8,10 @@
 
 PY = python
 
-.PHONY: all test test_mpdb test_mconnection test_mthread
+.PHONY: all test test_mpdb test_mconnection test_mthread test_process
 all: test
 
-test: test_mpdb test_mconnection test_mthread
+test: test_mpdb test_mconnection test_mthread test_process
 
 test_mpdb:
 	@$(PY) test_mpdb.py
@@ -21,3 +21,6 @@
 
 test_mthread:
 	@$(PY) test_mthread.py
+
+test_process:
+	@$(PY) test_process.py

Modified: sandbox/trunk/pdb/test/files/proc.py
==============================================================================
--- sandbox/trunk/pdb/test/files/proc.py	(original)
+++ sandbox/trunk/pdb/test/files/proc.py	Tue Jul 25 19:11:12 2006
@@ -8,10 +8,14 @@
 sys.path.append('../..')
 
 import mpdb
-mpdb.process_debugging()
+mpdb.process_debugging(protocol='tcp', addr=':9000')
+
+try:
+    while True:
+        for i in range(10):
+            x = i
+except KeyboardInterrupt:
+    pass
+
 
-while True:
-    for i in range(10):
-        x = i
-        
 

Added: sandbox/trunk/pdb/test/support.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/pdb/test/support.py	Tue Jul 25 19:11:12 2006
@@ -0,0 +1,73 @@
+# This file contains classes that can be imported by any of the unit tests.
+
+import sys ; sys.path.append('..')
+import time
+import threading
+
+class DummyStdout(object):
+    """ This class is a replacement for sys.stdout. All output is
+    stored in this object's 'lines' instance variable.
+    """
+    def __init__(self):
+        self.lines = []
+        
+    def flush(self):
+        pass
+    
+    def write(self, msg):
+        pass
+    
+from mpdb import MPdb
+
+class MPdbTest(MPdb):
+    """ This class provides a version of the MPdb class that is
+    suitable for use in unit testing. All output is captured and
+    stored in this object's 'lines' instance variable.
+    """
+    def __init__(self, cmds=[]):
+        """ The optional argument 'cmds' is a list specifying commands
+        that this instance should interpret in it's 'cmdloop' method.
+        """
+        MPdb.__init__(self)
+        self.lines = []
+        self.cmdqueue = cmds
+        self.botframe = None
+
+    def msg_nocr(self, msg):
+        self.lines.append(msg)
+
+
+class Pdbserver(threading.Thread, MPdb):
+    """ This class provides a fully functional pdbserver that runs
+    in a separate thread. The optional argument 'addr' specifies a
+    protocol and an address to use for pdbserver.
+    """
+    def __init__(self, addr=None):
+        MPdb.__init__(self)
+        threading.Thread.__init__(self)
+        self._sys_argv = ['python', '-c', '"pass"']
+        self.botframe = None
+
+        if not addr:
+            self.addr = 'tcp localhost:8000'
+        
+    def run(self):
+        self.do_pdbserver(self.addr)
+        while True:
+                self.cmdloop()
+                if self._user_requested_quit:
+                    break
+
+class MPdbTestThread(threading.Thread, MPdbTest):
+    """ This class provides a MPdbTest object that runs in a separate
+    thread.
+    """
+    def __init__(self, cmds=[]):
+        threading.Thread.__init__(self)
+        MPdbTest.__init__(self, cmds)
+        
+                
+
+        
+
+    

Modified: sandbox/trunk/pdb/test/test_mconnection.py
==============================================================================
--- sandbox/trunk/pdb/test/test_mconnection.py	(original)
+++ sandbox/trunk/pdb/test/test_mconnection.py	Tue Jul 25 19:11:12 2006
@@ -6,6 +6,7 @@
 import os
 import sys
 import socket
+import time
 import thread
 import unittest
 
@@ -39,14 +40,12 @@
         self.client = MConnectionClientTCP()
 
     def testClientConnectToServer(self):
-        """(tcp) Connect client to server. """
         thread.start_new_thread(repeatedConnect, (self.client, __addr__))
         self.server.connect(__addr__)
 
 	self.server.disconnect()
 
     def testClientConnectAndRead(self):
-        """(tcp) Connect to server and read/write. """
         thread.start_new_thread(repeatedConnect, (self.client,__addr__))
         self.server.connect(__addr__)
 
@@ -58,14 +57,12 @@
         self.assertEqual('success\n', line, 'Could not read from client')
 
     def testDisconnectDisconnected(self):
-        """(tcp) Disconnect a disconnected session. """
         s = MConnectionServerTCP()
 
         s.disconnect()
         s.disconnect()
 
     def testReadline(self):
-        """(tcp) Make sure readline method works. """
         thread.start_new_thread(repeatedConnect, (self.client,__addr__))
         self.server.connect(__addr__)
 
@@ -76,7 +73,6 @@
 	self.server.disconnect()
 
     def testErrorAddressAlreadyInUse(self):
-        """(tcp) Test address already in use error. """
         thread.start_new_thread(repeatedConnect, (self.client, __addr__))
         self.server.connect(__addr__)
 
@@ -85,31 +81,27 @@
         self.assertRaises(ConnectionFailed, s.connect, __addr__, False)
 
     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 testInvalidAddressPortPair(self):
-        """(tcp) Test invald hostname, port pair. """
         addr = 'localhost 8000'
         self.assertRaises(ConnectionFailed, self.server.connect, addr)
 
     def testServerReadError(self):
-        """(tcp) Test the ReadError exception."""
         thread.start_new_thread(self.server.connect, (__addr__,))
 
         while not self.server._sock:
-            pass
+            time.sleep(0.1)
             
         repeatedConnect(self.client, __addr__)
 
         # Wait to make _absolutely_ sure that the client has connected
         while not self.server.output:
-            pass
+            time.sleep(0.1)
         self.client.disconnect()
         self.assertRaises(ReadError, self.server.readline)
 
@@ -138,12 +130,10 @@
         self.client.connect(TESTFN)
         
     def testClientToServerConnect(self):
-        """(serial) Connect client to server. """
         self.client.disconnect()
         self.server.disconnect()
 
     def testClientWriteRead(self):
-        """(serial) Connect client to server and read/write. """
         self.client.write('success!')
         line = self.server.readline()
         self.assertEquals('success!\n', line, 'Could not read from client.')
@@ -157,11 +147,9 @@
         self.assertEquals('great!\n', line, 'Could not read from server.')
 
     def testDisconnectDisconnected(self):
-        """(serial) Disconnect a disconnected session. """
         self.server.disconnect()
 
     def testReadline(self):
-        """(serial) Make sure readline method works. """
         self.client.write('success!\nNext line.')
         self.client.disconnect()
         line = self.server.readline()
@@ -172,7 +160,6 @@
         self.assertEquals('', line, 'Could not read third line.')
 
     def testInvalidFilename(self):
-        """(serial) Connect to an invalid server. """
         client = MConnectionSerial()
         self.assertRaises(ConnectionFailed, client.connect,
                           '/dev/pleasepleasepleasedontexit')
@@ -188,12 +175,10 @@
         self.client = MConnectionClientFIFO()
 
     def testConnect(self):
-        """(FIFO) Connect a client to a server. """
         thread.start_new_thread(self.client.connect, ('test_file',))
         self.server.connect('test_file')
 
     def testReadWrite(self):
-        """(FIFO) Test reading and writing to and from server/client."""
         thread.start_new_thread(self.client.connect, ('test_file',))
         self.server.connect('test_file')
 
@@ -212,12 +197,10 @@
         self.assertEquals('received\n', line)
 
     def testMultipleDisconnect(self):
-        """(FIFO) Disconnect disconnected connections."""
         self.client.disconnect()
         self.server.disconnect()
 
     def testReadError(self):
-        """(FIFO) Test ReadError."""
         thread.start_new_thread(self.client.connect, ('test_file',))
         self.server.connect('test_file')
 
@@ -233,7 +216,6 @@
         self.assertRaises(ReadError, self.client.readline)
 
     def testWriteError(self):
-        """(FIFO) Test WriteError."""
         thread.start_new_thread(self.client.connect, ('test_file',))
         self.server.connect('test_file')
 
@@ -249,7 +231,6 @@
         self.assertRaises(WriteError, self.client.write, 'Ni!\n')
 
     def testInvalidPipe(self):
-        """(FIFO) Connect to an invalid named pipe."""
         self.assertRaises(ConnectionFailed,self.client.connect, 'invalid')
         os.unlink('invalid0')
 

Modified: sandbox/trunk/pdb/test/test_mpdb.py
==============================================================================
--- sandbox/trunk/pdb/test/test_mpdb.py	(original)
+++ sandbox/trunk/pdb/test/test_mpdb.py	Tue Jul 25 19:11:12 2006
@@ -19,7 +19,7 @@
 from mpdb import MPdb, pdbserver, target
 from mconnection import (MConnectionClientTCP, MConnectionServerTCP,
                          ConnectionFailed)
-
+from support import MPdbTest, Pdbserver
 TESTFN = 'tester'
 
 # This provides us with a fine-grain way of connecting to a server
@@ -39,32 +39,6 @@
             else: raise ConnectionFailed, e
         break
             
-class MPdbTest(MPdb):
-    def __init__(self):
-        MPdb.__init__(self)
-        self.lines = []
-        self.botframe = None
-
-    def msg_nocr(self, msg):
-        self.lines.append(msg)
-
-class Pdbserver(threading.Thread, MPdb):
-    def __init__(self):
-        MPdb.__init__(self)
-        threading.Thread.__init__(self)
-        self.botframe = None
-        self._sys_argv = ['python', '-c', '"pass"']
-
-        
-    def run(self):
-        self.do_pdbserver('tcp localhost:8000')
-        while True:
-            try:
-                self.cmdloop()
-            except Exit:
-                break
-        
-        
 class TestRemoteDebugging(unittest.TestCase):
     """ Test Case to make sure debugging remotely works properly. """
     def tearDown(self):
@@ -77,7 +51,6 @@
 # and vice versa.
 
     def testPdbserver(self):
-        """ Test the pdbserver. """
         client = MPdbTest()
         thread.start_new_thread(connect_to_target, (client,))
         
@@ -92,7 +65,6 @@
         self.assertEquals('*** Unknown protocol\n', line)
 
     def testTarget(self):
-        """ Test the target command. """
         server = MConnectionServerTCP()
         thread.start_new_thread(server.connect, (__addr__,True))
 
@@ -127,7 +99,6 @@
             else: break
 
     def testRebindOutput(self):
-        """ Test rebinding output. """
         self.server = MPdb()
         f = open(TESTFN, 'w+')
         self.server._rebind_output(f)
@@ -140,7 +111,6 @@
         self.assertEquals('some text\n', line, 'Could not rebind output')
         
     def testRebindInput(self):
-        """ Test rebinding input. """
         self.server = MPdb()
         
         f = open(TESTFN, 'w+')
@@ -155,7 +125,6 @@
         self.assertEquals(line, 'help', 'Could not rebind input.')
 
     def testRestart(self):
-        """ Test the restart command. """
         server = Pdbserver()
         server.start()
 
@@ -175,8 +144,71 @@
             if server.connection != None: pass
             else: break
 
+
+class TestMpdbDoc(unittest.TestCase):
+    """ Test the expected output from help commands against actual
+    output to ensure that documentation constantly stays the same.
+    """
+    def setUp(self):
+        self.m = MPdbTest()
+
+    def tearDown(self):
+        del self.m
+        
+    def testHelpInfo(self):
+        self.m.onecmd('help info')
+
+        exp = ['Generic command for showing things about the program being debugged.\n',
+               '\nList of info subcommands:\n\n',
+               'info args --', 'Argument variables of current stack frame\n',
+               'info breakpoints --', 'Status of user-settable breakpoints\n',
+               'info display --', 'Expressions to display when program stops, with code numbers\n',
+               'info globals --', 'Global variables of current stack frame\n',
+               'info line --', 'Current line number in source file\n',
+               'info locals --', 'Local variables of current stack frame\n',
+               'info program --', 'Execution status of the program\n',
+               'info source --', 'Information about the current Python file\n',
+               'info target --', 'Display information about the current target.\n']
+        self.assertEquals(self.m.lines, exp)
+        
+
+    def testInfoCmd(self):
+        self.m.reset()
+        self.m.onecmd('info')
+        exp = ['args: ', 'No stack.\n',
+               'breakpoints: ', 'No breakpoints.\n',
+               'display: ', 'There are no auto-display expressions now.\n',
+               'globals: ', 'No frame selected.\n',
+               'line: ', 'No line number information available.\n',
+               'locals: ', 'No frame selected.\n',
+               'program: ', 'The program being debugged is not being run.\n',
+               'source: ', 'No current source file.\n',
+               'target: ', 'target is local\n']
+        self.assertEquals(self.m.lines, exp)
+
+    def testShowCmd(self):
+        self.m.reset()
+        self.m.onecmd('show')
+
+        exp =  ['args: ', 'Argument list to give program being debugged when it is started is \n', '"".\n',
+                'basename: ', 'basename is off.\n',
+                'cmdtrace: ', 'cmdtrace is off.\n',
+                # Readline is not always available
+                'debug-signal: ', 'debug-signal not set.\n',
+                'history: ',
+                'interactive: ', 'interactive is on.\n',
+                'linetrace: ', 'line tracing is off.\n',
+                'listsize: ', 'Number of source lines pydb will list by default is 10.\n',
+                'logging: ', 'Future logs will be written to pydb.txt.\n', 'Logs will be appended to the log file.\n', 'Output will be logged and displayed.\n',
+                'prompt: ', 'pydb\'s prompt is "(MPdb)".\n',
+                'target-address: ', "target-address is ''.\n",
+                'version: ', 'pydb version 1.17cvs.\n']
+
+        self.assertEquals(self.m.lines, exp)
+
+        
 def test_main():
-    test_support.run_unittest(TestRemoteDebugging)
+    test_support.run_unittest(TestMpdbDoc, TestRemoteDebugging)
     
 if __name__ == '__main__':
     test_main()

Modified: sandbox/trunk/pdb/test/test_mthread.py
==============================================================================
--- sandbox/trunk/pdb/test/test_mthread.py	(original)
+++ sandbox/trunk/pdb/test/test_mthread.py	Tue Jul 25 19:11:12 2006
@@ -11,7 +11,6 @@
 
 class TestThreadDebugging(unittest.TestCase):
     def testMthreadInit(self):
-        """ Test the init method of the mthread file. """
         m = mpdb.MPdb()
         mthread.init(m)
 

Added: sandbox/trunk/pdb/test/test_process.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/pdb/test/test_process.py	Tue Jul 25 19:11:12 2006
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+
+# Unit test for debugging running processes
+
+import os
+import signal
+import sys
+import time
+import threading
+import unittest
+
+from test import test_support
+from support import DummyStdout, MPdbTest, MPdbTestThread
+
+sys.path.append('..')
+
+import mpdb
+
+PROTOCOL = 'tcp'
+ADDRESS = ':9000'
+TESTFN = os.path.abspath(os.curdir+os.path.sep+'.mpdb')
+
+class TestProcessDebugging(unittest.TestCase):
+    def child(self):
+        os.chdir('./files')
+        pid = os.spawnlp(os.P_NOWAIT, './proc.py', './proc.py')
+        os.chdir('..')
+        return pid
+    
+    def testTopLevelRoutine(self):
+        sys.stdout = DummyStdout()
+
+        mpdb.process_debugging()
+        self.assertRaises(ValueError, mpdb.process_debugging, -1)
+        self.assertRaises(TypeError, mpdb.process_debugging, 'invalid')
+        self.assertRaises(ValueError, mpdb.process_debugging, signal.NSIG)
+
+        # XXX We can't currently check the validity of the connection
+        # params.
+
+        sys.stdout = sys.__stdout__
+
+    def testSignalHandler(self):
+        pid = self.child()
+        client = MPdbTest()
+        # Allow the child process to catch up
+        time.sleep(0.1)
+        client.onecmd('set target-address tcp :9000')
+        client.onecmd('attach %s' % str(pid))
+        client.onecmd('where')
+        line = client.lines[1]
+        self.assertEquals(line, '  <module>\n(MPdb)')
+        try:
+            client.onecmd('detach')
+        except KeyboardInterrupt:
+            pass
+        os.kill(pid, signal.SIGINT)
+
+    def testCmdLineOption(self):
+        sys.stdout = DummyStdout()
+
+        pid = self.child()
+        os.chdir('..')
+        time.sleep(0.1)
+        os.system("./mpdb.py --exec='set target-address tcp :9000," + \
+                  "attach %s,where,detach' -o %s" % (str(pid), TESTFN))
+        os.chdir('./test')
+        os.kill(pid, signal.SIGINT)
+
+        os.waitpid(pid, 0)
+
+        pid = self.child()
+        os.chdir('..')
+        time.sleep(0.1)
+        os.system("./mpdb.py --pid='%s tcp :9000' --exec='where,detach' -o %s" \
+                   % (str(pid), TESTFN))
+        os.chdir('./test')
+        os.kill(pid, signal.SIGINT)
+
+        sys.stdout = sys.__stdout__
+
+    def testFifoFilename(self):
+        cls, addr = mpdb.process_debugging().split()
+
+        pid = os.getpid()
+        import tempfile
+        from tempfile import gettempdir
+        tmp = gettempdir()
+
+        path = tmp + os.path.sep + str(pid) + 'mpdb'
+        self.assertEquals(path, addr)
+
+    def tearDown(self):
+        try:
+            os.unlink(TESTFN)
+        except OSError:
+            pass
+            
+def test_main():
+    test_support.run_unittest(TestProcessDebugging)
+
+if __name__ == '__main__':
+    test_main()


More information about the Python-checkins mailing list