[Lldb-commits] [lldb] r116552 - in /lldb/trunk/test: dotest.py foundation/TestObjCMethods.py lldbtest.py lldbutil.py

Johnny Chen johnny.chen at apple.com
Thu Oct 14 18:18:30 PDT 2010


Author: johnny
Date: Thu Oct 14 20:18:29 2010
New Revision: 116552

URL: http://llvm.org/viewvc/llvm-project?rev=116552&view=rev
Log:
This is an initial version of test driver enhanceent to be able to dump the
session info after a test case failure, allowing more direct inspection of
debugger session which leads to the test failure.

For a simple usage scenario:

[18:06:26] johnny:/Volumes/data/lldb/svn/trunk/test $ ./dotest.py -v . 2> ~/Developer/Log/lldbtest.log 

...

[18:14:43] johnny:/Volumes/data/lldb/svn/trunk/test $ ls -l .session-*
-rw-r--r--  1 johnny  admin  1359 Oct 14 18:06 .session-TestArrayTypes.ArrayTypesTestCase.test_with_dwarf_and_run_command
-rw-r--r--  1 johnny  admin  2054 Oct 14 18:07 .session-TestClassTypes.ClassTypesTestCase.test_with_dsym_and_expr_parser
-rw-r--r--  1 johnny  admin  2055 Oct 14 18:07 .session-TestClassTypes.ClassTypesTestCase.test_with_dwarf_and_expr_parser
-rw-r--r--  1 johnny  admin  1351 Oct 14 17:57 .session-TestClassTypes.ClassTypesTestCase.test_with_dwarf_and_run_command
[18:14:51] johnny:/Volumes/data/lldb/svn/trunk/test $ 

The test case which failed will have its recorded session info dumped to a
.session-* file in the current working directory.  For test suite using
relocated directory, expect to find the .session-* files there.

In this checkin, I also add @skip decorator to the two test methods in
test/foundation/TestObjCMethods.py as it looks like the test suite is
deadlocking when running the tests.  More investigations are needed.

Modified:
    lldb/trunk/test/dotest.py
    lldb/trunk/test/foundation/TestObjCMethods.py
    lldb/trunk/test/lldbtest.py
    lldb/trunk/test/lldbutil.py

Modified: lldb/trunk/test/dotest.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/dotest.py?rev=116552&r1=116551&r2=116552&view=diff
==============================================================================
--- lldb/trunk/test/dotest.py (original)
+++ lldb/trunk/test/dotest.py Thu Oct 14 20:18:29 2010
@@ -597,7 +597,29 @@
                             suite.countTestCases() != 1 and "s" or ""))
 
         # Invoke the test runner.
-        result = unittest2.TextTestRunner(stream=sys.stderr, verbosity=verbose).run(suite)
+        class LLDBTestResult(unittest2.TextTestResult):
+            """
+            Enforce a singleton pattern to allow inspection of test progress.
+            """
+            __singleton__ = None
+
+            def __init__(self, *args):
+                if LLDBTestResult.__singleton__:
+                    raise "LLDBTestResult instantiated more than once"
+                super(LLDBTestResult, self).__init__(*args)
+                LLDBTestResult.__singleton__ = self
+                # Now put this singleton into the lldb module namespace.
+                lldb.test_result = self
+
+            def addFailure(self, test, err):
+                super(LLDBTestResult, self).addFailure(test, err)
+                method = getattr(test, "markFailure", None)
+                if method:
+                    method()
+                setattr(test, "__failed__", True)
+
+        result = unittest2.TextTestRunner(stream=sys.stderr, verbosity=verbose,
+                                          resultclass=LLDBTestResult).run(suite)
         
 
 # Terminate the test suite if ${LLDB_TESTSUITE_FORCE_FINISH} is defined.

Modified: lldb/trunk/test/foundation/TestObjCMethods.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/foundation/TestObjCMethods.py?rev=116552&r1=116551&r2=116552&view=diff
==============================================================================
--- lldb/trunk/test/foundation/TestObjCMethods.py (original)
+++ lldb/trunk/test/foundation/TestObjCMethods.py Thu Oct 14 20:18:29 2010
@@ -23,6 +23,7 @@
         self.buildDwarf()
         self.break_on_objc_methods()
 
+    @unittest2.skip("Skip due to deadlock?")
     @unittest2.expectedFailure
     # rdar://problem/8542091
     # rdar://problem/8492646
@@ -31,6 +32,7 @@
         self.buildDsym()
         self.data_type_and_expr_objc()
 
+    @unittest2.skip("Skip due to deadlock?")
     @unittest2.expectedFailure
     # rdar://problem/8542091
     # rdar://problem/8492646

Modified: lldb/trunk/test/lldbtest.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/lldbtest.py?rev=116552&r1=116551&r2=116552&view=diff
==============================================================================
--- lldb/trunk/test/lldbtest.py (original)
+++ lldb/trunk/test/lldbtest.py Thu Oct 14 20:18:29 2010
@@ -99,6 +99,7 @@
 import os, sys, traceback
 import re
 from subprocess import *
