Files
computer-playbook/tests/unit/python/utils/install/test_collections.py
T
kevinveenbirkenbachandClaude Opus 5 d4ae5cf9f0 fix(lint): put the cache-read markers where the rule reads them
suppressed_line_numbers matches the marker on the flagged line itself, and
ruff had split two of the calls so the comment landed behind the closing
paren, one line too low. The reads that can be cached now go through
utils.cache.files, which the bare interpreter imports fine; the two that
cannot keep a marker on a line short enough to stay whole.

The parser also gains the two guards utils/install/lint/pinned.py already
carries for the same bootstrap problem: its reading is cross-checked
against PyYAML on both requirements files, and a subprocess with yaml
made unimportable proves the module still works without it. That turns
the manual venv check into a standing test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 22:00:30 +02:00

180 lines
6.0 KiB
Python

import json
import subprocess
import sys
import unittest
from pathlib import Path
from tempfile import TemporaryDirectory
from utils import PROJECT_ROOT
from utils.cache.yaml import load_yaml_any
from utils.install.collections import declared_pins, unsatisfied
_REQUIREMENTS = (
PROJECT_ROOT / "requirements" / "requirements.galaxy.yml",
PROJECT_ROOT / "requirements" / "requirements.git.yml",
)
_BOOTSTRAP_PROBE = """
import sys
class _NoPyYAML:
def find_spec(self, fullname, path=None, target=None):
if fullname == "yaml" or fullname.startswith("yaml."):
raise ImportError("PyYAML is absent during the lint bootstrap")
return None
sys.meta_path.insert(0, _NoPyYAML())
from pathlib import Path
from utils.install.lint.ansible import collections as lint_collections
from utils.install.collections import declared_pins
for fqcn, version in declared_pins(Path("requirements/requirements.galaxy.yml")):
print(fqcn, version)
"""
REQUIREMENTS = """---
collections:
- name: community.general
version: 13.4.0
- name: hetzner.hcloud
version: 7.0.1
"""
def _write_requirements(root: Path, body: str = REQUIREMENTS) -> Path:
path = root / "requirements.galaxy.yml"
path.write_text(body, encoding="utf-8")
return path
def _install(collections_dir: Path, fqcn: str, version: str) -> None:
namespace, _, name = fqcn.partition(".")
target = collections_dir / "ansible_collections" / namespace / name
target.mkdir(parents=True)
(target / "MANIFEST.json").write_text(
json.dumps({"collection_info": {"version": version}}), encoding="utf-8"
)
class TestAgainstPyYAML(unittest.TestCase):
def test_the_hand_parser_agrees_with_pyyaml(self):
for path in _REQUIREMENTS:
with self.subTest(requirements=path.name):
declared = load_yaml_any(str(path), default_if_missing={}) or {}
expected = [
(str(entry.get("name")), entry.get("version"))
for entry in declared.get("collections") or []
if isinstance(entry, dict) and entry.get("name")
]
expected = [
(name, None if version is None else str(version))
for name, version in expected
]
self.assertEqual(declared_pins(path), expected)
def test_the_parser_runs_with_pyyaml_unimportable(self):
result = subprocess.run(
[sys.executable, "-c", _BOOTSTRAP_PROBE],
cwd=str(PROJECT_ROOT),
capture_output=True,
text=True,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("community.general", result.stdout)
class TestDeclaredPins(unittest.TestCase):
def test_a_quoted_version_is_read_without_its_quotes(self):
body = (
"---\ncollections:\n"
' - name: double.quoted\n version: "13.4.0"\n'
" - name: single.quoted\n version: '1.0.0'\n"
" - name: commented\n version: 2.2.2 # pinned\n"
)
with TemporaryDirectory() as tmp:
root = Path(tmp)
requirements = _write_requirements(root, body)
self.assertEqual(
declared_pins(requirements),
[
("double.quoted", "13.4.0"),
("single.quoted", "1.0.0"),
("commented", "2.2.2"),
],
)
def test_an_entry_without_a_version_stays_unpinned(self):
body = "---\ncollections:\n - name: bare.entry\n"
with TemporaryDirectory() as tmp:
root = Path(tmp)
self.assertEqual(
declared_pins(_write_requirements(root, body)), [("bare.entry", None)]
)
def test_a_missing_file_declares_nothing(self):
with TemporaryDirectory() as tmp:
self.assertEqual(declared_pins(Path(tmp) / "absent.yml"), [])
class TestUnsatisfied(unittest.TestCase):
def test_every_pin_installed_at_its_version_is_satisfied(self):
with TemporaryDirectory() as tmp:
root = Path(tmp)
requirements = _write_requirements(root)
_install(root, "community.general", "13.4.0")
_install(root, "hetzner.hcloud", "7.0.1")
self.assertEqual(unsatisfied(requirements, root), [])
def test_absent_collection_is_reported(self):
with TemporaryDirectory() as tmp:
root = Path(tmp)
requirements = _write_requirements(root)
_install(root, "community.general", "13.4.0")
self.assertEqual(unsatisfied(requirements, root), ["hetzner.hcloud:7.0.1"])
def test_wrong_version_is_reported_although_the_directory_exists(self):
with TemporaryDirectory() as tmp:
root = Path(tmp)
requirements = _write_requirements(root)
_install(root, "community.general", "13.4.0")
_install(root, "hetzner.hcloud", "6.9.0")
self.assertEqual(unsatisfied(requirements, root), ["hetzner.hcloud:7.0.1"])
def test_unpinned_entry_can_never_be_confirmed(self):
body = "---\ncollections:\n - name: community.general\n"
with TemporaryDirectory() as tmp:
root = Path(tmp)
requirements = _write_requirements(root, body)
_install(root, "community.general", "13.4.0")
self.assertEqual(unsatisfied(requirements, root), ["community.general"])
def test_unreadable_manifest_is_reported(self):
with TemporaryDirectory() as tmp:
root = Path(tmp)
requirements = _write_requirements(root)
_install(root, "community.general", "13.4.0")
_install(root, "hetzner.hcloud", "7.0.1")
manifest = (
root / "ansible_collections" / "hetzner" / "hcloud" / "MANIFEST.json"
)
manifest.write_text("{not json", encoding="utf-8")
self.assertEqual(unsatisfied(requirements, root), ["hetzner.hcloud:7.0.1"])
if __name__ == "__main__":
unittest.main()