Files
EVEngine/scripts/tests/test_patch_third_party.py

139 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""Regression tests for third-party patch roots and idempotent application.
The temporary checkouts contain pinned HEAD blobs from the local sources;
this test never contacts a remote repository and never mutates the checkout
used by the engine build.
"""
from __future__ import annotations
import subprocess
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
PATCH_SCRIPT = ROOT / "cmake" / "patch_third_party.cmake"
PATCHES = ROOT / "cmake" / "patches"
THIRD_PARTY = ROOT / "third-party"
MEDIALOADER = THIRD_PARTY / "medialoader"
def run(command: list[str], *, check: bool = True) -> subprocess.CompletedProcess[str]:
result = subprocess.run(command, text=True, capture_output=True, check=False)
if check and result.returncode != 0:
raise AssertionError(
f"command failed ({result.returncode}): {' '.join(command)}\n"
f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}"
)
return result
def checkout_patch_inputs(source: Path, destination: Path, patches: list[Path]) -> None:
"""Materialize real upstream inputs without copying unrelated files or history."""
paths = {"CMakeLists.txt"}
for patch in patches:
for line in patch.read_text(encoding="utf-8").splitlines():
if line.startswith("--- a/"):
paths.add(line.removeprefix("--- a/"))
destination.mkdir()
for relative in sorted(paths):
blob = subprocess.run(
["git", "-C", str(source), "show", f"HEAD:{relative}"],
capture_output=True, check=True, text=True, encoding="utf-8",
).stdout
target = destination / relative
target.parent.mkdir(parents=True, exist_ok=True)
# Match the platform-native checkout used by the patch file itself.
# In particular, Git for Windows materializes both as CRLF.
target.write_text(blob, encoding="utf-8")
run(["git", "-C", str(destination), "init", "--quiet"])
run(["git", "-C", str(destination), "add", "."])
run(["git", "-C", str(destination), "-c", "user.name=Fixture",
"-c", "user.email=fixture@example.invalid", "-c", "commit.gpgsign=false",
"commit", "--quiet", "-m", "Pinned patch inputs"])
assert not run(["git", "-C", str(destination), "status", "--porcelain"]).stdout
def apply_twice(patch: Path, patch_dir: Path) -> None:
command = [
"cmake",
f"-DPATCH={patch}",
f"-DPATCH_DIR={patch_dir}",
"-P",
str(PATCH_SCRIPT),
]
first = run(command)
second = run(command)
assert "Applied third-party patch" in first.stdout
assert "already applied" in second.stdout
def main() -> int:
with tempfile.TemporaryDirectory(prefix="evengine-third-party-patch-") as raw:
temporary = Path(raw)
clean_aggregate = temporary / "aggregate-clean"
checkout_patch_inputs(THIRD_PARTY, clean_aggregate,
[PATCHES / "third-party-squirrel-ssq-export.patch"])
apply_twice(PATCHES / "third-party-squirrel-ssq-export.patch", clean_aggregate)
# A dirty aggregate checkout is a supported state because other patches
# and profile preparation may have changed unrelated files. The export
# patch must remain applicable when its own target files are untouched.
dirty_aggregate = temporary / "aggregate-dirty"
checkout_patch_inputs(THIRD_PARTY, dirty_aggregate,
[PATCHES / "third-party-squirrel-ssq-export.patch"])
with (dirty_aggregate / "CMakeLists.txt").open("a", encoding="utf-8") as output:
output.write("\n# Unrelated local profile configuration\n")
assert run(["git", "-C", str(dirty_aggregate), "diff", "--name-only"]).stdout.strip() == "CMakeLists.txt"
apply_twice(PATCHES / "third-party-squirrel-ssq-export.patch", dirty_aggregate)
clean_medialoader = temporary / "medialoader-clean"
medialoader_patches = [
PATCHES / "medialoader-smooth-normals.patch",
PATCHES / "mpg123-signal-handler.patch",
PATCHES / "medialoader-image-formats.patch",
PATCHES / "medialoader-audio-gapless.patch",
]
checkout_patch_inputs(MEDIALOADER, clean_medialoader, medialoader_patches)
for patch in medialoader_patches:
apply_twice(patch, clean_medialoader)
# A genuine target drift must fail and expose git's diagnostic; it may
# not be mistaken for an already-applied patch or silently skipped.
drifted = temporary / "aggregate-drifted"
checkout_patch_inputs(THIRD_PARTY, drifted,
[PATCHES / "third-party-squirrel-ssq-export.patch"])
target = drifted / "squirrel" / "squirrel" / "CMakeLists.txt"
target.write_text(
target.read_text(encoding="utf-8").replace(
"target_include_directories(squirrel_static PUBLIC",
"target_include_directories(squirrel_static BROKEN",
1,
),
encoding="utf-8",
)
result = run(
[
"cmake",
f"-DPATCH={PATCHES / 'third-party-squirrel-ssq-export.patch'}",
f"-DPATCH_DIR={drifted}",
"-P",
str(PATCH_SCRIPT),
],
check=False,
)
diagnostic = result.stdout + result.stderr
assert result.returncode != 0
assert "no longer applies cleanly" in diagnostic
assert "git apply --check output" in diagnostic
assert "patch failed" in diagnostic
return 0
if __name__ == "__main__":
raise SystemExit(main())