| OLD | NEW |
| (Empty) |
| 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 import logging | |
| 34 import unittest | |
| 35 | |
| 36 | |
| 37 class TestLogStream(object): | |
| 38 """Represents a file-like object for unit-testing logging. | |
| 39 | |
| 40 This is meant for passing to the logging.StreamHandler constructor. | |
| 41 Log messages captured by instances of this object can be tested | |
| 42 using self.assertMessages() below. | |
| 43 """ | |
| 44 | |
| 45 def __init__(self, test_case): | |
| 46 """Creates an instance. | |
| 47 | |
| 48 Args: | |
| 49 test_case: A unittest.TestCase instance. | |
| 50 """ | |
| 51 self._test_case = test_case | |
| 52 self.messages = [] # A list of log messages written to the stream. | |
| 53 | |
| 54 # Python documentation says that any object passed to the StreamHandler | |
| 55 # constructor should support write() and flush(). | |
| 56 # | |
| 57 # http://docs.python.org/library/logging.html#module-logging.handlers | |
| 58 | |
| 59 def write(self, message): | |
| 60 self.messages.append(message) | |
| 61 | |
| 62 def flush(self): | |
| 63 pass | |
| 64 | |
| 65 def assertMessages(self, messages): | |
| 66 """Asserts that the given messages match the logged messages.""" | |
| 67 self._test_case.assertEqual(messages, self.messages) | |
| 68 | |
| 69 | |
| 70 class LogTesting(object): | |
| 71 """Supports end-to-end unit-testing of log messages. | |
| 72 | |
| 73 Sample usage: | |
| 74 | |
| 75 class SampleTest(unittest.TestCase): | |
| 76 | |
| 77 def setUp(self): | |
| 78 self._log = LogTesting.setUp(self) # Turn logging on. | |
| 79 | |
| 80 def tearDown(self): | |
| 81 self._log.tearDown() # Turn off and reset logging. | |
| 82 | |
| 83 def test_logging_in_some_method(self): | |
| 84 call_some_method() # Contains calls to _log.info(), etc. | |
| 85 | |
| 86 # Check the resulting log messages. | |
| 87 self._log.assertMessages(["INFO: expected message #1", | |
| 88 "WARNING: expected message #2"]) | |
| 89 """ | |
| 90 | |
| 91 def __init__(self, test_stream, handler): | |
| 92 """Creates an instance. | |
| 93 | |
| 94 This method should never be called directly. Instances should | |
| 95 instead be created using the static setUp() method. | |
| 96 | |
| 97 Args: | |
| 98 test_stream: A TestLogStream instance. | |
| 99 handler: The handler added to the logger. | |
| 100 """ | |
| 101 self._test_stream = test_stream | |
| 102 self._handler = handler | |
| 103 | |
| 104 @staticmethod | |
| 105 def _getLogger(): | |
| 106 """Returns the logger being tested.""" | |
| 107 # It is possible we might want to return something other than | |
| 108 # the root logger in some special situation. For now, the | |
| 109 # root logger seems to suffice. | |
| 110 return logging.getLogger() | |
| 111 | |
| 112 @staticmethod | |
| 113 def setUp(test_case, logging_level=logging.INFO): | |
| 114 """Configures logging for unit testing. | |
| 115 | |
| 116 Configures the root logger to log to a testing log stream. | |
| 117 Only messages logged at or above the given level are logged | |
| 118 to the stream. Messages logged to the stream are formatted | |
| 119 in the following way, for example-- | |
| 120 | |
| 121 "INFO: This is a test log message." | |
| 122 | |
| 123 This method should normally be called in the setUp() method | |
| 124 of a unittest.TestCase. See the docstring of this class | |
| 125 for more details. | |
| 126 | |
| 127 Args: | |
| 128 test_case: A unittest.TestCase instance. | |
| 129 logging_level: An integer logging level that is the minimum level | |
| 130 of log messages you would like to test. | |
| 131 | |
| 132 Returns: | |
| 133 A LogTesting instance. | |
| 134 """ | |
| 135 stream = TestLogStream(test_case) | |
| 136 handler = logging.StreamHandler(stream) | |
| 137 handler.setLevel(logging_level) | |
| 138 formatter = logging.Formatter("%(levelname)s: %(message)s") | |
| 139 handler.setFormatter(formatter) | |
| 140 | |
| 141 # Notice that we only change the root logger by adding a handler | |
| 142 # to it. In particular, we do not reset its level using | |
| 143 # logger.setLevel(). This ensures that we have not interfered | |
| 144 # with how the code being tested may have configured the root | |
| 145 # logger. | |
| 146 logger = LogTesting._getLogger() | |
| 147 logger.setLevel(logging_level) | |
| 148 logger.addHandler(handler) | |
| 149 | |
| 150 return LogTesting(stream, handler) | |
| 151 | |
| 152 def tearDown(self): | |
| 153 """Resets logging.""" | |
| 154 logger = LogTesting._getLogger() | |
| 155 logger.removeHandler(self._handler) | |
| 156 | |
| 157 def messages(self): | |
| 158 """Returns the current list of log messages.""" | |
| 159 return self._test_stream.messages | |
| 160 | |
| 161 # FIXME: Add a clearMessages() method for cases where the caller | |
| 162 # deliberately doesn't want to assert every message. | |
| 163 | |
| 164 def assertMessages(self, messages): | |
| 165 """Asserts the current array of log messages, and clear its contents. | |
| 166 | |
| 167 We clear the log messages after asserting since they are no longer | |
| 168 needed after asserting. This serves two purposes: (1) it simplifies | |
| 169 the calling code when we want to check multiple logging calls in a | |
| 170 single test method, and (2) it lets us check in the tearDown() method | |
| 171 that there are no remaining log messages to be asserted. | |
| 172 | |
| 173 The latter ensures that no extra log messages are getting logged that | |
| 174 the caller might not be aware of or may have forgotten to check for. | |
| 175 This gets us a bit more mileage out of our tests without writing any | |
| 176 additional code. | |
| 177 | |
| 178 We want to clear the array of messages even in the case of | |
| 179 an Exception (e.g. an AssertionError). Otherwise, another | |
| 180 AssertionError can occur in the tearDown() because the | |
| 181 array might not have gotten emptied. | |
| 182 | |
| 183 Args: | |
| 184 messages: A list of log message strings. | |
| 185 """ | |
| 186 try: | |
| 187 self._test_stream.assertMessages(messages) | |
| 188 finally: | |
| 189 self._test_stream.messages = [] | |
| 190 | |
| 191 | |
| 192 class LoggingTestCase(unittest.TestCase): | |
| 193 | |
| 194 """Supports end-to-end unit-testing of log messages. | |
| 195 | |
| 196 This class needs to inherit from unittest.TestCase. Otherwise, the | |
| 197 setUp() and tearDown() methods will not get fired for test case classes | |
| 198 that inherit from this class -- even if the class inherits from *both* | |
| 199 unittest.TestCase and LoggingTestCase. | |
| 200 | |
| 201 Sample usage: | |
| 202 | |
| 203 class SampleTest(LoggingTestCase): | |
| 204 | |
| 205 def test_logging_in_some_method(self): | |
| 206 call_some_method() # Contains calls to _log.info(), etc. | |
| 207 | |
| 208 # Check the resulting log messages. | |
| 209 self.assertLog(["INFO: expected message #1", | |
| 210 "WARNING: expected message #2"]) | |
| 211 """ | |
| 212 | |
| 213 def setUp(self): | |
| 214 self._log = LogTesting.setUp(self) | |
| 215 | |
| 216 def tearDown(self): | |
| 217 self._log.tearDown() | |
| 218 | |
| 219 def logMessages(self): | |
| 220 """Return the current list of log messages.""" | |
| 221 return self._log.messages() | |
| 222 | |
| 223 # FIXME: Add a clearMessages() method for cases where the caller | |
| 224 # deliberately doesn't want to assert every message. | |
| 225 | |
| 226 # See the docstring for LogTesting.assertMessages() for an explanation | |
| 227 # of why we clear the array of messages after asserting its contents. | |
| 228 def assertLog(self, messages): | |
| 229 """Asserts the current array of log messages, and clear its contents.""" | |
| 230 self._log.assertMessages(messages) | |
| OLD | NEW |