+import StringIO
 import time
 import types
 import unittest2
@@ -244,6 +245,37 @@
     return 8 * ctypes.sizeof(a_pointer)
 
 
+class recording(StringIO.StringIO):
+    """
+    A nice little context manager for recording the debugger interactions into
+    our session object.  If trace flag is ON, it also emits the interactions
+    into the stderr.
+    """
+    def __init__(self, test, trace):
+        """Create a StringIO instance; record session, stderr, and trace."""
+        StringIO.StringIO.__init__(self)
+        self.session = test.session
+        self.stderr = test.old_stderr
+        self.trace = trace
+
+    def __enter__(self):
+        """
+        Context management protocol on entry to the body of the with statement.
+        Just return the StringIO object.
+        """
+        return self
+
+    def __exit__(self, type, value, tb):
+        """
+        Context management protocol on exit from the body of the with statement.
+        If trace is ON, it emits the recordings into stderr.  Always add the
+        recordings to our session object.  And close the StringIO object, too.
+        """
+        if self.trace:
+            print >> self.stderr, self.getvalue()
+        print >> self.session, self.getvalue()
+        self.close()
+
 class TestBase(unittest2.TestCase):
     """This LLDB abstract base class is meant to be subclassed."""
 
@@ -369,10 +401,39 @@
         self.dict = None
         self.doTearDownCleanup = False
 
+        # Create a string buffer to record the session info.
+        self.session = StringIO.StringIO()
+
+        # Substitute self.session as the sys.stderr and restore it at the end of
+        # the test during tearDown().  If trace is ON, we dump the session info
+        # into the real stderr as well.  The session info will be dumped into a
+        # test case specific file if a failure is encountered.
+        self.old_stderr = sys.stderr
+        sys.stderr = self.session
+
     def setTearDownCleanup(self, dictionary=None):
         self.dict = dictionary
         self.doTearDownCleanup = True
 
+    def markFailure(self):
+        """Callback invoked when we (the test case instance) failed."""
+        with recording(self, False) as sbuf:
+            # False because there's no need to write "FAIL" to the stderr again.
+            print >> sbuf, "FAIL"
+
+    def dumpSessionInfo(self):
+        """
+        Dump the debugger interactions leading to a test failure.  This allows
+        for more convenient postmortem analysis.
+        """
+        for test, err in lldb.test_result.failures:
+            if test is self:
+                print >> self.session, err
+
+        fname = os.path.join(os.environ["LLDB_TEST"], ".session-" + self.id())
+        with open(fname, "w") as f:
+            print >> f, self.session.getvalue()
+
     def tearDown(self):
         #import traceback
         #traceback.print_stack()
@@ -393,6 +454,15 @@
             if not module.cleanup(dictionary=self.dict):
                 raise Exception("Don't know how to do cleanup")
 
