From f94c1313fbaa3171db9d3f868ec2621646695940 Mon Sep 17 00:00:00 2001 From: wep21 Date: Sun, 1 Mar 2026 01:34:30 +0900 Subject: [PATCH] fix: add get_launch_test_fixture_scope for pytest compatibility Signed-off-by: wep21 --- launch_pytest/launch_pytest/plugin.py | 21 ++++++++++++++++--- .../test/launch_pytest/test_plugin.py | 15 +++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/launch_pytest/launch_pytest/plugin.py b/launch_pytest/launch_pytest/plugin.py index 31eaab231..c279e474b 100644 --- a/launch_pytest/launch_pytest/plugin.py +++ b/launch_pytest/launch_pytest/plugin.py @@ -169,6 +169,21 @@ def get_launch_test_fixturename(item): return None if fixture is None else fixture.__name__ +def get_launch_test_fixture_scope(fixture): + """Return launch fixture scope for multiple pytest fixture representations.""" + # Pytest < 8.4 decorates fixtures in-place and stores metadata in + # `_pytestfixturefunction`; pytest >= 8.4 returns a fixture object with + # `_fixture_function_marker`. + fixture_marker = getattr(fixture, '_pytestfixturefunction', None) + if fixture_marker is None: + fixture_marker = getattr(fixture, '_fixture_function_marker', None) + if fixture_marker is None: + raise AttributeError( + f'Unable to retrieve fixture scope from fixture {fixture!r}.' + ) + return fixture_marker.scope + + def is_valid_test_item(obj): """Return true if obj is a valid launch test item.""" return ( @@ -236,7 +251,7 @@ def pytest_pycollect_makeitem(collector, name, obj): return [item] fixture = get_launch_test_fixture(item) fixturename = fixture.__name__ - scope = fixture._pytestfixturefunction.scope + scope = get_launch_test_fixture_scope(fixture) is_shutdown = has_shutdown_kwarg(item) items = generate_test_items( collector, name, obj, fixturename, is_shutdown=is_shutdown, needs_renaming=False) @@ -264,7 +279,7 @@ def is_same_launch_test_fixture(left_item, right_item): return False if lfn is not rfn: return False - if lfn._pytestfixturefunction.scope == 'function': + if get_launch_test_fixture_scope(lfn) == 'function': return False name = lfn.__name__ @@ -331,7 +346,7 @@ def pytest_pyfunc_call(pyfuncitem): return shutdown_test = is_shutdown_test(pyfuncitem) fixture = get_launch_test_fixture(pyfuncitem) - scope = fixture._pytestfixturefunction.scope + scope = get_launch_test_fixture_scope(fixture) event_loop = pyfuncitem.funcargs['event_loop'] ls = pyfuncitem.funcargs['launch_service'] auto_shutdown = fixture._launch_pytest_fixture_options['auto_shutdown'] diff --git a/launch_pytest/test/launch_pytest/test_plugin.py b/launch_pytest/test/launch_pytest/test_plugin.py index 00ee51245..90047b72e 100644 --- a/launch_pytest/test/launch_pytest/test_plugin.py +++ b/launch_pytest/test/launch_pytest/test_plugin.py @@ -14,6 +14,9 @@ from pathlib import Path import shutil +from types import SimpleNamespace + +from launch_pytest.plugin import get_launch_test_fixture_scope def test_launch_fixture_is_not_a_launch_description(testdir): @@ -330,3 +333,15 @@ def test_examples(testdir): shutil.copytree(examples_dir / 'executables', Path(testdir.tmpdir) / 'executables') result = testdir.runpytest() result.assert_outcomes(passed=22) + + +def test_get_launch_test_fixture_scope_with_new_and_old_pytest_repr(): + old_pytest_fixture = SimpleNamespace( + _pytestfixturefunction=SimpleNamespace(scope='module') + ) + new_pytest_fixture = SimpleNamespace( + _fixture_function_marker=SimpleNamespace(scope='class') + ) + + assert get_launch_test_fixture_scope(old_pytest_fixture) == 'module' + assert get_launch_test_fixture_scope(new_pytest_fixture) == 'class'