Add recursive capability resolver, integration tests, and GitHub workflow (see: https://chatgpt.com/share/6936abc9-87cc-800f-97e6-f7429fb1a910)

This commit is contained in:
Kevin Veen-Birkenbach
2025-12-08 11:43:39 +01:00
parent 775c30149c
commit f641b95d81
7 changed files with 990 additions and 4 deletions

View File

@@ -8,21 +8,32 @@ from pkgmgr.capabilities import (
PythonRuntimeCapability,
MakeInstallCapability,
NixFlakeCapability,
CapabilityMatcher,
detect_capabilities,
resolve_effective_capabilities,
LAYER_ORDER,
)
class DummyCtx:
"""Minimal RepoContext stub with just repo_dir."""
def __init__(self, repo_dir: str):
self.repo_dir = repo_dir
class TestCapabilities(unittest.TestCase):
# ---------------------------------------------------------------------------
# Tests for individual capability detectors
# ---------------------------------------------------------------------------
class TestCapabilitiesDetectors(unittest.TestCase):
def setUp(self):
self.ctx = DummyCtx("/tmp/repo")
@patch("pkgmgr.capabilities.os.path.exists")
def test_python_runtime_python_layer_pyproject(self, mock_exists):
"""PythonRuntimeCapability: python layer is provided if pyproject.toml exists."""
cap = PythonRuntimeCapability()
def exists_side_effect(path):
@@ -32,10 +43,17 @@ class TestCapabilities(unittest.TestCase):
self.assertTrue(cap.applies_to_layer("python"))
self.assertTrue(cap.is_provided(self.ctx, "python"))
# Other layers should not be treated as python-runtime by this branch
self.assertFalse(cap.is_provided(self.ctx, "nix"))
self.assertFalse(cap.is_provided(self.ctx, "os-packages"))
@patch("pkgmgr.capabilities._read_text_if_exists")
@patch("pkgmgr.capabilities.os.path.exists")
def test_python_runtime_nix_layer_flake(self, mock_exists, mock_read):
"""
PythonRuntimeCapability: nix layer is provided if flake.nix contains
Python-related patterns like buildPythonApplication.
"""
cap = PythonRuntimeCapability()
def exists_side_effect(path):
@@ -54,6 +72,7 @@ class TestCapabilities(unittest.TestCase):
read_data="install:\n\t echo 'installing'\n",
)
def test_make_install_makefile_layer(self, mock_file, mock_exists):
"""MakeInstallCapability: makefile layer is provided if Makefile has an install target."""
cap = MakeInstallCapability()
self.assertTrue(cap.applies_to_layer("makefile"))
@@ -61,6 +80,7 @@ class TestCapabilities(unittest.TestCase):
@patch("pkgmgr.capabilities.os.path.exists")
def test_nix_flake_capability_on_nix_layer(self, mock_exists):
"""NixFlakeCapability: nix layer is provided if flake.nix exists."""
cap = NixFlakeCapability()
def exists_side_effect(path):
@@ -72,5 +92,283 @@ class TestCapabilities(unittest.TestCase):
self.assertTrue(cap.is_provided(self.ctx, "nix"))
# ---------------------------------------------------------------------------
# Dummy capability matcher for resolver tests
# ---------------------------------------------------------------------------
class DummyCapability(CapabilityMatcher):
"""
Simple test capability that returns True/False based on a static mapping:
mapping = {
"makefile": True,
"python": False,
"nix": True,
...
}
"""
def __init__(self, name: str, mapping: dict[str, bool]):
self.name = name
self._mapping = mapping
def applies_to_layer(self, layer: str) -> bool:
return layer in self._mapping
def is_provided(self, ctx: DummyCtx, layer: str) -> bool:
# ctx is unused here; we are testing the resolution logic, not IO
return self._mapping.get(layer, False)
# ---------------------------------------------------------------------------
# Tests for detect_capabilities (raw detection)
# ---------------------------------------------------------------------------
class TestDetectCapabilities(unittest.TestCase):
def setUp(self):
self.ctx = DummyCtx("/tmp/repo")
def test_detect_capabilities_with_dummy_matchers(self):
"""
detect_capabilities should aggregate all capabilities per layer
based on the matchers' applies_to_layer/is_provided logic.
"""
layers = ["makefile", "python", "nix", "os-packages"]
dummy1 = DummyCapability(
"cap-a",
{
"makefile": True,
"python": False,
"nix": True,
},
)
dummy2 = DummyCapability(
"cap-b",
{
"python": True,
"os-packages": True,
},
)
with patch("pkgmgr.capabilities.CAPABILITY_MATCHERS", [dummy1, dummy2]):
caps = detect_capabilities(self.ctx, layers)
self.assertEqual(
caps,
{
"makefile": {"cap-a"},
"python": {"cap-b"},
"nix": {"cap-a"},
"os-packages": {"cap-b"},
},
)
# ---------------------------------------------------------------------------
# Tests for resolve_effective_capabilities (bottom-up shadowing)
# ---------------------------------------------------------------------------
class TestResolveEffectiveCapabilities(unittest.TestCase):
def setUp(self):
self.ctx = DummyCtx("/tmp/repo")
def test_bottom_up_shadowing_makefile_python_nix(self):
"""
Scenario:
- makefile: provides make-install
- python: provides python-runtime, make-install
- nix: provides python-runtime, make-install, nix-flake
- os-packages: none
Expected effective capabilities:
- makefile: {}
- python: {}
- nix: {python-runtime, make-install, nix-flake}
- os-packages: {}
"""
layers = ["makefile", "python", "nix", "os-packages"]
cap_make_install = DummyCapability(
"make-install",
{
"makefile": True,
"python": True,
"nix": True,
"os-packages": False,
},
)
cap_python_runtime = DummyCapability(
"python-runtime",
{
"makefile": False,
"python": True,
"nix": True,
"os-packages": False,
},
)
cap_nix_flake = DummyCapability(
"nix-flake",
{
"makefile": False,
"python": False,
"nix": True,
"os-packages": False,
},
)
with patch(
"pkgmgr.capabilities.CAPABILITY_MATCHERS",
[cap_make_install, cap_python_runtime, cap_nix_flake],
):
effective = resolve_effective_capabilities(self.ctx, layers)
self.assertEqual(effective["makefile"], set())
self.assertEqual(effective["python"], set())
self.assertEqual(
effective["nix"],
{"python-runtime", "make-install", "nix-flake"},
)
self.assertEqual(effective["os-packages"], set())
def test_os_packages_shadow_all_lower_layers(self):
"""
Scenario:
- python: provides python-runtime
- nix: provides python-runtime
- os-packages: provides python-runtime
Expected effective capabilities:
- python: {}
- nix: {}
- os-packages: {python-runtime}
"""
layers = ["python", "nix", "os-packages"]
cap_python_runtime = DummyCapability(
"python-runtime",
{
"python": True,
"nix": True,
"os-packages": True,
},
)
with patch(
"pkgmgr.capabilities.CAPABILITY_MATCHERS",
[cap_python_runtime],
):
effective = resolve_effective_capabilities(self.ctx, layers)
self.assertEqual(effective["python"], set())
self.assertEqual(effective["nix"], set())
self.assertEqual(effective["os-packages"], {"python-runtime"})
def test_capability_only_in_lowest_layer(self):
"""
If a capability is only provided by the lowest layer, it should remain
attached to that layer as an effective capability.
"""
layers = ["makefile", "python", "nix"]
cap_only_make = DummyCapability(
"make-install",
{
"makefile": True,
"python": False,
"nix": False,
},
)
with patch("pkgmgr.capabilities.CAPABILITY_MATCHERS", [cap_only_make]):
effective = resolve_effective_capabilities(self.ctx, layers)
self.assertEqual(effective["makefile"], {"make-install"})
self.assertEqual(effective["python"], set())
self.assertEqual(effective["nix"], set())
def test_capability_only_in_highest_layer(self):
"""
If a capability is only provided by the highest layer, it should appear
only there as an effective capability.
"""
layers = ["makefile", "python", "nix"]
cap_only_nix = DummyCapability(
"nix-flake",
{
"makefile": False,
"python": False,
"nix": True,
},
)
with patch("pkgmgr.capabilities.CAPABILITY_MATCHERS", [cap_only_nix]):
effective = resolve_effective_capabilities(self.ctx, layers)
self.assertEqual(effective["makefile"], set())
self.assertEqual(effective["python"], set())
self.assertEqual(effective["nix"], {"nix-flake"})
def test_partial_layer_subset_order_respected(self):
"""
When passing a custom subset of layers, the resolver must respect
that custom order for shadowing.
Scenario:
- layers = ["python", "nix"]
- both provide "python-runtime"
Expected:
- python: {}
- nix: {"python-runtime"}
"""
layers = ["python", "nix"]
cap_python_runtime = DummyCapability(
"python-runtime",
{
"python": True,
"nix": True,
},
)
with patch(
"pkgmgr.capabilities.CAPABILITY_MATCHERS",
[cap_python_runtime],
):
effective = resolve_effective_capabilities(self.ctx, layers)
self.assertEqual(effective["python"], set())
self.assertEqual(effective["nix"], {"python-runtime"})
def test_default_layer_order_is_used_if_none_given(self):
"""
If no explicit layers are passed, resolve_effective_capabilities
should use LAYER_ORDER.
"""
cap_dummy = DummyCapability(
"dummy-cap",
{
# Only provide something on the highest default layer
LAYER_ORDER[-1]: True,
},
)
with patch(
"pkgmgr.capabilities.CAPABILITY_MATCHERS",
[cap_dummy],
):
effective = resolve_effective_capabilities(self.ctx)
# All lower layers must be empty; highest default layer must have the cap
for layer in LAYER_ORDER[:-1]:
self.assertEqual(effective[layer], set())
self.assertEqual(effective[LAYER_ORDER[-1]], {"dummy-cap"})
if __name__ == "__main__":
unittest.main()