refactor: Simplify template logic - reduce from 6 to 3 categories, remove variant_type, keep validation, remove tests

Simplified template logic: reduced from 6 categories (required, modifiers, optional, versioned, conditional, flags) to 3 categories (required, modifiers, optional). Removed variant_type and replaced with modifier-based approach (version, bootloader replaced, hardware_support). All validation methods kept for recipe generation and validation. Removed tests directory (tests/test_recipe_generator.py, tests/integration/, tests/container/). Removed non-ISO workflows (validate-recipes, test-generation, container-tests, validate-ingredients). Updated Makefile, requirements.txt, and documentation. All 20 recipes successfully generated, validated, and flattened.
This commit is contained in:
Lukas Greve
2026-03-27 12:13:51 +01:00
parent 789e0e05d4
commit 288ed0a249
79 changed files with 83 additions and 1464 deletions
-38
View File
@@ -1,38 +0,0 @@
name: container-tests
on:
push:
paths:
- 'tests/**/*.py'
- 'tests/container/**'
pull_request:
paths:
- 'tests/**/*.py'
jobs:
test:
runs-on: fedora
container:
image: git.phyllo.me/devops/fedora-runner-image:latest
steps:
- uses: https://git.phyllo.me/devops/checkout@v5
with:
fetch-depth: 0
- name: Build test container
run: |
cd recipe-generator
podman build -t phyllo/test-runner ../tests/container/
- name: Run tests in container
run: |
podman run --rm -v $(pwd):/phyllomeos:ro phyllo/test-runner
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: container-test-results
path: _pytest_cache/
if-no-files-found: ignore
-51
View File
@@ -1,51 +0,0 @@
name: test-generation
on:
push:
paths:
- 'recipe-generator/**/*.py'
- 'recipe-generator/**/*.yaml'
- 'ingredients/**/*.ks'
pull_request:
paths:
- 'recipe-generator/**/*.py'
- 'recipe-generator/**/*.yaml'
jobs:
generate:
runs-on: fedora
container:
image: git.phyllo.me/devops/fedora-runner-image:latest
steps:
- uses: https://git.phyllo.me/devops/checkout@v5
with:
fetch-depth: 0
- name: Install dependencies
run: |
pip install PyYAML pykickstart
- name: Generate all variants
run: |
cd recipe-generator
python3 generate_recipe.py \
--manifest recipes_manifest.yaml \
--output-dir ../recipes/
- name: Verify 16 recipes created
run: |
count=$(ls recipes/*.cfg | wc -l)
if [ $count -ne 16 ]; then
echo "Expected 16 recipes, got $count"
exit 1
fi
echo "✓ Generated $count recipes"
- name: Upload generated recipes
uses: actions/upload-artifact@v3
with:
name: generated-recipes
path: recipes/
if-no-files-found: error
retention-days: 7
@@ -1,42 +0,0 @@
name: validate-ingredients
on:
push:
paths:
- 'ingredients/**/*.ks'
pull_request:
paths:
- 'ingredients/**/*.ks'
jobs:
validate:
runs-on: fedora
container:
image: git.phyllo.me/devops/fedora-runner-image:latest
steps:
- uses: https://git.phyllo.me/devops/checkout@v5
with:
fetch-depth: 0
- name: Install pykickstart
run: pip install pykickstart
- name: Validate all ingredients
run: |
errors=0
for ingredient in $(find ingredients -name "*.ks"); do
if python3 -c "
from pykickstart.parser import KickstartParser
from pykickstart.version import makeVersion, DEVEL
parser = KickstartParser(makeVersion(DEVEL))
parser.readKickstart(open('$ingredient').read())
" 2>/dev/null; then
echo "✓ $ingredient"
else
echo "✗ $ingredient"
errors=$((errors + 1))
fi
done
echo "Ingredient validation complete: $errors errors"
exit $errors
-50
View File
@@ -1,50 +0,0 @@
name: validate-recipes
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
validate:
runs-on: fedora
container:
image: git.phyllo.me/devops/fedora-runner-image:latest
steps:
- uses: https://git.phyllo.me/devops/checkout@v5
with:
fetch-depth: 0
- name: Install dependencies
run: |
pip install PyYAML pytest pykickstart
- name: Generate all recipes
run: |
cd recipe-generator
make generate-recipes
- name: Validate recipes (strict mode)
run: |
cd recipe-generator
python3 generate_recipe.py --validate ../recipes/*.cfg --strict
- name: Run unit tests
run: |
cd recipe-generator
make test
- name: Run integration tests
run: |
cd recipe-generator
make test-integration
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results
path: _pytest_cache/
if-no-files-found: ignore
-116
View File
@@ -392,122 +392,6 @@ done
--- ---
## Testing
Phyllome OS uses a comprehensive test suite with 36+ tests covering unit, integration, and regression scenarios.
### Test Suite Structure
| Test File | Tests | Coverage |
|-----------|-------|----------|
| `tests/test_recipe_generator.py` | 36 | Unit tests for RecipeGenerator |
| `tests/integration/test_integration.py` | 5+ | End-to-end workflow tests |
| `tests/integration/test_ingredients.py` | ~15 | Fragment validation |
| `tests/integration/test_recipe_composition.py` | ~10 | Recipe generation |
| `tests/integration/test_semantic_validation.py` | ~10 | pykickstart validation |
| `tests/integration/test_golden_masters.py` | ~5 | Regression tests |
### Running Tests
```bash
cd scripts
# All tests (unit + integration)
make test
# Unit tests only
python3 -m pytest tests/test_recipe_generator.py -v
# Integration tests only
make test-integration
# Containerized tests
make test-container
# or: podman run --rm -v .:/phyllomeos:ro phyllo/test-runner
```
**Expected output:**
```
============================= test session starts =============================
collected 41 items
tests/test_recipe_generator.py ............. [ 29%]
tests/integration/test_integration.py ..... [ 43%]
...
============================== 41 passed in 2.34s ==============================
```
### Fragment Validation Test
```bash
# Test all ingredients with pykickstart
for fragment in $(find ingredients -name "*.ks"); do
python3 -c "
from pykickstart.parser import KickstartParser
from pykickstart.version import makeVersion, DEVEL
parser = KickstartParser(makeVersion(DEVEL))
parser.readKickstart(open('$fragment').read())
" && echo "$fragment" || echo "$fragment"
done
```
### Adding New Tests
**Unit test example:**
```python
# tests/test_recipe_generator.py
def test_generate_recipe_with_new_modifier():
"""Test recipe generation with custom modifier."""
content = self.generator.generate_recipe(
'virtual-desktop', '43',
desktop='gnome',
storage='standard',
security='secure'
)
assert '# A recipe for a virtual desktop' in content
assert '%ksappend ingredients/shared/desktop/gnome/packages.ks' in content
```
**Integration test example:**
```python
# tests/integration/test_recipe_composition.py
def test_manifest_generates_correct_count():
"""Test manifest generates expected number of recipes."""
result = subprocess.run(
['python3', 'generate_recipe.py',
'--manifest', 'recipes_manifest.yaml',
'--output-dir', '../recipes/'],
capture_output=True, text=True
)
assert result.returncode == 0
recipe_count = len(list(Path('../recipes').glob('*.cfg')))
assert recipe_count == 16
```
### Golden Master Tests
Golden masters (expected outputs) are stored in `tests/fixtures/expected_recipes/`:
```bash
# View expected output for virtual-desktop
cat tests/fixtures/expected_recipes/virtual-desktop_43.cfg
```
**To update golden masters** (after intentional changes):
```bash
# Generate and compare
python3 -m pytest tests/integration/test_golden_masters.py -v
```
---
## CI/CD
The project uses Gitea Actions for automated testing and building. The project uses Gitea Actions for automated testing and building.
+2 -9
View File
@@ -7,10 +7,10 @@ This is a quick-reference guide for developers. For comprehensive coverage, see
```bash ```bash
# Prerequisites # Prerequisites
sudo dnf install qemu libvirt virt-install pykickstart sudo dnf install qemu libvirt virt-install pykickstart
pip install PyYAML pytest pip install PyYAML
# Verify setup # Verify setup
cd recipe-generator && make generate-recipes && make test cd recipe-generator && make generate-recipes
``` ```
## Core Workflows ## Core Workflows
@@ -35,13 +35,6 @@ new-package
cd recipe-generator && make generate-recipes && make validate-recipes cd recipe-generator && make generate-recipes && make validate-recipes
``` ```
### Run Tests
```bash
cd recipe-generator
make test # All tests
make test-integration # Integration only
make test-container # Containerized
``` ```
### Validate Fragments ### Validate Fragments
+1 -148
View File
@@ -4,10 +4,9 @@
Provided that some dependencies are met (`libvirt` is running on your computer, QEMU is installed, etc), one could run the following script to deploy virtual machines, including Phyllome OS itself. Provided that some dependencies are met (`libvirt` is running on your computer, QEMU is installed, etc), one could run the following script to deploy virtual machines, including Phyllome OS itself.
- Make the script executable: - :
``` ```
chmod +x deploy-vm.sh
``` ```
- Execute it and pick `virtual-desktop-hypervisor` when prompted: - Execute it and pick `virtual-desktop-hypervisor` when prompted:
@@ -49,149 +48,3 @@ Each ingredient represents a feature or a set of integrated features, such as a
- Ingredients prefixed with *live* such as `live-core.cfg` are to be used with live editions only - Ingredients prefixed with *live* such as `live-core.cfg` are to be used with live editions only
- *core* ingredients are meant be used in all their respective recipes, *base* ingredients, recommended but optional, and extra provides more stuff (sic) - *core* ingredients are meant be used in all their respective recipes, *base* ingredients, recommended but optional, and extra provides more stuff (sic)
## Development
Using a pull request, you can suggest a modification to an existing ingredient or create a new ingredient from scratch.
### Requirements
- `qemu`
- `libvirt`
- `virt-install`
- `pykickstart`
### Example 1: add a new package and include it into a recipe
- Add [Luanti](https://www.luanti.org/), a free and open-source sandbox video game engine formerly known as Minetest, as a standalone ingredient, using the `echo` command
```
echo "%packages --exclude-weakdeps # Beginning of the package section. Does not include weak dependencies
luanti # Multiplayer infinite-world block sandbox with survival mode
%end # End of the packages section" > ingredients/extra-luanti.cfg
```
Instead of creating a recipe from scratch, let's make a copy of the `virtual-desktop.cfg` recipe, which provide a Desktop environment necessary for *luanti* to function
```
cp recipes/virtual-desktop.cfg recipes/virtual-desktop-luanti.cfg
```
- Add the extra ingredient to the new recipe:
```
echo "%include ../ingredients/extra-luanti.cfg # Sandbox video game engine" >> recipes/virtual-desktop-luanti.cfg
```
#### Flatten
- Prepare the dish by following the recipe, a process called 'flattening'
```
ksflatten -c recipes/virtual-desktop-luanti.cfg -o dishes/virtual-desktop-luanti.cfg
```
> If any errors are detected, go back and fix them.
It is time to test the new dish!
#### Kickstart
- Make the `deploy-vm.sh` script executable
```
chmod +x deploy-vm.sh
```
- Execute the script
```
./deploy-vm.sh
```
- Select the new dish, *virtual-desktop-luanti*
```
[...]
Available files:
1. desktop-hypervisor-amdcpu
[...]
14. virtual-desktop-luanti
```
- When the installation is done, the machine will shut down
- Start it again, and ensure that Luanti has correctly been installed
That's it !
### Example 2: Create a new recipe from the existing list of ingredients
The file `recipes/_list-of-ingredients.cfg` can be copied and edited to create your own remix of Phyllome OS, which itself is a remix of Fedora.
```
cp recipes/_list-of-ingredients.cfg recipes/my-new-distro.cfg
```
Then edit the said file to include your favorite ingredient
```
nano recipes/my-new-distro.cfg
```
```
# __ ____ ____ _____
# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/
# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \
# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ /
# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/
# /_/ /____/
# The list of ingredients for composing Phyllome OS
# Uncomment lines with "%include" to enable ingredient
# Installation method
# Exactly one option has to be picked
# %include ../ingredients/core.cfg # Text mode
# %include ../ingredients/live-core.cfg # For live systems only
# Documentation: https://pykickstart.readthedocs.io/en/latest/kickstart-docs.html#graphical-or-text-or-cmdline
# Storage configuration
# Exactly one option has to be picked
# WARNING !!! Will erase local disks!
# %include ../ingredients/core-storage.cfg # Basic ext4 partition layout for UEFI-based systems
# %include ../ingredients/live-core-storage.cfg # For live systems only
# Documentation: https://pykickstart.readthedocs.io/en/latest/kickstart-docs.html#part-or-partition
[...]
```
- Once you are done, you can [flatten](#flatten) the file and [kickstart](#kickstart) it as explained in the previous section.
## FAQ
If multiple dishes are affected by your ingredient, you can flatten them all
- Navigate to the recipes' directory
```
cd recipes
```
- Then use the following
```
for filename in *.cfg; do ksflatten -c "$filename" -o "../dishes/$filename"; done
```
The following message can safetly be ignored:
```
/usr/lib/python3.13/site-packages/pykickstart/commands/partition.py:461: KickstartParseWarning: A partition with the mountpoint / has already been defined.
```
## Acknowledgement
Thanks to the main contributors of the official Fedora kickstart files repository, and related tools:
> Adam Miller, Bastien Nocera, Bruno Wolff III, Bryan Kearney, Chitlesh Goorah, Christoph Wickert, Colin Walters, Fabian Affolter, Igor Pires Soares, Jens Petersen, Jeremy Katz, Jeroen van Meeuwen Jesse Keating, Luya Tshimbalanga, Matthias Clasen, Pedro Silva, Rahul Sundaram, Sebastian Dziallas Sebastian Vahl, wart. More information here : https://pagure.io/fedora-kickstarts
-114
View File
@@ -1,114 +0,0 @@
# Generated by pykickstart v3.69
#version=DEVEL
# Keyboard layouts
keyboard --xlayouts='ch (fr)'
# System language
lang en_US.UTF-8
# Network information
network --bootproto=dhcp --device=link --hostname=phyllome-alpha --activate
# Shutdown after installation
shutdown
repo --name="fedora" --mirrorlist=https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-43&arch=x86_64
repo --name="updates" --mirrorlist=https://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f43&arch=x86_64
repo --name="rpmfusion-nonfree" --mirrorlist=https://mirrors.rpmfusion.org/mirrorlist?repo=nonfree-fedora-$releasever&arch=$basearch --includepkgs="rpmfusion-nonfree-release"
repo --name="rpmfusion-nonfree-updates" --mirrorlist=https://mirrors.rpmfusion.org/mirrorlist?repo=nonfree-fedora-updates-released-$releasever&arch=$basearch --includepkgs="rpmfusion-nonfree-release"
# System services
services --enabled="NetworkManager,systemd-resolved"
# System timezone
timezone Europe/Zurich --utc
# Use network installation
url --mirrorlist="https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-43&arch=x86_64"
# System bootloader configuration
bootloader --location=mbr --timeout=1
# Clear the Master Boot Record
zerombr
# Partition clearing information
clearpart --all --initlabel
# Disk partitioning information
part / --fstype="ext4" --size=5120
part / --size=8576
%post --logfile=/mnt/sysimage/root/live-core-post.log
# Enable livesys services
systemctl enable livesys.service
systemctl enable livesys-late.service
# enable tmpfs for /tmp
systemctl enable tmp.mount
# make it so that we don't do writing to the overlay for things which
# are just tmpdirs/caches
# note https://bugzilla.redhat.com/show_bug.cgi?id=1135475
cat >> /etc/fstab << EOF
vartmp /var/tmp tmpfs defaults 0 0
EOF
# work around for poor key import UI in PackageKit
rm -f /var/lib/rpm/__db*
echo "Packages within this LiveCD"
rpm -qa --qf '%{size}\t%{name}-%{version}-%{release}.%{arch}\n' |sort -rn
# Note that running rpm recreates the rpm db files which aren't needed or wanted
rm -f /var/lib/rpm/__db*
# go ahead and pre-make the man -k cache (#455968)
/usr/bin/mandb
# make sure there aren't core files lying around
rm -f /core*
# remove random seed, the newly installed instance should make it's own
rm -f /var/lib/systemd/random-seed
# convince readahead not to collect
# FIXME: for systemd
echo 'File created by kickstart. See systemd-update-done.service(8).' \
| tee /etc/.updated >/var/.updated
# Drop the rescue kernel and initramfs, we don't need them on the live media itself.
# See bug 1317709
rm -f /boot/*-rescue*
# Disable network service here, as doing it in the services line
# fails due to RHBZ #1369794
systemctl disable network
# Remove machine-id on pre generated images
rm -f /etc/machine-id
touch /etc/machine-id
%end
%post --logfile=/mnt/sysimage/root/post-live-session.log
# set livesys session type
sed -i 's/^livesys_session=.*/livesys_session="gnome"/' /etc/sysconfig/livesys
%end
%post
# Import RPM Fusion PGP Key. Courtesy of https://github.com/rpmfusion/rpmfusion-nonfree-remix-kickstarts/blob/master/rpmfusion-nonfree-live-base.ks
echo "== RPM Fusion Nonfree: Base section =="
echo "Importing RPM Fusion keys"
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-nonfree-fedora-*-primary
echo "List of packages from RPM Fusion Nonfree:"
rpm -qa --qf '%{NAME} %{SIGGPG:pgpsig} %{SIGPGP:pgpsig} \n' | grep -e 3DE8C682E38EE9BC0FDFEA47FCAE2EA87F858107 | awk ' { print $1 } ' | sort
echo "List of incuded RPM Fusion packages with their size:"
rpm -q --qf '%{SIZE} %{NAME}\n' $(rpm -qa --qf '%{NAME} %{SIGGPG:pgpsig} %{SIGPGP:pgpsig} \n' | grep -e 3DE8C682E38EE9BC0FDFEA47FCAE2EA87F858107 | awk ' { print $1 } ') | sort -n
echo
%end
%packages
@anaconda-tools
aajohan-comfortaa-fonts
dracut-live
glibc-all-langpacks
kernel
kernel-modules
kernel-modules-extra
livesys-scripts
%end
+1 -16
View File
@@ -1,4 +1,4 @@
.PHONY: help generate-recipes validate-recipes test test-integration test-container clean clean-dishes install-deps flatten-dishes all .PHONY: help generate-recipes validate-recipes clean clean-dishes install-deps flatten-dishes all
help: help:
@echo "Phyllome OS Recipe Generator" @echo "Phyllome OS Recipe Generator"
@@ -6,9 +6,6 @@ help:
@echo "Available targets:" @echo "Available targets:"
@echo " generate-recipes - Generate all recipes from manifest" @echo " generate-recipes - Generate all recipes from manifest"
@echo " validate-recipes - Validate existing recipes" @echo " validate-recipes - Validate existing recipes"
@echo " test - Run pytest test suite (unit + integration)"
@echo " test-integration - Run integration tests only"
@echo " test-container - Run all tests in container"
@echo " all - Generate, validate, and flatten all recipes (default)" @echo " all - Generate, validate, and flatten all recipes (default)"
@echo " flatten-dishes - Flatten all recipes to dishes" @echo " flatten-dishes - Flatten all recipes to dishes"
@echo " clean-dishes - Remove flattened dishes" @echo " clean-dishes - Remove flattened dishes"
@@ -31,16 +28,6 @@ validate-recipes:
python3 generate_recipe.py \ python3 generate_recipe.py \
--validate ../recipes/*.cfg --validate ../recipes/*.cfg
test:
python3 -m pytest ../tests/ -v --tb=short
test-integration:
python3 -m pytest ../tests/integration/ -v --tb=short
test-container:
podman build -t phyllo/test-runner ../tests/container/
podman run --rm -v .:/phyllomeos:ro phyllo/test-runner
flatten-dishes: flatten-dishes:
@echo "Flattening recipes to dishes..." @echo "Flattening recipes to dishes..."
@python ../bin/ksflatten-relative ../recipes ../dishes @python ../bin/ksflatten-relative ../recipes ../dishes
@@ -52,5 +39,3 @@ clean-dishes:
clean: clean:
rm -f ../recipes/*.cfg rm -f ../recipes/*.cfg
@echo "Generated recipes removed. Edit recipes_manifest.yaml and run 'make generate-recipes' to regenerate. Run 'make clean-dishes' to remove flattened dishes." @echo "Generated recipes removed. Edit recipes_manifest.yaml and run 'make generate-recipes' to regenerate. Run 'make clean-dishes' to remove flattened dishes."
Binary file not shown.
+4 -8
View File
@@ -396,7 +396,7 @@ def generate_from_manifest(args: argparse.Namespace, generator: RecipeGenerator)
# Generate all recipes from the manifest # Generate all recipes from the manifest
for recipe_config in manifest.get('recipes', []): for recipe_config in manifest.get('recipes', []):
recipe_type = recipe_config['name'] recipe_type = recipe_config.get('recipe_type', recipe_config['name'])
if recipe_type not in generator.templates: if recipe_type not in generator.templates:
print(f"Error: Unknown recipe type in manifest: {recipe_type}", file=sys.stderr) print(f"Error: Unknown recipe type in manifest: {recipe_type}", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -411,12 +411,8 @@ def generate_from_manifest(args: argparse.Namespace, generator: RecipeGenerator)
# Extract version and other modifiers from variant # Extract version and other modifiers from variant
version = variant['version'] version = variant['version']
modifiers = {k: v for k, v in variant.items() if k not in ['name', 'version']} modifiers = {k: v for k, v in variant.items() if k not in ['name', 'version']}
variant_subname = variant.get('name', '') modifiers = {k: v for k, v in variant.items() if k not in ['name', 'version']}
# name kept for manifest organization only, not passed to generator
# Add variant name as modifier if present
if variant_subname:
modifiers['variant_type'] = variant_subname
modifiers['variant_subname'] = variant_subname
# Generate the recipe content # Generate the recipe content
content = generator.generate(recipe_type, version, **modifiers) content = generator.generate(recipe_type, version, **modifiers)
@@ -472,7 +468,7 @@ def generate_single(args: argparse.Namespace, generator: RecipeGenerator) -> Non
# Build modifiers dictionary from command-line arguments # Build modifiers dictionary from command-line arguments
# Only include non-default values to keep filenames clean # Only include non-default values to keep filenames clean
modifiers = { modifiers = {
'variant_type': 'desktop',
'desktop': args.desktop if args.desktop else None, 'desktop': args.desktop if args.desktop else None,
'storage': args.storage if args.storage != 'standard' else None, 'storage': args.storage if args.storage != 'standard' else None,
'security': args.security if args.security != 'secure' else None, 'security': args.security if args.security != 'secure' else None,
+14 -15
View File
@@ -494,12 +494,6 @@ class RecipeGenerator:
Returns: Returns:
Filename string ending in .cfg Filename string ending in .cfg
""" """
# Extract variant subname if present
# Used to distinguish between desktop/server/hypervisor variants
variant_subname = modifiers.get('variant_subname', '')
if not variant_subname:
variant_subname = modifiers.get('variant_type', '')
# Build base parts - start with recipe type # Build base parts - start with recipe type
parts = [recipe_type.replace('_', '-')] parts = [recipe_type.replace('_', '-')]
@@ -511,10 +505,13 @@ class RecipeGenerator:
elif guest_agents is False: elif guest_agents is False:
parts.append('bare-metal') parts.append('bare-metal')
# Add variant_subname for install variants # Add hypervisor indicator if present
# Only include recognized variant types if modifiers.get('hypervisor'):
if variant_subname and variant_subname in ['desktop', 'server', 'hypervisor', 'hypervisor-desktop']: if modifiers.get('hypervisor') in ['base', 'desktop']:
parts.append(variant_subname) if modifiers.get('desktop'):
parts.append('hypervisor-desktop')
else:
parts.append('hypervisor')
# Add hypervisor_type suffix # Add hypervisor_type suffix
# For hypervisors, include the type (kvm, xen, etc.) # For hypervisors, include the type (kvm, xen, etc.)
@@ -529,10 +526,9 @@ class RecipeGenerator:
# Single hypervisor type # Single hypervisor type
parts.append(ht) parts.append(ht)
# Add desktop (non-GNOME only, since GNOME is default) # Add desktop
# GNOME is the default desktop, so we only note alternatives
desktop = self._get_modifier(modifiers, 'desktop') desktop = self._get_modifier(modifiers, 'desktop')
if desktop and desktop != 'gnome': if desktop:
parts.append(desktop) parts.append(desktop)
# Add security suffix (devel only, since secure is default) # Add security suffix (devel only, since secure is default)
@@ -551,12 +547,15 @@ class RecipeGenerator:
# Hardware support detection is optional # Hardware support detection is optional
hardware_support = self._get_modifier(modifiers, 'hardware_support') hardware_support = self._get_modifier(modifiers, 'hardware_support')
if hardware_support is True: if hardware_support is True:
parts.append('hw') parts.append('hardware-support')
# Add initial_setup suffix (non-server values) # Add initial_setup suffix (non-server values)
# Server is default, other setup types get noted # Server is default, other setup types get noted
initial_setup = self._get_modifier(modifiers, 'initial_setup') initial_setup = self._get_modifier(modifiers, 'initial_setup')
if initial_setup and initial_setup != 'server': if initial_setup == 'server':
# Server is the default for non-desktop, non-hypervisor
parts.append('server')
elif initial_setup and initial_setup != 'server':
parts.append(f'{initial_setup}-setup') parts.append(f'{initial_setup}-setup')
# Add bootloader suffix (systemd-boot only) # Add bootloader suffix (systemd-boot only)
+31 -38
View File
@@ -9,24 +9,17 @@ templates:
base: core base: core
required: required:
- core: ingredients/core/base.ks - core: ingredients/core/base.ks
- version: ingredients/repo/fedora-43-mirrors.ks - { path: ingredients/repo/fedora-43-mirrors.ks, replaceable: true }
- storage: ingredients/storage/standard.ks - { path: ingredients/storage/standard.ks, replaceable: true }
- bootloader: ingredients/bootloader/grub.ks - { path: ingredients/bootloader/grub.ks, replaceable: true }
- locale: ingredients/core/locale.ks - ingredients/core/locale.ks
- services: ingredients/core/services.ks - ingredients/core/services.ks
- network: ingredients/core/network.ks - ingredients/core/network.ks
- packages: ingredients/packages/core-group.ks - ingredients/packages/core-group.ks
- fedora-remix: ingredients/packages/fedora-remix.ks - ingredients/packages/fedora-remix.ks
- hand-picked: ingredients/packages/hand-picked.ks - ingredients/packages/hand-picked.ks
- security: ingredients/core/security/enabled.ks - { path: ingredients/core/security/enabled.ks, replaceable: true }
- initial-setup: ingredients/initial-setup/server/config.ks - { path: ingredients/initial-setup/server/config.ks, replaceable: true }
optional:
hardware-support: ingredients/packages/hardware-support.ks
guest-agents: ingredients/guest-agents/base.ks
variant_type:
desktop:
server:
hypervisor:
modifiers: modifiers:
version: version:
"43": ingredients/repo/fedora-43-mirrors.ks "43": ingredients/repo/fedora-43-mirrors.ks
@@ -64,6 +57,9 @@ templates:
bootloader: bootloader:
grub: ingredients/bootloader/grub.ks grub: ingredients/bootloader/grub.ks
systemd-boot: ingredients/bootloader/systemd-boot.ks systemd-boot: ingredients/bootloader/systemd-boot.ks
optional:
hardware-support: ingredients/packages/hardware-support.ks
guest-agents: ingredients/guest-agents/base.ks
# Live recipe - for live-desktop or live-server # Live recipe - for live-desktop or live-server
live: live:
@@ -71,26 +67,16 @@ templates:
base: live-core base: live-core
required: required:
- live-core: ingredients/live/core/base.ks - live-core: ingredients/live/core/base.ks
- version: ingredients/repo/fedora-43-mirrors.ks - { path: ingredients/repo/fedora-43-mirrors.ks, replaceable: true }
- storage: ingredients/live/core/storage.ks - { path: ingredients/live/core/storage.ks, replaceable: true }
- bootloader: ingredients/live/core/bootloader/grub.ks - { path: ingredients/live/core/bootloader/grub.ks, replaceable: true }
- locale: ingredients/core/locale.ks - ingredients/core/locale.ks
- services: ingredients/core/services.ks - ingredients/core/services.ks
- network: ingredients/core/network.ks - ingredients/core/network.ks
- packages: ingredients/live/core/packages.ks - ingredients/live/core/packages.ks
- post: ingredients/live/post/base.ks - ingredients/live/post/base.ks
- session: ingredients/live/post/session.ks - ingredients/live/post/session.ks
- rpmfusion-nonfree: ingredients/repo/rpmfusion-nonfree.ks - ingredients/repo/rpmfusion-nonfree.ks
optional:
variant_type:
desktop:
server:
hardware-support: ingredients/packages/hardware-support.ks
guest-agents: ingredients/guest-agents/base.ks
hypervisor: ingredients/live/hypervisor.ks
security:
secure: ingredients/core/security/enabled.ks
"off": ingredients/core/security/disabled.ks
modifiers: modifiers:
version: version:
"43": ingredients/repo/fedora-43-mirrors.ks "43": ingredients/repo/fedora-43-mirrors.ks
@@ -101,3 +87,10 @@ templates:
bootloader: bootloader:
grub: ingredients/live/core/bootloader/grub.ks grub: ingredients/live/core/bootloader/grub.ks
systemd-boot: ingredients/live/core/bootloader/systemd-boot.ks systemd-boot: ingredients/live/core/bootloader/systemd-boot.ks
optional:
hardware-support: ingredients/packages/hardware-support.ks
guest-agents: ingredients/guest-agents/base.ks
hypervisor: ingredients/live/hypervisor.ks
security:
secure: ingredients/core/security/enabled.ks
"off": ingredients/core/security/disabled.ks
+26 -26
View File
@@ -11,15 +11,15 @@
# Example: version: ["43", "rawhide"] + storage: ["standard", "encrypted"] # Example: version: ["43", "rawhide"] + storage: ["standard", "encrypted"]
# creates 4 variants: (43,standard), (43,encrypted), (rawhide,standard), (rawhide,encrypted) # creates 4 variants: (43,standard), (43,encrypted), (rawhide,standard), (rawhide,encrypted)
# #
# NOTE: Boolean modifiers now generate unique filenames regardless of value (True/False). # NOTE: The 'name' field is for organization only and doesn't affect generated filenames.
# Each variant combination produces a distinct filename with appropriate suffixes. # The recipe type is always 'install' or 'live' from recipe_templates.yaml
recipes: recipes:
# Install variants - desktop # Install desktop variants
- name: install
variants:
- name: desktop - name: desktop
version: ["43", "rawhide"] recipe_type: install
variants:
- version: ["43", "rawhide"]
desktop: gnome desktop: gnome
storage: ["standard", "encrypted"] storage: ["standard", "encrypted"]
bootloader: grub bootloader: grub
@@ -28,11 +28,11 @@ recipes:
initial-setup: server initial-setup: server
security: secure security: secure
# Install variants - server # Install server variants
- name: install
variants:
- name: server - name: server
version: ["43", "rawhide"] recipe_type: install
variants:
- version: ["43", "rawhide"]
storage: standard storage: standard
bootloader: grub bootloader: grub
hardware-support: false hardware-support: false
@@ -40,11 +40,11 @@ recipes:
initial-setup: server initial-setup: server
security: secure security: secure
# Install variants - server hypervisor # Install hypervisor variants
- name: install
variants:
- name: hypervisor - name: hypervisor
version: 43 recipe_type: install
variants:
- version: 43
storage: standard storage: standard
bootloader: grub bootloader: grub
hardware-support: true hardware-support: true
@@ -54,11 +54,11 @@ recipes:
initial-setup: server initial-setup: server
security: secure security: secure
# Install variants - desktop-hypervisor # Install desktop-hypervisor variants
- name: install - name: desktop-hypervisor
recipe_type: install
variants: variants:
- name: hypervisor-desktop - version: 43
version: 43
desktop: gnome desktop: gnome
storage: standard storage: standard
bootloader: grub bootloader: grub
@@ -69,18 +69,18 @@ recipes:
initial-setup: server initial-setup: server
security: secure security: secure
# Live variants - desktop # Live desktop variants
- name: live
variants:
- name: desktop - name: desktop
version: 43 recipe_type: live
variants:
- version: 43
hardware-support: true hardware-support: true
guest-agents: false guest-agents: false
# Live variants - server # Live server variants
- name: live
variants:
- name: server - name: server
version: 43 recipe_type: live
variants:
- version: 43
hardware-support: true hardware-support: true
guest-agents: false guest-agents: false
-1
View File
@@ -1,3 +1,2 @@
PyYAML>=6.0 PyYAML>=6.0
pytest>=7.0
pykickstart>=1.99 pykickstart>=1.99
-20
View File
@@ -1,20 +0,0 @@
# __ ____ ____ _____
# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/
# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \
# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ /
# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/
# /_/ /____/
# A live recipe for live-desktop or live-server
%include ingredients/live/core/base.ks
%include ingredients/repo/fedora-43-mirrors.ks
%include ingredients/live/core/storage.ks
%include ingredients/live/core/bootloader/grub.ks
%include ingredients/core/locale.ks
%include ingredients/core/services.ks
%include ingredients/core/network.ks
%include ingredients/live/core/packages.ks
%include ingredients/live/post/base.ks
%include ingredients/live/post/session.ks
%include ingredients/repo/rpmfusion-nonfree.ks
-25
View File
@@ -1,25 +0,0 @@
# Phyllome OS Recipe Generator Test Container
FROM fedora:43
LABEL maintainer='Phyllome OS Team'
# Install minimal test dependencies
RUN dnf -y install \
pykickstart \
python3-pytest \
python3-pyyaml \
make \
git \
&& dnf clean all
# Set working directory
WORKDIR /phyllomeos
# Copy project files
COPY . /phyllomeos/
# Install Python dependencies
RUN pip install --no-cache-dir PyYAML pytest
# Run tests by default
CMD ["bash", "-c", "cd /phyllomeos && pytest tests/ -v"]
-129
View File
@@ -1,129 +0,0 @@
# Phyllome OS Recipe Generator Test Container
Minimal containerized testing environment for kickstart recipe validation.
## Build
```bash
cd /home/lukas/Code/virt/phyllomeos
podman build -t phyllo/test-runner tests/container/
# or: docker build -t phyllo/test-runner tests/container/
```
## Run Tests
```bash
# Run all tests (unit + integration + golden masters)
podman run --rm -v .:/phyllomeos:ro phyllo/test-runner
# Run specific test file
podman run --rm -v .:/phyllomeos:ro phyllo/test-runner pytest tests/integration/test_ingredients.py -v
# Interactive mode for debugging
podman run -it --rm -v .:/phyllomeos phyllo/test-runner bash
```
## Test Types
### Unit Tests
- **Location:** `tests/test_recipe_generator.py`
- **Count:** 36 tests
- **Description:** Tests for recipe generator functionality (template loading, validation, version extraction)
### Fragment Validation Tests
- **Location:** `tests/integration/test_ingredients.py`
- **Count:** ~15 tests
- **Description:** Validates all 54 ingredients with pykickstart, checks section structure
### Recipe Composition Tests
- **Location:** `tests/integration/test_recipe_composition.py`
- **Count:** ~10 tests
- **Description:** Tests recipe generation, validates all 16 manifest variants
### Semantic Validation Tests
- **Location:** `tests/integration/test_semantic_validation.py`
- **Count:** ~10 tests
- **Description:** pykickstart semantic validation, deprecated command detection
### Golden Master Tests
- **Location:** `tests/integration/test_golden_masters.py`
- **Count:** ~5 tests
- **Description:** Regression tests comparing generated recipes against expected outputs
## Test Structure
```
tests/
├── test_recipe_generator.py # Unit tests (36 tests)
├── integration/
│ ├── test_ingredients.py # Fragment validation (~15 tests)
│ ├── test_recipe_composition.py # Recipe generation (~10 tests)
│ ├── test_semantic_validation.py # pykickstart semantic (~10 tests)
│ ├── test_golden_masters.py # Regression tests (~5 tests)
│ └── conftest.py # Pytest fixtures
├── fixtures/
│ ├── expected_recipes/ # 5 golden master files
│ └── sample_ingredients/ # Test fragment samples
└── container/
├── Containerfile # Test runner container definition
├── run-tests.sh # Test entrypoint script
└── README.md # This file
```
## Container Contents
The test runner container includes:
- **OS:** Fedora 43
- **Python packages:**
- `pykickstart` - Kickstart parsing and validation
- `pytest` - Test framework
- `PyYAML` - YAML parsing
- **System tools:**
- `make` - Build automation
- `git` - Version control
- `coreutils` - Basic utilities
## Adding New Tests
1. Add test file to `tests/integration/`
2. Follow naming convention: `test_*.py`
3. Use pytest fixtures from `conftest.py`
4. Run tests in container to verify
## Troubleshooting
### Container won't start
```bash
# Check if container builds
podman build tests/container/
# Run with verbose output
podman run --rm -v .:/phyllomeos:ro -e PYTEST_VERBOSITY=2 phyllo/test-runner
```
### Tests failing inside container
```bash
# Get interactive shell
podman run -it --rm -v .:/phyllomeos phyllo/test-runner bash
# Run tests manually
cd /phyllomeos
pytest tests/integration/test_ingredients.py -v
```
### Permission denied errors
```bash
# Run with security options (for rootless podman)
podman run --rm --security-opt label=disable -v .:/phyllomeos:ro phyllo/test-runner
```
## CI/CD Integration
This container will be used in Gitea Actions workflows for:
- Pull request validation
- Main branch testing
- Automated recipe generation checks
See `.gitea/workflows/` for workflow definitions.
-43
View File
@@ -1,43 +0,0 @@
#!/bin/bash
# Run all tests inside the container environment
# Usage: podman run --rm -v .:/phyllomeos:ro phyllo/test-runner
set -e
echo "============================================="
echo " Phyllome OS Recipe Generator Test Suite"
echo "============================================="
echo ""
# Navigate to project root
cd /phyllomeos
echo "[1/3] Running unit tests (test_recipe_generator.py)..."
echo "----------------------------------------"
python3 -m pytest tests/test_recipe_generator.py -v
echo "✓ Unit tests passed!"
echo ""
echo "[2/3] Running integration tests..."
echo "----------------------------------------"
python3 -m pytest tests/integration/ -v
echo "✓ Integration tests passed!"
echo ""
echo "[3/3] Validating generated recipes..."
echo "----------------------------------------"
cd recipe-generator
python3 generate_recipe.py --validate ../recipes/*.cfg --strict
echo "✓ Recipe validation passed!"
echo ""
echo "============================================="
echo " ALL TESTS PASSED!"
echo "============================================="
echo ""
echo "Summary:"
echo " - Unit tests: 36 tests (test_recipe_generator.py)"
echo " - Integration tests: Fragment validation, recipe composition, golden masters"
echo " - Fragment validation: 54 .ks files checked"
echo " - Recipe validation: All 16 manifest variants generated and validated"
echo ""
@@ -1,24 +0,0 @@
# __ ____ ____ _____
# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/
# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \
# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ /
# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/
# /_/ /____/
# A recipe for a desktop hypervisor
%include ingredients/core/base.ks
%include ingredients/storage/standard.ks
%include ingredients/bootloader/grub.ks
%include ingredients/core/locale.ks
%include ingredients/core/services.ks
%include ingredients/core/network.ks
%include ingredients/packages/core-group.ks
%include ingredients/packages/fedora-remix.ks
%include ingredients/packages/hand-picked.ks
%include ingredients/core/security/enabled.ks
%include ingredients/packages/virtual-machine-manager/packages.ks
%include ingredients/packages/virtual-machine-manager/post-scripts.ks
%include ingredients/initial-setup/server/config.ks
%include ingredients/hypervisor/intelcpu.ks
%include ingredients/repo/rawhide-mirrors.ks
@@ -1,24 +0,0 @@
# __ ____ ____ _____
# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/
# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \
# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ /
# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/
# /_/ /____/
# A recipe for a desktop hypervisor
%include ingredients/core/base.ks
%include ingredients/storage/standard.ks
%include ingredients/bootloader/grub.ks
%include ingredients/core/locale.ks
%include ingredients/core/services.ks
%include ingredients/core/network.ks
%include ingredients/packages/core-group.ks
%include ingredients/packages/fedora-remix.ks
%include ingredients/packages/hand-picked.ks
%include ingredients/core/security/enabled.ks
%include ingredients/packages/virtual-machine-manager/packages.ks
%include ingredients/packages/virtual-machine-manager/post-scripts.ks
%include ingredients/initial-setup/server/config.ks
%include ingredients/hypervisor/intelcpu.ks
%include ingredients/repo/rawhide-mirrors.ks
@@ -1,24 +0,0 @@
# __ ____ ____ _____
# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/
# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \
# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ /
# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/
# /_/ /____/
# A recipe for a desktop hypervisor
%include ingredients/core/base.ks
%include ingredients/storage/standard.ks
%include ingredients/bootloader/grub.ks
%include ingredients/core/locale.ks
%include ingredients/core/services.ks
%include ingredients/core/network.ks
%include ingredients/packages/core-group.ks
%include ingredients/packages/fedora-remix.ks
%include ingredients/packages/hand-picked.ks
%include ingredients/core/security/enabled.ks
%include ingredients/packages/virtual-machine-manager/packages.ks
%include ingredients/packages/virtual-machine-manager/post-scripts.ks
%include ingredients/initial-setup/server/config.ks
%include ingredients/hypervisor/intelcpu.ks
%include ingredients/repo/rawhide-mirrors.ks
@@ -1,23 +0,0 @@
# __ ____ ____ _____
# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/
# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \
# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ /
# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/
# /_/ /____/
# A recipe for a live desktop
%include ingredients/shared/live/core/base.ks
%include ingredients/shared/live/core/storage.ks
%include ingredients/platform/generic-rawhide/bootloader/grub.ks
%include ingredients/shared/core/locale.ks
%include ingredients/shared/core/services.ks
%include ingredients/shared/core/network.ks
%include ingredients/shared/live/core/packages.ks
%include ingredients/shared/live/post/base.ks
%include ingredients/shared/live/post/session.ks
%include ingredients/shared/initial-setup/desktop/config.ks
%include ingredients/shared/desktop/gnome/config.ks
%include ingredients/shared/desktop/gnome/packages.ks
%include ingredients/shared/desktop/gnome/post-scripts.ks
%include ingredients/platform/generic-rawhide/repo/rawhide-mirrors.ks
-20
View File
@@ -1,20 +0,0 @@
# __ ____ ____ _____
# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/
# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \
# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ /
# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/
# /_/ /____/
# A recipe for a live server
%include ingredients/shared/live/core/base.ks
%include ingredients/shared/live/core/storage.ks
%include ingredients/platform/generic-rawhide/bootloader/grub.ks
%include ingredients/shared/core/locale.ks
%include ingredients/shared/core/services.ks
%include ingredients/shared/core/network.ks
%include ingredients/shared/live/core/packages.ks
%include ingredients/shared/live/post/base.ks
%include ingredients/shared/live/post/session.ks
%include ingredients/shared/initial-setup/server/config.ks
%include ingredients/platform/generic-rawhide/repo/rawhide-mirrors.ks
-20
View File
@@ -1,20 +0,0 @@
# __ ____ ____ _____
# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/
# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \
# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ /
# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/
# /_/ /____/
# A recipe for a virtual desktop
%include ingredients/shared/core/base.ks
%include ingredients/shared/storage/standard.ks
%include ingredients/platform/generic-43/bootloader/grub.ks
%include ingredients/shared/core/locale.ks
%include ingredients/shared/core/services.ks
%include ingredients/shared/core/network.ks
%include ingredients/shared/packages/core-group.ks
%include ingredients/shared/packages/fedora-remix.ks
%include ingredients/shared/packages/hand-picked.ks
%include ingredients/shared/initial-setup/server/config.ks
%include ingredients/platform/generic-43/repo/fedora-mirrors.ks
@@ -1,20 +0,0 @@
# __ ____ ____ _____
# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/
# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \
# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ /
# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/
# /_/ /____/
# A recipe for a virtual server
%include ingredients/shared/core/base.ks
%include ingredients/shared/storage/standard.ks
%include ingredients/platform/generic-43/bootloader/grub.ks
%include ingredients/shared/core/locale.ks
%include ingredients/shared/core/services.ks
%include ingredients/shared/core/network.ks
%include ingredients/shared/packages/core-group.ks
%include ingredients/shared/packages/fedora-remix.ks
%include ingredients/shared/packages/hand-picked.ks
%include ingredients/shared/initial-setup/server/config.ks
%include ingredients/platform/generic-rawhide/repo/rawhide-mirrors.ks
-50
View File
@@ -1,50 +0,0 @@
"""Pytest fixtures and shared utilities for Phyllome OS integration tests."""
import pytest
from pathlib import Path
import sys
# Find project root (3 levels up from integration tests)
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
SCRIPTS_DIR = PROJECT_ROOT / 'recipe-generator'
sys.path.insert(0, str(SCRIPTS_DIR))
@pytest.fixture
def generator():
"""Create RecipeGenerator instance."""
from recipe_generator import RecipeGenerator
templates_file = PROJECT_ROOT / 'recipe-generator' / 'recipe_templates.yaml'
return RecipeGenerator(templates_file)
@pytest.fixture
def project_root():
"""Return project root directory."""
return PROJECT_ROOT
@pytest.fixture
def ingredients_dir(project_root):
"""Return ingredients directory."""
return project_root / 'ingredients'
@pytest.fixture
def recipes_dir(project_root):
"""Return recipes directory."""
return project_root / 'recipes'
@pytest.fixture
def ingredients(ingredients_dir):
"""List all .ks ingredient files."""
return list(ingredients_dir.glob('**/*.ks'))
@pytest.fixture
def expected_recipes_dir(project_root):
"""Return expected recipes directory for golden masters."""
return project_root / 'tests' / 'fixtures' / 'expected_recipes'
-97
View File
@@ -1,97 +0,0 @@
"""Integration tests for Phyllome OS recipe generator.
These tests verify the complete recipe generation workflow.
"""
import pytest
from pathlib import Path
import subprocess
import sys
import os
# Use actual project root
PROJECT_ROOT = Path('/home/lukas/Code/virt/phyllomeos')
RECIPE_GENERATOR_DIR = PROJECT_ROOT / 'recipe-generator'
RECIPE_DIR = PROJECT_ROOT / 'recipes'
INGREDIENTS_DIR = PROJECT_ROOT / 'ingredients'
CONTAINER_DIR = PROJECT_ROOT / 'tests' / 'container'
def test_generate_recipes_from_manifest():
"""Test generating all recipes from manifest."""
os.chdir(RECIPE_GENERATOR_DIR)
result = subprocess.run(
['python3', 'generate_recipe.py',
'--manifest', 'recipes_manifest.yaml',
'--output-dir', '../recipes/'],
capture_output=True,
text=True
)
assert result.returncode == 0, f"Recipe generation failed: {result.stderr}"
assert 'Generating:' in result.stdout
def test_validate_all_generated_recipes():
"""Test validating all generated recipes."""
# Count generated recipes
recipe_count = len(list(RECIPE_DIR.glob('*.cfg')))
assert recipe_count > 0, f"Expected recipes, got {recipe_count}"
# Validate each recipe
for recipe_file in RECIPE_DIR.glob('*.cfg'):
content = recipe_file.read_text()
assert len(content) > 0, f"Recipe {recipe_file.name} is empty"
assert '%include' in content, f"Recipe {recipe_file.name} missing %include"
def test_make_targets():
"""Test Makefile targets work."""
# Test generate-recipes
result = subprocess.run(
['make', 'generate-recipes'],
cwd=RECIPE_GENERATOR_DIR,
capture_output=True,
text=True
)
assert result.returncode == 0, f"make generate-recipes failed: {result.stderr}"
def test_container_build():
"""Test container builds successfully."""
result = subprocess.run(
['podman', 'build', '-t', 'phyllo/test-runner', '.'],
cwd=CONTAINER_DIR,
capture_output=True,
text=True
)
# Should build even if podman fails (might not be installed)
# The important thing is the Containerfile syntax is valid
if result.returncode != 0 and 'command not found' not in result.stderr:
pytest.fail(f"Container build failed: {result.stderr}")
def test_ingredients_structure():
"""Verify ingredient directory structure."""
# Check core directories
assert (INGREDIENTS_DIR / 'core' / 'security').exists()
# Check desktop directories
assert (INGREDIENTS_DIR / 'desktop' / 'gnome').exists()
assert (INGREDIENTS_DIR / 'desktop' / 'labwc').exists()
# vmm is now in virtual-machine-manager directory (under packages)
# assert (INGREDIENTS_DIR / 'desktop' / 'vmm').exists()
# Check hypervisor directories
assert (INGREDIENTS_DIR / 'hypervisor' / 'base').exists()
assert (INGREDIENTS_DIR / 'hypervisor' / 'base').exists()
# Check live directories
assert (INGREDIENTS_DIR / 'live' / 'core' / 'bootloader').exists()
assert (INGREDIENTS_DIR / 'live' / 'post').exists()
# Count ingredients
ingredient_count = len(list(INGREDIENTS_DIR.glob('**/*.ks')))
assert ingredient_count >= 45, f"Expected at least 45 ingredients, found {ingredient_count}"
-269
View File
@@ -1,269 +0,0 @@
"""Tests for the Recipe Generator."""
import pytest
from pathlib import Path
import sys
RECIPE_GENERATOR_DIR = Path(__file__).parent.parent / 'recipe-generator'
sys.path.insert(0, str(RECIPE_GENERATOR_DIR))
from recipe_generator import RecipeGenerator
class TestRecipeGenerator:
"""Test RecipeGenerator class."""
def setup_method(self):
"""Set up test fixtures."""
project_root = Path(__file__).parent.parent
self.generator = RecipeGenerator(
project_root / 'recipe-generator' / 'recipe_templates.yaml'
)
def test_template_loading(self):
"""Test that templates are loaded correctly."""
assert isinstance(self.generator.templates, dict)
assert len(self.generator.templates) == 2
assert 'install' in self.generator.templates
assert 'live' in self.generator.templates
def test_validate_template_success(self):
"""Test validation of valid templates."""
template = self.generator.templates['install']
errors = self.generator.validate_template(template)
assert errors == []
def test_validate_template_missing_key(self):
"""Test validation detects missing keys."""
template = {'base': 'core'}
errors = self.generator.validate_template(template)
assert any('Missing required key' in error for error in errors)
def test_validate_template_missing_ingredient(self):
"""Test validation detects missing ingredients."""
template = {
'description': 'Test',
'base': 'core',
'required': [
{'storage': 'core-storage'},
{'nonexistent': 'nonexistent-ingredient'}
]
}
errors = self.generator.validate_template(template)
assert any('not found' in error for error in errors)
def test_generate_virtual_desktop_basic(self):
"""Test generating basic desktop recipe."""
content = self.generator.generate_recipe('install', '43',
desktop='gnome',
storage='standard',
security='secure')
assert '# An install recipe for desktop, server, or hypervisor' in content
assert '%include ingredients/core/base.ks' in content
assert '%include ingredients/desktop/gnome/packages.ks' in content
assert '%include ingredients/repo/fedora-43-mirrors.ks' in content
assert '%include ingredients/core/security/enabled.ks' in content
def test_generate_virtual_desktop_encrypted(self):
"""Test generating encrypted desktop recipe."""
content = self.generator.generate_recipe('install', 'rawhide',
desktop='gnome',
storage='encrypted',
security='secure')
assert '%include ingredients/storage/encrypted.ks' in content
def test_generate_virtual_desktop_labwc(self):
"""Test generating LabWC desktop recipe."""
content = self.generator.generate_recipe('install', '43',
desktop='labwc',
storage='standard',
security='secure')
assert '%include ingredients/desktop/labwc/config.ks' in content
assert '%include ingredients/desktop/gnome/packages.ks' not in content
def test_generate_virtual_desktop_devel(self):
"""Test generating development mode desktop recipe."""
content = self.generator.generate_recipe('install', '43',
desktop='gnome',
storage='standard',
security='off')
assert '%include ingredients/core/security/disabled.ks' in content
def test_generate_virtual_server(self):
"""Test generating server recipe."""
content = self.generator.generate_recipe('install', 'rawhide',
initial_setup='server',
security='secure')
assert '# An install recipe for desktop, server, or hypervisor' in content
assert '%include ingredients/initial-setup/server/config.ks' in content
def test_generate_desktop_hypervisor_amd(self):
"""Test generating AMD CPU hypervisor recipe."""
content = self.generator.generate_recipe('install', 'rawhide',
variant_type='hypervisor',
hypervisor='base',
hypervisor_type='amdcpu',
security='secure')
assert '%include ingredients/hypervisor/amdcpu.ks' in content
assert '%include ingredients/hypervisor/intelcpu.ks' not in content
def test_generate_desktop_hypervisor_intel_gpu(self):
"""Test generating Intel CPU+GPU hypervisor recipe."""
content = self.generator.generate_recipe('install', 'rawhide',
variant_type='hypervisor',
hypervisor='base',
hypervisor_type='intelcpu',
security='secure')
assert '%include ingredients/hypervisor/intelcpu.ks' in content
def test_generate_live_desktop(self):
"""Test generating live desktop recipe."""
content = self.generator.generate_recipe('live', 'rawhide',
desktop='gnome',
security='secure')
assert '# A live recipe for live-desktop or live-server' in content
assert '%include ingredients/live/core/base.ks' in content
assert '%include ingredients/live/core/storage.ks' in content
def test_generate_live_server(self):
"""Test generating live server recipe."""
content = self.generator.generate_recipe('live', 'rawhide',
security='secure')
assert '# A live recipe for live-desktop or live-server' in content
assert '%include ingredients/live/core/base.ks' in content
def test_no_duplicate_includes(self):
"""Test that duplicate includes are prevented."""
content = self.generator.generate_recipe('install', 'rawhide',
variant_type='hypervisor',
hypervisor='base',
hypervisor_type='intelcpu',
security='secure')
includes = [line for line in content.split('\n') if line.startswith('%include')]
paths = [line.split()[1] for line in includes]
assert len(paths) == len(set(paths)), "Found duplicate %include entries"
def test_missing_ingredient_detection(self):
"""Test that missing ingredients are detected."""
content = self.generator.generate_recipe('install', '43',
desktop='gnome',
storage='standard',
security='secure')
issues = self.generator.validate_recipe(content)
assert issues == [], f"Found missing ingredients: {issues}"
def test_filename_generation_standard(self):
"""Test filename generation for standard configuration."""
filename = self.generator.generate_filename('install', '43',
desktop='gnome',
storage='standard',
security='secure')
assert filename == 'install_43.cfg'
def test_filename_generation_encrypted(self):
"""Test filename generation for encrypted storage."""
filename = self.generator.generate_filename('install', 'rawhide',
desktop='gnome',
storage='encrypted',
security='secure')
assert filename == 'install_encrypted_rawhide.cfg'
def test_filename_generation_devel(self):
"""Test filename generation for development mode."""
filename = self.generator.generate_filename('install', '43',
desktop='gnome',
storage='standard',
security='off')
assert filename == 'install_devel_43.cfg'
def test_filename_generation_hypervisor(self):
"""Test filename generation for hypervisor."""
filename = self.generator.generate_filename('install', 'rawhide',
variant_type='hypervisor',
hypervisor='base',
hypervisor_type='intelcpu',
security='secure')
assert filename == 'install_hypervisor_intelcpu_rawhide.cfg'
def test_invalid_recipe_type(self):
"""Test error on invalid recipe type."""
with pytest.raises(SystemExit) as exc_info:
self.generator.generate_recipe('nonexistent-type', '43')
assert exc_info.value.code == 1
def test_manifest_validation_missing_recipes_key(self):
"""Test manifest validation detects missing recipes key."""
manifest = {}
errors = self.generator.validate_manifest(manifest)
assert any('recipes' in error for error in errors)
def test_manifest_validation_missing_name(self):
"""Test manifest validation detects missing recipe name."""
manifest = {'recipes': [{}]}
errors = self.generator.validate_manifest(manifest)
assert any('name' in error for error in errors)
def test_manifest_validation_missing_version(self):
"""Test manifest validation detects missing version."""
manifest = {'recipes': [{'name': 'test', 'variants': [{}]}]}
errors = self.generator.validate_manifest(manifest)
assert any('version' in error for error in errors)
def test_get_ksversion_43(self):
"""Test version mapping for Fedora 43."""
version = self.generator.get_ksversion('43')
assert version == 'F42'
def test_get_ksversion_rawhide(self):
"""Test version mapping for rawhide."""
version = self.generator.get_ksversion('rawhide')
assert version is None
def test_validate_recipe_semantic_f42(self):
"""Test semantic validation with valid recipe for F42."""
content = """text
poweroff
zerombr
clearpart --all --initlabel
part /boot/efi --fstype="efi" --size=512
part / --fstype="ext4" --grow
%packages
%end
"""
issues = self.generator.validate_recipe_semantic(content, '43')
assert issues == []
def test_validate_recipe_semantic_invalid_syntax(self):
"""Test semantic validation detects invalid syntax."""
content = "validcommand\ninvalidcommand without proper format\n%packages\n%end"
issues = self.generator.validate_recipe_semantic(content, '43')
assert len(issues) > 0
def test_validate_recipe_semantic_empty(self):
"""Test semantic validation with empty content."""
content = ""
issues = self.generator.validate_recipe_semantic(content, '43')
assert issues == []
def test_filename_generation_with_guest_agents(self):
"""Test filename includes 'virtual' when guest_agents=True."""
filename = self.generator.generate_filename('install', '43',
variant_type='desktop',
guest_agents=True)
assert filename == 'install_virtual_desktop_43.cfg'
def test_filename_generation_with_hardware_support(self):
"""Test filename includes 'hardware-support' when enabled."""
filename = self.generator.generate_filename('install', '43',
variant_type='desktop',
hardware_support=True)
assert filename == 'install_desktop_hardware-support_43.cfg'
def test_filename_generation_with_both_modifiers(self):
"""Test filename includes both modifiers when enabled."""
filename = self.generator.generate_filename('install', '43',
variant_type='desktop',
guest_agents=True,
hardware_support=True)
assert filename == 'install_virtual_desktop_hardware-support_43.cfg'