diff --git a/changelog/5092.bugfix.rst b/changelog/5092.bugfix.rst new file mode 100644 index 00000000000..3fd29677cc7 --- /dev/null +++ b/changelog/5092.bugfix.rst @@ -0,0 +1 @@ +Produce a warning when unknown keywords are passed to ``pytest.param(...)``. diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 6134ca77b33..fa7e89364d9 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -93,3 +93,9 @@ "pytest.warns() got unexpected keyword arguments: {args!r}.\n" "This will be an error in future versions.", ) + +PYTEST_PARAM_UNKNOWN_KWARGS = UnformattedWarning( + PytestDeprecationWarning, + "pytest.param() got unexpected keyword arguments: {args!r}.\n" + "This will be an error in future versions.", +) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 0021dd5d66e..59a4ba20fdb 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -10,6 +10,7 @@ from ..compat import getfslineno from ..compat import MappingMixin from ..compat import NOTSET +from _pytest.deprecated import PYTEST_PARAM_UNKNOWN_KWARGS from _pytest.outcomes import fail EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" @@ -60,20 +61,25 @@ def get_empty_parameterset_mark(config, argnames, func): class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): @classmethod - def param(cls, *values, **kw): - marks = kw.pop("marks", ()) + def param(cls, *values, **kwargs): + marks = kwargs.pop("marks", ()) if isinstance(marks, MarkDecorator): marks = (marks,) else: assert isinstance(marks, (tuple, list, set)) - id_ = kw.pop("id", None) + id_ = kwargs.pop("id", None) if id_ is not None: if not isinstance(id_, six.string_types): raise TypeError( "Expected id to be a string, got {}: {!r}".format(type(id_), id_) ) id_ = ascii_escaped(id_) + + if kwargs: + warnings.warn( + PYTEST_PARAM_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=3 + ) return cls(values, marks, id_) @classmethod diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 6c2dfb5ae57..f5798391854 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -97,8 +97,7 @@ def skip(msg="", **kwargs): __tracebackhide__ = True allow_module_level = kwargs.pop("allow_module_level", False) if kwargs: - keys = [k for k in kwargs.keys()] - raise TypeError("unexpected keyword arguments: {}".format(keys)) + raise TypeError("unexpected keyword arguments: {}".format(sorted(kwargs))) raise Skipped(msg=msg, allow_module_level=allow_module_level) diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 1b643d4301f..66de85468b6 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -682,7 +682,7 @@ def raises(expected_exception, *args, **kwargs): match_expr = kwargs.pop("match") if kwargs: msg = "Unexpected keyword arguments passed to pytest.raises: " - msg += ", ".join(kwargs.keys()) + msg += ", ".join(sorted(kwargs)) raise TypeError(msg) return RaisesContext(expected_exception, message, match_expr) elif isinstance(args[0], str): diff --git a/testing/test_mark.py b/testing/test_mark.py index 2851dbc16c1..2e9eea0922d 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -13,6 +13,7 @@ from _pytest.mark import MarkGenerator as Mark from _pytest.nodes import Collector from _pytest.nodes import Node +from _pytest.warning_types import PytestDeprecationWarning from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG try: @@ -991,3 +992,15 @@ def test_pytest_param_id_requires_string(): @pytest.mark.parametrize("s", (None, "hello world")) def test_pytest_param_id_allows_none_or_string(s): assert pytest.param(id=s) + + +def test_pytest_param_warning_on_unknown_kwargs(): + with pytest.warns(PytestDeprecationWarning) as warninfo: + # typo, should be marks= + pytest.param(1, 2, mark=pytest.mark.xfail()) + assert warninfo[0].filename == __file__ + msg, = warninfo[0].message.args + assert msg == ( + "pytest.param() got unexpected keyword arguments: ['mark'].\n" + "This will be an error in future versions." + )