From 224e721ed4010c5608494fca6f8c48da6391da3e Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Tue, 5 May 2026 02:26:44 -0500 Subject: [PATCH] Fix Pytest 8/9 compatibility and coroutine leaks in launch_pytest (#972) This commit addresses two issues: 1. Renames deprecated path argument to module_path/collection_path in pytest hooks to support Pytest 8/9. 2. Ensures LaunchService run_async task is always awaited by registering the finalizer earlier and passing the task explicitly. Assisted-by: Gemini CLI:2.0-Flash [run_shell_command, replace, git, gh] Signed-off-by: Michael Carroll (cherry picked from commit 06308316b46a734188a6688603763185d2b3de54) # Conflicts: # launch/conftest.py # launch_testing/launch_testing/pytest/hooks.py # launch_testing/launch_testing/pytest/hookspecs.py --- launch/conftest.py | 13 +++++++++++ launch_testing/launch_testing/pytest/hooks.py | 23 +++++++++++++++++++ .../launch_testing/pytest/hookspecs.py | 4 ++++ 3 files changed, 40 insertions(+) diff --git a/launch/conftest.py b/launch/conftest.py index c12837230..6a5c9716b 100644 --- a/launch/conftest.py +++ b/launch/conftest.py @@ -17,6 +17,7 @@ import pytest +<<<<<<< HEAD def _pytest_version_ge(major, minor=0, patch=0): """Return True if pytest version is >= the given version.""" pytest_version = tuple(int(v) for v in pytest.__version__.split('.')) @@ -42,3 +43,15 @@ def pytest_ignore_collect(collection_path, config): else: def pytest_ignore_collect(path, config): return _should_ignore(path) +======= +def pytest_ignore_collect(collection_path=None, path=None): + if collection_path is not None: + path = collection_path + # pytest doctest messes up when trying to import .launch.py packages, ignore them. + # It also messes up when trying to import launch.logging.handlers due to conflicts with + # logging.handlers, ignore that as well. + return str(path).endswith(( + '.launch.py', + str(PurePath('logging') / 'handlers.py'), + )) +>>>>>>> 0630831 (Fix Pytest 8/9 compatibility and coroutine leaks in launch_pytest (#972)) diff --git a/launch_testing/launch_testing/pytest/hooks.py b/launch_testing/launch_testing/pytest/hooks.py index 266a2b416..8cb92bed4 100644 --- a/launch_testing/launch_testing/pytest/hooks.py +++ b/launch_testing/launch_testing/pytest/hooks.py @@ -200,6 +200,7 @@ def find_launch_test_entrypoint(path): return None +<<<<<<< HEAD @pytest.hookimpl(tryfirst=True) def pytest_ignore_collect(collection_path=None, path=None, config=None): # Pytest 8.x signature: (collection_path, path, config) @@ -221,6 +222,8 @@ def pytest_ignore_collect(collection_path=None, path=None, config=None): return False +======= +>>>>>>> 0630831 (Fix Pytest 8/9 compatibility and coroutine leaks in launch_pytest (#972)) if _pytest_version_ge(8): def pytest_pycollect_makemodule(module_path, parent): return _pytest_pycollect_makemodule(module_path, parent) @@ -234,7 +237,11 @@ def _pytest_pycollect_makemodule(path, parent): if entrypoint is not None: ihook = parent.session.gethookproxy(path) module = ihook.pytest_launch_collect_makemodule( +<<<<<<< HEAD module_path=path, path=path, parent=parent, entrypoint=entrypoint +======= + module_path=path, parent=parent, entrypoint=entrypoint +>>>>>>> 0630831 (Fix Pytest 8/9 compatibility and coroutine leaks in launch_pytest (#972)) ) if module is not None: return module @@ -261,6 +268,7 @@ def _pytest_pycollect_makemodule(path, parent): @pytest.hookimpl(trylast=True) +<<<<<<< HEAD def pytest_launch_collect_makemodule(module_path, path, parent, entrypoint): p = module_path or path if _pytest_version_ge(7): @@ -275,6 +283,21 @@ def pytest_launch_collect_makemodule(module_path, path, parent, entrypoint): decorator = decorator.with_args(*mark.args, **mark.kwargs) module.add_marker(decorator) return module +======= +def pytest_launch_collect_makemodule(module_path, parent, entrypoint): + marks = getattr(entrypoint, 'pytestmark', []) + if marks and any(m.name == 'launch_test' for m in marks): + if _pytest_version_ge(7): + path = pathlib.Path(module_path) + module = LaunchTestModule.from_parent(parent=parent, path=path) + else: + module = LaunchTestModule.from_parent(parent=parent, fspath=module_path) + for mark in marks: + decorator = getattr(pytest.mark, mark.name) + decorator = decorator.with_args(*mark.args, **mark.kwargs) + module.add_marker(decorator) + return module +>>>>>>> 0630831 (Fix Pytest 8/9 compatibility and coroutine leaks in launch_pytest (#972)) def pytest_addhooks(pluginmanager): diff --git a/launch_testing/launch_testing/pytest/hookspecs.py b/launch_testing/launch_testing/pytest/hookspecs.py index c35e5c3b8..0d07b89e7 100644 --- a/launch_testing/launch_testing/pytest/hookspecs.py +++ b/launch_testing/launch_testing/pytest/hookspecs.py @@ -16,6 +16,10 @@ @pytest.hookspec(firstresult=True) +<<<<<<< HEAD def pytest_launch_collect_makemodule(module_path, path, parent, entrypoint): +======= +def pytest_launch_collect_makemodule(module_path, parent, entrypoint): +>>>>>>> 0630831 (Fix Pytest 8/9 compatibility and coroutine leaks in launch_pytest (#972)) """Make launch test module appropriate for the found test entrypoint.""" pass