|
20 | 20 | from packaging.version import Version |
21 | 21 |
|
22 | 22 | from fromager import candidate, context, packagesettings, resolver, sources, wheels |
| 23 | +from fromager.requirements_file import RequirementType |
23 | 24 |
|
24 | 25 | _BOOTSTRAP_TIME = datetime.datetime(2026, 3, 26, 0, 0, 0, tzinfo=datetime.UTC) |
25 | 26 | _COOLDOWN_7_DAYS = datetime.timedelta(days=7) |
@@ -247,6 +248,43 @@ def test_cooldown_applied_automatically_via_ctx(tmp_path: pathlib.Path) -> None: |
247 | 248 | assert str(version) == "1.3.2" |
248 | 249 |
|
249 | 250 |
|
| 251 | +def test_toplevel_equality_pin_bypasses_cooldown_via_resolve( |
| 252 | + tmp_path: pathlib.Path, |
| 253 | +) -> None: |
| 254 | + """Top-level == pin threads through resolve() and bypasses cooldown end-to-end. |
| 255 | +
|
| 256 | + Verifies the req_type plumbing in resolver.resolve() actually causes |
| 257 | + resolve_package_cooldown() to disable cooldown, allowing a recent version |
| 258 | + that would normally be filtered. |
| 259 | + """ |
| 260 | + ctx = context.WorkContext( |
| 261 | + active_settings=None, |
| 262 | + constraints_file=None, |
| 263 | + patches_dir=tmp_path / "patches", |
| 264 | + sdists_repo=tmp_path / "sdists-repo", |
| 265 | + wheels_repo=tmp_path / "wheels-repo", |
| 266 | + work_dir=tmp_path / "work-dir", |
| 267 | + cooldown=_COOLDOWN, |
| 268 | + ) |
| 269 | + |
| 270 | + with requests_mock.Mocker() as r: |
| 271 | + r.get( |
| 272 | + "https://pypi.org/simple/test-pkg/", |
| 273 | + json=_cooldown_json_response, |
| 274 | + headers={"Content-Type": _PYPI_SIMPLE_JSON_CONTENT_TYPE}, |
| 275 | + ) |
| 276 | + |
| 277 | + _, version = resolver.resolve( |
| 278 | + ctx=ctx, |
| 279 | + req=Requirement("test-pkg==2.0.0"), |
| 280 | + sdist_server_url="https://pypi.org/simple/", |
| 281 | + include_sdists=True, |
| 282 | + include_wheels=True, |
| 283 | + req_type=RequirementType.TOP_LEVEL, |
| 284 | + ) |
| 285 | + assert str(version) == "2.0.0" |
| 286 | + |
| 287 | + |
250 | 288 | def test_cooldown_applied_via_get_source_provider(tmp_path: pathlib.Path) -> None: |
251 | 289 | """ctx.cooldown propagates through sources.get_source_provider() to any provider. |
252 | 290 |
|
@@ -860,3 +898,69 @@ def test_compute_max_age_cutoff_disabled( |
860 | 898 | """_compute_max_age_cutoff returns None when max_release_age is not set.""" |
861 | 899 | cutoff = resolver._compute_max_age_cutoff(tmp_context) |
862 | 900 | assert cutoff is None |
| 901 | + |
| 902 | + |
| 903 | +def test_resolve_package_cooldown_exempt_toplevel_equality_pin( |
| 904 | + tmp_path: pathlib.Path, |
| 905 | +) -> None: |
| 906 | + """Top-level == pin bypasses cooldown.""" |
| 907 | + ctx = _make_ctx(tmp_path, cooldown=_COOLDOWN) |
| 908 | + result = resolver.resolve_package_cooldown( |
| 909 | + ctx, Requirement("test-pkg==1.3.2"), req_type=RequirementType.TOP_LEVEL |
| 910 | + ) |
| 911 | + assert result is None |
| 912 | + |
| 913 | + |
| 914 | +def test_resolve_package_cooldown_enforced_transitive_equality_pin( |
| 915 | + tmp_path: pathlib.Path, |
| 916 | +) -> None: |
| 917 | + """Transitive == pin does NOT bypass cooldown.""" |
| 918 | + ctx = _make_ctx(tmp_path, cooldown=_COOLDOWN) |
| 919 | + result = resolver.resolve_package_cooldown( |
| 920 | + ctx, Requirement("test-pkg==1.3.2"), req_type=RequirementType.INSTALL |
| 921 | + ) |
| 922 | + assert result is _COOLDOWN |
| 923 | + |
| 924 | + |
| 925 | +def test_resolve_package_cooldown_enforced_toplevel_no_pin( |
| 926 | + tmp_path: pathlib.Path, |
| 927 | +) -> None: |
| 928 | + """Top-level requirement without == still gets cooldown.""" |
| 929 | + ctx = _make_ctx(tmp_path, cooldown=_COOLDOWN) |
| 930 | + result = resolver.resolve_package_cooldown( |
| 931 | + ctx, Requirement("test-pkg>=1.0"), req_type=RequirementType.TOP_LEVEL |
| 932 | + ) |
| 933 | + assert result is _COOLDOWN |
| 934 | + |
| 935 | + |
| 936 | +def test_resolve_package_cooldown_none_req_type_not_exempt( |
| 937 | + tmp_path: pathlib.Path, |
| 938 | +) -> None: |
| 939 | + """Unknown req_type (None) with == does NOT bypass cooldown.""" |
| 940 | + ctx = _make_ctx(tmp_path, cooldown=_COOLDOWN) |
| 941 | + result = resolver.resolve_package_cooldown( |
| 942 | + ctx, Requirement("test-pkg==1.3.2"), req_type=None |
| 943 | + ) |
| 944 | + assert result is _COOLDOWN |
| 945 | + |
| 946 | + |
| 947 | +def test_resolve_package_cooldown_toplevel_wildcard_equality_not_exempt( |
| 948 | + tmp_path: pathlib.Path, |
| 949 | +) -> None: |
| 950 | + """Top-level wildcard equality (==1.*) is not an exact pin — cooldown applies.""" |
| 951 | + ctx = _make_ctx(tmp_path, cooldown=_COOLDOWN) |
| 952 | + result = resolver.resolve_package_cooldown( |
| 953 | + ctx, Requirement("test-pkg==1.*"), req_type=RequirementType.TOP_LEVEL |
| 954 | + ) |
| 955 | + assert result is _COOLDOWN |
| 956 | + |
| 957 | + |
| 958 | +def test_resolve_package_cooldown_toplevel_compound_specifier_not_exempt( |
| 959 | + tmp_path: pathlib.Path, |
| 960 | +) -> None: |
| 961 | + """Top-level compound specifier (==1.0,>0.9) is not a single exact pin.""" |
| 962 | + ctx = _make_ctx(tmp_path, cooldown=_COOLDOWN) |
| 963 | + result = resolver.resolve_package_cooldown( |
| 964 | + ctx, Requirement("test-pkg==1.0,>0.9"), req_type=RequirementType.TOP_LEVEL |
| 965 | + ) |
| 966 | + assert result is _COOLDOWN |
0 commit comments