diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2020-04-25 08:35:18 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-25 08:35:18 (GMT) |
commit | 515fce4fc4bb0d2db97b17df275cf90640017f56 (patch) | |
tree | 472b80622f4f7e7a8307c3f4db4122eef549dbd9 | |
parent | 16994912c93e8e5db7365d48b75d67d3f70dd7b2 (diff) | |
download | cpython-515fce4fc4bb0d2db97b17df275cf90640017f56.zip cpython-515fce4fc4bb0d2db97b17df275cf90640017f56.tar.gz cpython-515fce4fc4bb0d2db97b17df275cf90640017f56.tar.bz2 |
bpo-40275: Avoid importing logging in test.support (GH-19601)
Import logging lazily in assertLogs() in unittest.
Move TestHandler from test.support to logging_helper.
-rw-r--r-- | Doc/library/test.rst | 5 | ||||
-rw-r--r-- | Lib/test/support/__init__.py | 34 | ||||
-rw-r--r-- | Lib/test/support/logging_helper.py | 29 | ||||
-rw-r--r-- | Lib/test/test_logging.py | 5 | ||||
-rw-r--r-- | Lib/unittest/_log.py | 69 | ||||
-rw-r--r-- | Lib/unittest/case.py | 70 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2020-04-20-19-06-55.bpo-40275.9UcN2g.rst | 2 |
7 files changed, 105 insertions, 109 deletions
diff --git a/Doc/library/test.rst b/Doc/library/test.rst index 1e6b111..c2aaecc 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1410,11 +1410,6 @@ The :mod:`test.support` module defines the following classes: Run *test* and return the result. -.. class:: TestHandler(logging.handlers.BufferingHandler) - - Class for logging support. - - .. class:: FakePath(path) Simple :term:`path-like object`. It implements the :meth:`__fspath__` diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 15cf45d..f48decc 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -15,7 +15,6 @@ import hashlib import importlib import importlib.util import locale -import logging.handlers import os import platform import re @@ -99,8 +98,6 @@ __all__ = [ "open_urlresource", # processes 'temp_umask', "reap_children", - # logging - "TestHandler", # threads "threading_setup", "threading_cleanup", "reap_threads", "start_threads", # miscellaneous @@ -2368,37 +2365,6 @@ def optim_args_from_interpreter_flags(): optimization settings in sys.flags.""" return subprocess._optim_args_from_interpreter_flags() -#============================================================ -# Support for assertions about logging. -#============================================================ - -class TestHandler(logging.handlers.BufferingHandler): - def __init__(self, matcher): - # BufferingHandler takes a "capacity" argument - # so as to know when to flush. As we're overriding - # shouldFlush anyway, we can set a capacity of zero. - # You can call flush() manually to clear out the - # buffer. - logging.handlers.BufferingHandler.__init__(self, 0) - self.matcher = matcher - - def shouldFlush(self): - return False - - def emit(self, record): - self.format(record) - self.buffer.append(record.__dict__) - - def matches(self, **kwargs): - """ - Look for a saved dict whose keys/values match the supplied arguments. - """ - result = False - for d in self.buffer: - if self.matcher.matches(d, **kwargs): - result = True - break - return result class Matcher(object): diff --git a/Lib/test/support/logging_helper.py b/Lib/test/support/logging_helper.py new file mode 100644 index 0000000..12fcca4 --- /dev/null +++ b/Lib/test/support/logging_helper.py @@ -0,0 +1,29 @@ +import logging.handlers + +class TestHandler(logging.handlers.BufferingHandler): + def __init__(self, matcher): + # BufferingHandler takes a "capacity" argument + # so as to know when to flush. As we're overriding + # shouldFlush anyway, we can set a capacity of zero. + # You can call flush() manually to clear out the + # buffer. + logging.handlers.BufferingHandler.__init__(self, 0) + self.matcher = matcher + + def shouldFlush(self): + return False + + def emit(self, record): + self.format(record) + self.buffer.append(record.__dict__) + + def matches(self, **kwargs): + """ + Look for a saved dict whose keys/values match the supplied arguments. + """ + result = False + for d in self.buffer: + if self.matcher.matches(d, **kwargs): + result = True + break + return result diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 241ed2c..e1d0eb8 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -44,6 +44,7 @@ import tempfile from test.support.script_helper import assert_python_ok, assert_python_failure from test import support from test.support import socket_helper +from test.support.logging_helper import TestHandler import textwrap import threading import time @@ -3524,7 +3525,7 @@ class QueueHandlerTest(BaseTest): @unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'), 'logging.handlers.QueueListener required for this test') def test_queue_listener(self): - handler = support.TestHandler(support.Matcher()) + handler = TestHandler(support.Matcher()) listener = logging.handlers.QueueListener(self.queue, handler) listener.start() try: @@ -3540,7 +3541,7 @@ class QueueHandlerTest(BaseTest): # Now test with respect_handler_level set - handler = support.TestHandler(support.Matcher()) + handler = TestHandler(support.Matcher()) handler.setLevel(logging.CRITICAL) listener = logging.handlers.QueueListener(self.queue, handler, respect_handler_level=True) diff --git a/Lib/unittest/_log.py b/Lib/unittest/_log.py new file mode 100644 index 0000000..94e7e75 --- /dev/null +++ b/Lib/unittest/_log.py @@ -0,0 +1,69 @@ +import logging +import collections + +from .case import _BaseTestCaseContext + + +_LoggingWatcher = collections.namedtuple("_LoggingWatcher", + ["records", "output"]) + +class _CapturingHandler(logging.Handler): + """ + A logging handler capturing all (raw and formatted) logging output. + """ + + def __init__(self): + logging.Handler.__init__(self) + self.watcher = _LoggingWatcher([], []) + + def flush(self): + pass + + def emit(self, record): + self.watcher.records.append(record) + msg = self.format(record) + self.watcher.output.append(msg) + + +class _AssertLogsContext(_BaseTestCaseContext): + """A context manager used to implement TestCase.assertLogs().""" + + LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s" + + def __init__(self, test_case, logger_name, level): + _BaseTestCaseContext.__init__(self, test_case) + self.logger_name = logger_name + if level: + self.level = logging._nameToLevel.get(level, level) + else: + self.level = logging.INFO + self.msg = None + + def __enter__(self): + if isinstance(self.logger_name, logging.Logger): + logger = self.logger = self.logger_name + else: + logger = self.logger = logging.getLogger(self.logger_name) + formatter = logging.Formatter(self.LOGGING_FORMAT) + handler = _CapturingHandler() + handler.setFormatter(formatter) + self.watcher = handler.watcher + self.old_handlers = logger.handlers[:] + self.old_level = logger.level + self.old_propagate = logger.propagate + logger.handlers = [handler] + logger.setLevel(self.level) + logger.propagate = False + return handler.watcher + + def __exit__(self, exc_type, exc_value, tb): + self.logger.handlers = self.old_handlers + self.logger.propagate = self.old_propagate + self.logger.setLevel(self.old_level) + if exc_type is not None: + # let unexpected exceptions pass through + return False + if len(self.watcher.records) == 0: + self._raiseFailure( + "no logs of level {} or higher triggered on {}" + .format(logging.getLevelName(self.level), self.logger.name)) diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index d0ee561..f8bc865 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -3,7 +3,6 @@ import sys import functools import difflib -import logging import pprint import re import warnings @@ -297,73 +296,6 @@ class _AssertWarnsContext(_AssertRaisesBaseContext): -_LoggingWatcher = collections.namedtuple("_LoggingWatcher", - ["records", "output"]) - - -class _CapturingHandler(logging.Handler): - """ - A logging handler capturing all (raw and formatted) logging output. - """ - - def __init__(self): - logging.Handler.__init__(self) - self.watcher = _LoggingWatcher([], []) - - def flush(self): - pass - - def emit(self, record): - self.watcher.records.append(record) - msg = self.format(record) - self.watcher.output.append(msg) - - - -class _AssertLogsContext(_BaseTestCaseContext): - """A context manager used to implement TestCase.assertLogs().""" - - LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s" - - def __init__(self, test_case, logger_name, level): - _BaseTestCaseContext.__init__(self, test_case) - self.logger_name = logger_name - if level: - self.level = logging._nameToLevel.get(level, level) - else: - self.level = logging.INFO - self.msg = None - - def __enter__(self): - if isinstance(self.logger_name, logging.Logger): - logger = self.logger = self.logger_name - else: - logger = self.logger = logging.getLogger(self.logger_name) - formatter = logging.Formatter(self.LOGGING_FORMAT) - handler = _CapturingHandler() - handler.setFormatter(formatter) - self.watcher = handler.watcher - self.old_handlers = logger.handlers[:] - self.old_level = logger.level - self.old_propagate = logger.propagate - logger.handlers = [handler] - logger.setLevel(self.level) - logger.propagate = False - return handler.watcher - - def __exit__(self, exc_type, exc_value, tb): - self.logger.handlers = self.old_handlers - self.logger.propagate = self.old_propagate - self.logger.setLevel(self.old_level) - if exc_type is not None: - # let unexpected exceptions pass through - return False - if len(self.watcher.records) == 0: - self._raiseFailure( - "no logs of level {} or higher triggered on {}" - .format(logging.getLevelName(self.level), self.logger.name)) - - class _OrderedChainMap(collections.ChainMap): def __iter__(self): seen = set() @@ -854,6 +786,8 @@ class TestCase(object): self.assertEqual(cm.output, ['INFO:foo:first message', 'ERROR:foo.bar:second message']) """ + # Lazy import to avoid importing logging if it is not needed. + from ._log import _AssertLogsContext return _AssertLogsContext(self, logger, level) def _getAssertEqualityFunc(self, first, second): diff --git a/Misc/NEWS.d/next/Library/2020-04-20-19-06-55.bpo-40275.9UcN2g.rst b/Misc/NEWS.d/next/Library/2020-04-20-19-06-55.bpo-40275.9UcN2g.rst new file mode 100644 index 0000000..09e0a97 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-04-20-19-06-55.bpo-40275.9UcN2g.rst @@ -0,0 +1,2 @@ +The :mod:`logging` package is now imported lazily in :mod:`unittest` only +when the :meth:`~unittest.TestCase.assertLogs` assertion is used. |