From 1f8f46bb0ce0f09911b6bec6e6ef0aac057411ab Mon Sep 17 00:00:00 2001 From: Dane Parin Date: Tue, 8 Sep 2026 14:58:38 +0700 Subject: [PATCH 1/3] Improve error for invalid -W filters with valid choices and -Wait hint Co-authored-by: Claude Sonnet 5 --- changelog/8096.improvement.rst | 1 + src/_pytest/config/__init__.py | 7 ++++++- testing/test_config.py | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 changelog/8096.improvement.rst diff --git a/changelog/8096.improvement.rst b/changelog/8096.improvement.rst new file mode 100644 index 00000000000..9b66794b093 --- /dev/null +++ b/changelog/8096.improvement.rst @@ -0,0 +1 @@ +Clearer error message for invalid ``-W`` filters, listing valid actions and the ``-Wait`` pitfall. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index c7bd3e1afab..d2c58d77904 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -2367,7 +2367,12 @@ 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, module, once)." + " Note that '-W' takes a value, so '-Wait' is parsed as '-W ait'." + " See https://docs.python.org/3/library/warnings.html#describing-warning-filters" + ) + raise UsageError(error_template.format(error=f"{e}{hint}")) from None try: category: type[Warning] = _resolve_warning_category(category_) except ImportError: diff --git a/testing/test_config.py b/testing/test_config.py index dad1653e299..ee3c6e16fc3 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -3112,6 +3112,13 @@ def test_parse_warning_filter_failure(arg: str) -> None: parse_warning_filter(arg, escape=True) +@pytest.mark.parametrize("arg", ["ait", "FOO"]) +def test_parse_warning_filter_invalid_action_hint(arg: str) -> None: + """Invalid -W actions show valid choices plus the -Wait pitfall.""" + with pytest.raises(pytest.UsageError, match=r"invalid action.*choose from.*-Wait"): + parse_warning_filter(arg, escape=True) + + class TestDebugOptions: def test_without_debug_does_not_write_log(self, pytester: Pytester) -> None: result = pytester.runpytest() From ab65a184bb7a49c70f540b194846eef86c5052b0 Mon Sep 17 00:00:00 2001 From: Dane Parin Date: Tue, 8 Sep 2026 15:44:01 +0700 Subject: [PATCH 2/3] Scope -Wait hint to ait only Co-authored-by: Claude Sonnet 5 --- src/_pytest/config/__init__.py | 6 +++++- testing/test_config.py | 10 ++++++++-- testing/test_warnings.py | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index d2c58d77904..f28a61a4d4f 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -2369,9 +2369,13 @@ def parse_warning_filter( except warnings._OptionError as e: hint = ( " (choose from: default, error, ignore, always, module, once)." - " Note that '-W' takes a value, so '-Wait' is parsed as '-W ait'." " See https://docs.python.org/3/library/warnings.html#describing-warning-filters" ) + if action_ == "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_) diff --git a/testing/test_config.py b/testing/test_config.py index ee3c6e16fc3..ee53571851c 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -3114,9 +3114,15 @@ def test_parse_warning_filter_failure(arg: str) -> None: @pytest.mark.parametrize("arg", ["ait", "FOO"]) def test_parse_warning_filter_invalid_action_hint(arg: str) -> None: - """Invalid -W actions show valid choices plus the -Wait pitfall.""" - with pytest.raises(pytest.UsageError, match=r"invalid action.*choose from.*-Wait"): + """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) + if arg == "ait": + assert "-Wait" in str(exc_info.value) + else: + assert "-Wait" not in str(exc_info.value) class TestDebugOptions: diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 0344cb453d8..3728bb844d5 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -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: From 28651fd563d39ec05c67b0068fb7e0c34cc42e1e Mon Sep 17 00:00:00 2001 From: Dane Parin Date: Sun, 13 Sep 2026 10:27:45 +0700 Subject: [PATCH 3/3] Handle uppercase -Wait variant and deduplicate docs URL --- src/_pytest/config/__init__.py | 18 +++++++++++------- testing/test_config.py | 14 ++++++++------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index f28a61a4d4f..e4a699c6562 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -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 @@ -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( @@ -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)) @@ -2368,10 +2372,10 @@ def parse_warning_filter( action: warnings._ActionKind = warnings._getaction(action_) # type: ignore[attr-defined] except warnings._OptionError as e: hint = ( - " (choose from: default, error, ignore, always, module, once)." - " See https://docs.python.org/3/library/warnings.html#describing-warning-filters" + " (choose from: default, error, ignore, always, all, module, once)." + f" See {WARNING_FILTER_DOC_URL}" ) - if action_ == "ait": + if action_.lower() == "ait": hint = ( " Note that '-W' takes a value, so '-Wait' is parsed as '-W ait'." + hint diff --git a/testing/test_config.py b/testing/test_config.py index ee53571851c..23c97b2e0e2 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -3112,17 +3112,19 @@ def test_parse_warning_filter_failure(arg: str) -> None: parse_warning_filter(arg, escape=True) -@pytest.mark.parametrize("arg", ["ait", "FOO"]) -def test_parse_warning_filter_invalid_action_hint(arg: str) -> None: +@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) - if arg == "ait": - assert "-Wait" in str(exc_info.value) - else: - assert "-Wait" not in str(exc_info.value) + assert ("-Wait" in str(exc_info.value)) == expect_wait_hint class TestDebugOptions: