|
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) |