[Misc] Cleanup: use consistent pytest-mock in unit tests (#2698)

Signed-off-by: yuanheng <jonathan.zhaoyh@gmail.com>
This commit is contained in:
Yuanheng Zhao authored and GitHub committed 2026-04-13 19:21:42 +08:00
1 parent 2c67c30550
commit e0cdbe9a5d
26 files changed
+1598 -1369

No files matched your search

@@ -2,10 +2,10 @@
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from types import SimpleNamespace
from unittest.mock import Mock
import pytest
import torch
from pytest_mock import MockerFixture
from vllm_omni.model_executor.models.mimo_audio.config_mimo_audio import TALKER_CODEC_PAD_TOKEN_ID
from vllm_omni.model_executor.models.mimo_audio.mimo_audio_code2wav import (
@@ -51,7 +51,7 @@ def _make_invalid_flat_immediate_eostm(eostm_id: int = 666) -> torch.Tensor:
return g.reshape(-1)
def _minimal_model():
def _minimal_model(mocker: MockerFixture):
"""Avoid __init__ (HF tokenizer paths); only fields used by _batch_decode_waveforms."""
model = object.__new__(MiMoAudioToken2WavForConditionalGenerationVLLM)
model.device = torch.device("cpu")
@@ -59,7 +59,7 @@ def _minimal_model():
model.streamer_config = AudioStreamerConfig(group_size=_GROUP, audio_channels=_AC)
model.codes = _codes_ns()
decode_vq = Mock(
decode_vq = mocker.Mock(
side_effect=lambda audio_codes: torch.ones(
audio_codes.shape[1],
7,
@@ -67,7 +67,7 @@ def _minimal_model():
device=audio_codes.device,
)
)
decoder = Mock()
decoder = mocker.Mock()
audio_tok = SimpleNamespace(
encoder=SimpleNamespace(decode_vq=decode_vq),
@@ -78,9 +78,9 @@ def _minimal_model():
return model, audio_tok
def test_batch_decode_waveforms_empty_input_list():
def test_batch_decode_waveforms_empty_input_list(mocker: MockerFixture):
"""Empty input list returns a single zero-length float32 tensor on model device."""
model, _ = _minimal_model()
model, _ = _minimal_model(mocker)
out = MiMoAudioToken2WavForConditionalGenerationVLLM._batch_decode_waveforms(model, [])
assert len(out) == 1
assert out[0].dtype == torch.float32
@@ -88,9 +88,9 @@ def test_batch_decode_waveforms_empty_input_list():
assert out[0].device == model.device
def test_batch_decode_waveforms_single_vs_multiple_decoder_shapes():
def test_batch_decode_waveforms_single_vs_multiple_decoder_shapes(mocker: MockerFixture):
"""Single and multi-request batches produce correctly shaped packed hidden states and trimmed waveforms."""
model, audio_tok = _minimal_model()
model, audio_tok = _minimal_model(mocker)
decoder = audio_tok.decoder
# Single valid request: decoder output rank-3 for double squeeze path
@@ -118,9 +118,9 @@ def test_batch_decode_waveforms_single_vs_multiple_decoder_shapes():
assert out2[1].shape == (8 * _FTP,)
def test_batch_decode_waveforms_mixed_valid_invalid_requests():
def test_batch_decode_waveforms_mixed_valid_invalid_requests(mocker: MockerFixture):
"""Mixed valid and invalid requests: invalid slots get empty tensors, valid slots get decoded waveforms."""
model, audio_tok = _minimal_model()
model, audio_tok = _minimal_model(mocker)
valid_a = _make_valid_flat_codes(1)
valid_b = _make_valid_flat_codes(1)
dummy = _make_dummy_code_tensor()
@@ -151,9 +151,9 @@ def test_batch_decode_waveforms_mixed_valid_invalid_requests():
assert input_lengths.tolist() == [4, 4]
def test_batch_decode_waveforms_all_invalid_returns_per_request_empty():
def test_batch_decode_waveforms_all_invalid_returns_per_request_empty(mocker: MockerFixture):
"""All-invalid batch skips decoder entirely and returns empty tensors for every slot."""
model, audio_tok = _minimal_model()
model, audio_tok = _minimal_model(mocker)
out = MiMoAudioToken2WavForConditionalGenerationVLLM._batch_decode_waveforms(
model,
[None, _make_dummy_code_tensor(), torch.tensor([], dtype=torch.long)],
@@ -163,9 +163,9 @@ def test_batch_decode_waveforms_all_invalid_returns_per_request_empty():
audio_tok.decoder.assert_not_called()
def test_batch_decode_waveforms_output_shape_trim_when_decoder_returns_extra_samples():
def test_batch_decode_waveforms_output_shape_trim_when_decoder_returns_extra_samples(mocker: MockerFixture):
"""Decoder output longer than valid_len is trimmed to the exact expected waveform length."""
model, audio_tok = _minimal_model()
model, audio_tok = _minimal_model(mocker)
flat = _make_valid_flat_codes(1)
# Longer than valid_len so branch wav = wav[:valid_len] runs
audio_tok.decoder.return_value = torch.ones(1, 1, 10_000, dtype=torch.float32)
@@ -175,9 +175,9 @@ def test_batch_decode_waveforms_output_shape_trim_when_decoder_returns_extra_sam
assert out[0].dtype == torch.float32
def test_batch_decode_waveforms_multi_request_trims_each_row_when_decoder_returns_extra():
def test_batch_decode_waveforms_multi_request_trims_each_row_when_decoder_returns_extra(mocker: MockerFixture):
"""Else-branch split: per-request wav[:valid_len] when decoder pads each batch row."""
model, audio_tok = _minimal_model()
model, audio_tok = _minimal_model(mocker)
a = _make_valid_flat_codes(1)
b = _make_valid_flat_codes(2)
audio_tok.decoder.return_value = torch.ones(2, 1, 10_000, dtype=torch.float32)
@@ -189,9 +189,9 @@ def test_batch_decode_waveforms_multi_request_trims_each_row_when_decoder_return
assert out[1].dtype == torch.float32
def test_batch_decode_waveforms_valid_only_at_edges_maps_to_correct_indices():
def test_batch_decode_waveforms_valid_only_at_edges_maps_to_correct_indices(mocker: MockerFixture):
"""Tensor packing order must match valid_indices when invalid requests are in the middle."""
model, audio_tok = _minimal_model()
model, audio_tok = _minimal_model(mocker)
first = _make_valid_flat_codes(1)
last = _make_valid_flat_codes(2)
inputs = [
@@ -212,9 +212,9 @@ def test_batch_decode_waveforms_valid_only_at_edges_maps_to_correct_indices():
assert input_lengths.tolist() == [4, 8]
def test_batch_decode_waveforms_output_shapes_1d_float32_for_all_slots():
def test_batch_decode_waveforms_output_shapes_1d_float32_for_all_slots(mocker: MockerFixture):
"""Every slot is a 1-D float32 vector (empty or waveform), matching downstream expectations."""
model, audio_tok = _minimal_model()
model, audio_tok = _minimal_model(mocker)
inputs = [_make_valid_flat_codes(1), None, _make_valid_flat_codes(1)]
audio_tok.decoder.return_value = torch.ones(2, 1, 5000, dtype=torch.float32)
out = MiMoAudioToken2WavForConditionalGenerationVLLM._batch_decode_waveforms(model, inputs)
@@ -10,10 +10,9 @@ Regression test for: https://github.com/vllm-project/vllm/issues/34506
- Interleaved (use_audio_in_video) should also work correctly.
"""
from unittest.mock import Mock
import pytest
import torch
from pytest_mock import MockerFixture
from vllm.model_executor.models.qwen2_5_omni_thinker import (
check_interleaved_audio_video,
merge_interleaved_embeddings,
@@ -107,7 +106,7 @@ class TestCheckInterleavedAudioVideo:
# ---------------------------------------------------------------------------
def make_mock_model(hidden: int = 8):
def make_mock_model(mocker: MockerFixture, hidden: int = 8):
"""
Return a minimal mock of Qwen2_5OmniThinkerForConditionalGeneration
that has enough structure to run embed_input_ids.
@@ -116,10 +115,10 @@ def make_mock_model(hidden: int = 8):
Qwen2_5OmniThinkerForConditionalGeneration,
)
model = Mock(spec=Qwen2_5OmniThinkerForConditionalGeneration)
model = mocker.Mock(spec=Qwen2_5OmniThinkerForConditionalGeneration)
# Config with token IDs
cfg = Mock()
cfg = mocker.Mock()
cfg.video_token_index = VIDEO_TOKEN_ID
cfg.audio_token_index = AUDIO_TOKEN_ID
model.config = cfg
@@ -130,9 +129,9 @@ def make_mock_model(hidden: int = 8):
# view with shared memory, which masked_scatter_ cannot handle).
return ids.float().unsqueeze(-1).expand(-1, hidden).clone()
lang_model = Mock()
lang_model = mocker.Mock()
lang_model.embed_input_ids = fake_lm_embed
model.get_language_model = Mock(return_value=lang_model)
model.get_language_model = mocker.Mock(return_value=lang_model)
from vllm.model_executor.models.interfaces import SupportsMultiModal
@@ -169,7 +168,7 @@ def build_mm_embeds(audio_n, image_n, video_n, hidden, audio_val=10.0, image_val
class TestEmbedInputIds:
def _run(self, audio_n, image_n, video_n, hidden=8):
def _run(self, mocker: MockerFixture, audio_n, image_n, video_n, hidden=8):
"""
Run embed_input_ids for a non-interleaved mixed-modality sequence.
Returns (result_embeds, input_ids, is_multimodal).
@@ -177,33 +176,33 @@ class TestEmbedInputIds:
input_ids, is_multimodal = make_token_seq(audio_n, image_n, video_n)
mm_embeds = build_mm_embeds(audio_n, image_n, video_n, hidden)
model, _ = make_mock_model(hidden)
model, _ = make_mock_model(mocker, hidden)
result = model.embed_input_ids(input_ids, mm_embeds, is_multimodal=is_multimodal)
return result, input_ids, is_multimodal
def test_audio_only(self):
def test_audio_only(self, mocker: MockerFixture):
"""Audio-only: audio positions get audio embeddings."""
audio_n, hidden = 5, 8
audio_val = 10.0
result, input_ids, is_multimodal = self._run(audio_n, 0, 0, hidden)
result, input_ids, is_multimodal = self._run(mocker, audio_n, 0, 0, hidden)
audio_pos = (input_ids == AUDIO_TOKEN_ID).nonzero(as_tuple=True)[0]
assert result[audio_pos].allclose(torch.full((audio_n, hidden), audio_val)), (
"Audio positions should get audio embeddings"
)
def test_video_only(self):
def test_video_only(self, mocker: MockerFixture):
"""Video-only: video positions get video embeddings."""
video_n, hidden = 6, 8
video_val = 30.0
result, input_ids, is_multimodal = self._run(0, 0, video_n, hidden)
result, input_ids, is_multimodal = self._run(mocker, 0, 0, video_n, hidden)
video_pos = (input_ids == VIDEO_TOKEN_ID).nonzero(as_tuple=True)[0]
assert result[video_pos].allclose(torch.full((video_n, hidden), video_val)), (
"Video positions should get video embeddings"
)
def test_mixed_modalities_audio_goes_to_audio_pos(self):
def test_mixed_modalities_audio_goes_to_audio_pos(self, mocker: MockerFixture):
"""
Regression test for GitHub issue #34506:
With audio + image + video (non-interleaved), audio positions must
@@ -212,7 +211,7 @@ class TestEmbedInputIds:
audio_n, image_n, video_n, hidden = 5, 4, 6, 8
audio_val, image_val, video_val = 10.0, 20.0, 30.0
result, input_ids, is_multimodal = self._run(audio_n, image_n, video_n, hidden)
result, input_ids, is_multimodal = self._run(mocker, audio_n, image_n, video_n, hidden)
audio_pos = (input_ids == AUDIO_TOKEN_ID).nonzero(as_tuple=True)[0]
image_pos = (input_ids == IMAGE_TOKEN_ID).nonzero(as_tuple=True)[0]
@@ -233,10 +232,10 @@ class TestEmbedInputIds:
f"Video emb wrong: expected {video_val}, got mean={mean_v:.1f}"
)
def test_text_positions_unchanged(self):
def test_text_positions_unchanged(self, mocker: MockerFixture):
"""Text positions should keep their text embeddings."""
audio_n, image_n, video_n, hidden = 3, 2, 4, 8
result, input_ids, is_multimodal = self._run(audio_n, image_n, video_n, hidden)
result, input_ids, is_multimodal = self._run(mocker, audio_n, image_n, video_n, hidden)
text_pos = (~is_multimodal).nonzero(as_tuple=True)[0]
# Text tokens have value TEXT_TOKEN_ID=0, so embed -> 0.0
@@ -244,7 +243,7 @@ class TestEmbedInputIds:
"Text positions should keep text embeddings"
)
def test_interleaved_use_audio_in_video(self):
def test_interleaved_use_audio_in_video(self, mocker: MockerFixture):
"""
Interleaved (use_audio_in_video): video chunks interleaved with audio.
Video embeddings must go to video positions, audio to audio positions.
@@ -263,7 +262,7 @@ class TestEmbedInputIds:
torch.full((audio_n, hidden), audio_val),
]
model, _ = make_mock_model(hidden)
model, _ = make_mock_model(mocker, hidden)
result = model.embed_input_ids(input_ids, mm_embeds, is_multimodal=is_multimodal)
video_pos = (input_ids == VIDEO_TOKEN_ID).nonzero(as_tuple=True)[0]
@@ -15,9 +15,10 @@ import importlib.util
import os
import sys
import types
from unittest.mock import MagicMock, patch
import pytest
import torch
from pytest_mock import MockerFixture
# Direct file import to avoid vllm_omni.__init__ patch dependencies.
_BASE = os.path.join(
@@ -41,28 +42,31 @@ def _load_module(name: str, filename: str):
return mod
def _build_mock_modules() -> dict[str, object]:
def _build_mock_modules(mocker: MockerFixture) -> dict[str, object]:
"""Build the dict of modules to inject into sys.modules."""
platforms_mock = MagicMock()
platforms_mock = mocker.MagicMock()
platforms_mock.current_omni_platform.supports_torch_inductor.return_value = False
logger_mock = MagicMock()
logger_mock.init_logger = lambda name: MagicMock()
logger_mock = mocker.MagicMock()
logger_mock.init_logger = lambda name: mocker.MagicMock()
vllm_config_mod = MagicMock()
vllm_config_mod.set_current_vllm_config = lambda cfg: MagicMock(__enter__=MagicMock(), __exit__=MagicMock())
vllm_config_mod = mocker.MagicMock()
vllm_config_mod.set_current_vllm_config = lambda cfg: mocker.MagicMock(
__enter__=mocker.MagicMock(),
__exit__=mocker.MagicMock(),
)
weight_utils_mock = MagicMock()
weight_utils_mock = mocker.MagicMock()
weight_utils_mock.default_weight_loader = lambda p, w: None
pkg = types.ModuleType("vllm_omni.model_executor.models.qwen3_tts")
pkg.__path__ = [os.path.abspath(_BASE)]
return {
"vllm_omni": MagicMock(),
"vllm_omni": mocker.MagicMock(),
"vllm_omni.platforms": platforms_mock,
"vllm.logger": logger_mock,
"vllm.config": MagicMock(),
"vllm.config": mocker.MagicMock(),
"vllm.config.vllm": vllm_config_mod,
"vllm.model_executor.model_loader.weight_utils": weight_utils_mock,
"vllm_omni.model_executor": types.ModuleType("vllm_omni.model_executor"),
@@ -71,38 +75,47 @@ def _build_mock_modules() -> dict[str, object]:
}
def _load_target_classes():
def _load_target_classes(mocker: MockerFixture):
"""Load config and code predictor modules with mocked dependencies.
Uses patch.dict to ensure sys.modules is always restored, even on failure.
Uses mocker.patch.dict to ensure sys.modules is always restored, even on failure.
"""
mocks = _build_mock_modules()
with patch.dict(sys.modules, mocks):
config_mod = _load_module(
"vllm_omni.model_executor.models.qwen3_tts.configuration_qwen3_tts",
"configuration_qwen3_tts.py",
)
sys.modules["vllm_omni.model_executor.models.qwen3_tts.configuration_qwen3_tts"] = config_mod
mocks = _build_mock_modules(mocker)
mocker.patch.dict(sys.modules, mocks)
config_mod = _load_module(
"vllm_omni.model_executor.models.qwen3_tts.configuration_qwen3_tts",
"configuration_qwen3_tts.py",
)
sys.modules["vllm_omni.model_executor.models.qwen3_tts.configuration_qwen3_tts"] = config_mod
cp_mod = _load_module(
"vllm_omni.model_executor.models.qwen3_tts.qwen3_tts_code_predictor_vllm",
"qwen3_tts_code_predictor_vllm.py",
)
cp_mod = _load_module(
"vllm_omni.model_executor.models.qwen3_tts.qwen3_tts_code_predictor_vllm",
"qwen3_tts_code_predictor_vllm.py",
)
return config_mod, cp_mod
_config_mod, _cp_mod = _load_target_classes()
Qwen3TTSTalkerCodePredictorConfig = _config_mod.Qwen3TTSTalkerCodePredictorConfig
Qwen3TTSTalkerConfig = _config_mod.Qwen3TTSTalkerConfig
CodePredictorWrapper = _cp_mod.Qwen3TTSTalkerCodePredictorForConditionalGenerationVLLM
CodePredictorModel = _cp_mod.Qwen3TTSTalkerCodePredictorModelVLLM
@pytest.fixture
def loaded_target_classes(mocker: MockerFixture):
config_mod, cp_mod = _load_target_classes(mocker)
return (
config_mod.Qwen3TTSTalkerCodePredictorConfig,
config_mod.Qwen3TTSTalkerConfig,
cp_mod.Qwen3TTSTalkerCodePredictorForConditionalGenerationVLLM,
cp_mod.Qwen3TTSTalkerCodePredictorModelVLLM,
)
def _make_tiny_config() -> tuple:
def _make_tiny_config(loaded_target_classes) -> tuple:
"""Create minimal configs for a tiny code predictor model."""
cp_config = Qwen3TTSTalkerCodePredictorConfig(
(
qwen3_tts_talker_code_predictor_config,
qwen3_tts_talker_config,
_,
_,
) = loaded_target_classes
cp_config = qwen3_tts_talker_code_predictor_config(
vocab_size=64,
hidden_size=32,
intermediate_size=64,
@@ -113,16 +126,16 @@ def _make_tiny_config() -> tuple:
num_code_groups=4,
rms_norm_eps=1e-6,
)
talker_config = Qwen3TTSTalkerConfig(
talker_config = qwen3_tts_talker_config(
hidden_size=32,
num_code_groups=4,
)
return cp_config, talker_config
def _make_vllm_config(max_num_seqs: int = 4) -> MagicMock:
def _make_vllm_config(mocker: MockerFixture, max_num_seqs: int = 4):
"""Create a mock VllmConfig with scheduler_config."""
vllm_config = MagicMock()
vllm_config = mocker.MagicMock()
vllm_config.scheduler_config.max_num_seqs = max_num_seqs
return vllm_config
@@ -130,12 +143,13 @@ def _make_vllm_config(max_num_seqs: int = 4) -> MagicMock:
class TestCodePredictorDtypeAlignment:
"""Test that code predictor buffers match model parameter dtype."""
def test_ensure_buffers_uses_given_dtype(self) -> None:
def test_ensure_buffers_uses_given_dtype(self, mocker: MockerFixture, loaded_target_classes) -> None:
"""_ensure_buffers should create proj_buf with the given dtype."""
cp_config, talker_config = _make_tiny_config()
vllm_config = _make_vllm_config()
_, _, code_predictor_wrapper, _ = loaded_target_classes
cp_config, talker_config = _make_tiny_config(loaded_target_classes)
vllm_config = _make_vllm_config(mocker)
predictor = CodePredictorWrapper(
predictor = code_predictor_wrapper(
vllm_config=vllm_config,
config=cp_config,
talker_config=talker_config,
@@ -150,12 +164,13 @@ class TestCodePredictorDtypeAlignment:
predictor._ensure_buffers(torch.device("cpu"), torch.float32)
assert predictor._proj_buf.dtype == torch.float32
def test_warmup_aligns_buffer_to_model_params(self) -> None:
def test_warmup_aligns_buffer_to_model_params(self, mocker: MockerFixture, loaded_target_classes) -> None:
"""_warmup_buckets should align proj_buf dtype to model parameters."""
cp_config, talker_config = _make_tiny_config()
vllm_config = _make_vllm_config(max_num_seqs=2)
_, _, code_predictor_wrapper, _ = loaded_target_classes
cp_config, talker_config = _make_tiny_config(loaded_target_classes)
vllm_config = _make_vllm_config(mocker, max_num_seqs=2)
predictor = CodePredictorWrapper(
predictor = code_predictor_wrapper(
vllm_config=vllm_config,
config=cp_config,
talker_config=talker_config,
@@ -177,12 +192,13 @@ class TestCodePredictorDtypeAlignment:
assert predictor._proj_buf.dtype == torch.float16
def test_setup_compile_caches_model_dtype(self) -> None:
def test_setup_compile_caches_model_dtype(self, mocker: MockerFixture, loaded_target_classes) -> None:
"""_setup_compile should cache model parameter dtype."""
cp_config, talker_config = _make_tiny_config()
vllm_config = _make_vllm_config(max_num_seqs=2)
_, _, code_predictor_wrapper, _ = loaded_target_classes
cp_config, talker_config = _make_tiny_config(loaded_target_classes)
vllm_config = _make_vllm_config(mocker, max_num_seqs=2)
predictor = CodePredictorWrapper(
predictor = code_predictor_wrapper(
vllm_config=vllm_config,
config=cp_config,
talker_config=talker_config,
@@ -193,12 +209,13 @@ class TestCodePredictorDtypeAlignment:
predictor._setup_compile()
assert predictor._model_dtype == torch.float16
def test_forward_with_mismatched_input_dtype(self) -> None:
def test_forward_with_mismatched_input_dtype(self, mocker: MockerFixture, loaded_target_classes) -> None:
"""forward() should not crash when inputs are float32 but model is float16."""
cp_config, talker_config = _make_tiny_config()
vllm_config = _make_vllm_config(max_num_seqs=2)
_, _, code_predictor_wrapper, _ = loaded_target_classes
cp_config, talker_config = _make_tiny_config(loaded_target_classes)
vllm_config = _make_vllm_config(mocker, max_num_seqs=2)
predictor = CodePredictorWrapper(
predictor = code_predictor_wrapper(
vllm_config=vllm_config,
config=cp_config,
talker_config=talker_config,
@@ -231,10 +248,11 @@ class TestCodePredictorDtypeAlignment:
class TestCodePredictorModelDtype:
"""Test the inner model forward with different dtypes."""
def test_model_forward_float16(self) -> None:
def test_model_forward_float16(self, loaded_target_classes) -> None:
"""Inner model forward should work in float16."""
cp_config, _ = _make_tiny_config()
model = CodePredictorModel(cp_config, talker_hidden_size=32).to(torch.float16)
_, _, _, code_predictor_model = loaded_target_classes
cp_config, _ = _make_tiny_config(loaded_target_classes)
model = code_predictor_model(cp_config, talker_hidden_size=32).to(torch.float16)
bsz, seq_len = 1, 4
inputs = torch.randn(bsz, seq_len, 32, dtype=torch.float16)
@@ -244,10 +262,11 @@ class TestCodePredictorModelDtype:
assert output.dtype == torch.float16
assert output.shape == (bsz, seq_len, 32)
def test_model_forward_float32(self) -> None:
def test_model_forward_float32(self, loaded_target_classes) -> None:
"""Inner model forward should work in float32."""
cp_config, _ = _make_tiny_config()
model = CodePredictorModel(cp_config, talker_hidden_size=32).to(torch.float32)
_, _, _, code_predictor_model = loaded_target_classes
cp_config, _ = _make_tiny_config(loaded_target_classes)
model = code_predictor_model(cp_config, talker_hidden_size=32).to(torch.float32)
bsz, seq_len = 1, 4
inputs = torch.randn(bsz, seq_len, 32, dtype=torch.float32)
@@ -10,11 +10,11 @@ Covers:
import os
import tempfile
from unittest.mock import MagicMock, patch
import numpy as np
import pytest
import torch
from pytest_mock import MockerFixture
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@@ -61,18 +61,18 @@ class TestFishSpeechVoiceCacheIntegration:
"""Test the cache-hit / cache-miss / no-cache paths in the model."""
@pytest.fixture
def mock_model(self):
def mock_model(self, mocker: MockerFixture):
"""Create a mock FishSpeechSlowARForConditionalGeneration with cache."""
from vllm_omni.utils.voice_cache import VoiceEmbeddingCache
model = MagicMock()
model = mocker.MagicMock()
model._voice_cache = VoiceEmbeddingCache(max_entries=4)
model._semantic_begin_id = 151678
model._num_codebooks = 10
model._codebook_size = 4096
model.model_path = "/fake/model"
model.codebook_embeddings = MagicMock()
model.codebook_embeddings.weight = MagicMock()
model.codebook_embeddings = mocker.MagicMock()
model.codebook_embeddings.weight = mocker.MagicMock()
model.codebook_embeddings.weight.device = torch.device("cpu")
return model
@@ -166,9 +166,9 @@ class TestFishSpeechVoiceCacheIntegration:
class TestFishSpeechValidatorUploadedVoice:
"""Test _validate_fish_tts_request uploaded voice resolution."""
def test_uploaded_voice_resolves_ref_audio(self):
def test_uploaded_voice_resolves_ref_audio(self, mocker: MockerFixture):
"""When voice matches an uploaded speaker, ref_audio should be auto-set."""
request = MagicMock()
request = mocker.MagicMock()
request.input = "Hello"
request.voice = "alice"
request.ref_audio = None
@@ -185,17 +185,17 @@ class TestFishSpeechValidatorUploadedVoice:
}
# Simulate: voice in uploaded_speakers, file exists, get_audio returns data URL.
with patch("pathlib.Path.exists", return_value=True):
voice_lower = request.voice.lower()
assert voice_lower in uploaded_speakers
mocker.patch("pathlib.Path.exists", return_value=True)
voice_lower = request.voice.lower()
assert voice_lower in uploaded_speakers
speaker_info = uploaded_speakers[voice_lower]
ref_text_from_upload = speaker_info.get("ref_text")
assert ref_text_from_upload == "Hi this is Alice"
speaker_info = uploaded_speakers[voice_lower]
ref_text_from_upload = speaker_info.get("ref_text")
assert ref_text_from_upload == "Hi this is Alice"
def test_uploaded_voice_without_ref_text_uses_request_ref_text(self):
def test_uploaded_voice_without_ref_text_uses_request_ref_text(self, mocker: MockerFixture):
"""If upload has no ref_text but request provides it, use request's."""
request = MagicMock()
request = mocker.MagicMock()
request.input = "Hello"
request.voice = "bob"
request.ref_audio = None