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/unraisableexception.py b/src/_pytest/unraisableexception.py index 0faca36aa00..c606a04083f 100644 --- a/src/_pytest/unraisableexception.py +++ b/src/_pytest/unraisableexception.py @@ -86,15 +86,8 @@ def collect_unraisable(config: Config) -> None: def cleanup( *, config: Config, prev_hook: Callable[[sys.UnraisableHookArgs], object] ) -> None: - # A single collection doesn't necessarily collect everything. - # Constant determined experimentally by the Trio project. - gc_collect_iterations = config.stash.get(gc_collect_iterations_key, 5) try: - try: - gc_collect_harder(gc_collect_iterations) - collect_unraisable(config) - finally: - sys.unraisablehook = prev_hook + sys.unraisablehook = prev_hook finally: del config.stash[unraisable_exceptions] @@ -148,6 +141,11 @@ def pytest_configure(config: Config) -> None: sys.unraisablehook = functools.partial(unraisable_hook, append=deque.append) +def pytest_unconfigure(config: Config) -> None: + gc_collect_iterations = config.stash.get(gc_collect_iterations_key, 5) + gc_collect_harder(gc_collect_iterations) + collect_unraisable(config) + @pytest.hookimpl(trylast=True) def pytest_runtest_setup(item: Item) -> None: collect_unraisable(item.config) 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).