+        # lldb.test_result is an instance of unittest2.TextTestResult enforced
+        # as a singleton.  During tearDown(), lldb.test_result can be consulted
+        # in order to determine whether we failed for the current test instance.
+        if getattr(self, "__failed__", False):
+            self.dumpSessionInfo()
+
+        # Restore the sys.stderr to what it was before.
+        sys.stderr = self.old_stderr
+
     def runCmd(self, cmd, msg=None, check=True, trace=False, setCookie=True):
         """
         Ask the command interpreter to handle the command and then check its
@@ -409,13 +479,13 @@
         for i in range(self.maxLaunchCount if running else 1):
             self.ci.HandleCommand(cmd, self.res)
 
-            if trace:
-                print >> sys.stderr, "runCmd:", cmd
+            with recording(self, trace) as sbuf:
+                print >> sbuf, "runCmd:", cmd
                 if self.res.Succeeded():
-                    print >> sys.stderr, "output:", self.res.GetOutput()
+                    print >> sbuf, "output:", self.res.GetOutput()
                 else:
-                    print >> sys.stderr, "runCmd failed!"
-                    print >> sys.stderr, self.res.GetError()
+                    print >> sbuf, "runCmd failed!"
+                    print >> sbuf, self.res.GetError()
 
             if running:
                 # For process launch, wait some time before possible next try.
@@ -423,8 +493,9 @@
 
             if self.res.Succeeded():
                 break
-            elif running:                
-                print >> sys.stderr, "Command '" + cmd + "' failed!"
+            elif running:
+                with recording(self, True) as sbuf:
+                    print >> sbuf, "Command '" + cmd + "' failed!"
 
         # Modify runStarted only if "run" or "process launch" was encountered.
         if running:
@@ -474,35 +545,35 @@
         else:
             # No execution required, just compare str against the golden input.
             output = str
-            if trace:
-                print >> sys.stderr, "looking at:", output
+            with recording(self, trace) as sbuf:
+                print >> sbuf, "looking at:", output
 
         # The heading says either "Expecting" or "Not expecting".
-        if trace:
-            heading = "Expecting" if matching else "Not expecting"
+        heading = "Expecting" if matching else "Not expecting"
 
         # Start from the startstr, if specified.
         # If there's no startstr, set the initial state appropriately.
         matched = output.startswith(startstr) if startstr else (True if matching else False)
 
-        if startstr and trace:
-            print >> sys.stderr, "%s start string: %s" % (heading, startstr)
-            print >> sys.stderr, "Matched" if matched else "Not matched"
-            print >> sys.stderr
+        if startstr:
+            with recording(self, trace) as sbuf:
+                print >> sbuf, "%s start string: %s" % (heading, startstr)
+                print >> sbuf, "Matched" if matched else "Not matched"
+                print >> sbuf
 
         # Look for sub strings, if specified.
         keepgoing = matched if matching else not matched
         if substrs and keepgoing:
             for str in substrs:
                 matched = output.find(str) != -1
-                if trace:
-                    print >> sys.stderr, "%s sub string: %s" % (heading, str)
-                    print >> sys.stderr, "Matched" if matched else "Not matched"
+                with recording(self, trace) as sbuf:
+                    print >> sbuf, "%s sub string: %s" % (heading, str)
+                    print >> sbuf, "Matched" if matched else "Not matched"
                 keepgoing = matched if matching else not matched
                 if not keepgoing:
                     break
-            if trace:
-                print >> sys.stderr
+            with recording(self, trace) as sbuf:
+                print >> sbuf
 
         # Search for regular expression patterns, if specified.
         keepgoing = matched if matching else not matched
@@ -510,14 +581,14 @@
             for pattern in patterns:
                 # Match Objects always have a boolean value of True.
                 matched = bool(re.search(pattern, output))
-                if trace:
-                    print >> sys.stderr, "%s pattern: %s" % (heading, pattern)
-                    print >> sys.stderr, "Matched" if matched else "Not matched"
+                with recording(self, trace) as sbuf:
+                    print >> sbuf, "%s pattern: %s" % (heading, pattern)
+                    print >> sbuf, "Matched" if matched else "Not matched"
                 keepgoing = matched if matching else not matched
                 if not keepgoing:
                     break
-            if trace:
-                print >> sys.stderr
+            with recording(self, trace) as sbuf:
+                print >> sbuf
 
         self.assertTrue(matched if matching else not matched,
                         msg if msg else CMD_MSG(str, exe))
@@ -531,8 +602,8 @@
         self.assertTrue(inspect.ismethod(method),
                         name + "is a method name of object: " + str(obj))
         result = method()
-        if trace:
-            print >> sys.stderr, str(method) + ":",  result
+        with recording(self, trace) as sbuf:
+            print >> sbuf, str(method) + ":",  result
         return result
 
     def breakAfterLaunch(self, process, func, trace=False):
@@ -548,28 +619,28 @@
             # The stop reason of the thread should be breakpoint.
             thread = process.GetThreadAtIndex(0)
             SR = thread.GetStopReason()
-            if trace:
-                print >> sys.stderr, "StopReason =", StopReasonString(SR)
+            with recording(self, trace) as sbuf:
+                print >> sbuf, "StopReason =", StopReasonString(SR)
 
             if SR == StopReasonEnum("Breakpoint"):
                 frame = thread.GetFrameAtIndex(0)
                 name = frame.GetFunction().GetName()
-                if trace:
-                    print >> sys.stderr, "function =", name
+                with recording(self, trace) as sbuf:
+                    print >> sbuf, "function =", name
                 if (name == func):
                     # We got what we want; now break out of the loop.
                     return True
 
             # The inferior is in a transient state; continue the process.
             time.sleep(1.0)
-            if trace:
-                print >> sys.stderr, "Continuing the process:", process
+            with recording(self, trace) as sbuf:
+                print >> sbuf, "Continuing the process:", process
             process.Continue()
 
             count = count + 1
             if count == 15:
-                if trace:
-                    print >> sys.stderr, "Reached 15 iterations, giving up..."
+                with recording(self, trace) as sbuf:
+                    print >> sbuf, "Reached 15 iterations, giving up..."
                 # Enough iterations already, break out of the loop.
                 return False
 

Modified: lldb/trunk/test/lldbutil.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/lldbutil.py?rev=116552&r1=116551&r2=116552&view=diff
==============================================================================
--- lldb/trunk/test/lldbutil.py (original)
+++ lldb/trunk/test/lldbutil.py Thu Oct 14 20:18:29 2010
@@ -6,6 +6,12 @@
 import sys
 import StringIO
 
+################################################
+#                                              #
+# Iterator for lldb aggregate data structures. #
+#                                              #
+################################################
+
 def lldb_iter(obj, getsize, getelem):
     """
     A generator adaptor for lldb aggregate data structures.





More information about the lldb-commits mailing list