Files
phyllomeos/cook/tests/test_generator.py
T
Lukas Greve 8ad3589223 feat: add Fedora 44 support alongside Fedora 43
- add fedora-44-mirrors ingredient and register it in the repository choices
- expand the desktop variant matrix to [43, 44, rawhide] (48 dishes)
- bump CI workflows and deploy fallback URL to Fedora 44
- cover both release repositories in tests
2026-09-04 13:55:24 +02:00

281 lines
10 KiB
Python

"""Tests for the Phyllome OS recipe generator (cook/generate_recipe.py)."""
import os
import pytest
import yaml
import generate_recipe as gen
COOK_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@pytest.fixture
def templates():
return gen.load_yaml(os.path.join(COOK_DIR, "recipe_templates.yaml"))
BASE_FRAGMENTS = [
"core/base.ks",
"core/locale.ks",
"core/network.ks",
"core/services.ks",
"packages/core.ks",
"packages/fedora-remix.ks",
"packages/hand-picked.ks",
]
def desktop_group(variants):
return {"recipes": [{"name": "desktop", "variants": variants}]}
# ---------------------------------------------------------------------------
# expand_variants
# ---------------------------------------------------------------------------
def test_expand_variants_cartesian_count():
config = {
"repository": ["43", "rawhide"],
"desktop": "gnome",
"storage": ["standard", "encrypted"],
"bootloader": ["grub", "systemd-boot"],
"hardware-support": [True, False],
"guest-agents": [True, False],
}
variants = gen.expand_variants(config)
assert len(variants) == 2 * 2 * 2 * 2 * 2
# Every variant keeps the scalar key and resolves one value per list key.
for variant in variants:
assert variant["desktop"] == "gnome"
assert variant["repository"] in ("43", "rawhide")
assert variant["storage"] in ("standard", "encrypted")
assert variant["bootloader"] in ("grub", "systemd-boot")
assert isinstance(variant["hardware-support"], bool)
assert isinstance(variant["guest-agents"], bool)
def test_expand_variants_all_combos_present():
config = {
"storage": ["standard", "encrypted"],
"bootloader": ["grub", "systemd-boot"],
}
combos = {(v["storage"], v["bootloader"]) for v in gen.expand_variants(config)}
assert combos == {
("standard", "grub"),
("standard", "systemd-boot"),
("encrypted", "grub"),
("encrypted", "systemd-boot"),
}
# ---------------------------------------------------------------------------
# collect_fragments
# ---------------------------------------------------------------------------
def test_collect_fragments_gnome_standard(templates):
variant = {
"repository": "43",
"desktop": "gnome",
"storage": "standard",
"bootloader": "grub",
"hardware-support": False,
"guest-agents": False,
}
fragments, problems = gen.collect_fragments(templates, variant)
assert problems == []
assert fragments[:7] == BASE_FRAGMENTS
assert "repo/fedora-43-mirrors.ks" in fragments
assert "storage/standard.ks" in fragments
assert "storage/encrypted.ks" not in fragments
assert "bootloader/grub.ks" in fragments
assert "core/security/enabled.ks" in fragments
assert "core/security/disabled.ks" not in fragments
assert "initial-setup/server/config.ks" in fragments
assert "guest-agents/base.ks" not in fragments
assert "packages/hardware-support.ks" not in fragments
for slug in ("desktop/gnome/config.ks", "desktop/gnome/packages.ks",
"desktop/gnome/post-scripts.ks"):
assert slug in fragments
@pytest.mark.parametrize("release", ["43", "44"])
def test_collect_fragments_release_repository(templates, release):
variant = {"repository": release}
fragments, problems = gen.collect_fragments(templates, variant)
assert problems == []
assert f"repo/fedora-{release}-mirrors.ks" in fragments
def test_collect_fragments_choices_default_to_first(templates):
variant = {"repository": "43"}
fragments, problems = gen.collect_fragments(templates, variant)
assert problems == []
# Omitted choices fall back to their first declared value.
assert "storage/standard.ks" in fragments
assert "bootloader/grub.ks" in fragments
assert "core/security/enabled.ks" in fragments
assert "initial-setup/server/config.ks" in fragments
def test_collect_fragments_flags(templates):
off, off_problems = gen.collect_fragments(
templates, {"repository": "43", "hardware-support": False, "guest-agents": False})
on, on_problems = gen.collect_fragments(
templates, {"repository": "43", "hardware-support": True, "guest-agents": True})
assert off_problems == [] and on_problems == []
assert "packages/hardware-support.ks" not in off
assert "guest-agents/base.ks" not in off
assert "packages/hardware-support.ks" in on
assert "guest-agents/base.ks" in on
def test_collect_fragments_unknown_choice_reports_problem(templates):
_, problems = gen.collect_fragments(templates, {"repository": "sid"})
assert any("repository" in p and "sid" in p for p in problems)
def test_collect_fragments_unknown_feature_reports_problem(templates):
_, problems = gen.collect_fragments(templates, {"repository": "43", "desktop": "kde"})
assert any("desktop" in p and "kde" in p for p in problems)
def test_collect_fragments_dedups(templates):
variant = {"repository": "43", "desktop": "gnome"}
fragments, _ = gen.collect_fragments(templates, variant)
assert len(fragments) == len(set(fragments))
# ---------------------------------------------------------------------------
# render_filename
# ---------------------------------------------------------------------------
def test_render_filename(templates):
manifest = desktop_group([
{"repository": "43", "desktop": "gnome", "storage": "standard",
"bootloader": "grub", "hardware-support": True, "guest-agents": False},
])
group = manifest["recipes"][0]
name = gen.render_filename(templates, group["name"],
gen.expand_variants(group["variants"][0])[0])
assert name == "desktop_43_standard_grub_gnome_hardware-support.cfg"
def test_render_filename_key_order_irrelevant(templates):
# Naming must not depend on the order keys appear in the manifest.
a = {"repository": "43", "storage": "standard", "bootloader": "grub",
"desktop": "gnome"}
b = {"desktop": "gnome", "bootloader": "grub", "storage": "standard",
"repository": "43"}
assert gen.render_filename(templates, "desktop", a) == \
gen.render_filename(templates, "desktop", b)
def test_render_filenames_unique_across_matrix(templates):
manifest = desktop_group([
{"repository": ["43", "rawhide"], "desktop": "gnome",
"storage": ["standard", "encrypted"],
"bootloader": ["grub", "systemd-boot"],
"hardware-support": [True, False], "guest-agents": [True, False]},
])
names = []
for group in manifest["recipes"]:
for config in group["variants"]:
for variant in gen.expand_variants(config):
names.append(gen.render_filename(templates, group["name"], variant))
assert len(names) == 32
assert len(set(names)) == 32
# ---------------------------------------------------------------------------
# lint_manifest
# ---------------------------------------------------------------------------
def test_lint_clean_manifest(templates):
manifest = desktop_group([
{"repository": "43", "desktop": "gnome", "storage": "standard",
"bootloader": "grub", "hardware-support": False, "guest-agents": False},
])
problems = gen.lint_manifest(manifest, templates, os.path.join(COOK_DIR, "ingredients"))
assert problems == []
def test_lint_catches_missing_ingredient(templates, tmp_path):
# Point lint at an empty ingredients dir so every fragment is missing.
manifest = desktop_group([
{"repository": "43", "desktop": "gnome", "storage": "standard",
"bootloader": "grub"},
])
problems = gen.lint_manifest(manifest, templates, str(tmp_path))
assert any("missing ingredient fragment" in p for p in problems)
def test_lint_catches_unknown_key_and_duplicates(templates):
manifest = {
"recipes": [
{"name": "desktop", "variants": [
{"repository": "43", "storage": "btrfs", "bootloader": "grub",
"desktop": "kde", "typo-key": True}]},
{"name": "desktop", "variants": [
{"repository": "43", "storage": "standard", "bootloader": "grub"}]},
]
}
problems = gen.lint_manifest(manifest, templates, os.path.join(COOK_DIR, "ingredients"))
text = "\n".join(problems)
assert "unknown variant key 'typo-key'" in text
assert "unknown value 'btrfs'" in text
assert "unknown value 'kde'" in text
assert "duplicate group name 'desktop'" in text
# ---------------------------------------------------------------------------
# flatten / validate via pykickstart
# ---------------------------------------------------------------------------
def test_flatten_and_validate(tmp_path):
ingredients = tmp_path / "ingredients"
recipes = tmp_path / "recipes"
ingredients.mkdir()
recipes.mkdir()
(ingredients / "core.ks").write_text("text\nrootpw --lock\n")
(ingredients / "locale.ks").write_text("lang en_US.UTF-8\n")
recipe = recipes / "desktop_43.cfg"
recipe.write_text(
"%include ../ingredients/core.ks\n%include ../ingredients/locale.ks\n")
dish = gen.flatten_recipe(str(recipe))
assert "text" in dish
assert "rootpw --lock" in dish
assert "lang en_US.UTF-8" in dish
# The flattened output is standalone: no %include may survive.
assert "%include" not in dish
dish_path = tmp_path / "desktop_43.dish"
dish_path.write_text(dish)
ok, error = gen.validate_dish(str(dish_path))
assert ok, error
def test_generate_writes_recipes_and_dishes(tmp_path, templates):
manifest = tmp_path / "manifest.yaml"
manifest.write_text(yaml.safe_dump(desktop_group([
{"repository": "43", "desktop": "gnome", "storage": "standard",
"bootloader": "grub", "hardware-support": False, "guest-agents": False},
])))
# recipes/ must be a sibling of ingredients/ for %include resolution, so
# symlink the real ingredients tree next to the generated recipes.
(tmp_path / "ingredients").symlink_to(os.path.join(COOK_DIR, "ingredients"),
target_is_directory=True)
recipes = tmp_path / "recipes"
dishes = tmp_path / "dishes"
recipes.mkdir()
dishes.mkdir()
code = gen.generate(str(manifest), os.path.join(COOK_DIR, "recipe_templates.yaml"),
str(recipes), str(dishes), "ingredients")
assert code == 0
assert len(list(recipes.glob("*.cfg"))) == 1
assert len(list(dishes.glob("*.cfg"))) == 1
dish = (dishes / "desktop_43_standard_grub_gnome.cfg").read_text()
assert "%include" not in dish