test-cookbook-apply-patches-e2e: 4 integration tests for the cookbook helper
Extracts the cookbook_apply_patches function from
src/cook/script.rs and runs it against the real
kf6-karchive source + migration patch. Verifies the
end-to-end flow that C-7 step 2 relies on:
1. First apply: helper applies the patch via
`git apply` from inside ${COOKBOOK_SOURCE},
reports `applying 01-initial-migration.patch`
and `applied=1 skipped=0 failed=0`.
2. Idempotency: running the helper a second time
detects the patch is already applied via
`git apply --reverse --check` and reports
`already applied, skipping`.
3. Post-patch source state: the helper actually
modifies the source — verifies that
`ecm_install_po_files_as_qm` is now commented
out (line starts with `#`) in the source after
the helper runs.
4. 4-level path resolution: verifies that the
`${COOKBOOK_RECIPE}/../../../../local/patches/<name>`
path used in the recipe's [build].script
resolves to the actual patches dir
`local/patches/kf6-karchive`.
These tests use a real pristine/source/patch fixture
(not mocks) and run the actual cookbook helper
extracted from src/cook/script.rs. Any change to
the helper's behavior (path handling, idempotency
check, git apply flags) is caught by these tests.
Makefile:
- New `test-cookbook-apply-patches-e2e` target
- Added to `lint-build-system-all` aggregate
Total: 4 new tests, 164 Python tests total (160 + 4).
All 10 test files pass in <1s.
This commit is contained in:
@@ -305,6 +305,15 @@ test-cleanup-noop-seds:
|
||||
test-edit-kf6-recipes:
|
||||
@python3 -m unittest local.scripts.tests.test_edit_kf6_recipes_for_patches -v
|
||||
|
||||
# End-to-end test of cookbook_apply_patches: extracts
|
||||
# the helper from src/cook/script.rs and runs it
|
||||
# against the real kf6-karchive source + migration
|
||||
# patch. Verifies the first apply works, idempotency
|
||||
# skips the second, the post-patch source has the
|
||||
# expected state, and the 4-level path resolves.
|
||||
test-cookbook-apply-patches-e2e:
|
||||
@python3 -m unittest local.scripts.tests.test_cookbook_apply_patches_e2e -v
|
||||
|
||||
# Full scratch rebuild of autotools-using recipes + transitive
|
||||
# closure. Deletes target/<arch>/{build,sysroot,stage.tmp}/ per
|
||||
# recipe in the closure and re-cooks in dep order. Use after
|
||||
@@ -358,7 +367,7 @@ lint-build-system-full: lint-patches-full lint-kf6-deps lint-cook-recipe lint-re
|
||||
# The Gitea CI workflow wraps this in a case statement that
|
||||
# accepts 0 OR 2 as pass; the per-recipe lint target (.PHONY
|
||||
# target) does not run lint-patches.
|
||||
lint-build-system-all: test-lint-scripts test-migration-dry-run test-scratch-dry-run test-cleanup-noop-seds
|
||||
lint-build-system-all: test-lint-scripts test-migration-dry-run test-scratch-dry-run test-cleanup-noop-seds test-edit-kf6-recipes test-cookbook-apply-patches-e2e
|
||||
@echo "All build-system lint + tests complete."
|
||||
cascade.%: FORCE
|
||||
@bash local/scripts/rebuild-cascade.sh $(basename $(subst cascade,, $*))
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user