testsAndMisc-archive/python_pkg/music_gen/tests/test_music_generation.py
Krzysztof kuhy Rudnicki e5fd82c822 Reduce per-file-ignores by fixing lint violations across codebase
Fix ruff violations in ~15 source files and ~60+ test files to minimize
per-file-ignores in pyproject.toml. Remaining ignores are justified with
comments explaining why each suppression is necessary.

Source fixes: FBT003 (keyword args), S310 (URL validation), SLF001
(private access), T201 (print→logging), C901 (complexity), E501 (line
length), E402 (import order).

Test fixes: SIM117 (combined with), FBT (boolean args), PERF203 (try in
loop), S310/S607 (URLs/executables), E402/E501 (imports/lines), S108
(tmp paths), PLR0913 (too many args), ARG (unused args), ANN (type
annotations), RUF059 (unused unpacked vars), PT019 (fixture naming).

Remaining per-file-ignores (with justifications):
- Tests: ARG, D, PLC0415, PLR2004, S101, SLF001
- music_gen sources: PLC0415 (heavy ML lazy imports)
- moviepy_showcase: PLC0415 (circular dependency)
- generate_images: PLR0913 (matplotlib helpers need many params)
- praca_magisterska_video: E501, E402 (long paths, mpl.use)
2026-03-25 18:58:05 +01:00

395 lines
13 KiB
Python

