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