WebKitTools/Scripts/webkitpy/common/system/logtesting.py
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     1 # Copyright (C) 2010 Chris Jerdonek (cjerdonek@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.assertEquals(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)