From 46e751bbc10f2b6624edd93b8b65f25634f1fa39 Mon Sep 17 00:00:00 2001 From: Jatin Date: Sun, 14 Sep 2025 02:45:07 +0530 Subject: [PATCH 1/3] Improve error reporting for custom CLI args with missing path; add test --- src/_pytest/config/argparsing.py | 7 +++++++ testing/test_main.py | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 8d4ed823325..076c5738b9d 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -443,6 +443,13 @@ def __init__( def error(self, message: str) -> NoReturn: """Transform argparse error message into UsageError.""" msg = f"{self.prog}: error: {message}" + if "unrecognized arguments:" in message: + file_or_dir_args = getattr(self._parser, 'extra_info', {}).get('file_or_dir', []) + if file_or_dir_args: + from pathlib import Path + missing_paths = [str(p) for p in file_or_dir_args if not Path(str(p)).exists()] + if missing_paths: + msg += ("\nNote: The specified path(s) do not exist, so custom CLI options from conftest.py may not be available.") if hasattr(self._parser, "_config_source_hint"): msg = f"{msg} ({self._parser._config_source_hint})" diff --git a/testing/test_main.py b/testing/test_main.py index 3f173ec4e9f..51834584063 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -271,6 +271,18 @@ def test_absolute_paths_are_resolved_correctly(self, invocation_path: Path) -> N module_name=None, ) + def test_custom_cli_arg_with_missing_path(pytester: Pytester): + """Test that a helpful error message is shown when a custom CLI argument is used with a non-existent path.""" + pytester.makeconftest( + """ + def pytest_addoption(parser): + parser.addoption("--potato", default="") + """ + ) + result = pytester.runpytest("file_does_not_exist.py", "--potato=yum") + assert result.ret != 0 + assert "unrecognized arguments: --potato=yum" in result.stderr.str() + assert "Note: The specified path(s) do not exist" in result.stderr.str() def test_module_full_path_without_drive(pytester: Pytester) -> None: """Collect and run test using full path except for the drive letter (#7628). From 8cc508cb114e144e7d021fa7cb4c299d6e32d6ad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 13 Sep 2025 21:31:04 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/config/argparsing.py | 11 ++++++++--- testing/test_main.py | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 076c5738b9d..a3b1b9d2297 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -444,12 +444,17 @@ def error(self, message: str) -> NoReturn: """Transform argparse error message into UsageError.""" msg = f"{self.prog}: error: {message}" if "unrecognized arguments:" in message: - file_or_dir_args = getattr(self._parser, 'extra_info', {}).get('file_or_dir', []) + file_or_dir_args = getattr(self._parser, "extra_info", {}).get( + "file_or_dir", [] + ) if file_or_dir_args: from pathlib import Path - missing_paths = [str(p) for p in file_or_dir_args if not Path(str(p)).exists()] + + missing_paths = [ + str(p) for p in file_or_dir_args if not Path(str(p)).exists() + ] if missing_paths: - msg += ("\nNote: The specified path(s) do not exist, so custom CLI options from conftest.py may not be available.") + msg += "\nNote: The specified path(s) do not exist, so custom CLI options from conftest.py may not be available." if hasattr(self._parser, "_config_source_hint"): msg = f"{msg} ({self._parser._config_source_hint})" diff --git a/testing/test_main.py b/testing/test_main.py index 51834584063..ffbb382fe71 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -284,6 +284,7 @@ def pytest_addoption(parser): assert "unrecognized arguments: --potato=yum" in result.stderr.str() assert "Note: The specified path(s) do not exist" in result.stderr.str() + def test_module_full_path_without_drive(pytester: Pytester) -> None: """Collect and run test using full path except for the drive letter (#7628). From 45b32db958e8943c0ed7e0a784bd758072cd7a57 Mon Sep 17 00:00:00 2001 From: SR Jatin Nag Date: Sat, 21 Feb 2026 23:22:38 +0530 Subject: [PATCH 3/3] New config & CLI option --- src/_pytest/main.py | 13 +++++++++++++ src/_pytest/warnings.py | 6 ++++++ testing/test_warnings.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index b1eb22f1f61..d4a19d15f1b 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -106,6 +106,19 @@ def pytest_addoption(parser: Parser) -> None: "Processed after -W/--pythonwarnings.", ) + parser.addini( + "strict_warnings", + type="bool", + default=False, + help="If true, treat internal pytest warnings (PytestWarning and subclasses) as errors.", + ) + group.addoption( + "--strict-warnings", + action="store_true", + dest="strict_warnings", + help="Treat internal pytest warnings (PytestWarning and subclasses) as errors.", + ) + group = parser.getgroup("collect", "collection") group.addoption( "--collectonly", diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 806681a5020..3e85747150a 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -53,6 +53,12 @@ def catch_warnings_for_item( for arg in mark.args: warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) + strict_warnings = bool(config.getini("strict_warnings")) or bool( + getattr(config.option, "strict_warnings", False) + ) + if strict_warnings: + warnings.filterwarnings("error", category=pytest.PytestWarning) + try: yield finally: diff --git a/testing/test_warnings.py b/testing/test_warnings.py index b1c64dc9332..049f1fe48ab 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -590,6 +590,34 @@ def test_foo(): result, "assertion is always true, perhaps remove parentheses?" ) +def test_strict_warnings_treats_pytest_collection_warning_as_error( + pytester: Pytester, +) -> None: + """In strict warnings mode, PytestCollectionWarning should be treated as an error.""" + pytester.makepyfile( + """ + import pytest + pytestmark = pytest.mark.filterwarnings("default::pytest.PytestCollectionWarning") + + class TestFoo: + def __new__(cls): + return super().__new__(cls) + """ + ) + result = pytester.runpytest("--collect-only") + result.stdout.fnmatch_lines( + [ + "*warnings summary*", + "*PytestCollectionWarning: cannot collect test class 'TestFoo' because it has a __new__ constructor*", + ] + ) + result_strict = pytester.runpytest("--collect-only", "--strict-warnings") + assert result_strict.ret != 0 + result_strict.stdout.fnmatch_lines( + [ + "*PytestCollectionWarning: cannot collect test class 'TestFoo' because it has a __new__ constructor*", + ] + ) def test_warnings_checker_twice() -> None: """Issue #4617"""