|
36 | 36 | <!--SERIAL 22812307--> |
37 | 37 | """ |
38 | 38 |
|
| 39 | +_numpy_simple_response = """ |
| 40 | +<!DOCTYPE html> |
| 41 | +<html> |
| 42 | +<head> |
| 43 | +<meta name="pypi:repository-version" content="1.1"> |
| 44 | +<title>Links for numpy</title> |
| 45 | +</head> |
| 46 | +<body> |
| 47 | +<h1>Links for numpy</h1> |
| 48 | +<a href="https://files.pythonhosted.org/packages/numpy-1.24.0-py3-none-any.whl">numpy-1.24.0-py3-none-any.whl</a><br/> |
| 49 | +<a href="https://files.pythonhosted.org/packages/numpy-1.26.4-py3-none-any.whl">numpy-1.26.4-py3-none-any.whl</a><br/> |
| 50 | +<a href="https://files.pythonhosted.org/packages/numpy-2.0.0-py3-none-any.whl">numpy-2.0.0-py3-none-any.whl</a><br/> |
| 51 | +<a href="https://files.pythonhosted.org/packages/numpy-2.2.0-py3-none-any.whl">numpy-2.2.0-py3-none-any.whl</a><br/> |
| 52 | +</body> |
| 53 | +</html> |
| 54 | +""" |
| 55 | + |
39 | 56 |
|
40 | 57 | @pytest.fixture(autouse=True) |
41 | 58 | def reset_cache(): |
@@ -151,6 +168,73 @@ def test_provider_cache_key_github(github_fromager_resolver) -> None: |
151 | 168 | assert provider.cache_key == "python-wheel-build/fromager" |
152 | 169 |
|
153 | 170 |
|
| 171 | +def test_cache_not_overly_aggressive() -> None: |
| 172 | + """Test that resolver cache doesn't poison subsequent resolutions. |
| 173 | +
|
| 174 | + This test demonstrates the fix for issue #766 where the cache would |
| 175 | + store only candidates matching the first requirement's constraints, |
| 176 | + preventing subsequent less-constrained requirements from seeing |
| 177 | + newer versions. |
| 178 | +
|
| 179 | + Scenario: |
| 180 | + 1. First requirement: numpy<2 (e.g., from aotriton build dependency) |
| 181 | + 2. Second requirement: numpy (e.g., from torch build dependency) |
| 182 | +
|
| 183 | + Before the fix: Second resolution would incorrectly use numpy 1.26.4 |
| 184 | + After the fix: Second resolution correctly uses numpy 2.2.0 |
| 185 | + """ |
| 186 | + with requests_mock.Mocker() as r: |
| 187 | + r.get( |
| 188 | + "https://pypi.org/simple/numpy/", |
| 189 | + text=_numpy_simple_response, |
| 190 | + ) |
| 191 | + |
| 192 | + # First resolution: numpy<2 (simulating aotriton's build requirement) |
| 193 | + provider1 = resolver.PyPIProvider(include_sdists=False) |
| 194 | + reporter1: resolvelib.BaseReporter = resolvelib.BaseReporter() |
| 195 | + resolver1 = resolvelib.Resolver(provider1, reporter1) |
| 196 | + |
| 197 | + result1 = resolver1.resolve([Requirement("numpy<2")]) |
| 198 | + candidate1 = result1.mapping["numpy"] |
| 199 | + |
| 200 | + assert candidate1.version == Version("1.26.4") |
| 201 | + |
| 202 | + # Verify cache was populated with ALL candidates (not just <2) |
| 203 | + cache = resolver.BaseProvider.resolver_cache |
| 204 | + assert "numpy" in cache |
| 205 | + cached_candidates = cache["numpy"][ |
| 206 | + (resolver.PyPIProvider, "https://pypi.org/simple/") |
| 207 | + ] |
| 208 | + |
| 209 | + # Critical: Cache should have ALL 4 versions, not just the 2 that matched numpy<2 |
| 210 | + assert len(cached_candidates) == 4 |
| 211 | + versions = {c.version for c in cached_candidates} |
| 212 | + assert versions == { |
| 213 | + Version("1.24.0"), |
| 214 | + Version("1.26.4"), |
| 215 | + Version("2.0.0"), |
| 216 | + Version("2.2.0"), |
| 217 | + } |
| 218 | + |
| 219 | + # Second resolution: numpy (no constraint, simulating torch's build requirement) |
| 220 | + # This creates a new provider instance, but the cache is shared via the class-level |
| 221 | + # BaseProvider.resolver_cache, demonstrating that the cache works across instances |
| 222 | + provider2 = resolver.PyPIProvider(include_sdists=False) |
| 223 | + reporter2: resolvelib.BaseReporter = resolvelib.BaseReporter() |
| 224 | + resolver2 = resolvelib.Resolver(provider2, reporter2) |
| 225 | + |
| 226 | + result2 = resolver2.resolve([Requirement("numpy")]) |
| 227 | + candidate2 = result2.mapping["numpy"] |
| 228 | + |
| 229 | + # Critical assertion: Should get latest version (2.2.0), not 1.26.4 |
| 230 | + # This is the bug that issue #766 reported - before the fix, this would |
| 231 | + # incorrectly return 1.26.4 because the cache only had <2 versions |
| 232 | + assert candidate2.version == Version("2.2.0") |
| 233 | + |
| 234 | + # Verify cache is still intact with all candidates |
| 235 | + assert len(cached_candidates) == 4 |
| 236 | + |
| 237 | + |
154 | 238 | def test_provider_choose_wheel_prereleases(): |
155 | 239 | with requests_mock.Mocker() as r: |
156 | 240 | r.get( |
|
0 commit comments