diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 2c4361407..be412afd3 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -417,7 +417,14 @@ class PytestPluginManager(PluginManager): PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST ) - warnings.warn(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST) + from _pytest.warning_types import RemovedInPytest4Warning + + warnings.warn_explicit( + PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST, + RemovedInPytest4Warning, + filename=str(conftestpath), + lineno=0, + ) except Exception: raise ConftestImportFailure(conftestpath, sys.exc_info()) diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 237991c56..a77ebf6c8 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -63,7 +63,7 @@ METAFUNC_ADD_CALL = ( "Please use Metafunc.parametrize instead." ) -PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = RemovedInPytest4Warning( +PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = ( "Defining pytest_plugins in a non-top-level conftest is deprecated, " "because it affects the entire directory tree in a non-explicit way.\n" "Please move it to the top level conftest file instead." diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 8700bd82d..0e0ba96e5 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -94,7 +94,7 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): if legacy_force_tuple: argval = (argval,) - if newmarks: + if newmarks and item is not None: item.std_warn(MARK_PARAMETERSET_UNPACKING) return cls(argval, marks=newmarks, id=None) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index f20bd582f..4a7faff07 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -44,7 +44,7 @@ from _pytest.mark.structures import ( get_unpacked_marks, normalize_mark_list, ) -from _pytest.warning_types import PytestUsageWarning +from _pytest.warning_types import PytestUsageWarning, RemovedInPytest4Warning # relative paths that we use to filter traceback entries from appearing to the user; # see filter_traceback @@ -982,7 +982,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): arg_values_types = self._resolve_arg_value_types(argnames, indirect) - ids = self._resolve_arg_ids(argnames, ids, parameters) + ids = self._resolve_arg_ids(argnames, ids, parameters, item=self.definition) scopenum = scope2index(scope, descr="call to {}".format(self.parametrize)) @@ -1005,13 +1005,14 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): newcalls.append(newcallspec) self._calls = newcalls - def _resolve_arg_ids(self, argnames, ids, parameters): + def _resolve_arg_ids(self, argnames, ids, parameters, item): """Resolves the actual ids for the given argnames, based on the ``ids`` parameter given to ``parametrize``. :param List[str] argnames: list of argument names passed to ``parametrize()``. :param ids: the ids parameter of the parametrized call (see docs). :param List[ParameterSet] parameters: the list of parameter values, same size as ``argnames``. + :param Item item: the item that generated this parametrized call. :rtype: List[str] :return: the list of ids for each argname given """ @@ -1032,7 +1033,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): raise ValueError( msg % (saferepr(id_value), type(id_value).__name__) ) - ids = idmaker(argnames, parameters, idfn, ids, self.config) + ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item) return ids def _resolve_arg_value_types(self, argnames, indirect): @@ -1158,21 +1159,22 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect): return "function" -def _idval(val, argname, idx, idfn, config=None): +def _idval(val, argname, idx, idfn, config=None, item=None): if idfn: s = None try: s = idfn(val) - except Exception: + except Exception as e: # See issue https://github.com/pytest-dev/pytest/issues/2169 - import warnings - - msg = ( - "Raised while trying to determine id of parameter %s at position %d." - % (argname, idx) - ) - msg += "\nUpdate your code as this will raise an error in pytest-4.0." - warnings.warn(msg, DeprecationWarning) + if item is not None: + # should really be None only when unit-testing this function! + msg = ( + "While trying to determine id of parameter {} at position " + "{} the following exception was raised:\n".format(argname, idx) + ) + msg += " {}: {}\n".format(type(e).__name__, e) + msg += "This warning will be an error error in pytest-4.0." + item.std_warn(msg, RemovedInPytest4Warning) if s: return ascii_escaped(s) @@ -1196,12 +1198,12 @@ def _idval(val, argname, idx, idfn, config=None): return str(argname) + str(idx) -def _idvalset(idx, parameterset, argnames, idfn, ids, config=None): +def _idvalset(idx, parameterset, argnames, idfn, ids, config=None, item=None): if parameterset.id is not None: return parameterset.id if ids is None or (idx >= len(ids) or ids[idx] is None): this_id = [ - _idval(val, argname, idx, idfn, config) + _idval(val, argname, idx, idfn, config, item) for val, argname in zip(parameterset.values, argnames) ] return "-".join(this_id) @@ -1209,9 +1211,9 @@ def _idvalset(idx, parameterset, argnames, idfn, ids, config=None): return ascii_escaped(ids[idx]) -def idmaker(argnames, parametersets, idfn=None, ids=None, config=None): +def idmaker(argnames, parametersets, idfn=None, ids=None, config=None, item=None): ids = [ - _idvalset(valindex, parameterset, argnames, idfn, ids, config) + _idvalset(valindex, parameterset, argnames, idfn, ids, config, item) for valindex, parameterset in enumerate(parametersets) ] if len(set(ids)) != len(ids): diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index 2b86dd289..be06f39c9 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -6,5 +6,5 @@ class PytestUsageWarning(PytestWarning): """Warnings related to pytest usage: either command line or testing code.""" -class RemovedInPytest4Warning(PytestWarning): +class RemovedInPytest4Warning(PytestWarning, DeprecationWarning): """warning class for features that will be removed in pytest 4.0""" diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 6562d11a3..d043e64f7 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -91,7 +91,7 @@ def catch_warnings_for_item(config, ihook, item): def warning_record_to_str(warning_message): """Convert a warnings.WarningMessage to a string, taking in account a lot of unicode shenaningans in Python 2. - When Python 2 support is tropped this function can be greatly simplified. + When Python 2 support is dropped this function can be greatly simplified. """ warn_msg = warning_message.message unicode_warning = False @@ -131,3 +131,10 @@ def pytest_collection(session): config = session.config with catch_warnings_for_item(config=config, ihook=config.hook, item=None): yield + + +@pytest.hookimpl(hookwrapper=True) +def pytest_terminal_summary(terminalreporter): + config = terminalreporter.config + with catch_warnings_for_item(config=config, ihook=config.hook, item=None): + yield diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 966de66b2..15e66d718 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -1,5 +1,6 @@ from __future__ import absolute_import, division, print_function +import os import pytest @@ -197,8 +198,11 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir): ) res = testdir.runpytest_subprocess() assert res.ret == 0 - res.stderr.fnmatch_lines( - "*" + str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0] + msg = PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST.splitlines()[0] + res.stdout.fnmatch_lines( + "*subdirectory{sep}conftest.py:0: RemovedInPytest4Warning: {msg}*".format( + sep=os.sep, msg=msg + ) ) @@ -227,8 +231,11 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_top_level_confte res = testdir.runpytest_subprocess() assert res.ret == 0 - res.stderr.fnmatch_lines( - "*" + str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0] + msg = PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST.splitlines()[0] + res.stdout.fnmatch_lines( + "*subdirectory{sep}conftest.py:0: RemovedInPytest4Warning: {msg}*".format( + sep=os.sep, msg=msg + ) ) @@ -261,10 +268,8 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_false_positives( ) res = testdir.runpytest_subprocess() assert res.ret == 0 - assert ( - str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0] - not in res.stderr.str() - ) + msg = PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST.splitlines()[0] + assert msg not in res.stdout.str() def test_call_fixture_function_deprecated(): @@ -276,3 +281,22 @@ def test_call_fixture_function_deprecated(): with pytest.deprecated_call(): assert fix() == 1 + + +def test_pycollector_makeitem_is_deprecated(): + from _pytest.python import PyCollector + + class PyCollectorMock(PyCollector): + """evil hack""" + + def __init__(self): + self.called = False + + def _makeitem(self, *k): + """hack to disable the actual behaviour""" + self.called = True + + collector = PyCollectorMock() + with pytest.deprecated_call(): + collector.makeitem("foo", "bar") + assert collector.called diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index f5d839f08..608cd52d4 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -383,44 +383,7 @@ class TestMetafunc(object): ) assert result == ["a-a0", "a-a1", "a-a2"] - @pytest.mark.issue351 - def test_idmaker_idfn_exception(self): - from _pytest.python import idmaker - from _pytest.recwarn import WarningsRecorder - - class BadIdsException(Exception): - pass - - def ids(val): - raise BadIdsException("ids raised") - - rec = WarningsRecorder() - with rec: - idmaker( - ("a", "b"), - [ - pytest.param(10.0, IndexError()), - pytest.param(20, KeyError()), - pytest.param("three", [1, 2, 3]), - ], - idfn=ids, - ) - - assert [str(i.message) for i in rec.list] == [ - "Raised while trying to determine id of parameter a at position 0." - "\nUpdate your code as this will raise an error in pytest-4.0.", - "Raised while trying to determine id of parameter b at position 0." - "\nUpdate your code as this will raise an error in pytest-4.0.", - "Raised while trying to determine id of parameter a at position 1." - "\nUpdate your code as this will raise an error in pytest-4.0.", - "Raised while trying to determine id of parameter b at position 1." - "\nUpdate your code as this will raise an error in pytest-4.0.", - "Raised while trying to determine id of parameter a at position 2." - "\nUpdate your code as this will raise an error in pytest-4.0.", - "Raised while trying to determine id of parameter b at position 2." - "\nUpdate your code as this will raise an error in pytest-4.0.", - ] - + @pytest.mark.filterwarnings("default") def test_parametrize_ids_exception(self, testdir): """ :param testdir: the instance of Testdir class, a temporary @@ -438,13 +401,14 @@ class TestMetafunc(object): pass """ ) - with pytest.warns(DeprecationWarning): - result = testdir.runpytest("--collect-only") + result = testdir.runpytest("--collect-only") result.stdout.fnmatch_lines( [ "", " ", " ", + "*test_parametrize_ids_exception.py:5: *parameter arg at position 0*", + "*test_parametrize_ids_exception.py:5: *parameter arg at position 1*", ] ) diff --git a/testing/python/test_deprecations.py b/testing/python/test_deprecations.py deleted file mode 100644 index b0c11f0b0..000000000 --- a/testing/python/test_deprecations.py +++ /dev/null @@ -1,22 +0,0 @@ -import pytest - -from _pytest.python import PyCollector - - -class PyCollectorMock(PyCollector): - """evil hack""" - - def __init__(self): - self.called = False - - def _makeitem(self, *k): - """hack to disable the actual behaviour""" - self.called = True - - -def test_pycollector_makeitem_is_deprecated(): - - collector = PyCollectorMock() - with pytest.deprecated_call(): - collector.makeitem("foo", "bar") - assert collector.called diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 02e2824d9..cca704c4c 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1047,20 +1047,21 @@ def test_terminal_summary(testdir): ) +@pytest.mark.filterwarnings("default") def test_terminal_summary_warnings_are_displayed(testdir): """Test that warnings emitted during pytest_terminal_summary are displayed. (#1305). """ testdir.makeconftest( """ + import warnings def pytest_terminal_summary(terminalreporter): - config = terminalreporter.config - config.warn('C1', 'internal warning') + warnings.warn(UserWarning('internal warning')) """ ) - result = testdir.runpytest("-rw") + result = testdir.runpytest() result.stdout.fnmatch_lines( - ["", "*internal warning", "*== 1 warnings in *"] + ["*conftest.py:3:*internal warning", "*== 1 warnings in *"] ) assert "None" not in result.stdout.str()