PageRenderTime 26ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/third_party/blink/tools/blinkpy/common/system/log_testing.py

https://github.com/chromium/chromium
Python | 233 lines | 142 code | 11 blank | 80 comment | 0 complexity | aa279150efbe1175510475c7df88c4f5 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause
  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. """Supports the unit-testing of logging code.
  23. Provides support for unit-testing messages logged using the built-in
  24. logging module.
  25. Inherit from the LoggingTestCase class for basic testing needs. For
  26. more advanced needs (e.g. unit-testing methods that configure logging),
  27. see the TestLogStream class, and perhaps also the LogTesting class.
  28. """
  29. import logging
  30. import unittest
  31. # pylint: disable=invalid-name
  32. # Camel-case names were used here to match the style of the TestCase
  33. # methods. It would also be alright to change these to lowercase.
  34. class TestLogStream(object):
  35. """Represents a file-like object for unit-testing logging.
  36. This is meant for passing to the logging.StreamHandler constructor.
  37. Log messages captured by instances of this object can be tested
  38. using self.assertMessages() below.
  39. """
  40. def __init__(self, test_case):
  41. """Creates an instance.
  42. Args:
  43. test_case: A unittest.TestCase instance.
  44. """
  45. self._test_case = test_case
  46. self.messages = [] # A list of log messages written to the stream.
  47. # Python documentation says that any object passed to the StreamHandler
  48. # constructor should support write() and flush().
  49. #
  50. # http://docs.python.org/library/logging.html#module-logging.handlers
  51. def write(self, message):
  52. self.messages.append(message)
  53. def flush(self):
  54. pass
  55. def assertMessages(self, messages):
  56. """Asserts that the given messages match the logged messages."""
  57. self._test_case.assertEqual(sorted(messages), sorted(self.messages))
  58. class LogTesting(object):
  59. """Supports end-to-end unit-testing of log messages.
  60. Sample usage:
  61. class SampleTest(unittest.TestCase):
  62. def setUp(self):
  63. self._log = LogTesting.setUp(self) # Turn logging on.
  64. def tearDown(self):
  65. self._log.tearDown() # Turn off and reset logging.
  66. def test_logging_in_some_method(self):
  67. call_some_method() # Contains calls to _log.info(), etc.
  68. # Check the resulting log messages.
  69. self._log.assertMessages(["INFO: expected message #1",
  70. "WARNING: expected message #2"])
  71. """
  72. def __init__(self, test_stream, handler):
  73. """Creates an instance.
  74. This method should never be called directly. Instances should
  75. instead be created using the static setUp() method.
  76. Args:
  77. test_stream: A TestLogStream instance.
  78. handler: The handler added to the logger.
  79. """
  80. self._test_stream = test_stream
  81. self._handler = handler
  82. @staticmethod
  83. def _getLogger():
  84. """Returns the logger being tested."""
  85. # It is possible we might want to return something other than
  86. # the root logger in some special situation. For now, the
  87. # root logger seems to suffice.
  88. return logging.getLogger()
  89. @staticmethod
  90. def setUp(test_case, logging_level=logging.INFO):
  91. """Configures logging for unit testing.
  92. Configures the root logger to log to a testing log stream.
  93. Only messages logged at or above the given level are logged
  94. to the stream. Messages logged to the stream are formatted
  95. in the following way, for example--
  96. "INFO: This is a test log message."
  97. This method should normally be called in the setUp() method
  98. of a unittest.TestCase. See the docstring of this class
  99. for more details.
  100. Args:
  101. test_case: A unittest.TestCase instance.
  102. logging_level: An integer logging level that is the minimum level
  103. of log messages you would like to test.
  104. Returns:
  105. A LogTesting instance.
  106. """
  107. stream = TestLogStream(test_case)
  108. handler = logging.StreamHandler(stream)
  109. handler.setLevel(logging_level)
  110. formatter = logging.Formatter('%(levelname)s: %(message)s')
  111. handler.setFormatter(formatter)
  112. # Notice that we only change the root logger by adding a handler
  113. # to it. In particular, we do not reset its level using
  114. # logger.setLevel(). This ensures that we have not interfered
  115. # with how the code being tested may have configured the root
  116. # logger.
  117. logger = LogTesting._getLogger()
  118. logger.setLevel(logging_level)
  119. logger.addHandler(handler)
  120. return LogTesting(stream, handler)
  121. def tearDown(self):
  122. """Resets logging."""
  123. logger = LogTesting._getLogger()
  124. logger.removeHandler(self._handler)
  125. def messages(self):
  126. """Returns the current list of log messages."""
  127. return self._test_stream.messages
  128. def assertMessages(self, messages):
  129. """Asserts the current array of log messages, and clear its contents.
  130. We clear the log messages after asserting since they are no longer
  131. needed after asserting. This serves two purposes: (1) it simplifies
  132. the calling code when we want to check multiple logging calls in a
  133. single test method, and (2) it lets us check in the tearDown() method
  134. that there are no remaining log messages to be asserted.
  135. The latter ensures that no extra log messages are getting logged that
  136. the caller might not be aware of or may have forgotten to check for.
  137. This gets us a bit more mileage out of our tests without writing any
  138. additional code.
  139. We want to clear the array of messages even in the case of
  140. an Exception (e.g. an AssertionError). Otherwise, another
  141. AssertionError can occur in the tearDown() because the
  142. array might not have gotten emptied.
  143. Args:
  144. messages: A list of log message strings.
  145. """
  146. try:
  147. self._test_stream.assertMessages(messages)
  148. finally:
  149. self._test_stream.messages = []
  150. class LoggingTestCase(unittest.TestCase):
  151. """Supports end-to-end unit-testing of log messages.
  152. This class needs to inherit from unittest.TestCase. Otherwise, the
  153. setUp() and tearDown() methods will not get fired for test case classes
  154. that inherit from this class -- even if the class inherits from *both*
  155. unittest.TestCase and LoggingTestCase.
  156. Sample usage:
  157. class SampleTest(LoggingTestCase):
  158. def test_logging_in_some_method(self):
  159. call_some_method() # Contains calls to _log.info(), etc.
  160. # Check the resulting log messages.
  161. self.assertLog(["INFO: expected message #1",
  162. "WARNING: expected message #2"])
  163. """
  164. def setUp(self):
  165. self._log = LogTesting.setUp(self)
  166. def set_logging_level(self, logging_level):
  167. self._log = LogTesting.setUp(self, logging_level=logging_level)
  168. def tearDown(self):
  169. self._log.tearDown()
  170. def logMessages(self):
  171. """Return the current list of log messages."""
  172. return self._log.messages()
  173. # Note: If there's a case where the caller deliberately doesn't
  174. # want to assert every message, a clearMessages() method could
  175. # be added here.
  176. # See the docstring for LogTesting.assertMessages() for an explanation
  177. # of why we clear the array of messages after asserting its contents.
  178. def assertLog(self, messages):
  179. """Asserts the current array of log messages, and clear its contents."""
  180. self._log.assertMessages(messages)