Home | History | Annotate | Download | only in system
      1 # Copyright (C) 2010 Chris Jerdonek (cjerdonek (at] webkit.org)
      2 #
      3 # Redistribution and use in source and binary forms, with or without
      4 # modification, are permitted provided that the following conditions
      5 # are met:
      6 # 1.  Redistributions of source code must retain the above copyright
      7 #     notice, this list of conditions and the following disclaimer.
      8 # 2.  Redistributions in binary form must reproduce the above copyright
      9 #     notice, this list of conditions and the following disclaimer in the
     10 #     documentation and/or other materials provided with the distribution.
     11 #
     12 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
     13 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     14 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     15 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
     16 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     17 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
     18 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
     19 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     20 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     21 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     22 
     23 """Supports the unit-testing of logging code.
     24 
     25 Provides support for unit-testing messages logged using the built-in
     26 logging module.
     27 
     28 Inherit from the LoggingTestCase class for basic testing needs.  For
     29 more advanced needs (e.g. unit-testing methods that configure logging),
     30 see the TestLogStream class, and perhaps also the LogTesting class.
     31 
     32 """
     33 
     34 import logging
     35 import unittest
     36 
     37 
     38 class TestLogStream(object):
     39 
     40     """Represents a file-like object for unit-testing logging.
     41 
     42     This is meant for passing to the logging.StreamHandler constructor.
     43     Log messages captured by instances of this object can be tested
     44     using self.assertMessages() below.
     45 
     46     """
     47 
     48     def __init__(self, test_case):
     49         """Create an instance.
     50 
     51         Args:
     52           test_case: A unittest.TestCase instance.
     53 
     54         """
     55         self._test_case = test_case
     56         self.messages = []
     57         """A list of log messages written to the stream."""
     58 
     59     # Python documentation says that any object passed to the StreamHandler
     60     # constructor should support write() and flush():
     61     #
     62     # http://docs.python.org/library/logging.html#module-logging.handlers
     63     def write(self, message):
     64         self.messages.append(message)
     65 
     66     def flush(self):
     67         pass
     68 
     69     def assertMessages(self, messages):
     70         """Assert that the given messages match the logged messages.
     71 
     72         messages: A list of log message strings.
     73 
     74         """
     75         self._test_case.assertEqual(messages, self.messages)
     76 
     77 
     78 class LogTesting(object):
     79 
     80     """Supports end-to-end unit-testing of log messages.
     81 
     82         Sample usage:
     83 
     84           class SampleTest(unittest.TestCase):
     85 
     86               def setUp(self):
     87                   self._log = LogTesting.setUp(self)  # Turn logging on.
     88 
     89               def tearDown(self):
     90                   self._log.tearDown()  # Turn off and reset logging.
     91 
     92               def test_logging_in_some_method(self):
     93                   call_some_method()  # Contains calls to _log.info(), etc.
     94 
     95                   # Check the resulting log messages.
     96                   self._log.assertMessages(["INFO: expected message #1",
     97                                           "WARNING: expected message #2"])
     98 
     99     """
    100 
    101     def __init__(self, test_stream, handler):
    102         """Create an instance.
    103 
    104         This method should never be called directly.  Instances should
    105         instead be created using the static setUp() method.
    106 
    107         Args:
    108           test_stream: A TestLogStream instance.
    109           handler: The handler added to the logger.
    110 
    111         """
    112         self._test_stream = test_stream
    113         self._handler = handler
    114 
    115     @staticmethod
    116     def _getLogger():
    117         """Return the logger being tested."""
    118         # It is possible we might want to return something other than
    119         # the root logger in some special situation.  For now, the
    120         # root logger seems to suffice.
    121         return logging.getLogger()
    122 
    123     @staticmethod
    124     def setUp(test_case, logging_level=logging.INFO):
    125         """Configure logging for unit testing.
    126 
    127         Configures the root logger to log to a testing log stream.
    128         Only messages logged at or above the given level are logged
    129         to the stream.  Messages logged to the stream are formatted
    130         in the following way, for example--
    131 
    132         "INFO: This is a test log message."
    133 
    134         This method should normally be called in the setUp() method
    135         of a unittest.TestCase.  See the docstring of this class
    136         for more details.
    137 
    138         Returns:
    139           A LogTesting instance.
    140 
    141         Args:
    142           test_case: A unittest.TestCase instance.
    143           logging_level: An integer logging level that is the minimum level
    144                          of log messages you would like to test.
    145 
    146         """
    147         stream = TestLogStream(test_case)
    148         handler = logging.StreamHandler(stream)
    149         handler.setLevel(logging_level)
    150         formatter = logging.Formatter("%(levelname)s: %(message)s")
    151         handler.setFormatter(formatter)
    152 
    153         # Notice that we only change the root logger by adding a handler
    154         # to it.  In particular, we do not reset its level using
    155         # logger.setLevel().  This ensures that we have not interfered
    156         # with how the code being tested may have configured the root
    157         # logger.
    158         logger = LogTesting._getLogger()
    159         logger.addHandler(handler)
    160 
    161         return LogTesting(stream, handler)
    162 
    163     def tearDown(self):
    164         """Assert there are no remaining log messages, and reset logging.
    165 
    166         This method asserts that there are no more messages in the array of
    167         log messages, and then restores logging to its original state.
    168         This method should normally be called in the tearDown() method of a
    169         unittest.TestCase.  See the docstring of this class for more details.
    170 
    171         """
    172         self.assertMessages([])
    173         logger = LogTesting._getLogger()
    174         logger.removeHandler(self._handler)
    175 
    176     def messages(self):
    177         """Return the current list of log messages."""
    178         return self._test_stream.messages
    179 
    180     # FIXME: Add a clearMessages() method for cases where the caller
    181     #        deliberately doesn't want to assert every message.
    182 
    183     # We clear the log messages after asserting since they are no longer
    184     # needed after asserting.  This serves two purposes: (1) it simplifies
    185     # the calling code when we want to check multiple logging calls in a
    186     # single test method, and (2) it lets us check in the tearDown() method
    187     # that there are no remaining log messages to be asserted.
    188     #
    189     # The latter ensures that no extra log messages are getting logged that
    190     # the caller might not be aware of or may have forgotten to check for.
    191     # This gets us a bit more mileage out of our tests without writing any
    192     # additional code.
    193     def assertMessages(self, messages):
    194         """Assert the current array of log messages, and clear its contents.
    195 
    196         Args:
    197           messages: A list of log message strings.
    198 
    199         """
    200         try:
    201             self._test_stream.assertMessages(messages)
    202         finally:
    203             # We want to clear the array of messages even in the case of
    204             # an Exception (e.g. an AssertionError).  Otherwise, another
    205             # AssertionError can occur in the tearDown() because the
    206             # array might not have gotten emptied.
    207             self._test_stream.messages = []
    208 
    209 
    210 # This class needs to inherit from unittest.TestCase.  Otherwise, the
    211 # setUp() and tearDown() methods will not get fired for test case classes
    212 # that inherit from this class -- even if the class inherits from *both*
    213 # unittest.TestCase and LoggingTestCase.
    214 #
    215 # FIXME: Rename this class to LoggingTestCaseBase to be sure that
    216 #        the unittest module does not interpret this class as a unittest
    217 #        test case itself.
    218 class LoggingTestCase(unittest.TestCase):
    219 
    220     """Supports end-to-end unit-testing of log messages.
    221 
    222         Sample usage:
    223 
    224           class SampleTest(LoggingTestCase):
    225 
    226               def test_logging_in_some_method(self):
    227                   call_some_method()  # Contains calls to _log.info(), etc.
    228 
    229                   # Check the resulting log messages.
    230                   self.assertLog(["INFO: expected message #1",
    231                                   "WARNING: expected message #2"])
    232 
    233     """
    234 
    235     def setUp(self):
    236         self._log = LogTesting.setUp(self)
    237 
    238     def tearDown(self):
    239         self._log.tearDown()
    240 
    241     def logMessages(self):
    242         """Return the current list of log messages."""
    243         return self._log.messages()
    244 
    245     # FIXME: Add a clearMessages() method for cases where the caller
    246     #        deliberately doesn't want to assert every message.
    247 
    248     # See the code comments preceding LogTesting.assertMessages() for
    249     # an explanation of why we clear the array of messages after
    250     # asserting its contents.
    251     def assertLog(self, messages):
    252         """Assert the current array of log messages, and clear its contents.
    253 
    254         Args:
    255           messages: A list of log message strings.
    256 
    257         """
    258         self._log.assertMessages(messages)
    259