Rename WebKitTools to Tools
[WebKit-https.git] / Tools / Scripts / webkitpy / common / system / logtesting.py
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)