From 165ffccd01095a341208446ae993f3fe69909586 Mon Sep 17 00:00:00 2001 From: remsky Date: Thu, 6 Feb 2025 04:23:08 -0700 Subject: [PATCH] Remove voice manager tests and update Dockerfiles for improved dependency management and user permissions --- api/tests/test_voice_manager.py | 134 -------------------------------- docker/cpu/Dockerfile | 21 ++--- docker/gpu/Dockerfile | 26 ++++--- 3 files changed, 28 insertions(+), 153 deletions(-) delete mode 100644 api/tests/test_voice_manager.py diff --git a/api/tests/test_voice_manager.py b/api/tests/test_voice_manager.py deleted file mode 100644 index 01a479f..0000000 --- a/api/tests/test_voice_manager.py +++ /dev/null @@ -1,134 +0,0 @@ -import pytest -from unittest.mock import AsyncMock, patch, MagicMock -import torch -from pathlib import Path - -from ..src.inference.voice_manager import VoiceManager -from ..src.structures.model_schemas import VoiceConfig - - -@pytest.fixture -def mock_voice_tensor(): - return torch.randn(10, 10) # Dummy tensor - - -@pytest.fixture -def voice_manager(): - return VoiceManager(VoiceConfig()) - - -@pytest.mark.asyncio -async def test_load_voice(voice_manager, mock_voice_tensor): - """Test loading a single voice""" - with patch("api.src.core.paths.load_voice_tensor", new_callable=AsyncMock) as mock_load: - mock_load.return_value = mock_voice_tensor - with patch("os.path.exists", return_value=True): - voice = await voice_manager.load_voice("af_bella", "cpu") - assert torch.equal(voice, mock_voice_tensor) - - -@pytest.mark.asyncio -async def test_load_voice_not_found(voice_manager): - """Test loading non-existent voice""" - with patch("os.path.exists", return_value=False): - with pytest.raises(RuntimeError, match="Voice not found: invalid_voice"): - await voice_manager.load_voice("invalid_voice", "cpu") - - -@pytest.mark.skip(reason="Local saving is optional and not critical to functionality") -@pytest.mark.asyncio -async def test_combine_voices_with_saving(voice_manager, mock_voice_tensor): - """Test combining voices with local saving enabled""" - pass - - -@pytest.mark.asyncio -async def test_combine_voices_without_saving(voice_manager, mock_voice_tensor): - """Test combining voices without local saving""" - with patch("api.src.core.paths.load_voice_tensor", new_callable=AsyncMock) as mock_load, \ - patch("torch.save") as mock_save, \ - patch("os.makedirs"), \ - patch("os.path.exists", return_value=True): - - # Setup mocks - mock_load.return_value = mock_voice_tensor - - # Mock settings - with patch("api.src.core.config.settings") as mock_settings: - mock_settings.allow_local_voice_saving = False - mock_settings.voices_dir = "/mock/voices" - - # Combine voices - combined = await voice_manager.combine_voices(["af_bella", "af_sarah"], "cpu") - assert combined == "af_bella+af_sarah" # Note: using + separator - - # Verify voice was not saved - mock_save.assert_not_called() - - -@pytest.mark.asyncio -async def test_combine_voices_single_voice(voice_manager): - """Test combining with single voice""" - with pytest.raises(ValueError, match="At least 2 voices are required"): - await voice_manager.combine_voices(["af_bella"], "cpu") - - -@pytest.mark.asyncio -async def test_list_voices(voice_manager): - """Test listing available voices""" - with patch("os.listdir", return_value=["af_bella.pt", "af_sarah.pt", "af_bella+af_sarah.pt"]), \ - patch("os.makedirs"): - voices = await voice_manager.list_voices() - assert len(voices) == 3 - assert "af_bella" in voices - assert "af_sarah" in voices - assert "af_bella+af_sarah" in voices - - -@pytest.mark.asyncio -async def test_load_combined_voice(voice_manager, mock_voice_tensor): - """Test loading a combined voice""" - with patch("api.src.core.paths.load_voice_tensor", new_callable=AsyncMock) as mock_load: - mock_load.return_value = mock_voice_tensor - with patch("os.path.exists", return_value=True): - voice = await voice_manager.load_voice("af_bella+af_sarah", "cpu") - assert torch.equal(voice, mock_voice_tensor) - - -def test_cache_management(mock_voice_tensor): - """Test voice cache management""" - # Create voice manager with small cache size - config = VoiceConfig(cache_size=2) - voice_manager = VoiceManager(config) - - # Add items to cache - voice_manager._voice_cache = { - "voice1_cpu": torch.randn(5, 5), - "voice2_cpu": torch.randn(5, 5), - "voice3_cpu": torch.randn(5, 5), # Add one more than cache size - } - - # Try managing cache - voice_manager._manage_cache() - - # Check cache size maintained - assert len(voice_manager._voice_cache) <= 2 - - -@pytest.mark.asyncio -async def test_voice_loading_with_cache(voice_manager, mock_voice_tensor): - """Test voice loading with cache enabled""" - with patch("api.src.core.paths.load_voice_tensor", new_callable=AsyncMock) as mock_load, \ - patch("os.path.exists", return_value=True): - - mock_load.return_value = mock_voice_tensor - - # First load should hit disk - voice1 = await voice_manager.load_voice("af_bella", "cpu") - assert mock_load.call_count == 1 - - # Second load should hit cache - voice2 = await voice_manager.load_voice("af_bella", "cpu") - assert mock_load.call_count == 1 # Still 1 - - assert torch.equal(voice1, voice2) \ No newline at end of file diff --git a/docker/cpu/Dockerfile b/docker/cpu/Dockerfile index c43e7be..b97cd9c 100644 --- a/docker/cpu/Dockerfile +++ b/docker/cpu/Dockerfile @@ -1,12 +1,14 @@ -FROM --platform=$BUILDPLATFORM python:3.10-slim +FROM --platform=$BUILDPLATFORM python:3.10-slim -# Install dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ +# Install dependencies and check espeak location +RUN apt-get update && apt-get install -y \ espeak-ng \ git \ libsndfile1 \ curl \ ffmpeg \ + && dpkg -L espeak-ng \ + && find / -name "espeak-ng-data" \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* @@ -15,12 +17,12 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \ mv /root/.local/bin/uv /usr/local/bin/ && \ mv /root/.local/bin/uvx /usr/local/bin/ -# Create non-root user -RUN useradd -m -u 1000 appuser +# Create non-root user and set up directories and permissions +RUN useradd -m -u 1000 appuser && \ + mkdir -p /app/api/src/models/v1_0 && \ + chown -R appuser:appuser /app && \ + chown -R appuser:appuser /lib/x86_64-linux-gnu/espeak-ng-data -# Create directories and set ownership -RUN mkdir -p /app/api/src/models/v1_0 && \ - chown -R appuser:appuser /app USER appuser WORKDIR /app @@ -49,7 +51,6 @@ ENV PYTHONUNBUFFERED=1 \ UV_LINK_MODE=copy \ USE_GPU=false -# Core settings that differ from config.py defaults ENV DOWNLOAD_MODEL=true # Download model if enabled RUN if [ "$DOWNLOAD_MODEL" = "true" ]; then \ @@ -57,4 +58,4 @@ RUN if [ "$DOWNLOAD_MODEL" = "true" ]; then \ fi # Run FastAPI server -CMD ["uv", "run", "python", "-m", "uvicorn", "api.src.main:app", "--host", "0.0.0.0", "--port", "8880", "--log-level", "debug"] +CMD ["uv", "run", "python", "-m", "uvicorn", "api.src.main:app", "--host", "0.0.0.0", "--port", "8880", "--log-level", "debug"] \ No newline at end of file diff --git a/docker/gpu/Dockerfile b/docker/gpu/Dockerfile index 0a3bb20..7fcbb11 100644 --- a/docker/gpu/Dockerfile +++ b/docker/gpu/Dockerfile @@ -1,9 +1,10 @@ -FROM --platform=$BUILDPLATFORM nvidia/cuda:12.1.0-runtime-ubuntu22.04 +FROM --platform=$BUILDPLATFORM nvidia/cuda:12.1.0-cudnn8-runtime-ubuntu22.04 + # Set non-interactive frontend ENV DEBIAN_FRONTEND=noninteractive # Install Python and other dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN apt-get update && apt-get install -y \ python3.10 \ python3.10-venv \ espeak-ng \ @@ -11,19 +12,26 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libsndfile1 \ curl \ ffmpeg \ - && apt-get clean && rm -rf /var/lib/apt/lists/* + && ls -la /usr/lib/x86_64-linux-gnu/espeak-ng-data \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* -# Install UV using the installer script +# Create user and set up permissions +RUN useradd -m -u 1000 appuser && \ + mkdir -p /app/api/src/models/v1_0 && \ + chown -R appuser:appuser /app && \ + chown -R appuser:appuser /usr/lib/x86_64-linux-gnu/espeak-ng-data + + +# Rest of your Dockerfile... + +# Install UV in a separate step RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \ mv /root/.local/bin/uv /usr/local/bin/ && \ - mv /root/.local/bin/uvx /usr/local/bin/ && \ - useradd -m -u 1000 appuser && \ - mkdir -p /app/api/src/models/v1_0 && \ - chown -R appuser:appuser /app + mv /root/.local/bin/uvx /usr/local/bin/ USER appuser WORKDIR /app - # Copy dependency files COPY --chown=appuser:appuser pyproject.toml ./pyproject.toml