"""Tests for python_pkg.music_gen._music_generation module."""
from __future__ import annotations
from unittest.mock import MagicMock, patch
import numpy as np
import pytest
from python_pkg.music_gen._music_generation import (
SEGMENT_DURATION,
VRAM_THRESHOLD_LARGE,
VRAM_THRESHOLD_MEDIUM,
_calculate_segment_duration,
_generate_long_audio,
crossfade_audio,
generate_segment,
get_device,
get_vram_gb,
load_model,
select_model_size,
)
class TestGetDevice:
"""Tests for get_device()."""
def test_nvidia_gpu_with_cuda(self) -> None:
mock_torch = MagicMock()
mock_torch.cuda.is_available.return_value = True
mock_torch.cuda.get_device_name.return_value = "RTX 3080"
props = MagicMock()
props.total_memory = 12 * 1024**3
mock_torch.cuda.get_device_properties.return_value = props
mock_torch.backends.mps.is_available.return_value = False
mock_result = MagicMock()
mock_result.returncode = 0
with (
patch.dict("sys.modules", {"torch": mock_torch}),
patch("shutil.which", return_value="/usr/bin/nvidia-smi"),
patch("subprocess.run", return_value=mock_result),
):
result = get_device()
assert result == "cuda"
def test_nvidia_gpu_without_cuda_raises(self) -> None:
mock_torch = MagicMock()
mock_torch.cuda.is_available.return_value = False
mock_result = MagicMock()
mock_result.returncode = 0
with (
patch.dict("sys.modules", {"torch": mock_torch}),
patch("shutil.which", return_value="/usr/bin/nvidia-smi"),
patch("subprocess.run", return_value=mock_result),
pytest.raises(RuntimeError, match="NVIDIA GPU detected"),
):
get_device()
def test_nvidia_smi_not_found(self) -> None:
mock_torch = MagicMock()
mock_torch.cuda.is_available.return_value = False
mock_torch.backends.mps.is_available.return_value = False
# hasattr check: torch.backends has 'mps' attr
mock_backends = MagicMock()
mock_backends.mps.is_available.return_value = False
mock_torch.backends = mock_backends
with (
patch.dict("sys.modules", {"torch": mock_torch}),
patch("shutil.which", return_value=None),
):
result = get_device()
assert result == "cpu"
def test_nvidia_smi_returns_nonzero(self) -> None:
mock_torch = MagicMock()
mock_torch.cuda.is_available.return_value = False
mock_backends = MagicMock()
mock_backends.mps.is_available.return_value = False
mock_torch.backends = mock_backends
mock_result = MagicMock()
mock_result.returncode = 1
with (
patch.dict("sys.modules", {"torch": mock_torch}),
patch("shutil.which", return_value="/usr/bin/nvidia-smi"),
patch("subprocess.run", return_value=mock_result),
):
result = get_device()
assert result == "cpu"
def test_mps_device(self) -> None:
mock_torch = MagicMock()
mock_torch.cuda.is_available.return_value = False
mock_backends = MagicMock()
mock_backends.mps.is_available.return_value = True
mock_torch.backends = mock_backends
with (
patch.dict("sys.modules", {"torch": mock_torch}),
patch("shutil.which", return_value=None),
):
result = get_device()
assert result == "mps"
def test_file_not_found_error(self) -> None:
mock_torch = MagicMock()
mock_torch.cuda.is_available.return_value = False
mock_backends = MagicMock()
mock_backends.mps.is_available.return_value = False
mock_torch.backends = mock_backends
with (
patch.dict("sys.modules", {"torch": mock_torch}),
patch("shutil.which", side_effect=FileNotFoundError),
):
result = get_device()
assert result == "cpu"
class TestGetVramGb:
"""Tests for get_vram_gb()."""
def test_cuda_available(self) -> None:
mock_torch = MagicMock()
mock_torch.cuda.is_available.return_value = True
props = MagicMock()
props.total_memory = 8 * 1024**3
mock_torch.cuda.get_device_properties.return_value = props
with patch.dict("sys.modules", {"torch": mock_torch}):
result = get_vram_gb()
assert result == pytest.approx(8.0)
def test_no_cuda(self) -> None:
mock_torch = MagicMock()
mock_torch.cuda.is_available.return_value = False
with patch.dict("sys.modules", {"torch": mock_torch}):
result = get_vram_gb()
assert result is None
class TestSelectModelSize:
"""Tests for select_model_size()."""
def test_user_choice_provided(self) -> None:
assert select_model_size("small") == "small"
def test_no_gpu_returns_medium(self) -> None:
with patch(
"python_pkg.music_gen._music_generation.get_vram_gb",
return_value=None,
):
assert select_model_size() == "medium"
def test_large_vram(self) -> None:
with patch(
"python_pkg.music_gen._music_generation.get_vram_gb",
return_value=VRAM_THRESHOLD_LARGE,
):
assert select_model_size() == "large"
def test_medium_vram(self) -> None:
with patch(
"python_pkg.music_gen._music_generation.get_vram_gb",
return_value=VRAM_THRESHOLD_MEDIUM,
):
assert select_model_size() == "medium"
def test_small_vram(self) -> None:
with patch(
"python_pkg.music_gen._music_generation.get_vram_gb",
return_value=4.0,
):
assert select_model_size() == "small"
class TestLoadModel:
"""Tests for load_model()."""
def test_load_model(self) -> None:
mock_processor = MagicMock()
mock_model = MagicMock()
mock_model.to.return_value = mock_model
mock_auto_processor = MagicMock()
mock_auto_processor.from_pretrained.return_value = mock_processor
mock_musicgen = MagicMock()
mock_musicgen.from_pretrained.return_value = mock_model
with (
patch(
"python_pkg.music_gen._music_generation.get_device",
return_value="cpu",
),
patch.dict(
"sys.modules",
{"transformers": MagicMock()},
),
patch(
"python_pkg.music_gen._music_generation.AutoProcessor",
mock_auto_processor,
create=True,
),
patch(
"python_pkg.music_gen._music_generation.MusicgenForConditionalGeneration",
mock_musicgen,
create=True,
),
):
# We need to mock the imports inside load_model
pass
# Alternative approach - mock at the transformers import level
mock_transformers = MagicMock()
mock_transformers.AutoProcessor.from_pretrained.return_value = mock_processor
mock_from_pretrained = (
mock_transformers.MusicgenForConditionalGeneration.from_pretrained
)
mock_from_pretrained.return_value = mock_model
with (
patch(
"python_pkg.music_gen._music_generation.get_device",
return_value="cpu",
),
patch.dict("sys.modules", {"transformers": mock_transformers}),
):
model, processor = load_model("small")
assert model == mock_model
assert processor == mock_processor
mock_model.to.assert_called_once_with("cpu")
class TestCrossfadeAudio:
"""Tests for crossfade_audio()."""
def test_zero_crossfade_samples(self) -> None:
a1 = np.array([1.0, 2.0, 3.0])
a2 = np.array([4.0, 5.0, 6.0])
result = crossfade_audio(a1, a2, 0)
np.testing.assert_array_equal(result, np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]))
def test_negative_crossfade_samples(self) -> None:
a1 = np.array([1.0, 2.0])
a2 = np.array([3.0, 4.0])
result = crossfade_audio(a1, a2, -1)
np.testing.assert_array_equal(result, np.array([1.0, 2.0, 3.0, 4.0]))
def test_crossfade_larger_than_audio1(self) -> None:
a1 = np.array([1.0, 2.0])
a2 = np.array([3.0, 4.0, 5.0])
result = crossfade_audio(a1, a2, 5)
np.testing.assert_array_equal(result, np.array([1.0, 2.0, 3.0, 4.0, 5.0]))
def test_normal_crossfade(self) -> None:
a1 = np.array([1.0, 1.0, 1.0, 1.0], dtype=np.float64)
a2 = np.array([2.0, 2.0, 2.0, 2.0], dtype=np.float64)
result = crossfade_audio(a1, a2, 2)
assert len(result) == 6
# First 2 samples from a1 (non-crossfaded)
assert result[0] == 1.0
assert result[1] == 1.0
# Last 2 samples from a2 (non-crossfaded)
assert result[4] == 2.0
assert result[5] == 2.0
class TestGenerateSegment:
"""Tests for generate_segment()."""
def test_generate_segment(self) -> None:
mock_torch = MagicMock()
mock_torch.no_grad.return_value.__enter__ = MagicMock()
mock_torch.no_grad.return_value.__exit__ = MagicMock()
mock_processor = MagicMock()
mock_processor.return_value = {"input_ids": MagicMock()}
mock_model = MagicMock()
audio_tensor = MagicMock()
audio_tensor.cpu.return_value.numpy.return_value = np.array([0.1, 0.2])
# audio_values[0, 0] needs to work with tuple indexing
audio_values = MagicMock()
audio_values.__getitem__ = MagicMock(return_value=audio_tensor)
mock_model.generate.return_value = audio_values
with patch.dict("sys.modules", {"torch": mock_torch}):
result = generate_segment("test", mock_model, mock_processor, 10, "cpu")
np.testing.assert_array_equal(result, np.array([0.1, 0.2]))
class TestCalculateSegmentDuration:
"""Tests for _calculate_segment_duration()."""
def test_non_last_segment(self) -> None:
result = _calculate_segment_duration(0, 3, 0, 32000, 60)
assert result == SEGMENT_DURATION
def test_last_segment_remaining_large(self) -> None:
# Last segment with a lot of remaining time
result = _calculate_segment_duration(2, 3, 32000 * 40, 32000, 60)
# remaining = 60 - 40 = 20
# min_duration = max(5, 20 + 2) = 22
# min(25, 22) = 22
assert result == 22
def test_last_segment_remaining_small(self) -> None:
# Last segment with very little remaining
result = _calculate_segment_duration(2, 3, 32000 * 58, 32000, 60)
# remaining = 60 - 58 = 2
# min_duration = max(5, 2 + 2) = 5
# min(25, 5) = 5
assert result == 5
class TestGenerateLongAudio:
"""Tests for _generate_long_audio()."""
def test_generate_long_audio(self) -> None:
mock_model = MagicMock()
mock_param = MagicMock()
mock_param.device = "cpu"
mock_model.parameters.return_value = iter([mock_param])
mock_model.config.audio_encoder.sampling_rate = 100
mock_processor = MagicMock()
segment = np.ones(100 * SEGMENT_DURATION, dtype=np.float32)
with patch(
"python_pkg.music_gen._music_generation.generate_segment",
return_value=segment,
):
result = _generate_long_audio("test", mock_model, mock_processor, 60)
assert isinstance(result, np.ndarray)
def test_generate_long_audio_no_trim(self) -> None:
mock_model = MagicMock()
mock_param = MagicMock()
mock_param.device = "cpu"
mock_model.parameters.return_value = iter([mock_param])
mock_model.config.audio_encoder.sampling_rate = 10
mock_processor = MagicMock()
# Return a small segment so total < target, no trimming occurs
segment = np.ones(10 * 5, dtype=np.float32)
with patch(
"python_pkg.music_gen._music_generation.generate_segment",
return_value=segment,
):
result = _generate_long_audio("test", mock_model, mock_processor, 200)
# Result should not exceed 200 * 10 = 2000 samples
assert isinstance(result, np.ndarray)
def test_generate_long_audio_trims(self) -> None:
mock_model = MagicMock()
mock_param = MagicMock()
mock_param.device = "cpu"
mock_model.parameters.return_value = iter([mock_param])
mock_model.config.audio_encoder.sampling_rate = 10
mock_processor = MagicMock()
# Return large segment each time so result exceeds target
segment = np.ones(10 * SEGMENT_DURATION, dtype=np.float32)
with patch(
"python_pkg.music_gen._music_generation.generate_segment",
return_value=segment,
):
result = _generate_long_audio("test", mock_model, mock_processor, 30)
# Should be trimmed to exactly 30 * 10 = 300 samples
assert len(result) == 300