FA2 gives no measured speedup on GB10, and with it installed FastH3 V2 on a Spark produces different audio and frames than a native Spark install (which has no FA2). Without it the image matches the native install bit for bit at the same speed. Add INSTALL_FLASH_ATTN (default 1), set it to 0 for the CUDA 13 arm64 leg, and document the first-run FlashInfer compile (persist the cache, cap MAX_JOBS).
531 lines
22 KiB
Python
531 lines
22 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
|
|
|
|
import fastvideo.envs as envs
|
|
|
|
|
|
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, env_overrides) -> None:
|
|
env_overrides.enter_context(envs.override_external("GPU_BACKEND", None))
|
|
env_overrides.enter_context(envs.override_external("CMAKE_ARGS", None))
|
|
env_overrides.enter_context(envs.override_external("CFLAGS", None))
|
|
env_overrides.enter_context(envs.override_external("CXXFLAGS", None))
|
|
env_overrides.enter_context(envs.override_external("LDFLAGS", None))
|
|
env_overrides.enter_context(envs.override_external("TORCH_CUDA_ARCH_LIST", None))
|
|
env_overrides.enter_context(envs.override_external("FASTVIDEO_CONTAINER_IMAGE_REF", None))
|
|
env_overrides.enter_context(envs.override_external("CUDACXX", None))
|
|
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 configured_command, 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_gb10_image_builds_sm121a_without_fa4_cute() -> None:
|
|
"""The CUDA 13 arm64 leg is the DGX Spark image: sm_121a keeps fastvideo-kernel's FP4
|
|
attention, skipping the FA4 cute overlay keeps flashinfer importable, and leaving FA2
|
|
out keeps the output identical to a native Spark install."""
|
|
workflow = yaml.load(
|
|
(REPO_ROOT / ".github/workflows/infra-build-image.yml").read_text(encoding="utf-8"),
|
|
Loader=yaml.BaseLoader,
|
|
)
|
|
job = workflow["jobs"]["build-cuda-images"]
|
|
cuda = {entry["version"]: entry for entry in job["strategy"]["matrix"]["cuda"]}
|
|
|
|
assert cuda["13.0.0"]["torch_cuda_arch_list"]["arm64"] == "12.1a"
|
|
assert cuda["13.0.0"]["install_fa4_cute"] == {"amd64": "1", "arm64": "0"}
|
|
assert cuda["13.0.0"]["install_flash_attn"] == {"amd64": "1", "arm64": "0"}
|
|
assert cuda["12.6.3"]["install_flash_attn"] == {"amd64": "1", "arm64": "1"}
|
|
assert cuda["12.6.3"]["install_fa4_cute"] == {"amd64": "1", "arm64": "1"}
|
|
for arg in ("INSTALL_FLASH_ATTN", "INSTALL_FA4_CUTE"):
|
|
key = arg.lower()
|
|
assert f"{arg}=${{{{ matrix.cuda.{key}[matrix.architecture.name] }}}}" in job["with"]["build_args"]
|
|
|
|
dockerfile = (REPO_ROOT / "docker/Dockerfile").read_text(encoding="utf-8")
|
|
assert "ARG INSTALL_FA4_CUTE=1" in dockerfile
|
|
assert "ARG INSTALL_FLASH_ATTN=1" in dockerfile
|
|
assert 'if [ "${INSTALL_FLASH_ATTN}" != "1" ]; then' in dockerfile
|
|
assert 'if [ "${INSTALL_FA4_CUTE}" = "1" ]; then' 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, env_overrides, tmp_path) -> None:
|
|
_patch_stable_metadata(monkeypatch, env_overrides)
|
|
monkeypatch.setattr(kernel_build_cache, "_detect_arch_from_torch", lambda: "9.0a")
|
|
|
|
env_overrides.enter_context(envs.override_external("TORCH_CUDA_ARCH_LIST", "9.0a"))
|
|
explicit_hopper = kernel_build_cache._build_metadata(tmp_path)
|
|
|
|
env_overrides.enter_context(envs.override_external("TORCH_CUDA_ARCH_LIST", None))
|
|
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, env_overrides, tmp_path) -> None:
|
|
_patch_stable_metadata(monkeypatch, env_overrides)
|
|
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, env_overrides, tmp_path, environment_name) -> None:
|
|
_patch_stable_metadata(monkeypatch, env_overrides)
|
|
monkeypatch.setattr(kernel_build_cache, "_detect_arch_from_torch", lambda: "9.0a")
|
|
baseline = kernel_build_cache._build_metadata(tmp_path)
|
|
|
|
# The env policy contract test requires a literal name in override_external, so each flag has its own override.
|
|
flag_overrides = {
|
|
"CFLAGS": envs.override_external("CFLAGS", "-DFASTVIDEO_ABI_VARIANT=1"),
|
|
"CXXFLAGS": envs.override_external("CXXFLAGS", "-DFASTVIDEO_ABI_VARIANT=1"),
|
|
"LDFLAGS": envs.override_external("LDFLAGS", "-DFASTVIDEO_ABI_VARIANT=1"),
|
|
}
|
|
env_overrides.enter_context(flag_overrides[environment_name])
|
|
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, env_overrides, tmp_path) -> None:
|
|
_patch_stable_metadata(monkeypatch, env_overrides)
|
|
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, env_overrides)
|
|
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 == ""
|