diff options
| -rwxr-xr-x | lldb/test/dotest.py | 24 | ||||
| -rw-r--r-- | lldb/test/foundation/TestObjCMethods.py | 2 | ||||
| -rw-r--r-- | lldb/test/lldbtest.py | 141 | ||||
| -rw-r--r-- | lldb/test/lldbutil.py | 6 | 
4 files changed, 137 insertions, 36 deletions
diff --git a/lldb/test/dotest.py b/lldb/test/dotest.py index 4afb769fd3c..5172ac57dd8 100755 --- a/lldb/test/dotest.py +++ b/lldb/test/dotest.py @@ -597,7 +597,29 @@ for ia in range(len(archs) if iterArchs else 1):                              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. diff --git a/lldb/test/foundation/TestObjCMethods.py b/lldb/test/foundation/TestObjCMethods.py index b9ac8a5828c..34b5f6984f9 100644 --- a/lldb/test/foundation/TestObjCMethods.py +++ b/lldb/test/foundation/TestObjCMethods.py @@ -23,6 +23,7 @@ class FoundationTestCase(TestBase):          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 @@ class FoundationTestCase(TestBase):          self.buildDsym()          self.data_type_and_expr_objc() +    @unittest2.skip("Skip due to deadlock?")      @unittest2.expectedFailure      # rdar://problem/8542091      # rdar://problem/8492646 diff --git a/lldb/test/lldbtest.py b/lldb/test/lldbtest.py index 373f8bfc08b..c64e666e601 100644 --- a/lldb/test/lldbtest.py +++ b/lldb/test/lldbtest.py @@ -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 @@ def pointer_size():      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 @@ class TestBase(unittest2.TestCase):          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 @@ class TestBase(unittest2.TestCase):              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 @@ class TestBase(unittest2.TestCase):          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 @@ class TestBase(unittest2.TestCase):              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 @@ class TestBase(unittest2.TestCase):          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 @@ class TestBase(unittest2.TestCase):              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 @@ class TestBase(unittest2.TestCase):          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 @@ class TestBase(unittest2.TestCase):              # 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 diff --git a/lldb/test/lldbutil.py b/lldb/test/lldbutil.py index 90a0b40f608..d7a1e32a791 100644 --- a/lldb/test/lldbutil.py +++ b/lldb/test/lldbutil.py @@ -6,6 +6,12 @@ import lldb  import sys  import StringIO +################################################ +#                                              # +# Iterator for lldb aggregate data structures. # +#                                              # +################################################ +  def lldb_iter(obj, getsize, getelem):      """      A generator adaptor for lldb aggregate data structures.  | 

