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 |