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 bd7e543189daca5345f8cd91413e49012b5bbdb8 Mon Sep 17 00:00:00 2001 From: SR Jatin Nag Date: Sat, 7 Mar 2026 17:35:52 +0530 Subject: [PATCH 3/3] Unraisable Exception Plugin Fix --- src/_pytest/unraisableexception.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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)