* fix(install): Fedora and Arch need glslc, not glslang ggml's Vulkan CMake looks for glslc and will not take glslangValidator. install.sh was still installing glslang on Fedora, Arch, Alpine, Void, Solus, and Gentoo, so the pywhispercpp source build fell back to a CPU wheel. Ubuntu and Debian already pull glslc plus patchelf. See #604. * fix(install): Gentoo patchelf is under dev-util sys-devel/patchelf does not exist. emerge fails and install.sh exits, so a fresh Gentoo install never reaches the whisper.cpp build. See #604.
255 lines
11 KiB
Python
255 lines
11 KiB
Python
"""
|
|
Test cross-distro compatibility features.
|
|
|
|
This module tests the cross-distribution compatibility features including:
|
|
- Dynamic GI_TYPELIB_PATH detection
|
|
- Wrapper script generation with correct paths
|
|
- Desktop entry configuration
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
def get_repo_root() -> Path:
|
|
"""Get the repository root directory."""
|
|
# Start from current file and traverse up to find repo root
|
|
current = Path(__file__).resolve()
|
|
for parent in [current] + list(current.parents):
|
|
# Check for common repo indicators
|
|
if (parent / ".git").exists() or (parent / "install.sh").exists():
|
|
return parent
|
|
# Also check if we're in a 'vocalinux' subdirectory (CI structure)
|
|
if parent.name == "vocalinux" and (parent.parent / "vocalinux" / "install.sh").exists():
|
|
return parent.parent / "vocalinux"
|
|
# Fallback: use parent of tests directory
|
|
return current.parent.parent.parent
|
|
|
|
|
|
REPO_ROOT = get_repo_root()
|
|
|
|
|
|
class TestCrossDistroCompatibility:
|
|
"""Test cross-distro compatibility features."""
|
|
|
|
@pytest.fixture
|
|
def install_sh_content(self):
|
|
"""Fixture to load install.sh content."""
|
|
install_sh_path = REPO_ROOT / "install.sh"
|
|
if not install_sh_path.exists():
|
|
pytest.skip(f"install.sh not found at {install_sh_path}")
|
|
return install_sh_path.read_text()
|
|
|
|
@pytest.fixture
|
|
def workflow_content(self):
|
|
"""Fixture to load workflow content."""
|
|
workflow_path = REPO_ROOT / ".github" / "workflows" / "unified-pipeline.yml"
|
|
if not workflow_path.exists():
|
|
pytest.skip(f"Workflow file not found at {workflow_path}")
|
|
return workflow_path.read_text()
|
|
|
|
def test_detect_typelib_path_function_exists(self, install_sh_content):
|
|
"""Test that detect_typelib_path function exists in install.sh."""
|
|
assert (
|
|
"detect_typelib_path()" in install_sh_content
|
|
), "detect_typelib_path function should exist"
|
|
assert (
|
|
"pkg-config --variable=typelibdir" in install_sh_content
|
|
), "Should use pkg-config for detection"
|
|
|
|
def test_no_hardcoded_gi_typelib_path_in_verification(self, install_sh_content):
|
|
"""Test that package verification uses dynamic GI_TYPELIB_PATH."""
|
|
# The verification function should use the detected path, not hardcoded
|
|
assert (
|
|
"verify_package_installed()" in install_sh_content
|
|
), "verify_package_installed function should exist"
|
|
|
|
# Check that it uses GI_TYPELIB_DETECTED variable
|
|
verify_section = install_sh_content[
|
|
install_sh_content.find("verify_package_installed()") : install_sh_content.find(
|
|
"verify_package_installed()"
|
|
)
|
|
+ 500
|
|
]
|
|
assert (
|
|
"GI_TYPELIB_DETECTED" in verify_section or '"$GI_TYPELIB_DETECTED"' in verify_section
|
|
), "verify_package_installed should use GI_TYPELIB_DETECTED variable"
|
|
|
|
def test_wrapper_script_uses_detected_path(self, install_sh_content):
|
|
"""Test that wrapper scripts use the detected GI_TYPELIB_PATH."""
|
|
# Check that wrapper script creation uses GI_TYPELIB_DETECTED
|
|
assert (
|
|
"export GI_TYPELIB_PATH=$GI_TYPELIB_DETECTED" in install_sh_content
|
|
), "Wrapper script should export GI_TYPELIB_PATH with detected value"
|
|
|
|
def test_desktop_entry_uses_detected_path(self, install_sh_content):
|
|
"""Test that desktop entry uses detected GI_TYPELIB_PATH."""
|
|
# The sed command for desktop entry should use the detected path
|
|
assert (
|
|
"GI_TYPELIB_PATH=$GI_TYPELIB_DETECTED" in install_sh_content
|
|
or r"GI_TYPELIB_PATH=${GI_TYPELIB_DETECTED}" in install_sh_content
|
|
), "Desktop entry sed command should use GI_TYPELIB_DETECTED"
|
|
|
|
# Should NOT have hardcoded /usr/lib/girepository-1.0 in desktop entry section
|
|
desktop_start = install_sh_content.find("install_desktop_entry()")
|
|
if desktop_start == -1:
|
|
pytest.skip("install_desktop_entry function not found")
|
|
desktop_section = install_sh_content[desktop_start : desktop_start + 1000]
|
|
if "sed -i" in desktop_section and "vocalinux.desktop" in desktop_section:
|
|
assert (
|
|
"/usr/lib/girepository-1.0"
|
|
not in desktop_section.split("sed -i")[1].split("vocalinux.desktop")[0]
|
|
), "Desktop entry should not hardcode /usr/lib/girepository-1.0 path"
|
|
|
|
def test_detect_typelib_path_with_pkgconfig(self, install_sh_content):
|
|
"""Test that detect_typelib_path uses pkg-config when available."""
|
|
# Extract the detect_typelib_path function
|
|
func_start = install_sh_content.find("detect_typelib_path() {")
|
|
if func_start == -1:
|
|
pytest.skip("detect_typelib_path function not found")
|
|
func_end = install_sh_content.find("}", func_start)
|
|
func_content = install_sh_content[func_start:func_end]
|
|
|
|
# Should check for pkg-config first
|
|
assert "pkg-config" in func_content, "Should check for pkg-config"
|
|
assert "gobject-introspection-1.0" in func_content, "Should query gobject-introspection-1.0"
|
|
|
|
def test_detect_typelib_path_fallback_paths(self, install_sh_content):
|
|
"""Test that detect_typelib_path has fallback paths for various distros."""
|
|
# Should have fallback paths for different distros
|
|
expected_paths = [
|
|
"/usr/lib/x86_64-linux-gnu/girepository-1.0", # Ubuntu/Debian x86_64
|
|
"/usr/lib/aarch64-linux-gnu/girepository-1.0", # ARM64
|
|
"/usr/lib64/girepository-1.0", # Fedora/RHEL
|
|
"/usr/lib/girepository-1.0", # Arch and fallback
|
|
]
|
|
|
|
for path in expected_paths:
|
|
assert path in install_sh_content, f"Should have fallback path: {path}"
|
|
|
|
def test_opensuse_dependency_mapping_uses_tumbleweed_package_names(self, install_sh_content):
|
|
"""Test that openSUSE uses Tumbleweed-compatible dependency resolution."""
|
|
assert "suse_python_package_prefix()" in install_sh_content
|
|
assert "suse_python_package_candidates()" in install_sh_content
|
|
assert "typelib-1_0-AyatanaAppIndicator3-0_1" in install_sh_content
|
|
assert "typelib-1_0-Notify-0_7" in install_sh_content
|
|
assert "libnotify4" in install_sh_content
|
|
assert "suse_install_appindicator_runtime" in install_sh_content
|
|
assert "shaderc glslang-devel glslang" in install_sh_content
|
|
|
|
zypper_line = next(
|
|
line for line in install_sh_content.splitlines() if "local ZYPPER_PACKAGES=" in line
|
|
)
|
|
assert "python3-devel" not in zypper_line
|
|
assert "python3-virtualenv" not in zypper_line
|
|
assert "libappindicator-gtk3" not in zypper_line
|
|
assert "glslang" not in zypper_line
|
|
|
|
def test_rpm_arch_install_glslc_not_glslang(self, install_sh_content):
|
|
"""ggml Vulkan needs glslc (shaderc). glslangValidator is not a substitute (#604)."""
|
|
dnf_line = next(
|
|
line for line in install_sh_content.splitlines() if "local DNF_PACKAGES=" in line
|
|
)
|
|
pacman_line = next(
|
|
line for line in install_sh_content.splitlines() if "local PACMAN_PACKAGES=" in line
|
|
)
|
|
assert "glslc" in dnf_line
|
|
assert "patchelf" in dnf_line
|
|
assert "glslang" not in dnf_line
|
|
assert "shaderc" in pacman_line
|
|
assert "patchelf" in pacman_line
|
|
assert "glslang" not in pacman_line
|
|
assert (
|
|
"Fedora: sudo dnf install vulkan-loader-devel vulkan-tools glslc patchelf"
|
|
in install_sh_content
|
|
)
|
|
assert (
|
|
"Arch: sudo pacman -S vulkan-headers vulkan-tools shaderc patchelf"
|
|
in install_sh_content
|
|
)
|
|
emerge_line = next(
|
|
line for line in install_sh_content.splitlines() if "local EMERGE_PACKAGES=" in line
|
|
)
|
|
assert "media-libs/shaderc" in emerge_line
|
|
assert "dev-util/patchelf" in emerge_line
|
|
assert "sys-devel/patchelf" not in emerge_line
|
|
|
|
def test_skip_system_deps_option_is_implemented(self, install_sh_content):
|
|
"""Test that the documented --skip-system-deps option is parsed and honored."""
|
|
assert "SKIP_SYSTEM_DEPS" in install_sh_content
|
|
assert "--skip-system-deps" in install_sh_content
|
|
assert "Skipping system dependency installation" in install_sh_content
|
|
|
|
def test_noninteractive_tty_fallback_does_not_abort(self, install_sh_content):
|
|
"""Test that missing controlling TTY falls back to non-interactive mode."""
|
|
assert "{ true < /dev/tty; } 2>/dev/null" in install_sh_content
|
|
assert "exec < /dev/tty" in install_sh_content
|
|
assert 'NON_INTERACTIVE="yes"' in install_sh_content
|
|
|
|
def test_ci_workflow_uses_dynamic_detection(self, workflow_content):
|
|
"""Test that CI workflow uses dynamic GI_TYPELIB_PATH detection."""
|
|
# Should use pkg-config or dynamic detection
|
|
assert (
|
|
"pkg-config --variable=typelibdir" in workflow_content
|
|
), "CI workflow should use pkg-config for GI_TYPELIB_PATH"
|
|
|
|
|
|
class TestResourceManagerCrossDistro:
|
|
"""Test ResourceManager cross-distro path handling."""
|
|
|
|
def test_resource_manager_has_xdg_support(self):
|
|
"""Test that ResourceManager supports XDG directories."""
|
|
from vocalinux.utils.resource_manager import ResourceManager
|
|
|
|
rm = ResourceManager()
|
|
resources_dir = rm.resources_dir
|
|
|
|
# Should be a valid path string
|
|
assert isinstance(resources_dir, str), "resources_dir should be a string"
|
|
assert len(resources_dir) > 0, "resources_dir should not be empty"
|
|
|
|
|
|
class TestRecognitionManagerCrossDistro:
|
|
"""Test RecognitionManager cross-distro model path handling."""
|
|
|
|
def test_system_model_paths_exists(self):
|
|
"""Test that system model paths configuration exists."""
|
|
from vocalinux.speech_recognition.recognition_manager import SYSTEM_MODELS_DIRS
|
|
|
|
# Should be a list of paths
|
|
assert isinstance(SYSTEM_MODELS_DIRS, list), "SYSTEM_MODELS_DIRS should be a list"
|
|
assert len(SYSTEM_MODELS_DIRS) > 0, "SYSTEM_MODELS_DIRS should not be empty"
|
|
|
|
# Should include XDG paths
|
|
has_xdg = any("/share/" in path for path in SYSTEM_MODELS_DIRS)
|
|
assert has_xdg, "SYSTEM_MODELS_DIRS should include XDG paths"
|
|
|
|
|
|
class TestDocumentation:
|
|
"""Test that documentation is accurate."""
|
|
|
|
def test_distro_compatibility_documentation_exists(self):
|
|
"""Test that DISTRO_COMPATIBILITY.md exists and is accurate."""
|
|
docs_path = REPO_ROOT / "docs" / "DISTRO_COMPATIBILITY.md"
|
|
|
|
if not docs_path.exists():
|
|
pytest.skip(f"DISTRO_COMPATIBILITY.md not found at {docs_path}")
|
|
|
|
content = docs_path.read_text()
|
|
|
|
# Should mention the dynamic path detection
|
|
assert (
|
|
"pkg-config" in content or "detect_typelib_path" in content
|
|
), "Should document dynamic path detection"
|
|
|
|
# Should mention supported distros
|
|
expected_distros = ["Ubuntu", "Debian", "Fedora", "Arch", "openSUSE"]
|
|
for distro in expected_distros:
|
|
assert distro in content, f"Should mention {distro} support"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|