diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 8d4ed823325..a3b1b9d2297 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -443,6 +443,18 @@ 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/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_main.py b/testing/test_main.py index 3f173ec4e9f..ffbb382fe71 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -271,6 +271,19 @@ 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). 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"""