diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index f2cb2ab63..762e5761d 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -244,17 +244,9 @@ def _compare_eq_iterable(left, right, verbose=0): # dynamic import to speedup pytest import difflib - try: - left_formatting = pprint.pformat(left).splitlines() - right_formatting = pprint.pformat(right).splitlines() - explanation = ["Full diff:"] - except Exception: - # hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling - # sorted() on a list would raise. See issue #718. - # As a workaround, the full diff is generated by using the repr() string of each item of each container. - left_formatting = sorted(repr(x) for x in left) - right_formatting = sorted(repr(x) for x in right) - explanation = ["Full diff (fallback to calling repr on each item):"] + left_formatting = pprint.pformat(left).splitlines() + right_formatting = pprint.pformat(right).splitlines() + explanation = ["Full diff:"] explanation.extend( line.strip() for line in difflib.ndiff(left_formatting, right_formatting) ) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 6f1275e61..2c964f473 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -10,6 +10,7 @@ from contextlib import contextmanager from inspect import Parameter from inspect import signature +import attr import py import _pytest @@ -29,10 +30,6 @@ def _format_args(func): return str(signature(func)) -isfunction = inspect.isfunction -isclass = inspect.isclass -# used to work around a python2 exception info leak -exc_clear = getattr(sys, "exc_clear", lambda: None) # The type of re.compile objects is not exposed in Python. REGEX_TYPE = type(re.compile("")) @@ -129,11 +126,15 @@ def getfuncargnames(function, is_method=False, cls=None): return arg_names -@contextmanager -def dummy_context_manager(): - """Context manager that does nothing, useful in situations where you might need an actual context manager or not - depending on some condition. Using this allow to keep the same code""" - yield +if sys.version_info < (3, 7): + + @contextmanager + def nullcontext(): + yield + + +else: + from contextlib import nullcontext # noqa def get_default_arg_names(function): @@ -191,6 +192,7 @@ def ascii_escaped(val): return _translate_non_printable(ret) +@attr.s class _PytestWrapper: """Dummy wrapper around a function object for internal use only. @@ -199,8 +201,7 @@ class _PytestWrapper: to issue warnings when the fixture function is called directly. """ - def __init__(self, obj): - self.obj = obj + obj = attr.ib() def get_real_func(obj): @@ -280,7 +281,7 @@ def safe_getattr(object, name, default): def safe_isclass(obj): """Ignore any exception via isinstance on Python 3.""" try: - return isclass(obj) + return inspect.isclass(obj) except Exception: return False @@ -304,8 +305,8 @@ def _setup_collect_fakemodule(): pytest.collect = ModuleType("pytest.collect") pytest.collect.__all__ = [] # used for setns - for attr in COLLECT_FAKEMODULE_ATTRIBUTES: - setattr(pytest.collect, attr, getattr(pytest, attr)) + for attr_name in COLLECT_FAKEMODULE_ATTRIBUTES: + setattr(pytest.collect, attr_name, getattr(pytest, attr_name)) class CaptureIO(io.TextIOWrapper): diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 2f9b10b85..0a792d11d 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -16,7 +16,6 @@ from _pytest._code.code import FormattedExcinfo from _pytest._code.code import TerminalRepr from _pytest.compat import _format_args from _pytest.compat import _PytestWrapper -from _pytest.compat import exc_clear from _pytest.compat import FuncargnamesCompatAttr from _pytest.compat import get_real_func from _pytest.compat import get_real_method @@ -25,7 +24,6 @@ from _pytest.compat import getfuncargnames from _pytest.compat import getimfunc from _pytest.compat import getlocation from _pytest.compat import is_generator -from _pytest.compat import isclass from _pytest.compat import NOTSET from _pytest.compat import safe_getattr from _pytest.deprecated import FIXTURE_FUNCTION_CALL @@ -572,10 +570,6 @@ class FixtureRequest(FuncargnamesCompatAttr): # check if a higher-level scoped fixture accesses a lower level one subrequest._check_scope(argname, self.scope, scope) - - # clear sys.exc_info before invoking the fixture (python bug?) - # if it's not explicitly cleared it will leak into the call - exc_clear() try: # call the fixture function fixturedef.execute(request=subrequest) @@ -970,7 +964,7 @@ class FixtureFunctionMarker: name = attr.ib(default=None) def __call__(self, function): - if isclass(function): + if inspect.isclass(function): raise ValueError("class fixtures not supported (maybe in the future)") if getattr(function, "_pytestfixturefunction", False): diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index df18b81cd..2861baefd 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -6,7 +6,7 @@ from contextlib import contextmanager import py import pytest -from _pytest.compat import dummy_context_manager +from _pytest.compat import nullcontext from _pytest.config import create_terminal_writer from _pytest.pathlib import Path @@ -436,7 +436,7 @@ class LoggingPlugin: self.log_cli_handler = None - self.live_logs_context = lambda: dummy_context_manager() + self.live_logs_context = lambda: nullcontext() # Note that the lambda for the live_logs_context is needed because # live_logs_context can otherwise not be entered multiple times due # to limitations of contextlib.contextmanager. @@ -676,7 +676,7 @@ class _LiveLoggingStreamHandler(logging.StreamHandler): ctx_manager = ( self.capture_manager.global_and_fixture_disabled() if self.capture_manager - else dummy_context_manager() + else nullcontext() ) with ctx_manager: if not self._first_record_emitted: diff --git a/src/_pytest/python.py b/src/_pytest/python.py index b4d8f5ae0..66d853060 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -23,8 +23,6 @@ from _pytest.compat import getfslineno from _pytest.compat import getimfunc from _pytest.compat import getlocation from _pytest.compat import is_generator -from _pytest.compat import isclass -from _pytest.compat import isfunction from _pytest.compat import NOTSET from _pytest.compat import REGEX_TYPE from _pytest.compat import safe_getattr @@ -207,7 +205,7 @@ def pytest_pycollect_makeitem(collector, name, obj): # We need to try and unwrap the function if it's a functools.partial # or a funtools.wrapped. # We musn't if it's been wrapped with mock.patch (python 2 only) - if not (isfunction(obj) or isfunction(get_real_func(obj))): + if not (inspect.isfunction(obj) or inspect.isfunction(get_real_func(obj))): filename, lineno = getfslineno(obj) warnings.warn_explicit( message=PytestCollectionWarning( @@ -1172,8 +1170,6 @@ def _idval(val, argname, idx, idfn, item, config): # See issue https://github.com/pytest-dev/pytest/issues/2169 msg = "{}: error raised while trying to determine id of parameter '{}' at position {}\n" msg = msg.format(item.nodeid, argname, idx) - # we only append the exception type and message because on Python 2 reraise does nothing - msg += " {}: {}\n".format(type(e).__name__, e) raise ValueError(msg) from e elif config: hook_id = config.hook.pytest_make_parametrize_id( @@ -1190,7 +1186,7 @@ def _idval(val, argname, idx, idfn, item, config): return ascii_escaped(val.pattern) elif enum is not None and isinstance(val, enum.Enum): return str(val) - elif (isclass(val) or isfunction(val)) and hasattr(val, "__name__"): + elif (inspect.isclass(val) or inspect.isfunction(val)) and hasattr(val, "__name__"): return val.__name__ return str(argname) + str(idx) diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 011181a40..374fa598f 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -1,3 +1,4 @@ +import inspect import math import pprint import sys @@ -13,27 +14,12 @@ from more_itertools.more import always_iterable import _pytest._code from _pytest import deprecated -from _pytest.compat import isclass from _pytest.compat import STRING_TYPES from _pytest.outcomes import fail BASE_TYPE = (type, STRING_TYPES) -def _cmp_raises_type_error(self, other): - """__cmp__ implementation which raises TypeError. Used - by Approx base classes to implement only == and != and raise a - TypeError for other comparisons. - - Needed in Python 2 only, Python 3 all it takes is not implementing the - other operators at all. - """ - __tracebackhide__ = True - raise TypeError( - "Comparison operators other than == and != not supported by approx objects" - ) - - def _non_numeric_type_error(value, at): at_str = " at {}".format(at) if at else "" return TypeError( @@ -658,7 +644,9 @@ def raises(expected_exception, *args, **kwargs): """ __tracebackhide__ = True - for exc in filterfalse(isclass, always_iterable(expected_exception, BASE_TYPE)): + for exc in filterfalse( + inspect.isclass, always_iterable(expected_exception, BASE_TYPE) + ): msg = ( "exceptions must be old-style classes or" " derived from BaseException, not %s" diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 4702f0b57..6b26be72b 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -506,8 +506,8 @@ class TestMetafunc: result = testdir.runpytest() result.stdout.fnmatch_lines( [ - "*test_foo: error raised while trying to determine id of parameter 'arg' at position 0", "*Exception: bad ids", + "*test_foo: error raised while trying to determine id of parameter 'arg' at position 0", ] )