130 lines
4.5 KiB
Python
130 lines
4.5 KiB
Python
"""
|
|
Phase 1.2 Test: ROCm build script configuration.
|
|
|
|
Validates that build_binary.py --rocm generates the correct PyInstaller
|
|
arguments and optionally performs a true E2E build.
|
|
|
|
Usage:
|
|
python -m pytest backend/tests/test_rocm_build.py -v
|
|
python -m pytest backend/tests/test_rocm_build.py -v -m "slow" # include E2E
|
|
"""
|
|
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from build_binary import build_server
|
|
|
|
|
|
class TestRocmBuildArgs:
|
|
"""Validate PyInstaller arguments for ROCm builds."""
|
|
|
|
@pytest.fixture
|
|
def captured_args(self):
|
|
"""Run build_server(rocm=True) with mocked PyInstaller and return args."""
|
|
with (
|
|
patch("build_binary.PyInstaller.__main__.run") as mock_run,
|
|
patch("build_binary.platform.system", return_value="Linux"),
|
|
patch("build_binary.os.chdir"),
|
|
):
|
|
build_server(rocm=True)
|
|
return mock_run.call_args[0][0]
|
|
|
|
def test_binary_name(self, captured_args):
|
|
idx = captured_args.index("--name")
|
|
assert captured_args[idx + 1] == "voicebox-server-rocm"
|
|
|
|
def test_pack_mode_is_onedir(self, captured_args):
|
|
assert "--onedir" in captured_args
|
|
assert "--onefile" not in captured_args
|
|
|
|
def test_hidden_imports_cuda(self, captured_args):
|
|
"""ROCm builds must include torch.cuda hidden imports."""
|
|
assert "torch.cuda" in captured_args
|
|
|
|
def test_no_cudnn_hidden_import_for_rocm(self, captured_args):
|
|
"""ROCm builds must NOT include NVIDIA-specific cudnn hidden imports."""
|
|
assert "torch.backends.cudnn" not in captured_args
|
|
|
|
def test_nvidia_excludes_present(self, captured_args):
|
|
"""ROCm builds must exclude nvidia packages to avoid bundling ~3GB of bloat."""
|
|
excludes = []
|
|
for i, arg in enumerate(captured_args):
|
|
if arg == "--exclude-module":
|
|
excludes.append(captured_args[i + 1])
|
|
assert "nvidia" in excludes
|
|
assert "nvidia.cudnn" in excludes
|
|
|
|
|
|
class TestRocmBuildCli:
|
|
"""Validate CLI argument parsing for --rocm."""
|
|
|
|
def test_rocm_flag_parses(self):
|
|
build_script = Path(__file__).parent.parent / "build_binary.py"
|
|
result = subprocess.run(
|
|
[sys.executable, str(build_script), "--rocm", "--help"],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
assert result.returncode == 0
|
|
assert "--rocm" in result.stdout
|
|
|
|
def test_cannot_combine_cuda_and_rocm(self):
|
|
"""Building with both CUDA and ROCm should raise ValueError."""
|
|
with pytest.raises(ValueError, match="Cannot build with both CUDA and ROCm"):
|
|
build_server(cuda=True, rocm=True)
|
|
|
|
|
|
@pytest.mark.slow()
|
|
@pytest.mark.skipif(sys.platform != "win32", reason="ROCm build E2E only runs on Windows")
|
|
class TestRocmBuildE2E:
|
|
"""
|
|
True end-to-end build test.
|
|
Executes build_binary.py --rocm, verifies the binary exists, and runs it
|
|
with --help to confirm it boots without import errors.
|
|
"""
|
|
|
|
def test_rocm_binary_compiles_and_runs(self, tmp_path):
|
|
backend_dir = Path(__file__).parent.parent
|
|
build_script = backend_dir / "build_binary.py"
|
|
dist_dir = backend_dir / "dist"
|
|
binary_dir = dist_dir / "voicebox-server-rocm"
|
|
binary_exe = binary_dir / "voicebox-server-rocm.exe"
|
|
|
|
# Clean previous dist if it exists to ensure a fresh build
|
|
if binary_dir.exists():
|
|
import shutil
|
|
shutil.rmtree(binary_dir)
|
|
|
|
# Run the full build (this can take several minutes)
|
|
result = subprocess.run(
|
|
[sys.executable, str(build_script), "--rocm"],
|
|
capture_output=True,
|
|
text=True,
|
|
cwd=str(backend_dir),
|
|
timeout=900,
|
|
)
|
|
|
|
assert result.returncode == 0, (
|
|
f"Build failed with stdout:\n{result.stdout}\nstderr:\n{result.stderr}"
|
|
)
|
|
assert binary_exe.exists(), (
|
|
f"Expected binary not found at {binary_exe}"
|
|
)
|
|
|
|
# Run the binary with --help to ensure it boots without import errors
|
|
run_result = subprocess.run(
|
|
[str(binary_exe), "--help"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
)
|
|
|
|
# A frozen binary may not have argparse help, but it should not crash
|
|
# with a ModuleNotFoundError or similar import error.
|
|
assert "ModuleNotFoundError" not in run_result.stderr
|
|
assert "ImportError" not in run_result.stderr
|