From af7a8850fb65717ab230e1080443928ed566895d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sun, 23 Feb 2025 17:17:28 +0100 Subject: [PATCH] make complex marker unions and intersections deterministic --- src/poetry/core/version/markers.py | 12 ++++++------ tests/version/test_markers.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/poetry/core/version/markers.py b/src/poetry/core/version/markers.py index 72d24a293..b3782f0ec 100644 --- a/src/poetry/core/version/markers.py +++ b/src/poetry/core/version/markers.py @@ -734,13 +734,13 @@ def union_simplify(self, other: BaseMarker) -> BaseMarker | None: if not shared_markers: return None - unique_markers = our_markers - their_markers - other_unique_markers = their_markers - our_markers + # Do not use sets to create MultiMarkers for deterministic order! + unique_markers = [m for m in self.markers if m not in their_markers] + other_unique_markers = [m for m in other.markers if m not in our_markers] unique_union = MultiMarker(*unique_markers).union( MultiMarker(*other_unique_markers) ) if isinstance(unique_union, (SingleMarkerLike, AnyMarker)): - # Use list instead of set for deterministic order. common_markers = [ marker for marker in self.markers if marker in shared_markers ] @@ -908,13 +908,13 @@ def intersect_simplify(self, other: BaseMarker) -> BaseMarker | None: if not shared_markers: return None - unique_markers = our_markers - their_markers - other_unique_markers = their_markers - our_markers + # Do not use sets to create MarkerUnions for deterministic order! + unique_markers = [m for m in self.markers if m not in their_markers] + other_unique_markers = [m for m in other.markers if m not in our_markers] unique_intersection = MarkerUnion(*unique_markers).intersect( MarkerUnion(*other_unique_markers) ) if isinstance(unique_intersection, (SingleMarkerLike, EmptyMarker)): - # Use list instead of set for deterministic order. common_markers = [ marker for marker in self.markers if marker in shared_markers ] diff --git a/tests/version/test_markers.py b/tests/version/test_markers.py index 31c73c288..08ae4acc7 100644 --- a/tests/version/test_markers.py +++ b/tests/version/test_markers.py @@ -2154,6 +2154,35 @@ def test_complex_intersection() -> None: ) +def test_complex_union_is_deterministic() -> None: + """ + This test might fail sporadically if marker operations are not deterministic! + """ + m1 = parse_marker( + 'sys_platform != "darwin" and python_version >= "3.12"' + ' and platform_system != "Emscripten" and (python_version < "4.0"' + ' and sys_platform == "linux" and extra == "stretch"' + ' or platform_system == "Windows" or extra == "test"' + ' and sys_platform == "win32")' + ) + m2 = parse_marker( + 'sys_platform == "linux" and python_version >= "3.12"' + ' and platform_system == "Emscripten" and python_version < "4.0"' + ' and extra == "stretch" or sys_platform == "win32"' + ' and python_version >= "3.12" and platform_system == "Emscripten"' + ' and extra == "test"' + ) + assert str(m1.union(m2)) == ( + 'python_version >= "3.12" and platform_system == "Windows"' + ' and sys_platform != "darwin" or sys_platform == "linux"' + ' and python_version >= "3.12" and python_version < "4.0"' + ' and extra == "stretch" or python_version >= "3.12" and python_version < "4.0"' + ' and extra == "stretch" and extra == "test" and (sys_platform == "linux"' + ' or sys_platform == "win32") or sys_platform == "win32"' + ' and python_version >= "3.12" and extra == "test"' + ) + + def test_union_avoids_combinatorial_explosion() -> None: """ combinatorial explosion without AtomicMultiMarker and AtomicMarkerUnion