499 lines
19 KiB
Python
499 lines
19 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
from __future__ import annotations
|
|
|
|
import errno
|
|
import importlib.util
|
|
import json
|
|
import subprocess
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
|
|
def _load_kernel_build_cache():
|
|
module_path = Path(__file__).with_name("kernel_build_cache.py")
|
|
spec = importlib.util.spec_from_file_location("kernel_build_cache_for_test", module_path)
|
|
assert spec is not None
|
|
assert spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
kernel_build_cache = _load_kernel_build_cache()
|
|
REPO_ROOT = Path(__file__).resolve().parents[3]
|
|
WHEEL_NAME = "fastvideo_kernel-0.3.2-py3-none-any.whl"
|
|
TRUSTED_KERNEL_IMAGE_INPUTS = {
|
|
".dockerignore",
|
|
".github/workflows/_template-build-image.yml",
|
|
".github/workflows/infra-build-image.yml",
|
|
".gitmodules",
|
|
"docker/Dockerfile",
|
|
"docker/uv-excludes",
|
|
"fastvideo-kernel/**",
|
|
"fastvideo/tests/modal/kernel_build_cache.py",
|
|
"pyproject.toml",
|
|
}
|
|
|
|
|
|
def _write_wheel(path: Path, *, distribution: str = "fastvideo-kernel", payload: str = "payload") -> Path:
|
|
dist_info = "fastvideo_kernel-0.3.2.dist-info"
|
|
with zipfile.ZipFile(path, "w") as archive:
|
|
archive.writestr("fastvideo_kernel/__init__.py", payload)
|
|
archive.writestr(
|
|
f"{dist_info}/WHEEL",
|
|
"Wheel-Version: 1.0\nGenerator: test\nRoot-Is-Purelib: true\nTag: py3-none-any\n",
|
|
)
|
|
archive.writestr(
|
|
f"{dist_info}/METADATA",
|
|
f"Metadata-Version: 2.1\nName: {distribution}\nVersion: 0.3.2\n",
|
|
)
|
|
return path
|
|
|
|
|
|
def _metadata(cache_key: str = "cache-key") -> dict[str, object]:
|
|
return {
|
|
"cache_key": cache_key,
|
|
"schema_version": kernel_build_cache.CACHE_SCHEMA_VERSION,
|
|
}
|
|
|
|
|
|
def _write_cache_entry(cache_root: Path, cache_key: str, *, payload: str = "payload") -> Path:
|
|
cache_entry = cache_root / cache_key
|
|
cache_entry.mkdir()
|
|
wheel = _write_wheel(cache_entry / WHEEL_NAME, payload=payload)
|
|
stored_metadata = {
|
|
**_metadata(cache_key),
|
|
"artifact": kernel_build_cache._wheel_artifact(wheel),
|
|
}
|
|
(cache_entry / kernel_build_cache.METADATA_FILE).write_text(json.dumps(stored_metadata), encoding="utf-8")
|
|
return cache_entry
|
|
|
|
|
|
def _write_prebuilt_artifact(prebuilt_root: Path, name: str, cache_key: str) -> Path:
|
|
artifact_dir = prebuilt_root / name
|
|
artifact_dir.mkdir(parents=True)
|
|
wheel = _write_wheel(artifact_dir / WHEEL_NAME)
|
|
payload = {
|
|
**_metadata(cache_key),
|
|
"artifact": kernel_build_cache._wheel_artifact(wheel),
|
|
"wheel_path": str(wheel),
|
|
}
|
|
(artifact_dir / kernel_build_cache.METADATA_FILE).write_text(json.dumps(payload), encoding="utf-8")
|
|
return wheel
|
|
|
|
|
|
def _patch_stable_metadata(monkeypatch) -> None:
|
|
for name in (
|
|
"GPU_BACKEND",
|
|
"CMAKE_ARGS",
|
|
"CFLAGS",
|
|
"CXXFLAGS",
|
|
"LDFLAGS",
|
|
"TORCH_CUDA_ARCH_LIST",
|
|
"FASTVIDEO_CONTAINER_IMAGE_REF",
|
|
"CUDACXX",
|
|
):
|
|
monkeypatch.delenv(name, raising=False)
|
|
monkeypatch.setattr(kernel_build_cache, "_kernel_source_hash", lambda repo_root: {"source_hash": "source"})
|
|
monkeypatch.setattr(kernel_build_cache, "_read_kernel_version", lambda repo_root: "0.3.2")
|
|
monkeypatch.setattr(
|
|
kernel_build_cache,
|
|
"_torch_metadata",
|
|
lambda: {
|
|
"torch_version": "2.9.0",
|
|
"torch_cuda_version": "12.8",
|
|
"torch_git_version": "stable-build-commit",
|
|
"torch_file": "/opt/venv/lib/python3.12/site-packages/torch/__init__.py",
|
|
"torch_config": "USE_CUDA=ON",
|
|
"cxx11_abi": True,
|
|
},
|
|
)
|
|
monkeypatch.setattr(
|
|
kernel_build_cache,
|
|
"_compiler_libc_metadata",
|
|
lambda: {
|
|
"compiler": {
|
|
"cc": {
|
|
"path": "/usr/bin/gcc",
|
|
"version": "gcc 11.4.0"
|
|
}
|
|
},
|
|
"libc": {
|
|
"ldd_version": "ldd 2.35"
|
|
},
|
|
},
|
|
)
|
|
monkeypatch.setattr(
|
|
kernel_build_cache,
|
|
"_selected_command_metadata",
|
|
lambda environment_name, default_command: {
|
|
"raw": default_command,
|
|
"command": json.dumps([default_command]),
|
|
"resolved_executable": f"/usr/bin/{default_command}",
|
|
"version": f"{default_command} 12.8",
|
|
},
|
|
)
|
|
|
|
|
|
def test_kernel_only_main_change_republishes_trusted_l40s_artifact() -> None:
|
|
workflow = yaml.load(
|
|
(REPO_ROOT / ".github/workflows/infra-build-image.yml").read_text(encoding="utf-8"),
|
|
Loader=yaml.BaseLoader,
|
|
)
|
|
push = workflow["on"]["push"]
|
|
|
|
assert push["branches"] == ["main"]
|
|
assert TRUSTED_KERNEL_IMAGE_INPUTS <= set(push["paths"])
|
|
assert "github.event_name == 'push'" in workflow["jobs"]["build-cuda-images"]["if"]
|
|
|
|
dockerfile = (REPO_ROOT / "docker/Dockerfile").read_text(encoding="utf-8")
|
|
assert 'l40s_wheel_dir="${FASTVIDEO_KERNEL_PREBUILT_DIR}/8.9"' in dockerfile
|
|
assert 'export TORCH_CUDA_ARCH_LIST=8.9' in dockerfile
|
|
assert '--output "${l40s_wheel_dir}/metadata.json"' in dockerfile
|
|
|
|
|
|
def test_ci_runner_image_targets_arm64_sm100_with_opencv_runtime() -> None:
|
|
workflow = yaml.load(
|
|
(REPO_ROOT / ".github/workflows/infra-build-image.yml").read_text(encoding="utf-8"),
|
|
Loader=yaml.BaseLoader,
|
|
)
|
|
job = workflow["jobs"]["build-ci-runner-image"]
|
|
|
|
assert job["with"]["architecture"] == "arm64"
|
|
assert job["with"]["runner"] == "ubuntu-24.04-arm"
|
|
assert job["with"]["tag_suffix"] == "py3.12-cuda13.0.0-sm100"
|
|
assert "CUDA_VERSION=13.0.0" in job["with"]["build_args"]
|
|
assert "UV_TORCH_BACKEND=cu130" in job["with"]["build_args"]
|
|
assert "TORCH_CUDA_ARCH_LIST=10.0" in job["with"]["build_args"]
|
|
|
|
dockerfile = (REPO_ROOT / "docker/Dockerfile").read_text(encoding="utf-8")
|
|
assert " ffmpeg \\\n" in dockerfile
|
|
assert " libgl1 \\\n" in dockerfile
|
|
assert " libglib2.0-0 \\\n" in dockerfile
|
|
assert 'python -c "import cv2; print(\'OpenCV\', cv2.__version__)"' in dockerfile
|
|
|
|
|
|
def test_docker_image_bakes_modal_apt_and_rust_layer() -> None:
|
|
"""The dormant Modal rollback image layers apt packages + rustup on top of
|
|
the release image (pr_test.py). Keep those packages baked into the release
|
|
image so the archived manual path remains reproducible."""
|
|
modal_source = (REPO_ROOT / "fastvideo/tests/modal/pr_test.py").read_text(encoding="utf-8")
|
|
assert '.apt_install(\n "cmake", "pkg-config", "build-essential", "curl", "libssl-dev", "ffmpeg")' in modal_source
|
|
|
|
dockerfile = (REPO_ROOT / "docker/Dockerfile").read_text(encoding="utf-8")
|
|
for package in ("cmake", "pkg-config", "build-essential", "libssl-dev", "curl", "ffmpeg"):
|
|
assert f" {package} \\\n" in dockerfile, f"{package} missing from the docker/Dockerfile apt layer"
|
|
assert "sh.rustup.rs | sh -s -- -y --default-toolchain stable" in dockerfile
|
|
assert "ENV PATH=/root/.cargo/bin:${PATH}" in dockerfile
|
|
|
|
|
|
def test_cache_key_uses_resolved_arch_not_raw_env(monkeypatch, tmp_path) -> None:
|
|
_patch_stable_metadata(monkeypatch)
|
|
monkeypatch.setattr(kernel_build_cache, "_detect_arch_from_torch", lambda: "9.0a")
|
|
|
|
monkeypatch.setenv("TORCH_CUDA_ARCH_LIST", "9.0a")
|
|
explicit_hopper = kernel_build_cache._build_metadata(tmp_path)
|
|
|
|
monkeypatch.delenv("TORCH_CUDA_ARCH_LIST", raising=False)
|
|
detected_hopper = kernel_build_cache._build_metadata(tmp_path)
|
|
|
|
monkeypatch.setattr(kernel_build_cache, "_detect_arch_from_torch", lambda: "8.9")
|
|
detected_l40s = kernel_build_cache._build_metadata(tmp_path)
|
|
|
|
assert explicit_hopper["cache_key"] == detected_hopper["cache_key"]
|
|
assert explicit_hopper["build"]["torch_cuda_arch_list"] == "9.0a"
|
|
assert detected_hopper["build"]["torch_cuda_arch_list"] == ""
|
|
assert detected_l40s["cache_key"] != detected_hopper["cache_key"]
|
|
|
|
|
|
def test_cache_key_ignores_runtime_only_torch_config(monkeypatch, tmp_path) -> None:
|
|
_patch_stable_metadata(monkeypatch)
|
|
build_host = kernel_build_cache._build_metadata(tmp_path)
|
|
torch_metadata = kernel_build_cache._torch_metadata()
|
|
monkeypatch.setattr(
|
|
kernel_build_cache,
|
|
"_torch_metadata",
|
|
lambda: {
|
|
**torch_metadata,
|
|
"torch_config": torch_metadata["torch_config"] + "\nCUDA Runtime 13.0\nCuDNN 92.0",
|
|
},
|
|
)
|
|
|
|
gpu_host = kernel_build_cache._build_metadata(tmp_path)
|
|
|
|
assert gpu_host["torch"]["torch_config"] != build_host["torch"]["torch_config"]
|
|
assert gpu_host["cache_key"] == build_host["cache_key"]
|
|
|
|
|
|
@pytest.mark.parametrize("environment_name", ["CFLAGS", "CXXFLAGS", "LDFLAGS"])
|
|
def test_cache_key_changes_with_build_flags(monkeypatch, tmp_path, environment_name) -> None:
|
|
_patch_stable_metadata(monkeypatch)
|
|
monkeypatch.setattr(kernel_build_cache, "_detect_arch_from_torch", lambda: "9.0a")
|
|
baseline = kernel_build_cache._build_metadata(tmp_path)
|
|
|
|
monkeypatch.setenv(environment_name, "-DFASTVIDEO_ABI_VARIANT=1")
|
|
changed = kernel_build_cache._build_metadata(tmp_path)
|
|
|
|
assert baseline["cache_key"] != changed["cache_key"]
|
|
|
|
|
|
def test_cache_key_changes_with_compiler_or_torch_abi(monkeypatch, tmp_path) -> None:
|
|
_patch_stable_metadata(monkeypatch)
|
|
monkeypatch.setattr(kernel_build_cache, "_detect_arch_from_torch", lambda: "9.0a")
|
|
baseline = kernel_build_cache._build_metadata(tmp_path)
|
|
|
|
monkeypatch.setattr(
|
|
kernel_build_cache,
|
|
"_compiler_libc_metadata",
|
|
lambda: {
|
|
"compiler": {
|
|
"cc": {
|
|
"path": "/opt/gcc-12/bin/gcc",
|
|
"version": "gcc 12.3.0"
|
|
}
|
|
},
|
|
"libc": {
|
|
"ldd_version": "ldd 2.35"
|
|
},
|
|
},
|
|
)
|
|
compiler_changed = kernel_build_cache._build_metadata(tmp_path)
|
|
|
|
_patch_stable_metadata(monkeypatch)
|
|
torch_metadata = kernel_build_cache._torch_metadata()
|
|
monkeypatch.setattr(kernel_build_cache, "_torch_metadata", lambda: {**torch_metadata, "cxx11_abi": False})
|
|
torch_abi_changed = kernel_build_cache._build_metadata(tmp_path)
|
|
|
|
assert baseline["schema_version"] == 4
|
|
assert baseline["cache_key"] != compiler_changed["cache_key"]
|
|
assert baseline["cache_key"] != torch_abi_changed["cache_key"]
|
|
|
|
|
|
def test_find_wheel_uses_fastvideo_kernel_wheel_patterns(tmp_path) -> None:
|
|
(tmp_path / "unrelated-0.1.0.whl").write_text("")
|
|
(tmp_path / "fastvideo_kernel-0.3.1-py3-none-any.whl").write_text("")
|
|
expected = tmp_path / WHEEL_NAME
|
|
expected.write_text("")
|
|
|
|
assert kernel_build_cache._find_wheel(tmp_path) == expected
|
|
|
|
|
|
def test_wheel_artifact_validates_archive_digest_size_and_distribution(tmp_path) -> None:
|
|
wheel = _write_wheel(tmp_path / WHEEL_NAME)
|
|
artifact = kernel_build_cache._wheel_artifact(wheel)
|
|
|
|
valid, reason = kernel_build_cache._validate_wheel(wheel, artifact)
|
|
|
|
assert valid
|
|
assert reason == ""
|
|
assert artifact["distribution"] == "fastvideo-kernel"
|
|
assert artifact["size_bytes"] == wheel.stat().st_size
|
|
assert len(artifact["sha256"]) == 64
|
|
|
|
|
|
@pytest.mark.parametrize("corruption", ["digest", "size", "archive", "distribution"])
|
|
def test_wheel_validation_rejects_corrupt_or_unexpected_artifacts(tmp_path, corruption) -> None:
|
|
wheel = _write_wheel(tmp_path / WHEEL_NAME)
|
|
artifact = kernel_build_cache._wheel_artifact(wheel)
|
|
if corruption == "digest":
|
|
artifact["sha256"] = "0" * 64
|
|
elif corruption == "size":
|
|
artifact["size_bytes"] = int(artifact["size_bytes"]) + 1
|
|
elif corruption == "archive":
|
|
wheel.write_bytes(b"not a wheel")
|
|
else:
|
|
wheel = _write_wheel(tmp_path / "unexpected.whl", distribution="other-package")
|
|
artifact = {**artifact, "wheel_name": wheel.name}
|
|
|
|
valid, reason = kernel_build_cache._validate_wheel(wheel, artifact)
|
|
|
|
assert not valid
|
|
assert reason
|
|
|
|
|
|
def test_store_cache_entry_handles_concurrent_valid_entry(monkeypatch, tmp_path) -> None:
|
|
cache_root = tmp_path / "cache"
|
|
cache_root.mkdir()
|
|
wheel = _write_wheel(tmp_path / WHEEL_NAME)
|
|
rename_calls = []
|
|
|
|
def fake_rename(self, target):
|
|
rename_calls.append((self, target))
|
|
_write_cache_entry(target.parent, target.name)
|
|
raise OSError(errno.ENOTEMPTY, "Directory not empty")
|
|
|
|
monkeypatch.setattr(Path, "rename", fake_rename)
|
|
|
|
cache_entry = kernel_build_cache._store_cache_entry(cache_root, _metadata(), wheel)
|
|
|
|
assert cache_entry == cache_root / "cache-key"
|
|
assert rename_calls
|
|
assert not list(cache_root.glob(".cache-key.tmp-*"))
|
|
|
|
|
|
def test_store_cache_entry_evicts_existing_invalid_entry(tmp_path) -> None:
|
|
cache_root = tmp_path / "cache"
|
|
cache_root.mkdir()
|
|
invalid_entry = cache_root / "cache-key"
|
|
invalid_entry.mkdir()
|
|
sentinel = invalid_entry / "stale"
|
|
sentinel.write_text("invalid")
|
|
wheel = _write_wheel(tmp_path / WHEEL_NAME)
|
|
|
|
cache_entry = kernel_build_cache._store_cache_entry(cache_root, _metadata(), wheel)
|
|
cached_wheel, reason = kernel_build_cache._cache_entry_wheel(cache_entry, "cache-key")
|
|
|
|
assert cached_wheel == cache_entry / WHEEL_NAME
|
|
assert reason == ""
|
|
assert not sentinel.exists()
|
|
|
|
|
|
def test_store_cache_entry_raises_when_renamed_entry_fails_validation(monkeypatch, tmp_path) -> None:
|
|
cache_root = tmp_path / "cache"
|
|
cache_root.mkdir()
|
|
wheel = _write_wheel(tmp_path / WHEEL_NAME)
|
|
original_rename = Path.rename
|
|
|
|
def fake_rename(self, target):
|
|
original_rename(self, target)
|
|
metadata_path = target / kernel_build_cache.METADATA_FILE
|
|
payload = json.loads(metadata_path.read_text(encoding="utf-8"))
|
|
payload["cache_key"] = "other-key"
|
|
metadata_path.write_text(json.dumps(payload), encoding="utf-8")
|
|
|
|
monkeypatch.setattr(Path, "rename", fake_rename)
|
|
|
|
with pytest.raises(RuntimeError, match="could not be validated"):
|
|
kernel_build_cache._store_cache_entry(cache_root, _metadata(), wheel)
|
|
|
|
assert not (cache_root / "cache-key").exists()
|
|
|
|
|
|
def test_consumer_installs_valid_cache_hit_without_build(monkeypatch, tmp_path) -> None:
|
|
cache_root = tmp_path / "cache"
|
|
cache_root.mkdir()
|
|
cache_entry = _write_cache_entry(cache_root, "cache-key")
|
|
installed = []
|
|
monkeypatch.setattr(kernel_build_cache, "_build_metadata", lambda repo_root: _metadata())
|
|
monkeypatch.setattr(kernel_build_cache, "_try_install_prebuilt", lambda metadata, path: False)
|
|
monkeypatch.setattr(kernel_build_cache, "_install_wheel", installed.append)
|
|
monkeypatch.setattr(
|
|
kernel_build_cache,
|
|
"_build_and_install_local",
|
|
lambda repo_root, metadata: pytest.fail("cache hit unexpectedly built locally"),
|
|
)
|
|
|
|
kernel_build_cache.install_cached_or_build(tmp_path, cache_root, tmp_path / "missing-prebuilt.json")
|
|
|
|
assert installed == [cache_entry / WHEEL_NAME]
|
|
|
|
|
|
def test_consumer_rejects_corrupt_cache_without_mutating_it(monkeypatch, tmp_path) -> None:
|
|
cache_root = tmp_path / "cache"
|
|
cache_root.mkdir()
|
|
cache_entry = _write_cache_entry(cache_root, "cache-key")
|
|
wheel = cache_entry / WHEEL_NAME
|
|
wheel.write_bytes(b"corrupt")
|
|
before_metadata = (cache_entry / kernel_build_cache.METADATA_FILE).read_bytes()
|
|
built = []
|
|
monkeypatch.setattr(kernel_build_cache, "_build_metadata", lambda repo_root: _metadata())
|
|
monkeypatch.setattr(kernel_build_cache, "_try_install_prebuilt", lambda metadata, path: False)
|
|
monkeypatch.setattr(kernel_build_cache, "_build_and_install_local", lambda repo_root, metadata: built.append(True))
|
|
|
|
kernel_build_cache.install_cached_or_build(tmp_path, cache_root, tmp_path / "missing-prebuilt.json")
|
|
|
|
assert built == [True]
|
|
assert wheel.read_bytes() == b"corrupt"
|
|
assert (cache_entry / kernel_build_cache.METADATA_FILE).read_bytes() == before_metadata
|
|
|
|
|
|
def test_consumer_falls_back_when_cached_install_fails(monkeypatch, tmp_path) -> None:
|
|
cache_root = tmp_path / "cache"
|
|
cache_root.mkdir()
|
|
_write_cache_entry(cache_root, "cache-key")
|
|
built = []
|
|
monkeypatch.setattr(kernel_build_cache, "_build_metadata", lambda repo_root: _metadata())
|
|
monkeypatch.setattr(kernel_build_cache, "_try_install_prebuilt", lambda metadata, path: False)
|
|
monkeypatch.setattr(
|
|
kernel_build_cache,
|
|
"_install_wheel",
|
|
lambda wheel: (_ for _ in ()).throw(subprocess.CalledProcessError(1, ["uv"])),
|
|
)
|
|
monkeypatch.setattr(kernel_build_cache, "_build_and_install_local", lambda repo_root, metadata: built.append(True))
|
|
|
|
kernel_build_cache.install_cached_or_build(tmp_path, cache_root, tmp_path / "missing-prebuilt.json")
|
|
|
|
assert built == [True]
|
|
|
|
|
|
def test_normal_l40s_pr_ssim_install_uses_docker_prebuilt_without_build(monkeypatch, tmp_path, capsys) -> None:
|
|
prebuilt_root = tmp_path / "prebuilt"
|
|
_write_prebuilt_artifact(prebuilt_root, "9.0a", "hopper-key")
|
|
l40s_wheel = _write_prebuilt_artifact(prebuilt_root, "8.9", "l40s-key")
|
|
installed = []
|
|
monkeypatch.setattr(kernel_build_cache, "_build_metadata", lambda repo_root: _metadata("l40s-key"))
|
|
monkeypatch.setattr(kernel_build_cache, "_install_wheel", installed.append)
|
|
monkeypatch.setattr(
|
|
kernel_build_cache,
|
|
"_build_and_install_local",
|
|
lambda repo_root, metadata: pytest.fail("L40S prebuilt hit unexpectedly called build.sh"),
|
|
)
|
|
|
|
kernel_build_cache.install_cached_or_build(
|
|
tmp_path,
|
|
cache_root=None,
|
|
prebuilt_info_path=prebuilt_root,
|
|
)
|
|
|
|
output = capsys.readouterr().out
|
|
assert installed == [l40s_wheel]
|
|
assert "Docker-prebuilt cache hit for key l40s-key" in output
|
|
assert "build.sh" not in output
|
|
|
|
|
|
def test_prebuilt_requires_validated_artifact(monkeypatch, tmp_path) -> None:
|
|
wheel = _write_wheel(tmp_path / WHEEL_NAME)
|
|
prebuilt_info = tmp_path / "prebuilt.json"
|
|
payload = {
|
|
**_metadata(),
|
|
"artifact": kernel_build_cache._wheel_artifact(wheel),
|
|
"wheel_path": str(wheel),
|
|
}
|
|
prebuilt_info.write_text(json.dumps(payload), encoding="utf-8")
|
|
installed = []
|
|
monkeypatch.setattr(kernel_build_cache, "_install_wheel", installed.append)
|
|
|
|
assert kernel_build_cache._try_install_prebuilt(_metadata(), prebuilt_info)
|
|
assert installed == [wheel]
|
|
|
|
installed.clear()
|
|
wheel.write_bytes(b"corrupt")
|
|
assert not kernel_build_cache._try_install_prebuilt(_metadata(), prebuilt_info)
|
|
assert installed == []
|
|
|
|
|
|
def test_producer_repairs_invalid_entry_and_stores_valid_wheel(monkeypatch, tmp_path) -> None:
|
|
cache_root = tmp_path / "cache"
|
|
cache_root.mkdir()
|
|
invalid_entry = cache_root / "cache-key"
|
|
invalid_entry.mkdir()
|
|
(invalid_entry / kernel_build_cache.METADATA_FILE).write_text("not json", encoding="utf-8")
|
|
built_dir = tmp_path / "built"
|
|
built_dir.mkdir()
|
|
built_wheel = _write_wheel(built_dir / WHEEL_NAME)
|
|
installed = []
|
|
monkeypatch.setattr(kernel_build_cache, "_build_metadata", lambda repo_root: _metadata())
|
|
monkeypatch.setattr(kernel_build_cache, "_build_wheel", lambda repo_root, metadata: built_wheel)
|
|
monkeypatch.setattr(kernel_build_cache, "_install_wheel", installed.append)
|
|
|
|
kernel_build_cache.produce_cached_wheel(tmp_path, cache_root)
|
|
|
|
cached_wheel, reason = kernel_build_cache._cache_entry_wheel(cache_root / "cache-key", "cache-key")
|
|
assert installed == [built_wheel]
|
|
assert cached_wheel == cache_root / "cache-key" / WHEEL_NAME
|
|
assert reason == ""
|