Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions changelog/8096.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Clearer error message for invalid ``-W`` filters, listing valid actions and the ``-Wait`` pitfall.
23 changes: 18 additions & 5 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2320,6 +2320,11 @@ def _strtobool(val: str) -> bool:
raise ValueError(f"invalid truth value {val!r}")


WARNING_FILTER_DOC_URL = (
"https://docs.python.org/3/library/warnings.html#describing-warning-filters"
)


@lru_cache(maxsize=50)
def parse_warning_filter(
arg: str, *, escape: bool
Expand All @@ -2331,6 +2336,8 @@ def parse_warning_filter(
* Does not apply the filter.
* Escaping is optional.
* Raises UsageError so we get nice error messages on failure.
* Invalid actions name the valid choices, and hint at the `-W`/`-Wait`
short-option pitfall when relevant.
"""
__tracebackhide__ = True
error_template = dedent(
Expand All @@ -2347,16 +2354,13 @@ def parse_warning_filter(

parts = arg.split(":")
if len(parts) > 5:
doc_url = (
"https://docs.python.org/3/library/warnings.html#describing-warning-filters"
)
error = dedent(
f"""\
Too many fields ({len(parts)}), expected at most 5 separated by colons:

action:message:category:module:line

For more information please consult: {doc_url}
For more information please consult: {WARNING_FILTER_DOC_URL}
"""
)
raise UsageError(error_template.format(error=error))
Expand All @@ -2367,7 +2371,16 @@ def parse_warning_filter(
try:
action: warnings._ActionKind = warnings._getaction(action_) # type: ignore[attr-defined]
except warnings._OptionError as e:
raise UsageError(error_template.format(error=str(e))) from None
hint = (
" (choose from: default, error, ignore, always, all, module, once)."
f" See {WARNING_FILTER_DOC_URL}"
)
if action_.lower() == "ait":
hint = (
" Note that '-W' takes a value, so '-Wait' is parsed as '-W ait'."
+ hint
)
raise UsageError(error_template.format(error=f"{e}{hint}")) from None
try:
category: type[Warning] = _resolve_warning_category(category_)
except ImportError:
Expand Down
15 changes: 15 additions & 0 deletions testing/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3112,6 +3112,21 @@ def test_parse_warning_filter_failure(arg: str) -> None:
parse_warning_filter(arg, escape=True)


@pytest.mark.parametrize(
"arg, expect_wait_hint",
[("ait", True), ("AIT", True), ("FOO", False)],
)
def test_parse_warning_filter_invalid_action_hint(
arg: str, expect_wait_hint: bool
) -> None:
"""Invalid -W actions show valid choices; the -Wait pitfall hint is scoped to that case."""
with pytest.raises(
pytest.UsageError, match=r"invalid action.*choose from"
) as exc_info:
parse_warning_filter(arg, escape=True)
assert ("-Wait" in str(exc_info.value)) == expect_wait_hint


class TestDebugOptions:
def test_without_debug_does_not_write_log(self, pytester: Pytester) -> None:
result = pytester.runpytest()
Expand Down
17 changes: 17 additions & 0 deletions testing/test_warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,23 @@ def test_invalid_regex_in_filterwarning(self, pytester: Pytester) -> None:
)


def test_dash_w_shows_wait_hint_on_usage_error(pytester: Pytester) -> None:
"""`-Wait` is parsed as `-W ait`; the hint should point at this."""
result = pytester.runpytest("-Wait")
assert result.ret == pytest.ExitCode.USAGE_ERROR
result.stderr.fnmatch_lines(
[
"ERROR: while parsing the following warning configuration:",
"",
" ait",
"",
"This error occurred:",
"",
"invalid action: 'ait'*-Wait*choose from*",
]
)


@pytest.mark.skip("not relevant until pytest 10.0")
@pytest.mark.parametrize("change_default", [None, "ini", "cmdline"])
def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> None:
Expand Down