mirror of
https://github.com/chidiwilliams/buzz.git
synced 2026-06-04 06:52:25 +00:00
Adding more tests
This commit is contained in:
@@ -1,60 +0,0 @@
|
||||
from PyQt6.QtGui import QIcon, QPixmap, QPainter, QPalette
|
||||
from PyQt6.QtCore import QSize
|
||||
from PyQt6.QtSvg import QSvgRenderer
|
||||
import os
|
||||
from buzz.assets import APP_BASE_DIR
|
||||
|
||||
class PresentationIcon:
|
||||
"Icons for presentation window controls"
|
||||
def __init__(self, parent, svg_path: str, color: str = None):
|
||||
self.parent = parent
|
||||
self.svg_path = svg_path
|
||||
self.color = color or self.get_default_color()
|
||||
|
||||
|
||||
def get_default_color(self) -> str:
|
||||
"""Get default icon color based on theme"""
|
||||
palette = self.parent.palette()
|
||||
is_dark = palette.window().color().black() > 127
|
||||
|
||||
return "#EEE" if is_dark else "#555"
|
||||
|
||||
def get_icon(self) -> QIcon:
|
||||
"""Load SVG icon and return as QIcon"""
|
||||
#Load from asset first
|
||||
full_path = os.path.join(APP_BASE_DIR, "assets", "icons", os.path.basename(self.svg_path))
|
||||
|
||||
if not os.path.exists(full_path):
|
||||
pixmap = QPixmap(24, 24)
|
||||
pixmap.fill(self.color)
|
||||
|
||||
return QIcon(pixmap)
|
||||
|
||||
#Load SVG
|
||||
renderer = QSvgRenderer(full_path)
|
||||
pixmap = QPixmap(24, 24)
|
||||
pixmap.fill(Qt.GlobalColor.transparent)
|
||||
painter = QPainter(pixmap)
|
||||
renderer.render(painter)
|
||||
painter.end()
|
||||
|
||||
return QIcon(pixmap)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch, call
|
||||
|
||||
import pytest
|
||||
|
||||
from buzz.cuda_manager import (
|
||||
CUDA_INDEX_URL,
|
||||
CUDA_NVIDIA_PACKAGES_LINUX,
|
||||
is_cuda_torch_installed,
|
||||
is_flatpak,
|
||||
is_nvidia_gpu_present,
|
||||
is_snap,
|
||||
should_offer_cuda_prompt,
|
||||
_get_install_target,
|
||||
_get_pip_cmd,
|
||||
_in_virtualenv,
|
||||
_pip_install,
|
||||
_subprocess_hide_window_kwargs,
|
||||
install_cuda,
|
||||
)
|
||||
|
||||
|
||||
class TestIsSnap:
|
||||
def test_returns_true_when_snap_env_set(self, monkeypatch):
|
||||
monkeypatch.setenv("SNAP", "/snap/buzz/current")
|
||||
assert is_snap() is True
|
||||
|
||||
def test_returns_false_when_snap_env_not_set(self, monkeypatch):
|
||||
monkeypatch.delenv("SNAP", raising=False)
|
||||
assert is_snap() is False
|
||||
|
||||
|
||||
class TestIsFlatpak:
|
||||
def test_returns_true_when_flatpak_env_set(self, monkeypatch):
|
||||
monkeypatch.setenv("FLATPAK_ID", "io.github.chidiwilliams.buzz")
|
||||
assert is_flatpak() is True
|
||||
|
||||
def test_returns_false_when_flatpak_env_not_set(self, monkeypatch):
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
assert is_flatpak() is False
|
||||
|
||||
|
||||
class TestShouldOfferCudaPrompt:
|
||||
def test_returns_true_on_windows(self, monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "win32")
|
||||
assert should_offer_cuda_prompt() is True
|
||||
|
||||
def test_returns_true_on_linux_snap(self, monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
monkeypatch.setenv("SNAP", "/snap/buzz/current")
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
assert should_offer_cuda_prompt() is True
|
||||
|
||||
def test_returns_true_on_linux_flatpak(self, monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
monkeypatch.delenv("SNAP", raising=False)
|
||||
monkeypatch.setenv("FLATPAK_ID", "io.github.chidiwilliams.buzz")
|
||||
assert should_offer_cuda_prompt() is True
|
||||
|
||||
def test_returns_false_on_linux_bare(self, monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
monkeypatch.delenv("SNAP", raising=False)
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
assert should_offer_cuda_prompt() is False
|
||||
|
||||
def test_returns_false_on_macos(self, monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "darwin")
|
||||
assert should_offer_cuda_prompt() is False
|
||||
|
||||
|
||||
class TestIsCudaTorchInstalled:
|
||||
def test_returns_true_when_cuda_available(self):
|
||||
mock_torch = MagicMock()
|
||||
mock_torch.cuda.is_available.return_value = True
|
||||
mock_torch.__version__ = "2.0.0+cu118"
|
||||
mock_torch.version.cuda = "11.8"
|
||||
with patch.dict("sys.modules", {"torch": mock_torch}):
|
||||
assert is_cuda_torch_installed() is True
|
||||
|
||||
def test_returns_false_when_cuda_not_available(self):
|
||||
mock_torch = MagicMock()
|
||||
mock_torch.cuda.is_available.return_value = False
|
||||
mock_torch.__version__ = "2.0.0"
|
||||
mock_torch.version.cuda = None
|
||||
with patch.dict("sys.modules", {"torch": mock_torch}):
|
||||
assert is_cuda_torch_installed() is False
|
||||
|
||||
def test_returns_false_when_torch_not_installed(self):
|
||||
with patch.dict("sys.modules", {"torch": None}):
|
||||
with patch("builtins.__import__", side_effect=ImportError):
|
||||
assert is_cuda_torch_installed() is False
|
||||
|
||||
def test_logs_warning_when_cuda_compiled_but_unavailable(self):
|
||||
mock_torch = MagicMock()
|
||||
mock_torch.cuda.is_available.return_value = False
|
||||
mock_torch.__version__ = "2.0.0+cu118"
|
||||
mock_torch.version.cuda = "11.8"
|
||||
with patch.dict("sys.modules", {"torch": mock_torch}):
|
||||
with patch("buzz.cuda_manager.logger") as mock_logger:
|
||||
is_cuda_torch_installed()
|
||||
mock_logger.warning.assert_called_once()
|
||||
|
||||
|
||||
class TestIsNvidiaGpuPresent:
|
||||
def test_returns_true_when_nvidia_smi_succeeds(self):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
assert is_nvidia_gpu_present() is True
|
||||
|
||||
def test_falls_back_to_proc_file_when_nvidia_smi_missing(self, tmp_path):
|
||||
with patch("subprocess.run", side_effect=FileNotFoundError):
|
||||
with patch("buzz.cuda_manager.Path") as mock_path_cls:
|
||||
mock_path_cls.return_value.exists.return_value = True
|
||||
assert is_nvidia_gpu_present() is True
|
||||
|
||||
def test_returns_false_when_nvidia_smi_fails_and_no_proc_file(self):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(returncode=1)
|
||||
with patch("pathlib.Path.exists", return_value=False):
|
||||
assert is_nvidia_gpu_present() is False
|
||||
|
||||
def test_handles_timeout(self):
|
||||
with patch("subprocess.run", side_effect=subprocess.TimeoutExpired(["nvidia-smi"], 5)):
|
||||
with patch("pathlib.Path.exists", return_value=False):
|
||||
assert is_nvidia_gpu_present() is False
|
||||
|
||||
|
||||
class TestInVirtualenv:
|
||||
def test_returns_true_when_virtual_env_set(self, monkeypatch):
|
||||
monkeypatch.setenv("VIRTUAL_ENV", "/some/venv")
|
||||
assert _in_virtualenv() is True
|
||||
|
||||
def test_returns_true_when_prefix_differs(self, monkeypatch):
|
||||
monkeypatch.delenv("VIRTUAL_ENV", raising=False)
|
||||
with patch.object(sys, "prefix", "/some/venv"):
|
||||
with patch.object(sys, "base_prefix", "/usr"):
|
||||
assert _in_virtualenv() is True
|
||||
|
||||
def test_returns_false_when_no_venv(self, monkeypatch):
|
||||
monkeypatch.delenv("VIRTUAL_ENV", raising=False)
|
||||
with patch.object(sys, "prefix", sys.base_prefix):
|
||||
assert _in_virtualenv() is False
|
||||
|
||||
|
||||
class TestGetInstallTarget:
|
||||
def test_snap_uses_snap_user_data(self, monkeypatch, tmp_path):
|
||||
snap_dir = tmp_path / "snap_data"
|
||||
monkeypatch.setenv("SNAP", "/snap/buzz/current")
|
||||
monkeypatch.setenv("SNAP_USER_DATA", str(snap_dir))
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
flags = _get_install_target()
|
||||
assert flags[0] == "--target"
|
||||
assert "cuda_packages" in flags[1]
|
||||
assert str(snap_dir) in flags[1]
|
||||
|
||||
def test_snap_falls_back_to_home_when_no_snap_user_data(self, monkeypatch):
|
||||
monkeypatch.setenv("SNAP", "/snap/buzz/current")
|
||||
monkeypatch.delenv("SNAP_USER_DATA", raising=False)
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
with patch("pathlib.Path.mkdir"):
|
||||
flags = _get_install_target()
|
||||
assert flags[0] == "--target"
|
||||
assert "cuda_packages" in flags[1]
|
||||
|
||||
def test_flatpak_uses_xdg_data_home(self, monkeypatch, tmp_path):
|
||||
monkeypatch.delenv("SNAP", raising=False)
|
||||
monkeypatch.setenv("FLATPAK_ID", "io.github.chidiwilliams.buzz")
|
||||
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
|
||||
with patch("pathlib.Path.mkdir"):
|
||||
flags = _get_install_target()
|
||||
assert flags[0] == "--target"
|
||||
assert "buzz" in flags[1]
|
||||
assert "cuda_packages" in flags[1]
|
||||
|
||||
def test_virtualenv_returns_empty(self, monkeypatch):
|
||||
monkeypatch.delenv("SNAP", raising=False)
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
monkeypatch.setenv("VIRTUAL_ENV", "/some/venv")
|
||||
assert _get_install_target() == []
|
||||
|
||||
def test_bare_returns_user_flag(self, monkeypatch):
|
||||
monkeypatch.delenv("SNAP", raising=False)
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
monkeypatch.delenv("VIRTUAL_ENV", raising=False)
|
||||
with patch.object(sys, "prefix", sys.base_prefix):
|
||||
assert _get_install_target() == ["--user"]
|
||||
|
||||
|
||||
class TestSubprocessHideWindowKwargs:
|
||||
def test_returns_empty_on_linux(self, monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
assert _subprocess_hide_window_kwargs() == {}
|
||||
|
||||
def test_returns_startupinfo_on_windows(self, monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "win32")
|
||||
# Only run on actual windows, otherwise mock the STARTUPINFO
|
||||
if sys.platform != "win32":
|
||||
mock_si = MagicMock()
|
||||
with patch("subprocess.STARTUPINFO", return_value=mock_si):
|
||||
with patch("subprocess.STARTF_USESHOWWINDOW", 1):
|
||||
with patch("subprocess.SW_HIDE", 0):
|
||||
with patch("subprocess.CREATE_NO_WINDOW", 0x08000000):
|
||||
result = _subprocess_hide_window_kwargs()
|
||||
assert "startupinfo" in result
|
||||
assert "creationflags" in result
|
||||
|
||||
|
||||
class TestGetPipCmd:
|
||||
def test_returns_sys_executable_when_pip_available(self):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
cmd = _get_pip_cmd()
|
||||
assert cmd == [sys.executable, "-m", "pip"]
|
||||
|
||||
def test_bootstraps_pip_when_not_available(self):
|
||||
responses = [
|
||||
MagicMock(returncode=1), # pip --version fails
|
||||
MagicMock(returncode=0), # ensurepip succeeds
|
||||
]
|
||||
with patch("subprocess.run", side_effect=responses):
|
||||
cmd = _get_pip_cmd()
|
||||
assert cmd == [sys.executable, "-m", "pip"]
|
||||
|
||||
def test_raises_when_ensurepip_also_fails(self):
|
||||
responses = [
|
||||
MagicMock(returncode=1), # pip --version fails
|
||||
MagicMock(returncode=1), # ensurepip fails
|
||||
]
|
||||
with patch("subprocess.run", side_effect=responses):
|
||||
with pytest.raises(RuntimeError, match="pip is not available"):
|
||||
_get_pip_cmd()
|
||||
|
||||
|
||||
class TestPipInstall:
|
||||
def test_calls_pip_with_packages(self):
|
||||
mock_proc = MagicMock()
|
||||
mock_proc.stdout = iter(["Collecting torch\n", "Successfully installed\n"])
|
||||
mock_proc.returncode = 0
|
||||
mock_proc.wait.return_value = None
|
||||
|
||||
with patch("buzz.cuda_manager._get_pip_cmd", return_value=[sys.executable, "-m", "pip"]):
|
||||
with patch("subprocess.Popen", return_value=mock_proc) as mock_popen:
|
||||
_pip_install(["torch==2.0.0"], extra_args=["--index-url", "https://example.com"])
|
||||
|
||||
cmd = mock_popen.call_args[0][0]
|
||||
assert "torch==2.0.0" in cmd
|
||||
assert "--index-url" in cmd
|
||||
|
||||
def test_raises_on_nonzero_exit(self):
|
||||
mock_proc = MagicMock()
|
||||
mock_proc.stdout = iter([])
|
||||
mock_proc.returncode = 1
|
||||
mock_proc.wait.return_value = None
|
||||
|
||||
with patch("buzz.cuda_manager._get_pip_cmd", return_value=[sys.executable, "-m", "pip"]):
|
||||
with patch("subprocess.Popen", return_value=mock_proc):
|
||||
with pytest.raises(RuntimeError, match="pip install failed"):
|
||||
_pip_install(["torch==2.0.0"])
|
||||
|
||||
def test_calls_progress_callback(self):
|
||||
mock_proc = MagicMock()
|
||||
mock_proc.stdout = iter(["line1\n", "line2\n"])
|
||||
mock_proc.returncode = 0
|
||||
mock_proc.wait.return_value = None
|
||||
|
||||
calls = []
|
||||
with patch("buzz.cuda_manager._get_pip_cmd", return_value=[sys.executable, "-m", "pip"]):
|
||||
with patch("subprocess.Popen", return_value=mock_proc):
|
||||
_pip_install(["pkg"], progress_callback=calls.append)
|
||||
|
||||
assert "line1" in calls
|
||||
assert "line2" in calls
|
||||
|
||||
|
||||
class TestInstallCuda:
|
||||
def test_calls_pip_install_twice(self, monkeypatch):
|
||||
monkeypatch.delenv("SNAP", raising=False)
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
monkeypatch.setenv("VIRTUAL_ENV", "/some/venv")
|
||||
|
||||
with patch("buzz.cuda_manager._pip_install") as mock_pip:
|
||||
install_cuda()
|
||||
|
||||
assert mock_pip.call_count == 2
|
||||
|
||||
def test_passes_progress_callback(self, monkeypatch):
|
||||
monkeypatch.delenv("SNAP", raising=False)
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
monkeypatch.setenv("VIRTUAL_ENV", "/some/venv")
|
||||
|
||||
messages = []
|
||||
with patch("buzz.cuda_manager._pip_install"):
|
||||
install_cuda(progress_callback=messages.append)
|
||||
|
||||
assert any("NVIDIA" in m for m in messages)
|
||||
assert any("PyTorch" in m for m in messages)
|
||||
|
||||
def test_linux_excludes_linux_only_packages_on_windows(self, monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "win32")
|
||||
monkeypatch.delenv("SNAP", raising=False)
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
|
||||
captured = []
|
||||
|
||||
def fake_pip(packages, **kwargs):
|
||||
captured.append(packages)
|
||||
|
||||
with patch("buzz.cuda_manager._pip_install", side_effect=fake_pip):
|
||||
with patch("buzz.cuda_manager._get_install_target", return_value=[]):
|
||||
install_cuda()
|
||||
|
||||
nvidia_pkgs = captured[0]
|
||||
for pkg in CUDA_NVIDIA_PACKAGES_LINUX:
|
||||
assert pkg not in nvidia_pkgs
|
||||
@@ -0,0 +1,238 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestGetCudaTargetDir:
|
||||
def test_returns_snap_path(self, monkeypatch, tmp_path):
|
||||
monkeypatch.setenv("SNAP_USER_DATA", str(tmp_path))
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
from buzz.cuda_setup import _get_cuda_target_dir
|
||||
result = _get_cuda_target_dir()
|
||||
assert result == tmp_path / "cuda_packages"
|
||||
|
||||
def test_returns_flatpak_path(self, monkeypatch, tmp_path):
|
||||
monkeypatch.delenv("SNAP_USER_DATA", raising=False)
|
||||
monkeypatch.setenv("FLATPAK_ID", "io.github.chidiwilliams.buzz")
|
||||
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
|
||||
from buzz.cuda_setup import _get_cuda_target_dir
|
||||
result = _get_cuda_target_dir()
|
||||
assert result == tmp_path / "buzz" / "cuda_packages"
|
||||
|
||||
def test_returns_none_when_no_env(self, monkeypatch):
|
||||
monkeypatch.delenv("SNAP_USER_DATA", raising=False)
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
from buzz.cuda_setup import _get_cuda_target_dir
|
||||
result = _get_cuda_target_dir()
|
||||
assert result is None
|
||||
|
||||
def test_flatpak_falls_back_to_home(self, monkeypatch):
|
||||
monkeypatch.delenv("SNAP_USER_DATA", raising=False)
|
||||
monkeypatch.setenv("FLATPAK_ID", "io.github.chidiwilliams.buzz")
|
||||
monkeypatch.delenv("XDG_DATA_HOME", raising=False)
|
||||
from buzz.cuda_setup import _get_cuda_target_dir
|
||||
result = _get_cuda_target_dir()
|
||||
assert result is not None
|
||||
assert "cuda_packages" in str(result)
|
||||
|
||||
|
||||
class TestGetSitePackagesDirs:
|
||||
def test_adds_cuda_target_to_sys_path(self, monkeypatch, tmp_path):
|
||||
cuda_target = tmp_path / "cuda_packages"
|
||||
cuda_target.mkdir()
|
||||
monkeypatch.setenv("SNAP_USER_DATA", str(tmp_path))
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
|
||||
from buzz.cuda_setup import _get_site_packages_dirs
|
||||
dirs = _get_site_packages_dirs()
|
||||
|
||||
assert cuda_target in dirs
|
||||
assert str(cuda_target) in sys.path
|
||||
|
||||
def test_returns_list_including_existing_site_packages(self, monkeypatch):
|
||||
monkeypatch.delenv("SNAP_USER_DATA", raising=False)
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
|
||||
from buzz.cuda_setup import _get_site_packages_dirs
|
||||
dirs = _get_site_packages_dirs()
|
||||
assert isinstance(dirs, list)
|
||||
|
||||
|
||||
class TestCollectCudaLibDirs:
|
||||
def test_includes_torch_lib(self, tmp_path):
|
||||
torch_lib = tmp_path / "torch" / "lib"
|
||||
torch_lib.mkdir(parents=True)
|
||||
|
||||
from buzz.cuda_setup import _collect_cuda_lib_dirs
|
||||
dirs = _collect_cuda_lib_dirs(tmp_path)
|
||||
assert str(torch_lib) in dirs
|
||||
|
||||
def test_includes_nvidia_package_libs(self, tmp_path):
|
||||
nvidia_cublas = tmp_path / "nvidia" / "cublas" / "lib"
|
||||
nvidia_cublas.mkdir(parents=True)
|
||||
|
||||
from buzz.cuda_setup import _collect_cuda_lib_dirs
|
||||
dirs = _collect_cuda_lib_dirs(tmp_path)
|
||||
assert str(nvidia_cublas) in dirs
|
||||
|
||||
def test_returns_empty_when_no_dirs_exist(self, tmp_path):
|
||||
from buzz.cuda_setup import _collect_cuda_lib_dirs
|
||||
dirs = _collect_cuda_lib_dirs(tmp_path)
|
||||
assert dirs == []
|
||||
|
||||
|
||||
class TestGetNvidiaPackageLibDirs:
|
||||
def test_finds_nvidia_lib_dirs(self, monkeypatch, tmp_path):
|
||||
sp = tmp_path / "site-packages"
|
||||
nvidia_lib = sp / "nvidia" / "cublas" / "lib"
|
||||
nvidia_lib.mkdir(parents=True)
|
||||
|
||||
monkeypatch.delenv("SNAP_USER_DATA", raising=False)
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
|
||||
with patch("buzz.cuda_setup._get_site_packages_dirs", return_value=[sp]):
|
||||
from buzz.cuda_setup import _get_nvidia_package_lib_dirs
|
||||
dirs = _get_nvidia_package_lib_dirs()
|
||||
|
||||
assert nvidia_lib in dirs
|
||||
|
||||
def test_finds_torch_lib_dir(self, monkeypatch, tmp_path):
|
||||
sp = tmp_path / "site-packages"
|
||||
torch_lib = sp / "torch" / "lib"
|
||||
torch_lib.mkdir(parents=True)
|
||||
|
||||
monkeypatch.delenv("SNAP_USER_DATA", raising=False)
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
|
||||
with patch("buzz.cuda_setup._get_site_packages_dirs", return_value=[sp]):
|
||||
from buzz.cuda_setup import _get_nvidia_package_lib_dirs
|
||||
dirs = _get_nvidia_package_lib_dirs()
|
||||
|
||||
assert torch_lib in dirs
|
||||
|
||||
|
||||
class TestSetupLinuxCuda:
|
||||
def test_skips_when_no_cuda_target(self, monkeypatch):
|
||||
monkeypatch.delenv("SNAP_USER_DATA", raising=False)
|
||||
monkeypatch.delenv("FLATPAK_ID", raising=False)
|
||||
|
||||
with patch("buzz.cuda_setup._get_cuda_target_dir", return_value=None):
|
||||
from buzz.cuda_setup import _setup_linux_cuda
|
||||
_setup_linux_cuda() # should not raise
|
||||
|
||||
def test_skips_when_cuda_target_does_not_exist(self, monkeypatch, tmp_path):
|
||||
nonexistent = tmp_path / "nonexistent"
|
||||
with patch("buzz.cuda_setup._get_cuda_target_dir", return_value=nonexistent):
|
||||
from buzz.cuda_setup import _setup_linux_cuda
|
||||
_setup_linux_cuda() # should not raise
|
||||
|
||||
def test_skips_when_no_torch_lib(self, monkeypatch, tmp_path):
|
||||
cuda_target = tmp_path / "cuda_packages"
|
||||
cuda_target.mkdir()
|
||||
with patch("buzz.cuda_setup._get_cuda_target_dir", return_value=cuda_target):
|
||||
from buzz.cuda_setup import _setup_linux_cuda
|
||||
_setup_linux_cuda() # should not raise
|
||||
|
||||
def test_reexecs_when_sentinel_not_in_ld_path(self, monkeypatch, tmp_path):
|
||||
cuda_target = tmp_path / "cuda_packages"
|
||||
torch_lib = cuda_target / "torch" / "lib"
|
||||
torch_lib.mkdir(parents=True)
|
||||
|
||||
monkeypatch.delenv("LD_LIBRARY_PATH", raising=False)
|
||||
|
||||
with patch("buzz.cuda_setup._get_cuda_target_dir", return_value=cuda_target):
|
||||
with patch("buzz.cuda_setup._collect_cuda_lib_dirs", return_value=[str(torch_lib)]):
|
||||
with patch("os.execv", side_effect=OSError("test")) as mock_execv:
|
||||
with patch("buzz.cuda_setup._preload_linux_libraries_fallback") as mock_fallback:
|
||||
from buzz.cuda_setup import _setup_linux_cuda
|
||||
_setup_linux_cuda()
|
||||
|
||||
mock_fallback.assert_called_once()
|
||||
|
||||
def test_no_reexec_when_sentinel_already_in_ld_path(self, monkeypatch, tmp_path):
|
||||
cuda_target = tmp_path / "cuda_packages"
|
||||
torch_lib = cuda_target / "torch" / "lib"
|
||||
torch_lib.mkdir(parents=True)
|
||||
|
||||
monkeypatch.setenv("LD_LIBRARY_PATH", str(torch_lib))
|
||||
|
||||
with patch("buzz.cuda_setup._get_cuda_target_dir", return_value=cuda_target):
|
||||
with patch("os.execv") as mock_execv:
|
||||
from buzz.cuda_setup import _setup_linux_cuda
|
||||
_setup_linux_cuda()
|
||||
|
||||
mock_execv.assert_not_called()
|
||||
|
||||
|
||||
class TestSetupWindowsDllDirectories:
|
||||
def test_calls_add_dll_directory(self, tmp_path):
|
||||
lib_dir = tmp_path / "lib"
|
||||
lib_dir.mkdir()
|
||||
|
||||
with patch("buzz.cuda_setup._get_nvidia_package_lib_dirs", return_value=[lib_dir]):
|
||||
with patch("buzz.cuda_setup.os") as mock_os:
|
||||
from buzz.cuda_setup import _setup_windows_dll_directories
|
||||
_setup_windows_dll_directories()
|
||||
|
||||
mock_os.add_dll_directory.assert_called_once_with(str(lib_dir))
|
||||
|
||||
def test_logs_warning_when_no_lib_dirs(self):
|
||||
with patch("buzz.cuda_setup._get_nvidia_package_lib_dirs", return_value=[]):
|
||||
with patch("buzz.cuda_setup.logger") as mock_logger:
|
||||
from buzz.cuda_setup import _setup_windows_dll_directories
|
||||
_setup_windows_dll_directories()
|
||||
|
||||
mock_logger.warning.assert_called_once()
|
||||
|
||||
|
||||
class TestSetupCudaLibraries:
|
||||
def test_calls_windows_setup_on_windows(self):
|
||||
with patch("platform.system", return_value="Windows"):
|
||||
with patch("buzz.cuda_setup._setup_windows_dll_directories") as mock_win:
|
||||
from buzz.cuda_setup import setup_cuda_libraries
|
||||
setup_cuda_libraries()
|
||||
mock_win.assert_called_once()
|
||||
|
||||
def test_calls_linux_setup_on_linux(self):
|
||||
with patch("platform.system", return_value="Linux"):
|
||||
with patch("buzz.cuda_setup._setup_linux_cuda") as mock_linux:
|
||||
from buzz.cuda_setup import setup_cuda_libraries
|
||||
setup_cuda_libraries()
|
||||
mock_linux.assert_called_once()
|
||||
|
||||
def test_does_nothing_on_macos(self):
|
||||
with patch("platform.system", return_value="Darwin"):
|
||||
with patch("buzz.cuda_setup._setup_windows_dll_directories") as mock_win:
|
||||
with patch("buzz.cuda_setup._setup_linux_cuda") as mock_linux:
|
||||
from buzz.cuda_setup import setup_cuda_libraries
|
||||
setup_cuda_libraries()
|
||||
mock_win.assert_not_called()
|
||||
mock_linux.assert_not_called()
|
||||
|
||||
|
||||
class TestPreloadLinuxLibrariesFallback:
|
||||
def test_loads_so_files(self, tmp_path):
|
||||
lib_dir = tmp_path / "lib"
|
||||
lib_dir.mkdir()
|
||||
so_file = lib_dir / "libfoo.so.1"
|
||||
so_file.touch()
|
||||
|
||||
with patch("buzz.cuda_setup._get_nvidia_package_lib_dirs", return_value=[lib_dir]):
|
||||
with patch("ctypes.CDLL") as mock_cdll:
|
||||
from buzz.cuda_setup import _preload_linux_libraries_fallback
|
||||
_preload_linux_libraries_fallback()
|
||||
mock_cdll.assert_called()
|
||||
|
||||
def test_skips_libnvblas(self, tmp_path):
|
||||
lib_dir = tmp_path / "lib"
|
||||
lib_dir.mkdir()
|
||||
skip_file = lib_dir / "libnvblas.so.1"
|
||||
skip_file.touch()
|
||||
|
||||
with patch("buzz.cuda_setup._get_nvidia_package_lib_dirs", return_value=[lib_dir]):
|
||||
with patch("ctypes.CDLL") as mock_cdll:
|
||||
from buzz.cuda_setup import _preload_linux_libraries_fallback
|
||||
_preload_linux_libraries_fallback()
|
||||
mock_cdll.assert_not_called()
|
||||
@@ -54,3 +54,30 @@ class TestAudioMeterWidget:
|
||||
widget = AudioMeterWidget()
|
||||
qtbot.add_widget(widget)
|
||||
assert widget.height() == 56
|
||||
|
||||
def test_update_queue_size(self, qtbot: QtBot):
|
||||
widget = AudioMeterWidget()
|
||||
qtbot.add_widget(widget)
|
||||
widget.update_queue_size(5)
|
||||
assert widget.queue_size == 5
|
||||
|
||||
def test_reset_amplitude_clears_queue_size(self, qtbot: QtBot):
|
||||
widget = AudioMeterWidget()
|
||||
qtbot.add_widget(widget)
|
||||
widget.update_queue_size(3)
|
||||
widget.reset_amplitude()
|
||||
assert widget.queue_size == 0
|
||||
|
||||
def test_initial_queue_size_is_zero(self, qtbot: QtBot):
|
||||
widget = AudioMeterWidget()
|
||||
qtbot.add_widget(widget)
|
||||
assert widget.queue_size == 0
|
||||
|
||||
def test_paint_event_does_not_raise(self, qtbot: QtBot):
|
||||
widget = AudioMeterWidget()
|
||||
qtbot.add_widget(widget)
|
||||
widget.show()
|
||||
widget.update_amplitude(0.5)
|
||||
widget.update_average_amplitude(0.1)
|
||||
widget.update_queue_size(2)
|
||||
widget.repaint() # triggers paintEvent
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from buzz.widgets.cuda_installer_widget import CudaInstallerDialog, _InstallWorker
|
||||
|
||||
|
||||
class TestInstallWorker:
|
||||
def test_calls_install_cuda_and_emits_finished(self):
|
||||
worker = _InstallWorker()
|
||||
finished_mock = MagicMock()
|
||||
worker.signals.finished.connect(finished_mock)
|
||||
|
||||
with patch("buzz.cuda_manager.install_cuda") as mock_install:
|
||||
worker.run()
|
||||
|
||||
mock_install.assert_called_once()
|
||||
finished_mock.assert_called_once()
|
||||
|
||||
def test_emits_error_on_exception(self):
|
||||
worker = _InstallWorker()
|
||||
error_mock = MagicMock()
|
||||
worker.signals.error.connect(error_mock)
|
||||
|
||||
with patch("buzz.cuda_manager.install_cuda", side_effect=RuntimeError("fail")):
|
||||
worker.run()
|
||||
|
||||
error_mock.assert_called_once_with("fail")
|
||||
|
||||
def test_passes_progress_callback_to_install(self):
|
||||
worker = _InstallWorker()
|
||||
progress_mock = MagicMock()
|
||||
worker.signals.progress.connect(progress_mock)
|
||||
|
||||
def fake_install(progress_callback=None):
|
||||
if progress_callback:
|
||||
progress_callback("installing...")
|
||||
|
||||
with patch("buzz.cuda_manager.install_cuda", side_effect=fake_install):
|
||||
worker.run()
|
||||
|
||||
progress_mock.assert_called_once_with("installing...")
|
||||
|
||||
|
||||
class TestCudaInstallerDialog:
|
||||
def test_dialog_creates_with_correct_title(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
assert "GPU" in dialog.windowTitle() or "Nvidia" in dialog.windowTitle()
|
||||
|
||||
def test_install_button_exists(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
assert dialog.install_button is not None
|
||||
assert dialog.install_button.isEnabled()
|
||||
|
||||
def test_decline_button_exists(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
assert dialog.decline_button is not None
|
||||
|
||||
def test_progress_bar_hidden_initially(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
assert not dialog.progress_bar.isVisible()
|
||||
|
||||
def test_log_view_hidden_initially(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
assert not dialog.log_view.isVisible()
|
||||
|
||||
def test_install_click_shows_progress_bar(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
dialog.show()
|
||||
|
||||
with patch("buzz.widgets.cuda_installer_widget.QThreadPool"):
|
||||
dialog._on_install_clicked()
|
||||
|
||||
assert dialog.progress_bar.isVisible()
|
||||
assert dialog.log_view.isVisible()
|
||||
assert not dialog.install_button.isEnabled()
|
||||
assert not dialog.decline_button.isEnabled()
|
||||
|
||||
def test_on_progress_appends_to_log(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
dialog._on_progress("step 1")
|
||||
assert "step 1" in dialog.log_view.toPlainText()
|
||||
|
||||
def test_on_progress_updates_status_label(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
dialog._on_progress("Installing packages...")
|
||||
assert dialog.status_label.text() != ""
|
||||
|
||||
def test_on_progress_truncates_long_message(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
long_msg = "x" * 200
|
||||
dialog._on_progress(long_msg)
|
||||
assert len(dialog.status_label.text()) <= 80
|
||||
|
||||
def test_on_finished_re_enables_install_button(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
dialog.install_button.setEnabled(False)
|
||||
dialog._on_finished()
|
||||
assert dialog.install_button.isEnabled()
|
||||
|
||||
def test_on_finished_hides_progress_bar(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
dialog.progress_bar.setVisible(True)
|
||||
dialog._on_finished()
|
||||
assert not dialog.progress_bar.isVisible()
|
||||
|
||||
def test_on_finished_shows_completion_message(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
dialog._on_finished()
|
||||
assert "complete" in dialog.status_label.text().lower() or "restart" in dialog.status_label.text().lower()
|
||||
|
||||
def test_on_error_shows_error_message(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
dialog._on_error("something went wrong")
|
||||
assert "something went wrong" in dialog.status_label.text()
|
||||
|
||||
def test_on_error_re_enables_buttons(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
dialog.install_button.setEnabled(False)
|
||||
dialog.decline_button.setEnabled(False)
|
||||
dialog._on_error("fail")
|
||||
assert dialog.install_button.isEnabled()
|
||||
assert dialog.decline_button.isEnabled()
|
||||
|
||||
def test_on_error_hides_progress_bar(self, qtbot: QtBot):
|
||||
dialog = CudaInstallerDialog()
|
||||
qtbot.add_widget(dialog)
|
||||
dialog.progress_bar.setVisible(True)
|
||||
dialog._on_error("fail")
|
||||
assert not dialog.progress_bar.isVisible()
|
||||
Reference in New Issue
Block a user