mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 14:43:01 +02:00
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)
493 lines
16 KiB
Python
493 lines
16 KiB
Python
"""Tests for python_pkg.music_gen._music_speech module."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from python_pkg.music_gen._music_speech import (
|
|
BARK_MAX_CHARS,
|
|
_generate_instrumental_for_song,
|
|
_generate_vocals_for_song,
|
|
_mix_audio,
|
|
_resample_audio,
|
|
_split_into_sentences,
|
|
generate_speech,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
class TestSplitIntoSentences:
|
|
"""Tests for _split_into_sentences()."""
|
|
|
|
def test_single_sentence(self) -> None:
|
|
result = _split_into_sentences("Hello world.")
|
|
assert result == ["Hello world."]
|
|
|
|
def test_multiple_sentences(self) -> None:
|
|
result = _split_into_sentences("First sentence. Second sentence. Third.")
|
|
assert len(result) >= 1
|
|
# All sentences should be present
|
|
combined = " ".join(result)
|
|
assert "First sentence." in combined
|
|
assert "Second sentence." in combined
|
|
|
|
def test_short_sentences_grouped(self) -> None:
|
|
result = _split_into_sentences("Hi. Ok. Yes.")
|
|
# Short sentences should be grouped together (< BARK_MAX_CHARS)
|
|
assert len(result) == 1
|
|
|
|
def test_long_text_splits(self) -> None:
|
|
# Create text that exceeds BARK_MAX_CHARS when combined
|
|
long_sentence = "A" * (BARK_MAX_CHARS - 10) + "."
|
|
text = f"{long_sentence} {long_sentence}"
|
|
result = _split_into_sentences(text)
|
|
assert len(result) >= 2
|
|
|
|
def test_empty_result_returns_original(self) -> None:
|
|
# A single word with no sentence boundaries
|
|
result = _split_into_sentences("hello")
|
|
assert result == ["hello"]
|
|
|
|
def test_whitespace_stripped(self) -> None:
|
|
result = _split_into_sentences(" Hello world. ")
|
|
assert result[0] == "Hello world."
|
|
|
|
def test_current_empty_in_else_branch(self) -> None:
|
|
# First sentence exceeds BARK_MAX_CHARS so current is empty when else hit
|
|
long_sent = "A" * (BARK_MAX_CHARS + 10) + "."
|
|
short_sent = "Short."
|
|
text = f"{long_sent} {short_sent}"
|
|
result = _split_into_sentences(text)
|
|
assert len(result) >= 2
|
|
|
|
def test_all_sentences_too_long(self) -> None:
|
|
# Each individual sentence is huge -- current is never empty at else
|
|
s1 = "A" * (BARK_MAX_CHARS + 10) + "."
|
|
s2 = "B" * (BARK_MAX_CHARS + 10) + "."
|
|
text = f"{s1} {s2}"
|
|
result = _split_into_sentences(text)
|
|
assert len(result) >= 2
|
|
|
|
def test_empty_string_input(self) -> None:
|
|
# Empty string → sentences=[''], current stays '' after loop
|
|
result = _split_into_sentences("")
|
|
assert result == [""]
|
|
|
|
|
|
class TestResampleAudio:
|
|
"""Tests for _resample_audio()."""
|
|
|
|
def test_same_rate_returns_unchanged(self) -> None:
|
|
audio = np.array([1.0, 2.0, 3.0], dtype=np.float32)
|
|
result = _resample_audio(audio, 44100, 44100)
|
|
np.testing.assert_array_equal(result, audio)
|
|
|
|
def test_resample_different_rate(self) -> None:
|
|
audio = np.ones(100, dtype=np.float32)
|
|
result = _resample_audio(audio, 44100, 22050)
|
|
# Should be shorter since target rate is lower
|
|
expected_length = int(len(audio) / 44100 * 22050)
|
|
assert len(result) == expected_length
|
|
assert result.dtype == np.float32
|
|
|
|
|
|
class TestMixAudio:
|
|
"""Tests for _mix_audio()."""
|
|
|
|
def test_vocals_shorter_than_instrumental(self) -> None:
|
|
instrumental = np.ones(100, dtype=np.float32)
|
|
vocals = np.ones(50, dtype=np.float32)
|
|
result = _mix_audio(instrumental, vocals)
|
|
assert len(result) == 100
|
|
|
|
def test_vocals_longer_than_instrumental(self) -> None:
|
|
instrumental = np.ones(50, dtype=np.float32)
|
|
vocals = np.ones(100, dtype=np.float32)
|
|
result = _mix_audio(instrumental, vocals)
|
|
assert len(result) == 50
|
|
|
|
def test_same_length(self) -> None:
|
|
instrumental = np.ones(100, dtype=np.float32)
|
|
vocals = np.ones(100, dtype=np.float32)
|
|
result = _mix_audio(instrumental, vocals)
|
|
assert len(result) == 100
|
|
|
|
def test_normalization_when_clipping(self) -> None:
|
|
instrumental = np.ones(10, dtype=np.float32) * 2.0
|
|
vocals = np.ones(10, dtype=np.float32) * 2.0
|
|
result = _mix_audio(
|
|
instrumental, vocals, vocal_volume=1.0, instrumental_volume=1.0
|
|
)
|
|
# Should be normalized so max <= 1.0
|
|
assert np.max(np.abs(result)) <= 1.0 + 1e-6
|
|
|
|
def test_no_normalization_needed(self) -> None:
|
|
instrumental = np.ones(10, dtype=np.float32) * 0.1
|
|
vocals = np.ones(10, dtype=np.float32) * 0.1
|
|
result = _mix_audio(
|
|
instrumental, vocals, vocal_volume=0.5, instrumental_volume=0.5
|
|
)
|
|
assert result.dtype == np.float32
|
|
|
|
def test_output_type(self) -> None:
|
|
instrumental = np.ones(10, dtype=np.float32) * 0.5
|
|
vocals = np.ones(10, dtype=np.float32) * 0.5
|
|
result = _mix_audio(instrumental, vocals)
|
|
assert result.dtype == np.float32
|
|
|
|
|
|
class TestGenerateSpeech:
|
|
"""Tests for generate_speech()."""
|
|
|
|
def test_single_sentence(self, tmp_path: Path) -> None:
|
|
mock_torch = MagicMock()
|
|
mock_bark = MagicMock()
|
|
mock_bark.SAMPLE_RATE = 24000
|
|
mock_bark.generate_audio.return_value = np.zeros(24000, dtype=np.float32)
|
|
|
|
np.zeros(24000, dtype=np.float32)
|
|
|
|
with (
|
|
patch.dict(
|
|
"sys.modules",
|
|
{
|
|
"torch": mock_torch,
|
|
"functools": __import__("functools"),
|
|
"numpy": np,
|
|
"scipy": MagicMock(),
|
|
"scipy.io": MagicMock(),
|
|
"scipy.io.wavfile": MagicMock(),
|
|
"bark": mock_bark,
|
|
},
|
|
),
|
|
patch(
|
|
"python_pkg.music_gen._music_speech._split_into_sentences",
|
|
return_value=["Hello world."],
|
|
),
|
|
patch("scipy.io.wavfile.write"),
|
|
):
|
|
result = generate_speech("Hello world.", output_dir=tmp_path)
|
|
|
|
assert result.parent == tmp_path
|
|
assert result.suffix == ".wav"
|
|
assert "speech" in result.name
|
|
|
|
def test_multiple_sentences(self, tmp_path: Path) -> None:
|
|
mock_torch = MagicMock()
|
|
mock_bark = MagicMock()
|
|
mock_bark.SAMPLE_RATE = 24000
|
|
mock_bark.generate_audio.return_value = np.zeros(24000, dtype=np.float32)
|
|
|
|
with (
|
|
patch.dict(
|
|
"sys.modules",
|
|
{
|
|
"torch": mock_torch,
|
|
"functools": __import__("functools"),
|
|
"numpy": np,
|
|
"scipy": MagicMock(),
|
|
"scipy.io": MagicMock(),
|
|
"scipy.io.wavfile": MagicMock(),
|
|
"bark": mock_bark,
|
|
},
|
|
),
|
|
patch(
|
|
"python_pkg.music_gen._music_speech._split_into_sentences",
|
|
return_value=["First sentence.", "Second sentence."],
|
|
),
|
|
patch("scipy.io.wavfile.write"),
|
|
):
|
|
result = generate_speech(
|
|
"First sentence. Second sentence.",
|
|
output_dir=tmp_path,
|
|
)
|
|
|
|
assert result.suffix == ".wav"
|
|
|
|
def test_default_output_dir(self) -> None:
|
|
mock_torch = MagicMock()
|
|
mock_bark = MagicMock()
|
|
mock_bark.SAMPLE_RATE = 24000
|
|
mock_bark.generate_audio.return_value = np.zeros(24000, dtype=np.float32)
|
|
|
|
with (
|
|
patch.dict(
|
|
"sys.modules",
|
|
{
|
|
"torch": mock_torch,
|
|
"functools": __import__("functools"),
|
|
"numpy": np,
|
|
"scipy": MagicMock(),
|
|
"scipy.io": MagicMock(),
|
|
"scipy.io.wavfile": MagicMock(),
|
|
"bark": mock_bark,
|
|
},
|
|
),
|
|
patch(
|
|
"python_pkg.music_gen._music_speech._split_into_sentences",
|
|
return_value=["Hello."],
|
|
),
|
|
patch("scipy.io.wavfile.write"),
|
|
patch("pathlib.Path.mkdir"),
|
|
):
|
|
result = generate_speech("Hello.")
|
|
|
|
assert "output" in str(result.parent)
|
|
|
|
def test_patched_load_called(self, tmp_path: Path) -> None:
|
|
"""Ensure the patched_load inner function is actually invoked."""
|
|
import sys
|
|
|
|
mock_torch = MagicMock()
|
|
original_load = MagicMock(return_value="loaded")
|
|
mock_torch.load = original_load
|
|
|
|
mock_bark = MagicMock()
|
|
mock_bark.SAMPLE_RATE = 24000
|
|
mock_bark.generate_audio.return_value = np.zeros(24000, dtype=np.float32)
|
|
|
|
# Make preload_models call torch.load so patched_load runs
|
|
def call_torch_load() -> None:
|
|
sys.modules["torch"].load("model.pt")
|
|
|
|
mock_bark.preload_models.side_effect = call_torch_load
|
|
|
|
with (
|
|
patch.dict(
|
|
"sys.modules",
|
|
{
|
|
"torch": mock_torch,
|
|
"functools": __import__("functools"),
|
|
"numpy": np,
|
|
"scipy": MagicMock(),
|
|
"scipy.io": MagicMock(),
|
|
"scipy.io.wavfile": MagicMock(),
|
|
"bark": mock_bark,
|
|
},
|
|
),
|
|
patch(
|
|
"python_pkg.music_gen._music_speech._split_into_sentences",
|
|
return_value=["Hello."],
|
|
),
|
|
patch("scipy.io.wavfile.write"),
|
|
):
|
|
generate_speech("Hello.", output_dir=tmp_path)
|
|
|
|
# The original_load should have been called via patched_load
|
|
original_load.assert_called_once_with("model.pt", weights_only=False)
|
|
|
|
def test_torch_load_restored_after_exception(self) -> None:
|
|
mock_torch = MagicMock()
|
|
original_load = mock_torch.load
|
|
|
|
mock_bark = MagicMock()
|
|
mock_bark.preload_models.side_effect = RuntimeError("test error")
|
|
|
|
with (
|
|
patch.dict(
|
|
"sys.modules",
|
|
{
|
|
"torch": mock_torch,
|
|
"functools": __import__("functools"),
|
|
"numpy": np,
|
|
"scipy": MagicMock(),
|
|
"scipy.io": MagicMock(),
|
|
"scipy.io.wavfile": MagicMock(),
|
|
"bark": mock_bark,
|
|
},
|
|
),
|
|
pytest.raises(RuntimeError, match="test error"),
|
|
):
|
|
generate_speech("Hello.")
|
|
|
|
# torch.load should be restored
|
|
assert mock_torch.load == original_load
|
|
|
|
|
|
class TestGenerateVocalsForSong:
|
|
"""Tests for _generate_vocals_for_song()."""
|
|
|
|
def test_single_sentence(self) -> None:
|
|
mock_torch = MagicMock()
|
|
mock_bark = MagicMock()
|
|
mock_bark.SAMPLE_RATE = 24000
|
|
audio_array = np.zeros(24000, dtype=np.float32)
|
|
mock_bark.generate_audio.return_value = audio_array
|
|
|
|
with (
|
|
patch.dict(
|
|
"sys.modules",
|
|
{
|
|
"torch": mock_torch,
|
|
"functools": __import__("functools"),
|
|
"numpy": np,
|
|
"bark": mock_bark,
|
|
},
|
|
),
|
|
patch(
|
|
"python_pkg.music_gen._music_speech._split_into_sentences",
|
|
return_value=["Hello."],
|
|
),
|
|
):
|
|
vocals, sr = _generate_vocals_for_song("Hello.", "v2/en_speaker_6")
|
|
|
|
assert sr == 24000
|
|
np.testing.assert_array_equal(vocals, audio_array)
|
|
|
|
def test_multiple_sentences(self) -> None:
|
|
mock_torch = MagicMock()
|
|
mock_bark = MagicMock()
|
|
mock_bark.SAMPLE_RATE = 24000
|
|
audio_array = np.ones(12000, dtype=np.float32)
|
|
mock_bark.generate_audio.return_value = audio_array
|
|
|
|
with (
|
|
patch.dict(
|
|
"sys.modules",
|
|
{
|
|
"torch": mock_torch,
|
|
"functools": __import__("functools"),
|
|
"numpy": np,
|
|
"bark": mock_bark,
|
|
},
|
|
),
|
|
patch(
|
|
"python_pkg.music_gen._music_speech._split_into_sentences",
|
|
return_value=["First.", "Second."],
|
|
),
|
|
):
|
|
vocals, sr = _generate_vocals_for_song(
|
|
"First. Second.",
|
|
"v2/en_speaker_6",
|
|
)
|
|
|
|
assert sr == 24000
|
|
assert len(vocals) == 24000 # Two 12000-sample arrays concatenated
|
|
|
|
def test_torch_load_restored(self) -> None:
|
|
mock_torch = MagicMock()
|
|
original_load = mock_torch.load
|
|
|
|
mock_bark = MagicMock()
|
|
mock_bark.preload_models.side_effect = RuntimeError("fail")
|
|
|
|
with (
|
|
patch.dict(
|
|
"sys.modules",
|
|
{
|
|
"torch": mock_torch,
|
|
"functools": __import__("functools"),
|
|
"numpy": np,
|
|
"bark": mock_bark,
|
|
},
|
|
),
|
|
pytest.raises(RuntimeError, match="fail"),
|
|
):
|
|
_generate_vocals_for_song("Hello.", "v2/en_speaker_6")
|
|
|
|
assert mock_torch.load == original_load
|
|
|
|
def test_patched_load_is_invoked(self) -> None:
|
|
"""Ensure patched_load inner function runs in _generate_vocals_for_song."""
|
|
import sys
|
|
|
|
mock_torch = MagicMock()
|
|
original_load = MagicMock(return_value="loaded_model")
|
|
mock_torch.load = original_load
|
|
|
|
mock_bark = MagicMock()
|
|
mock_bark.SAMPLE_RATE = 24000
|
|
audio_array = np.zeros(24000, dtype=np.float32)
|
|
mock_bark.generate_audio.return_value = audio_array
|
|
|
|
def call_torch_load() -> None:
|
|
sys.modules["torch"].load("weights.pt")
|
|
|
|
mock_bark.preload_models.side_effect = call_torch_load
|
|
|
|
with (
|
|
patch.dict(
|
|
"sys.modules",
|
|
{
|
|
"torch": mock_torch,
|
|
"functools": __import__("functools"),
|
|
"numpy": np,
|
|
"bark": mock_bark,
|
|
},
|
|
),
|
|
patch(
|
|
"python_pkg.music_gen._music_speech._split_into_sentences",
|
|
return_value=["Hello."],
|
|
),
|
|
):
|
|
_, sr = _generate_vocals_for_song("Hello.", "v2/en_speaker_6")
|
|
|
|
assert sr == 24000
|
|
# The original_load should have been called via patched_load
|
|
original_load.assert_called_once_with("weights.pt", weights_only=False)
|
|
|
|
|
|
class TestGenerateInstrumentalForSong:
|
|
"""Tests for _generate_instrumental_for_song()."""
|
|
|
|
def test_short_duration(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
|
|
|
|
audio = np.zeros(100 * 10, dtype=np.float32)
|
|
|
|
with (
|
|
patch(
|
|
"python_pkg.music_gen._music_speech.select_model_size",
|
|
return_value="small",
|
|
),
|
|
patch(
|
|
"python_pkg.music_gen._music_speech.load_model",
|
|
return_value=(mock_model, MagicMock()),
|
|
),
|
|
patch(
|
|
"python_pkg.music_gen._music_speech.generate_segment",
|
|
return_value=audio,
|
|
),
|
|
):
|
|
instrumental, sr = _generate_instrumental_for_song("test", 10)
|
|
|
|
assert sr == 100
|
|
np.testing.assert_array_equal(instrumental, audio)
|
|
|
|
def test_long_duration(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
|
|
|
|
audio = np.zeros(100 * 60, dtype=np.float32)
|
|
|
|
with (
|
|
patch(
|
|
"python_pkg.music_gen._music_speech.select_model_size",
|
|
return_value="small",
|
|
),
|
|
patch(
|
|
"python_pkg.music_gen._music_speech.load_model",
|
|
return_value=(mock_model, MagicMock()),
|
|
),
|
|
patch(
|
|
"python_pkg.music_gen._music_speech._generate_long_audio",
|
|
return_value=audio,
|
|
),
|
|
):
|
|
_, sr = _generate_instrumental_for_song("test", 60)
|
|
|
|
assert sr == 100
|