1- """Tests for requirement_resolver module."""
1+ """Tests for bootstrap_requirement_resolver module."""
22
33from unittest .mock import MagicMock , patch
44
77from packaging .utils import canonicalize_name
88from packaging .version import Version
99
10+ from fromager .bootstrap_requirement_resolver import BootstrapRequirementResolver
1011from fromager .context import WorkContext
1112from fromager .dependency_graph import DependencyGraph
12- from fromager .requirement_resolver import RequirementResolver
1313from fromager .requirements_file import RequirementType
1414
1515# Test fixture: previous dependency graph
6565
6666
6767def test_resolve_from_graph_no_changes (tmp_context : WorkContext ) -> None :
68- """RequirementResolver resolves from previous graph with no changes."""
69- resolver = RequirementResolver (tmp_context , old_graph )
68+ """BootstrapRequirementResolver resolves from previous graph with no changes."""
69+ resolver = BootstrapRequirementResolver (tmp_context , old_graph )
7070
7171 # Resolving new dependency that doesn't exist in graph
7272 assert (
@@ -105,8 +105,8 @@ def test_resolve_from_graph_no_changes(tmp_context: WorkContext) -> None:
105105
106106
107107def test_resolve_from_graph_install_dep_upgrade (tmp_context : WorkContext ) -> None :
108- """RequirementResolver prefers top-level requirements over history."""
109- resolver = RequirementResolver (tmp_context , old_graph )
108+ """BootstrapRequirementResolver prefers top-level requirements over history."""
109+ resolver = BootstrapRequirementResolver (tmp_context , old_graph )
110110
111111 # Simulating new bootstrap with a toplevel requirement of pbr==8
112112 tmp_context .dependency_graph .add_dependency (
@@ -143,8 +143,8 @@ def test_resolve_from_graph_install_dep_upgrade(tmp_context: WorkContext) -> Non
143143
144144
145145def test_resolve_from_graph_install_dep_downgrade (tmp_context : WorkContext ) -> None :
146- """RequirementResolver handles version downgrades from top-level requirements."""
147- resolver = RequirementResolver (tmp_context , old_graph )
146+ """BootstrapRequirementResolver handles version downgrades from top-level requirements."""
147+ resolver = BootstrapRequirementResolver (tmp_context , old_graph )
148148
149149 # Simulating new bootstrap with a toplevel requirement of pbr<=6
150150 tmp_context .dependency_graph .add_dependency (
@@ -181,8 +181,8 @@ def test_resolve_from_graph_install_dep_downgrade(tmp_context: WorkContext) -> N
181181
182182
183183def test_resolve_from_graph_toplevel_dep (tmp_context : WorkContext ) -> None :
184- """RequirementResolver resolves top-level dependencies correctly."""
185- resolver = RequirementResolver (tmp_context , old_graph )
184+ """BootstrapRequirementResolver resolves top-level dependencies correctly."""
185+ resolver = BootstrapRequirementResolver (tmp_context , old_graph )
186186
187187 # Simulating new bootstrap with a toplevel requirement for foo
188188 tmp_context .dependency_graph .add_dependency (
@@ -236,8 +236,8 @@ def test_resolve_from_graph_toplevel_dep(tmp_context: WorkContext) -> None:
236236
237237
238238def test_resolve_from_graph_no_previous_graph (tmp_context : WorkContext ) -> None :
239- """RequirementResolver returns None when no previous graph is available."""
240- resolver = RequirementResolver (tmp_context , prev_graph = None )
239+ """BootstrapRequirementResolver returns None when no previous graph is available."""
240+ resolver = BootstrapRequirementResolver (tmp_context , prev_graph = None )
241241
242242 assert (
243243 resolver ._resolve_from_graph (
@@ -278,7 +278,7 @@ def test_resolve_from_graph_new_parent_reuses_existing_version(
278278 req_version = Version ("25.0" ),
279279 )
280280
281- resolver = RequirementResolver (tmp_context , prev_graph )
281+ resolver = BootstrapRequirementResolver (tmp_context , prev_graph )
282282
283283 # Resolve packaging>=24.0 via a NEW parent "wheel" that is NOT in prev_graph.
284284 # packaging==25.0 satisfies >=24.0 and exists in the graph, so it should
@@ -322,7 +322,7 @@ def test_resolve_from_graph_different_req_type_reuses_existing_version(
322322 req_version = Version ("2.0" ),
323323 )
324324
325- resolver = RequirementResolver (tmp_context , prev_graph )
325+ resolver = BootstrapRequirementResolver (tmp_context , prev_graph )
326326
327327 # Now resolve bar>=1.5 as an INSTALL dep of foo (different req_type).
328328 # bar==2.0 satisfies >=1.5 and exists in the graph under the same parent
@@ -381,7 +381,7 @@ def test_resolve_from_graph_parent_specific_preferred_over_name_fallback(
381381 req_version = Version ("3.0" ),
382382 )
383383
384- resolver = RequirementResolver (tmp_context , prev_graph )
384+ resolver = BootstrapRequirementResolver (tmp_context , prev_graph )
385385
386386 # Resolve bar>=1.0 as install dep of foo. The parent-specific lookup
387387 # should return bar==2.0 (from foo), NOT bar==3.0 (from baz via fallback).
@@ -422,7 +422,7 @@ def test_resolve_from_graph_name_fallback_returns_none_for_missing_package(
422422 req_version = Version ("2.0" ),
423423 )
424424
425- resolver = RequirementResolver (tmp_context , prev_graph )
425+ resolver = BootstrapRequirementResolver (tmp_context , prev_graph )
426426
427427 result = resolver ._resolve_from_graph (
428428 req = Requirement ("missing-pkg>=1.0" ),
@@ -434,8 +434,8 @@ def test_resolve_from_graph_name_fallback_returns_none_for_missing_package(
434434
435435
436436def test_resolve_rejects_git_urls_for_source (tmp_context : WorkContext ) -> None :
437- """RequirementResolver .resolve() rejects git URLs when pre_built=False."""
438- resolver = RequirementResolver (tmp_context )
437+ """BootstrapRequirementResolver .resolve() rejects git URLs when pre_built=False."""
438+ resolver = BootstrapRequirementResolver (tmp_context )
439439
440440 with pytest .raises (
441441 ValueError , match = "Git URL requirements must be handled by Bootstrapper"
@@ -448,20 +448,17 @@ def test_resolve_rejects_git_urls_for_source(tmp_context: WorkContext) -> None:
448448 )
449449
450450
451- @patch ("fromager.requirement_resolver.wheels.resolve_prebuilt_wheel_all" )
452- @patch ("fromager.requirement_resolver.wheels.get_wheel_server_urls" )
451+ @patch ("fromager.resolver.resolve_from_provider" )
453452def test_resolve_allows_git_urls_for_prebuilt (
454- mock_get_servers : MagicMock ,
455- mock_resolve_wheel : MagicMock ,
453+ mock_resolve : MagicMock ,
456454 tmp_context : WorkContext ,
457455) -> None :
458- """RequirementResolver .resolve() allows git URLs when pre_built=True (test mode fallback)."""
459- resolver = RequirementResolver (tmp_context )
456+ """BootstrapRequirementResolver .resolve() allows git URLs when pre_built=True (test mode fallback)."""
457+ resolver = BootstrapRequirementResolver (tmp_context )
460458 req = Requirement ("mypkg @ git+https://github.com/example/repo.git" )
461459
462- # Mock wheel resolution to return expected result (as list)
463- mock_get_servers .return_value = ["https://pypi.org/simple" ]
464- mock_resolve_wheel .return_value = [
460+ # Mock resolution to return expected result (as list)
461+ mock_resolve .return_value = [
465462 ("https://files.pythonhosted.org/mypkg-1.0-py3-none-any.whl" , Version ("1.0" ))
466463 ]
467464
@@ -473,32 +470,30 @@ def test_resolve_allows_git_urls_for_prebuilt(
473470 parent_req = None ,
474471 )
475472
476- # Verify it routed to wheel resolution
477- mock_resolve_wheel .assert_called_once ()
473+ # Verify resolution was called
474+ mock_resolve .assert_called_once ()
478475 assert url == "https://files.pythonhosted.org/mypkg-1.0-py3-none-any.whl"
479476 assert version == Version ("1.0" )
480477
481478
482- @patch ("fromager.requirement_resolver.wheels.resolve_prebuilt_wheel_all" )
483- @patch ("fromager.requirement_resolver.wheels.get_wheel_server_urls" )
479+ @patch ("fromager.resolver.resolve_from_provider" )
484480def test_resolve_auto_routes_to_prebuilt (
485- mock_get_servers : MagicMock ,
486- mock_resolve_wheel : MagicMock ,
481+ mock_resolve : MagicMock ,
487482 tmp_context : WorkContext ,
488483) -> None :
489- """resolve(pre_built=None) with pbi.pre_built=True routes to wheels.resolve_prebuilt_wheel_all ."""
484+ """resolve(pre_built=None) with pbi.pre_built=True routes to wheel resolution ."""
490485 req = Requirement ("setuptools>=40" )
491486
492487 # Mock package build info to return pre_built=True
493488 mock_pbi = MagicMock ()
494489 mock_pbi .pre_built = True
490+ mock_pbi .wheel_server_url = None
495491
496492 with patch .object (tmp_context , "package_build_info" , return_value = mock_pbi ):
497- resolver = RequirementResolver (tmp_context )
493+ resolver = BootstrapRequirementResolver (tmp_context )
498494
499- # Mock wheel resolution to return expected result (as list)
500- mock_get_servers .return_value = ["https://pypi.org/simple" ]
501- mock_resolve_wheel .return_value = [
495+ # Mock resolution to return expected result (as list)
496+ mock_resolve .return_value = [
502497 (
503498 "https://files.pythonhosted.org/setuptools-1.0-py3-none-any.whl" ,
504499 Version ("1.0" ),
@@ -513,29 +508,35 @@ def test_resolve_auto_routes_to_prebuilt(
513508 pre_built = None ,
514509 )
515510
516- # Verify it routed to wheel resolution
517- mock_resolve_wheel .assert_called_once ()
511+ # Verify resolution was called
512+ mock_resolve .assert_called_once ()
518513 assert url == "https://files.pythonhosted.org/setuptools-1.0-py3-none-any.whl"
519514 assert version == Version ("1.0" )
520515
521516
522- @patch ("fromager.requirement_resolver.sources.resolve_source_all " )
517+ @patch ("fromager.resolver.resolve_from_provider " )
523518def test_resolve_auto_routes_to_source (
524- mock_resolve_source : MagicMock ,
519+ mock_resolve : MagicMock ,
525520 tmp_context : WorkContext ,
526521) -> None :
527- """resolve(pre_built=None) with pbi.pre_built=False routes to sources.resolve_source_all ."""
522+ """resolve(pre_built=None) with pbi.pre_built=False routes to source resolution ."""
528523 req = Requirement ("mypackage>=1.0" )
529524
530525 # Mock package build info to return pre_built=False
531526 mock_pbi = MagicMock ()
532527 mock_pbi .pre_built = False
528+ mock_pbi .resolver_include_sdists = True
529+ mock_pbi .resolver_include_wheels = True
530+ mock_pbi .resolver_ignore_platform = True
531+ mock_pbi .resolver_sdist_server_url = MagicMock (
532+ return_value = "https://pypi.org/simple"
533+ )
533534
534535 with patch .object (tmp_context , "package_build_info" , return_value = mock_pbi ):
535- resolver = RequirementResolver (tmp_context )
536+ resolver = BootstrapRequirementResolver (tmp_context )
536537
537538 # Mock source resolution to return expected result (as list)
538- mock_resolve_source .return_value = [
539+ mock_resolve .return_value = [
539540 ("https://files.pythonhosted.org/mypackage-2.0.tar.gz" , Version ("2.0" ))
540541 ]
541542
@@ -547,70 +548,57 @@ def test_resolve_auto_routes_to_source(
547548 pre_built = None ,
548549 )
549550
550- # Verify it routed to source resolution
551- mock_resolve_source .assert_called_once ()
551+ # Verify resolution was called
552+ mock_resolve .assert_called_once ()
552553 assert url == "https://files.pythonhosted.org/mypackage-2.0.tar.gz"
553554 assert version == Version ("2.0" )
554555
555556
556- @patch ("fromager.requirement_resolver.wheels.resolve_prebuilt_wheel_all" )
557- @patch ("fromager.requirement_resolver.wheels.get_wheel_server_urls" )
558- @patch ("fromager.requirement_resolver.sources.resolve_source_all" )
557+ @patch ("fromager.resolver.resolve_from_provider" )
559558def test_resolve_prebuilt_after_source_uses_separate_cache (
560- mock_resolve_source : MagicMock ,
561- mock_get_servers : MagicMock ,
562- mock_resolve_wheel : MagicMock ,
559+ mock_resolve : MagicMock ,
563560 tmp_context : WorkContext ,
564561) -> None :
565562 """resolve(pre_built=True) after same req resolved as source uses separate cache."""
566563 req = Requirement ("testpkg==1.5" )
567564
568- # Mock package build info to return pre_built=False initially
569- mock_pbi = MagicMock ()
570- mock_pbi .pre_built = False
571-
572- with patch .object (tmp_context , "package_build_info" , return_value = mock_pbi ):
573- resolver = RequirementResolver (tmp_context )
574-
575- # Mock source resolution (as list)
576- mock_resolve_source .return_value = [
577- ("https://files.pythonhosted.org/testpkg-1.5.tar.gz" , Version ("1.5" ))
578- ]
579-
580- # First call: resolve as source (pre_built=None, auto-detects to False)
581- url1 , version1 = resolver .resolve (
582- req = req ,
583- req_type = RequirementType .INSTALL ,
584- parent_req = None ,
585- pre_built = None ,
586- )
587-
588- assert url1 == "https://files.pythonhosted.org/testpkg-1.5.tar.gz"
589- assert version1 == Version ("1.5" )
590- assert mock_resolve_source .call_count == 1
591-
592- # Mock wheel resolution for second call (as list)
593- mock_get_servers .return_value = ["https://pypi.org/simple" ]
594- mock_resolve_wheel .return_value = [
565+ # Set up side_effect to return different results for each call
566+ mock_resolve .side_effect = [
567+ # First call: source resolution
568+ [("https://files.pythonhosted.org/testpkg-1.5.tar.gz" , Version ("1.5" ))],
569+ # Second call: wheel resolution
570+ [
595571 (
596572 "https://files.pythonhosted.org/testpkg-1.5-py3-none-any.whl" ,
597573 Version ("1.5" ),
598574 )
599- ]
575+ ],
576+ ]
600577
601- # Second call: resolve same req as prebuilt (explicit pre_built=True)
602- # This should NOT return the cached source result
603- url2 , version2 = resolver .resolve (
604- req = req ,
605- req_type = RequirementType .INSTALL ,
606- parent_req = None ,
607- pre_built = True ,
608- )
578+ resolver = BootstrapRequirementResolver (tmp_context )
579+
580+ # First call: resolve as source (explicit pre_built=False)
581+ url1 , version1 = resolver .resolve (
582+ req = req ,
583+ req_type = RequirementType .INSTALL ,
584+ parent_req = None ,
585+ pre_built = False ,
586+ )
609587
610- # Verify it called wheel resolution (not cached)
611- assert mock_resolve_wheel .call_count == 1
612- assert url2 == "https://files.pythonhosted.org/testpkg-1.5-py3-none-any.whl"
613- assert version2 == Version ("1.5" )
588+ assert url1 == "https://files.pythonhosted.org/testpkg-1.5.tar.gz"
589+ assert version1 == Version ("1.5" )
590+ assert mock_resolve .call_count == 1
591+
592+ # Second call: resolve same req as prebuilt (explicit pre_built=True)
593+ # This should NOT return the cached source result
594+ url2 , version2 = resolver .resolve (
595+ req = req ,
596+ req_type = RequirementType .INSTALL ,
597+ parent_req = None ,
598+ pre_built = True ,
599+ )
614600
615- # Verify source was only called once (first time, not second)
616- assert mock_resolve_source .call_count == 1
601+ # Verify it called resolution again (not cached) because cache keys differ
602+ assert mock_resolve .call_count == 2
603+ assert url2 == "https://files.pythonhosted.org/testpkg-1.5-py3-none-any.whl"
604+ assert version2 == Version ("1.5" )
0 commit comments