Files
RedBear-OS/local/scripts/tests/test_cookbook_apply_patches_e2e.py
T
vasilito ffbe098ef8 config: add tlc to redbear-mini and redbear-full; create recipe symlink
TLC (Twilight Commander) was missing from both ISO configs. Added
tlc = {} to [packages] in redbear-mini.toml and redbear-full.toml.
Created missing symlink: recipes/tui/tlc -> ../../local/recipes/tui/tlc.
2026-06-19 11:47:25 +03:00

151 lines
5.5 KiB
Python

"""
Tests for cookbook_apply_patches end-to-end
These tests extract the cookbook_apply_patches function
from src/cook/script.rs and run it against real recipe
sources + migration patches. Verifies:
1. First apply: patch is applied successfully
2. Idempotency: second apply reports "already applied"
3. The post-patch source matches the expected state
(e.g. the ecm_install_po_files_as_qm line is
commented out)
4. The 4-level path resolution works
This is the integration test for C-7 step 2 — the
recipe edit that replaces inline `sed -i` chains with
a `cookbook_apply_patches` call.
"""
import os
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).parent.parent.parent.parent
SCRIPT_RS = REPO_ROOT / "src" / "cook" / "script.rs"
COOKBOOK_RECIPE = REPO_ROOT / "local" / "recipes" / "kde" / "kf6-karchive"
COOKBOOK_SOURCE = COOKBOOK_RECIPE / "source"
COOKBOOK_PRISTINE = COOKBOOK_RECIPE / "source-pristine"
PATCH_DIR = REPO_ROOT / "local" / "patches" / "kf6-karchive"
PATCH_FILE = PATCH_DIR / "01-initial-migration.patch"
def extract_cookbook_helper() -> str:
"""Extract the cookbook_apply_patches function from
src/cook/script.rs and return it as a bash source string.
"""
text = SCRIPT_RS.read_text()
lines = text.splitlines()
start = end = None
for idx, line in enumerate(lines):
if line.startswith("function cookbook_apply_patches {"):
start = idx
continue
if start is not None and line.startswith("}"):
end = idx + 1
break
if start is None or end is None:
raise RuntimeError("could not find cookbook_apply_patches in script.rs")
extracted = lines[start:end]
return "\n".join(extracted)
def run_helper_in_subshell(args: dict[str, str]) -> tuple[int, str]:
"""Run the cookbook helper in a subshell with the
given env vars. Returns (exit_code, output).
"""
helper_src = extract_cookbook_helper()
env_assignments = " ".join(f'{k}="{v}"' for k, v in args.items())
cmd = f'{env_assignments} bash -c \'{helper_src.replace(chr(39), chr(39) + chr(92) + chr(39) + chr(39))}; cookbook_apply_patches "$1" 2>&1\' _ "{args["COOKBOOK_PATCHES_DIR"]}"'
proc = subprocess.run(
cmd,
shell=True,
capture_output=True,
text=True,
cwd=args["COOKBOOK_SOURCE"],
)
return proc.returncode, proc.stdout + proc.stderr
class TestCookbookApplyPatches(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Verify the test fixtures exist.
if not COOKBOOK_PRISTINE.exists():
raise unittest.SkipTest(
f"pristine dir missing: {COOKBOOK_PRISTINE}. "
"Run `repo fetch kf6-karchive` first."
)
if not PATCH_FILE.exists():
raise unittest.SkipTest(f"patch missing: {PATCH_FILE}")
def setUp(self):
# Reset source to pristine before each test.
if COOKBOOK_SOURCE.exists():
shutil.rmtree(COOKBOOK_SOURCE)
shutil.copytree(COOKBOOK_PRISTINE, COOKBOOK_SOURCE)
def test_first_apply_succeeds(self):
exit_code, output = run_helper_in_subshell(
{
"COOKBOOK_RECIPE": str(COOKBOOK_RECIPE),
"COOKBOOK_SOURCE": str(COOKBOOK_SOURCE),
"COOKBOOK_BUILD": str(COOKBOOK_SOURCE / "build"),
"COOKBOOK_PATCHES_DIR": str(PATCH_DIR),
}
)
self.assertEqual(exit_code, 0, f"helper failed: {output}")
self.assertIn("applying 01-initial-migration.patch", output)
self.assertIn("applied=1", output)
self.assertIn("skipped=0", output)
self.assertIn("failed=0", output)
def test_idempotency_second_apply_skips(self):
common_args = {
"COOKBOOK_RECIPE": str(COOKBOOK_RECIPE),
"COOKBOOK_SOURCE": str(COOKBOOK_SOURCE),
"COOKBOOK_BUILD": str(COOKBOOK_SOURCE / "build"),
"COOKBOOK_PATCHES_DIR": str(PATCH_DIR),
}
# First apply.
exit1, out1 = run_helper_in_subshell(common_args)
self.assertEqual(exit1, 0, f"first apply failed: {out1}")
# Second apply.
exit2, out2 = run_helper_in_subshell(common_args)
self.assertEqual(exit2, 0, f"second apply failed: {out2}")
self.assertIn("already applied, skipping", out2)
self.assertIn("applied=0", out2)
self.assertIn("skipped=1", out2)
self.assertIn("failed=0", out2)
def test_apply_modifies_source_correctly(self):
run_helper_in_subshell(
{
"COOKBOOK_RECIPE": str(COOKBOOK_RECIPE),
"COOKBOOK_SOURCE": str(COOKBOOK_SOURCE),
"COOKBOOK_BUILD": str(COOKBOOK_SOURCE / "build"),
"COOKBOOK_PATCHES_DIR": str(PATCH_DIR),
}
)
# The patch comments out the
# ecm_install_po_files_as_qm line.
cmake = (COOKBOOK_SOURCE / "CMakeLists.txt").read_text()
self.assertIn("#ecm_install_po_files_as_qm", cmake)
self.assertNotIn("^ecm_install_po_files_as_qm", cmake)
def test_four_level_path_resolution(self):
# The recipe at local/recipes/kde/kf6-karchive is 4
# levels deep. The path
# ${COOKBOOK_RECIPE}/../../../../local/patches/<name>
# must resolve to the patches dir.
from pathlib import Path
expected = (COOKBOOK_RECIPE / "../../../../local/patches/kf6-karchive").resolve()
self.assertEqual(expected, PATCH_DIR.resolve())
if __name__ == "__main__":
unittest.main()