FA4 cute supports SM 9/10/11 only, and the pinned revision forces nvidia-cutlass-dsl==4.6.0.dev0 over the 4.8.x that flashinfer 0.7.2 needs, so import flashinfer fails in the image. GB10 FP4 attention runs on fastvideo-kernel. Add INSTALL_FA4_CUTE (default 1), set it to 0 for the CUDA 13 arm64 leg, still remove the wheel's stale cute files, and check that flashinfer imports when the overlay is skipped.
525 lines
21 KiB
Python
525 lines
21 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, and skipping the FA4 cute overlay keeps flashinfer importable."""
|
|
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["12.6.3"]["install_fa4_cute"] == {"amd64": "1", "arm64": "1"}
|
|
assert ("INSTALL_FA4_CUTE=${{ matrix.cuda.install_fa4_cute[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 '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 == ""
|