From 33b94fda97a4d1f57a4d51bf2bd62647e6f6c351 Mon Sep 17 00:00:00 2001 From: Shubham Padkonde Date: Fri, 18 Sep 2026 00:38:42 +0530 Subject: [PATCH] Fix launch fixture setup hanging before readiness Signed-off-by: Shubham Padkonde --- launch_pytest/launch_pytest/plugin.py | 14 ++++-- .../test/launch_pytest/test_plugin.py | 49 +++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/launch_pytest/launch_pytest/plugin.py b/launch_pytest/launch_pytest/plugin.py index 16f6a6d81..e874469ab 100644 --- a/launch_pytest/launch_pytest/plugin.py +++ b/launch_pytest/launch_pytest/plugin.py @@ -128,9 +128,17 @@ def pytest_fixture_setup(fixturedef, request): )) ready = get_ready_to_test_action(ld) asyncio.set_event_loop(event_loop) - event = asyncio.Event() - ready._add_callback(lambda: event.set()) - run_until_complete(event_loop, event.wait()) + ready_future = event_loop.create_future() + + def wakeup(is_ready): + if not ready_future.done(): + ready_future.set_result(is_ready) + + ready._add_callback(functools.partial(wakeup, True)) + run_async_task.add_done_callback(lambda _: wakeup(False)) + run_until_complete(event_loop, ready_future) + if not ready_future.result(): + fail(f'{eprefix} launch service stopped before ReadyToTest') # this is guaranteed by the current run_async() implementation, let's check it just in case # it changes in the future assert ls.event_loop is event_loop diff --git a/launch_pytest/test/launch_pytest/test_plugin.py b/launch_pytest/test/launch_pytest/test_plugin.py index 00ee51245..368314c75 100644 --- a/launch_pytest/test/launch_pytest/test_plugin.py +++ b/launch_pytest/test/launch_pytest/test_plugin.py @@ -15,6 +15,55 @@ from pathlib import Path import shutil +import pytest + + +@pytest.mark.parametrize('action', [ + "IncludeLaunchDescription(PythonLaunchDescriptionSource('missing.launch.py'))", + "GroupAction(actions=[ReadyToTest()], condition=IfCondition('false'))", +]) +def test_launch_service_stops_before_ready(testdir, action): + testdir.makepyfile(f"""\ +from launch import LaunchDescription +from launch.actions import GroupAction, IncludeLaunchDescription +from launch.conditions import IfCondition +from launch.launch_description_sources import PythonLaunchDescriptionSource +import launch_pytest +from launch_pytest.actions import ReadyToTest +import pytest + +@launch_pytest.fixture +def launch_description(): + return LaunchDescription([{action}]) + +@pytest.mark.launch(fixture=launch_description) +def test_case(): + pytest.fail('test body must not run before ReadyToTest') +""") + result = testdir.runpytest_subprocess(timeout=10) + result.assert_outcomes(errors=1) + result.stdout.fnmatch_lines(['*launch service stopped before ReadyToTest*']) + + +def test_launch_fixture_waits_for_delayed_ready(testdir): + testdir.makepyfile("""\ +from launch import LaunchDescription +from launch.actions import TimerAction +import launch_pytest +from launch_pytest.actions import ReadyToTest +import pytest + +@launch_pytest.fixture +def launch_description(): + return LaunchDescription([TimerAction(period=0.01, actions=[ReadyToTest()])]) + +@pytest.mark.launch(fixture=launch_description) +def test_case(): + pass +""") + result = testdir.runpytest_subprocess(timeout=10) + result.assert_outcomes(passed=1) + def test_launch_fixture_is_not_a_launch_description(testdir): testdir.makepyfile("""\