ffbe098ef8
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.
151 lines
5.5 KiB
Python
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()
|