Skip to content

bpo-40275: Avoid importing logging in test.support #19601

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 25, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1460,11 +1460,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__`
Expand Down
34 changes: 0 additions & 34 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import importlib
import importlib.util
import locale
import logging.handlers
import nntplib
import os
import platform
Expand Down Expand Up @@ -109,8 +108,6 @@
"bind_unix_socket",
# processes
'temp_umask', "reap_children",
# logging
"TestHandler",
# threads
"threading_setup", "threading_cleanup", "reap_threads", "start_threads",
# miscellaneous
Expand Down Expand Up @@ -2522,37 +2519,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):

Expand Down
29 changes: 29 additions & 0 deletions Lib/test/support/logging_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import logging.handlers

class TestHandler(logging.handlers.BufferingHandler):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only used in test_logging. Why not simply moving this class into test_logging?

We don't provide any backward compatibility warranty for test.support. It should not be used outside CPython code base.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is what I did originally. But then move it into a separate submodule on the request of @vsajip (logging maintainer, who added this class 10 years ago).

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
5 changes: 3 additions & 2 deletions Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import tempfile
from test.support.script_helper import assert_python_ok, assert_python_failure
from test import support
from test.support.logging_helper import TestHandler
import textwrap
import threading
import time
Expand Down Expand Up @@ -3523,7 +3524,7 @@ def test_formatting(self):
@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:
Expand All @@ -3539,7 +3540,7 @@ def test_queue_listener(self):

# 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)
Expand Down
44 changes: 22 additions & 22 deletions Lib/unittest/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import sys
import functools
import difflib
import logging
import pprint
import re
import warnings
Expand Down Expand Up @@ -300,26 +299,6 @@ def __exit__(self, exc_type, exc_value, tb):
_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()."""

Expand All @@ -328,18 +307,39 @@ class _AssertLogsContext(_BaseTestCaseContext):
def __init__(self, test_case, logger_name, level):
_BaseTestCaseContext.__init__(self, test_case)
self.logger_name = logger_name
import logging
self.logging = logging
if level:
self.level = logging._nameToLevel.get(level, level)
else:
self.level = logging.INFO
self.msg = None

def __enter__(self):
logging = self.logging
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)

class _CapturingHandler(logging.Handler):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike defining a new _CapturingHandler class each time _AssertLogsContext context manager is used :-( Maybe a lazy import could be used, something similar to PR #19600?

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

handler = _CapturingHandler()
handler.setFormatter(formatter)
self.watcher = handler.watcher
Expand All @@ -361,7 +361,7 @@ def __exit__(self, exc_type, exc_value, tb):
if len(self.watcher.records) == 0:
self._raiseFailure(
"no logs of level {} or higher triggered on {}"
.format(logging.getLevelName(self.level), self.logger.name))
.format(self.logging.getLevelName(self.level), self.logger.name))


class _OrderedChainMap(collections.ChainMap):
Expand Down