From cdd60e4e2a29404a607b6f72227b9742a081c485 Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Mon, 15 Jun 2026 14:31:08 -0500 Subject: [PATCH] Re-work extension point test mocking These brittle mocks stopped working right in Python 3.15. Rather than mock the functions, we can mock the data and let normal Python metadata discovery happen. --- .../colcon_mock_dist1-1.0.dist-info/METADATA | 3 + .../entry_points.txt | 6 + .../colcon_mock_dist2-1.0.dist-info/METADATA | 3 + .../entry_points.txt | 5 + .../colcon_mock_dist3-1.0.dist-info/METADATA | 3 + .../entry_points.txt | 2 + .../METADATA | 3 + .../entry_points.txt | 2 + test/test_extension_point.py | 139 ++++++------------ 9 files changed, 68 insertions(+), 98 deletions(-) create mode 100644 test/mock_dist/dist1/colcon_mock_dist1-1.0.dist-info/METADATA create mode 100644 test/mock_dist/dist1/colcon_mock_dist1-1.0.dist-info/entry_points.txt create mode 100644 test/mock_dist/dist1/colcon_mock_dist2-1.0.dist-info/METADATA create mode 100644 test/mock_dist/dist1/colcon_mock_dist2-1.0.dist-info/entry_points.txt create mode 100644 test/mock_dist/dist1/colcon_mock_dist3-1.0.dist-info/METADATA create mode 100644 test/mock_dist/dist1/colcon_mock_dist3-1.0.dist-info/entry_points.txt create mode 100644 test/mock_dist/dist2/colcon_mock_dist_dup-1.0.dist-info/METADATA create mode 100644 test/mock_dist/dist2/colcon_mock_dist_dup-1.0.dist-info/entry_points.txt diff --git a/test/mock_dist/dist1/colcon_mock_dist1-1.0.dist-info/METADATA b/test/mock_dist/dist1/colcon_mock_dist1-1.0.dist-info/METADATA new file mode 100644 index 00000000..4658d395 --- /dev/null +++ b/test/mock_dist/dist1/colcon_mock_dist1-1.0.dist-info/METADATA @@ -0,0 +1,3 @@ +Metadata-Version: 2.1 +Name: colcon-mock-dist1 +Version: 1.0 diff --git a/test/mock_dist/dist1/colcon_mock_dist1-1.0.dist-info/entry_points.txt b/test/mock_dist/dist1/colcon_mock_dist1-1.0.dist-info/entry_points.txt new file mode 100644 index 00000000..1a9deea2 --- /dev/null +++ b/test/mock_dist/dist1/colcon_mock_dist1-1.0.dist-info/entry_points.txt @@ -0,0 +1,6 @@ +[colcon_core.extension_point] +group1 = g1 + +[group1] +extA = eA +extB = eB diff --git a/test/mock_dist/dist1/colcon_mock_dist2-1.0.dist-info/METADATA b/test/mock_dist/dist1/colcon_mock_dist2-1.0.dist-info/METADATA new file mode 100644 index 00000000..df1fb290 --- /dev/null +++ b/test/mock_dist/dist1/colcon_mock_dist2-1.0.dist-info/METADATA @@ -0,0 +1,3 @@ +Metadata-Version: 2.1 +Name: colcon-mock-dist2 +Version: 1.0 diff --git a/test/mock_dist/dist1/colcon_mock_dist2-1.0.dist-info/entry_points.txt b/test/mock_dist/dist1/colcon_mock_dist2-1.0.dist-info/entry_points.txt new file mode 100644 index 00000000..32c44cd4 --- /dev/null +++ b/test/mock_dist/dist1/colcon_mock_dist2-1.0.dist-info/entry_points.txt @@ -0,0 +1,5 @@ +[colcon_core.extension_point] +group2 = g2 + +[group2] +extC = eC diff --git a/test/mock_dist/dist1/colcon_mock_dist3-1.0.dist-info/METADATA b/test/mock_dist/dist1/colcon_mock_dist3-1.0.dist-info/METADATA new file mode 100644 index 00000000..2c73632d --- /dev/null +++ b/test/mock_dist/dist1/colcon_mock_dist3-1.0.dist-info/METADATA @@ -0,0 +1,3 @@ +Metadata-Version: 2.1 +Name: colcon-mock-dist3 +Version: 1.0 diff --git a/test/mock_dist/dist1/colcon_mock_dist3-1.0.dist-info/entry_points.txt b/test/mock_dist/dist1/colcon_mock_dist3-1.0.dist-info/entry_points.txt new file mode 100644 index 00000000..40c36788 --- /dev/null +++ b/test/mock_dist/dist1/colcon_mock_dist3-1.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[groupX] +extD = eD diff --git a/test/mock_dist/dist2/colcon_mock_dist_dup-1.0.dist-info/METADATA b/test/mock_dist/dist2/colcon_mock_dist_dup-1.0.dist-info/METADATA new file mode 100644 index 00000000..7d6a1bb6 --- /dev/null +++ b/test/mock_dist/dist2/colcon_mock_dist_dup-1.0.dist-info/METADATA @@ -0,0 +1,3 @@ +Metadata-Version: 2.1 +Name: colcon-mock-dist-dup +Version: 1.0 diff --git a/test/mock_dist/dist2/colcon_mock_dist_dup-1.0.dist-info/entry_points.txt b/test/mock_dist/dist2/colcon_mock_dist_dup-1.0.dist-info/entry_points.txt new file mode 100644 index 00000000..458d7084 --- /dev/null +++ b/test/mock_dist/dist2/colcon_mock_dist_dup-1.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[group2] +extC = eC-prime diff --git a/test/test_extension_point.py b/test/test_extension_point.py index f0fa8043..7be4b25d 100644 --- a/test/test_extension_point.py +++ b/test/test_extension_point.py @@ -3,15 +3,10 @@ # Licensed under the Apache License, Version 2.0 import os +import sys from unittest.mock import DEFAULT from unittest.mock import patch -try: - from importlib.metadata import Distribution -except ImportError: - # TODO: Drop this with Python 3.7 support - from importlib_metadata import Distribution - from colcon_core.environment_variable import EnvironmentVariable from colcon_core.extension_point import clear_entry_point_cache from colcon_core.extension_point import EntryPoint @@ -26,81 +21,47 @@ from .environment_context import EnvironmentContext -class _FakeDistribution(Distribution): - - def __init__(self, entry_points): - entry_points_spec = [] - for group_name, group_members in entry_points.items(): - entry_points_spec.append(f'[{group_name}]') - for member_name, member_value in group_members: - entry_points_spec.append(f'{member_name} = {member_value}') - entry_points_spec.append('') - - self._files = { - 'PKG-INFO': f'Name: dist-{id(self)}\nVersion: 0.0.0\n', - 'entry_points.txt': '\n'.join(entry_points_spec) + '\n', - } - - def read_text(self, filename): - return self._files.get(filename) - - def locate_file(self, path): - return path +@pytest.fixture(autouse=True) +def clear_cache(): + clear_entry_point_cache() + try: + yield + finally: + clear_entry_point_cache() -def _distributions(): - yield _FakeDistribution({ - EXTENSION_POINT_GROUP_NAME: [('group1', 'g1')], - 'group1': [('extA', 'eA'), ('extB', 'eB')], - }) - yield _FakeDistribution({ - EXTENSION_POINT_GROUP_NAME: [('group2', 'g2')], - 'group2': [('extC', 'eC')], - }) - yield _FakeDistribution({ - 'groupX': [('extD', 'eD')], - }) +@pytest.fixture(scope='module', autouse=True) +def mock_dist_path(): + dist1_path = os.path.join(os.path.dirname(__file__), 'mock_dist', 'dist1') + with patch('sys.path', [*sys.path, dist1_path]): + yield dist1_path -def _entry_points(): - for dist in _distributions(): - yield from dist.entry_points +@pytest.fixture +def redefined_extension_point_path(mock_dist_path): + dist2_path = os.path.join(os.path.dirname(__file__), 'mock_dist', 'dist2') + with patch('sys.path', [*sys.path, dist2_path]): + # Sanity check - we need both of the mock distributions on sys.path + assert mock_dist_path in sys.path + yield dist2_path def test_all_extension_points(): - with patch( - 'colcon_core.extension_point.entry_points', - side_effect=_entry_points - ): - with patch( - 'colcon_core.extension_point.distributions', - side_effect=_distributions - ): - clear_entry_point_cache() - - # successfully load a known entry point - extension_points = get_all_extension_points() - assert set(extension_points.keys()) == { - EXTENSION_POINT_GROUP_NAME, - 'group1', - 'group2', - } - assert set(extension_points['group1'].keys()) == {'extA', 'extB'} - assert extension_points['group1']['extA'][0] == 'eA' + # successfully load a known entry point + extension_points = get_all_extension_points() + assert { + EXTENSION_POINT_GROUP_NAME, + 'group1', + 'group2', + } <= set(extension_points.keys()) + assert set(extension_points['group1'].keys()) == {'extA', 'extB'} + assert extension_points['group1']['extA'] == ( + 'eA', 'colcon-mock-dist1', '1.0') def test_extension_point_blocklist(): # successful loading of extension point without a blocklist - with patch( - 'colcon_core.extension_point.entry_points', - side_effect=_entry_points - ): - with patch( - 'colcon_core.extension_point.distributions', - side_effect=_distributions - ): - clear_entry_point_cache() - extension_points = get_extension_points('group1') + extension_points = get_extension_points('group1') assert 'extA' in extension_points.keys() extension_point = extension_points['extA'] assert extension_point == 'eA' @@ -180,36 +141,18 @@ def test_extension_point_blocklist_override(): assert load.call_count == 0 -def test_redefined_extension_point(): - def _duped_distributions(): - yield from _distributions() - yield _FakeDistribution({ - 'group2': [('extC', 'eC-prime')], - }) - - def _duped_entry_points(): - for dist in _duped_distributions(): - yield from dist.entry_points - +def test_redefined_extension_point(redefined_extension_point_path): with patch('colcon_core.extension_point.logger.error') as error: - with patch( - 'colcon_core.extension_point.entry_points', - side_effect=_duped_entry_points - ): - with patch( - 'colcon_core.extension_point.distributions', - side_effect=_duped_distributions - ): - clear_entry_point_cache() - extension_points = get_all_extension_points() - assert 'eC-prime' == extension_points['group2']['extC'][0] - assert error.call_count == 1 + extension_points = get_all_extension_points() + assert 'eC-prime' == extension_points['group2']['extC'][0] + assert error.call_count == 1 - error.reset_mock() - clear_entry_point_cache() - extension_points = get_extension_points('group2') - assert 'eC-prime' == extension_points.get('extC') - assert error.call_count == 1 + error.reset_mock() + clear_entry_point_cache() + + extension_points = get_extension_points('group2') + assert 'eC-prime' == extension_points.get('extC') + assert error.call_count == 1 def entry_point_load(self, *args, **kwargs):