switch-pico/tests/test_prepare_bluepad32.py

206 lines
6.4 KiB
Python

from __future__ import annotations
import shutil
import subprocess
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent / "tools"))
from prepare_bluepad32 import ( # noqa: E402
PatchError,
check_paths,
main,
prepare_bluepad32,
resolve_paths,
)
def run_git(repository: Path, *args: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["git", "-C", str(repository), *args],
capture_output=True,
text=True,
check=True,
)
@pytest.fixture
def bluepad32_fixture(tmp_path: Path) -> tuple[Path, Path, Path, Path]:
source = tmp_path / "external" / "bluepad32"
source.mkdir(parents=True)
run_git(source, "init")
run_git(source, "config", "user.email", "test@example.com")
run_git(source, "config", "user.name", "Test User")
(source / "test.txt").write_text("line 1\n", encoding="utf-8")
run_git(source, "add", "test.txt")
run_git(source, "commit", "-m", "initial")
patches = tmp_path / "patches"
patches.mkdir()
patch = patches / "bluepad32-sdl3-imu.patch"
original = (source / "test.txt").read_text(encoding="utf-8")
(source / "test.txt").write_text(f"{original}line 2\n", encoding="utf-8")
patch.write_text(run_git(source, "diff", "--", "test.txt").stdout, encoding="utf-8")
run_git(source, "restore", "test.txt")
output = tmp_path / "build-aio" / "_deps" / "bluepad32-src"
return tmp_path, source, patch, output
def source_status(source: Path) -> str:
return run_git(source, "status", "--porcelain", "--untracked-files=all").stdout
def test_resolve_paths_uses_project_build_tree(tmp_path: Path) -> None:
source, patch, output = resolve_paths(tmp_path)
assert source == tmp_path / "external" / "bluepad32"
assert patch == tmp_path / "patches" / "bluepad32-sdl3-imu.patch"
assert output == tmp_path / "build" / "_deps" / "bluepad32-src"
def test_check_paths_rejects_missing_source(
bluepad32_fixture: tuple[Path, Path, Path, Path],
) -> None:
_, source, patch, output = bluepad32_fixture
shutil.rmtree(source)
with pytest.raises(PatchError, match="bluepad32 directory does not exist"):
check_paths(source, patch, output)
def test_check_paths_rejects_missing_patch(
bluepad32_fixture: tuple[Path, Path, Path, Path],
) -> None:
_, source, patch, output = bluepad32_fixture
patch.unlink()
with pytest.raises(PatchError, match="patch file does not exist"):
check_paths(source, patch, output)
def test_check_paths_rejects_non_repository(
bluepad32_fixture: tuple[Path, Path, Path, Path],
) -> None:
_, source, patch, output = bluepad32_fixture
shutil.rmtree(source / ".git")
with pytest.raises(PatchError, match="not a git repository"):
check_paths(source, patch, output)
def test_check_paths_rejects_output_inside_source(
bluepad32_fixture: tuple[Path, Path, Path, Path],
) -> None:
_, source, patch, _ = bluepad32_fixture
with pytest.raises(PatchError, match="outside the Bluepad32 source tree"):
check_paths(source, patch, source / "patched")
def test_prepare_patches_copy_and_preserves_pristine_source(
bluepad32_fixture: tuple[Path, Path, Path, Path],
) -> None:
_, source, patch, output = bluepad32_fixture
result = prepare_bluepad32(source, patch, output)
assert result == output
assert (source / "test.txt").read_text(encoding="utf-8") == "line 1\n"
assert (output / "test.txt").read_text(encoding="utf-8") == "line 1\nline 2\n"
assert not (output / ".git").exists()
assert source_status(source) == ""
def test_prepare_applies_patch_inside_parent_repository(
bluepad32_fixture: tuple[Path, Path, Path, Path],
) -> None:
root, source, patch, output = bluepad32_fixture
run_git(root, "init")
prepare_bluepad32(source, patch, output)
assert (output / "test.txt").read_text(encoding="utf-8") == "line 1\nline 2\n"
assert source_status(source) == ""
def test_prepare_replaces_existing_output_idempotently(
bluepad32_fixture: tuple[Path, Path, Path, Path],
) -> None:
_, source, patch, output = bluepad32_fixture
prepare_bluepad32(source, patch, output)
(output / "stale.txt").write_text("stale", encoding="utf-8")
prepare_bluepad32(source, patch, output)
assert not (output / "stale.txt").exists()
assert (output / "test.txt").read_text(encoding="utf-8") == "line 1\nline 2\n"
assert source_status(source) == ""
def test_prepare_rejects_dirty_source_without_replacing_output(
bluepad32_fixture: tuple[Path, Path, Path, Path],
) -> None:
_, source, patch, output = bluepad32_fixture
output.mkdir(parents=True)
sentinel = output / "sentinel.txt"
sentinel.write_text("keep", encoding="utf-8")
(source / "test.txt").write_text("local edit\n", encoding="utf-8")
with pytest.raises(PatchError, match="must be pristine"):
prepare_bluepad32(source, patch, output)
assert sentinel.read_text(encoding="utf-8") == "keep"
def test_prepare_rejects_diverged_source_revision(
bluepad32_fixture: tuple[Path, Path, Path, Path],
) -> None:
_, source, patch, output = bluepad32_fixture
(source / "test.txt").write_text("different base\n", encoding="utf-8")
run_git(source, "add", "test.txt")
run_git(source, "commit", "-m", "diverge")
with pytest.raises(PatchError, match="Patch validation failed"):
prepare_bluepad32(source, patch, output)
assert not output.exists()
def test_prepare_uses_default_paths(
bluepad32_fixture: tuple[Path, Path, Path, Path], monkeypatch: pytest.MonkeyPatch
) -> None:
root, source, _, _ = bluepad32_fixture
monkeypatch.chdir(root)
output = prepare_bluepad32()
assert output == root / "build" / "_deps" / "bluepad32-src"
assert (output / "test.txt").read_text(encoding="utf-8") == "line 1\nline 2\n"
assert source_status(source) == ""
def test_cli_accepts_explicit_output(
bluepad32_fixture: tuple[Path, Path, Path, Path], monkeypatch: pytest.MonkeyPatch
) -> None:
_, source, patch, output = bluepad32_fixture
monkeypatch.setattr(
sys,
"argv",
[
"prepare_bluepad32.py",
"--bluepad32",
str(source),
"--patch",
str(patch),
"--output",
str(output),
],
)
assert main() == 0
assert (output / "test.txt").read_text(encoding="utf-8") == "line 1\nline 2\n"
assert source_status(source) == ""