[Misc] Migrate L1 tests to use pytest-mock (#1315)

Signed-off-by: Yuanheng Zhao <jonathan.zhaoyh@gmail.com>
Signed-off-by: yuanheng <jonathan.zhaoyh@gmail.com>
This commit is contained in:
Yuanheng Zhao authored and GitHub committed 2026-02-26 17:08:39 +08:00
1 parent 9913c00144
commit ed08a216c1
20 files changed
+977 -954

No files matched your search

@@ -1,13 +1,13 @@
# tests/entrypoints/openai/test_serving_speech.py
import logging
from inspect import Signature, signature
from unittest.mock import MagicMock, patch
import numpy as np
import pytest
import torch
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pytest_mock import MockerFixture
from vllm_omni.entrypoints.openai.audio_utils_mixin import AudioMixin
from vllm_omni.entrypoints.openai.protocol.audio import CreateAudio, OpenAICreateSpeechRequest
@@ -26,25 +26,24 @@ class TestAudioMixin:
def audio_mixin(self):
return AudioMixin()
def test_stereo_to_mono_conversion(self, audio_mixin):
def test_stereo_to_mono_conversion(self, audio_mixin, mocker: MockerFixture):
stereo_tensor = np.random.rand(24000, 2).astype(np.float32)
audio_obj = CreateAudio(audio_tensor=stereo_tensor)
with (
patch.object(
audio_mixin, "_apply_speed_adjustment", side_effect=lambda tensor, speed, sr: (tensor, sr)
) as mock_speed,
patch("soundfile.write") as _,
):
audio_mixin.create_audio(audio_obj)
mock_speed = mocker.patch.object(
audio_mixin, "_apply_speed_adjustment", side_effect=lambda tensor, speed, sr: (tensor, sr)
)
mocker.patch("soundfile.write")
# Check that the tensor passed to speed adjustment is mono
mock_speed.assert_called_once()
adjusted_tensor = mock_speed.call_args[0][0]
assert len(adjusted_tensor) == 24000
audio_mixin.create_audio(audio_obj)
@patch("librosa.effects.time_stretch")
def test_speed_adjustment(self, mock_time_stretch, audio_mixin):
# Check that the tensor passed to speed adjustment is mono
mock_speed.assert_called_once()
adjusted_tensor = mock_speed.call_args[0][0]
assert len(adjusted_tensor) == 24000
def test_speed_adjustment(self, audio_mixin, mocker: MockerFixture):
mock_time_stretch = mocker.patch("librosa.effects.time_stretch")
mock_time_stretch.return_value = np.zeros(12000)
audio_tensor = np.random.rand(24000).astype(np.float32)
@@ -53,8 +52,8 @@ class TestAudioMixin:
mock_time_stretch.assert_called_with(y=audio_tensor, rate=2.0)
assert adjusted_audio.shape == (12000,)
@patch("soundfile.write")
def test_unsupported_format_fallback(self, mock_write, audio_mixin, caplog):
def test_unsupported_format_fallback(self, audio_mixin, caplog, mocker: MockerFixture):
mock_write = mocker.patch("soundfile.write")
audio_tensor = np.random.rand(24000).astype(np.float32)
# Use a format that is not in the list of supported formats
audio_obj = CreateAudio(audio_tensor=audio_tensor, response_format="vorbis")
@@ -66,50 +65,50 @@ class TestAudioMixin:
write_kwargs = mock_write.call_args.kwargs
assert write_kwargs["format"] == "WAV"
def test_mono_audio_preservation(self, audio_mixin):
def test_mono_audio_preservation(self, audio_mixin, mocker: MockerFixture):
"""Test that mono (1D) audio tensors are processed correctly and passed to writer."""
mono_tensor = np.random.rand(24000).astype(np.float32)
audio_obj = CreateAudio(audio_tensor=mono_tensor)
with patch("soundfile.write") as mock_write:
audio_mixin.create_audio(audio_obj)
mock_write = mocker.patch("soundfile.write")
audio_mixin.create_audio(audio_obj)
mock_write.assert_called_once()
# Verify the tensor passed to soundfile.write is the exact 1D tensor
output_tensor = mock_write.call_args[0][1]
assert output_tensor.ndim == 1
assert output_tensor.shape == (24000,)
assert np.array_equal(output_tensor, mono_tensor)
mock_write.assert_called_once()
# Verify the tensor passed to soundfile.write is the exact 1D tensor
output_tensor = mock_write.call_args[0][1]
assert output_tensor.ndim == 1
assert output_tensor.shape == (24000,)
assert np.array_equal(output_tensor, mono_tensor)
def test_stereo_audio_preservation(self, audio_mixin):
def test_stereo_audio_preservation(self, audio_mixin, mocker: MockerFixture):
"""Test that stereo (2D) audio tensors are processed correctly and preserved."""
stereo_tensor = np.random.rand(24000, 2).astype(np.float32)
audio_obj = CreateAudio(audio_tensor=stereo_tensor)
with patch("soundfile.write") as mock_write:
audio_mixin.create_audio(audio_obj)
mock_write = mocker.patch("soundfile.write")
audio_mixin.create_audio(audio_obj)
mock_write.assert_called_once()
# Verify the tensor passed to soundfile.write is the exact 2D tensor
output_tensor = mock_write.call_args[0][1]
assert output_tensor.ndim == 2
assert output_tensor.shape == (24000, 2)
assert np.array_equal(output_tensor, stereo_tensor)
mock_write.assert_called_once()
# Verify the tensor passed to soundfile.write is the exact 2D tensor
output_tensor = mock_write.call_args[0][1]
assert output_tensor.ndim == 2
assert output_tensor.shape == (24000, 2)
assert np.array_equal(output_tensor, stereo_tensor)
def test_speed_adjustment_bypass(self, audio_mixin):
def test_speed_adjustment_bypass(self, audio_mixin, mocker: MockerFixture):
"""Test that speed=1.0 bypasses the expensive librosa time stretching."""
audio_tensor = np.random.rand(24000).astype(np.float32)
with patch("librosa.effects.time_stretch") as mock_time_stretch:
# speed=1.0 should return immediately without calling librosa
result, _ = audio_mixin._apply_speed_adjustment(audio_tensor, speed=1.0, sample_rate=24000)
mock_time_stretch = mocker.patch("librosa.effects.time_stretch")
# speed=1.0 should return immediately without calling librosa
result, _ = audio_mixin._apply_speed_adjustment(audio_tensor, speed=1.0, sample_rate=24000)
mock_time_stretch.assert_not_called()
assert np.array_equal(result, audio_tensor)
mock_time_stretch.assert_not_called()
assert np.array_equal(result, audio_tensor)
@patch("librosa.effects.time_stretch")
def test_speed_adjustment_stereo_handling(self, mock_time_stretch, audio_mixin):
def test_speed_adjustment_stereo_handling(self, audio_mixin, mocker: MockerFixture):
"""Test that speed adjustment is attempted on stereo inputs."""
mock_time_stretch = mocker.patch("librosa.effects.time_stretch")
stereo_tensor = np.random.rand(24000, 2).astype(np.float32)
# Mock return value representing a sped-up version (half length)
mock_time_stretch.return_value = np.zeros((12000, 2), dtype=np.float32)
@@ -161,22 +160,22 @@ def create_mock_audio_output_for_test(
@pytest.fixture
def test_app():
def test_app(mocker: MockerFixture):
# Mock the engine client
mock_engine_client = MagicMock()
mock_engine_client = mocker.MagicMock()
mock_engine_client.errored = False
async def mock_generate_fn(*args, **kwargs):
yield create_mock_audio_output_for_test(request_id=kwargs.get("request_id"))
mock_engine_client.generate = MagicMock(side_effect=mock_generate_fn)
mock_engine_client.generate = mocker.MagicMock(side_effect=mock_generate_fn)
mock_engine_client.default_sampling_params_list = [{}]
# Mock models to have an is_base_model method
mock_models = MagicMock()
mock_models = mocker.MagicMock()
mock_models.is_base_model.return_value = True
mock_request_logger = MagicMock()
mock_request_logger = mocker.MagicMock()
speech_server = OmniOpenAIServingSpeech(
engine_client=mock_engine_client,
@@ -186,7 +185,7 @@ def test_app():
# Patch the signature of create_speech to remove 'raw_request' for FastAPI route introspection
original_create_speech = speech_server.create_speech
_ = MagicMock(side_effect=original_create_speech)
_ = mocker.MagicMock(side_effect=original_create_speech)
sig = signature(original_create_speech)
@@ -253,11 +252,13 @@ class TestSpeechAPI:
response = client.post("/v1/audio/speech", json=payload)
assert response.status_code == 422 # Unprocessable Entity
@patch("vllm_omni.entrypoints.openai.serving_speech.OmniOpenAIServingSpeech.create_audio")
def test_speed_parameter_is_used(self, mock_create_audio, test_app):
def test_speed_parameter_is_used(self, test_app, mocker: MockerFixture):
mock_create_audio = mocker.patch(
"vllm_omni.entrypoints.openai.serving_speech.OmniOpenAIServingSpeech.create_audio"
)
client = TestClient(test_app)
mock_audio_response = MagicMock()
mock_audio_response = mocker.MagicMock()
mock_audio_response.audio_data = b"dummy_audio"
mock_audio_response.media_type = "audio/wav"
mock_create_audio.return_value = mock_audio_response
@@ -287,17 +288,17 @@ class TestTTSMethods:
"""Unit tests for TTS validation and parameter building."""
@pytest.fixture
def speech_server(self):
mock_engine_client = MagicMock()
def speech_server(self, mocker: MockerFixture):
mock_engine_client = mocker.MagicMock()
mock_engine_client.errored = False
mock_engine_client.stage_list = None
mock_engine_client.tts_max_instructions_length = None
mock_models = MagicMock()
mock_models = mocker.MagicMock()
mock_models.is_base_model.return_value = True
return OmniOpenAIServingSpeech(
engine_client=mock_engine_client,
models=mock_models,
request_logger=MagicMock(),
request_logger=mocker.MagicMock(),
)
def test_is_tts_detection_no_stage(self, speech_server):
@@ -306,25 +307,25 @@ class TestTTSMethods:
assert speech_server._is_tts is False
assert speech_server._tts_stage is None
def test_is_tts_detection_with_tts_stage(self):
def test_is_tts_detection_with_tts_stage(self, mocker: MockerFixture):
"""Test TTS model detection when TTS stage exists."""
mock_engine_client = MagicMock()
mock_engine_client = mocker.MagicMock()
mock_engine_client.errored = False
mock_engine_client.tts_max_instructions_length = None
# Create a TTS stage
mock_stage = MagicMock()
mock_stage = mocker.MagicMock()
mock_stage.model_stage = "qwen3_tts"
mock_stage.tts_args = {}
mock_engine_client.stage_list = [mock_stage]
mock_models = MagicMock()
mock_models = mocker.MagicMock()
mock_models.is_base_model.return_value = True
server = OmniOpenAIServingSpeech(
engine_client=mock_engine_client,
models=mock_models,
request_logger=MagicMock(),
request_logger=mocker.MagicMock(),
)
assert server._is_tts is True
@@ -379,24 +380,24 @@ class TestTTSMethods:
assert params["language"] == ["English"]
assert params["task_type"] == ["CustomVoice"]
def test_load_supported_speakers(self):
def test_load_supported_speakers(self, mocker: MockerFixture):
"""Test _load_supported_speakers."""
mock_engine_client = MagicMock()
mock_engine_client = mocker.MagicMock()
mock_engine_client.errored = False
mock_engine_client.stage_list = None
# Mock talker_config with mixed-case speaker names
mock_talker_config = MagicMock()
mock_talker_config = mocker.MagicMock()
mock_talker_config.spk_id = {"Ryan": 0, "Vivian": 1, "Aiden": 2}
mock_engine_client.model_config.hf_config.talker_config = mock_talker_config
mock_models = MagicMock()
mock_models = mocker.MagicMock()
mock_models.is_base_model.return_value = True
server = OmniOpenAIServingSpeech(
engine_client=mock_engine_client,
models=mock_models,
request_logger=MagicMock(),
request_logger=mocker.MagicMock(),
)
# Verify speakers are normalized to lowercase
@@ -407,34 +408,34 @@ class TestTTSMethods:
# Fixture creates server with no CLI override and no TTS stage
assert speech_server._max_instructions_length == 500
def test_max_instructions_length_cli_override(self):
def test_max_instructions_length_cli_override(self, mocker: MockerFixture):
"""Test CLI override (stored in engine_client) takes highest priority."""
mock_engine_client = MagicMock()
mock_engine_client = mocker.MagicMock()
mock_engine_client.errored = False
mock_engine_client.stage_list = None
# CLI override is stored in engine_client
mock_engine_client.tts_max_instructions_length = 1000
mock_models = MagicMock()
mock_models = mocker.MagicMock()
mock_models.is_base_model.return_value = True
server = OmniOpenAIServingSpeech(
engine_client=mock_engine_client,
models=mock_models,
request_logger=MagicMock(),
request_logger=mocker.MagicMock(),
)
# Value is cached during __init__
assert server._max_instructions_length == 1000
def test_max_instructions_length_stage_config(self):
def test_max_instructions_length_stage_config(self, mocker: MockerFixture):
"""Test stage config value is used when no CLI override."""
mock_engine_client = MagicMock()
mock_engine_client = mocker.MagicMock()
mock_engine_client.errored = False
mock_engine_client.tts_max_instructions_length = None # No CLI override
mock_models = MagicMock()
mock_models = mocker.MagicMock()
mock_models.is_base_model.return_value = True
# Mock stage with tts_args
mock_stage = MagicMock()
mock_stage = mocker.MagicMock()
mock_stage.model_stage = "qwen3_tts"
mock_stage.tts_args = {"max_instructions_length": 750}
mock_engine_client.stage_list = [mock_stage]
@@ -442,22 +443,22 @@ class TestTTSMethods:
server = OmniOpenAIServingSpeech(
engine_client=mock_engine_client,
models=mock_models,
request_logger=MagicMock(),
request_logger=mocker.MagicMock(),
)
# Value is cached during __init__
assert server._max_instructions_length == 750
def test_max_instructions_length_cli_overrides_stage_config(self):
def test_max_instructions_length_cli_overrides_stage_config(self, mocker: MockerFixture):
"""Test CLI override (in engine_client) takes precedence over stage config."""
mock_engine_client = MagicMock()
mock_engine_client = mocker.MagicMock()
mock_engine_client.errored = False
# CLI override stored in engine_client
mock_engine_client.tts_max_instructions_length = 2000
mock_models = MagicMock()
mock_models = mocker.MagicMock()
mock_models.is_base_model.return_value = True
# Mock stage with tts_args
mock_stage = MagicMock()
mock_stage = mocker.MagicMock()
mock_stage.model_stage = "qwen3_tts"
mock_stage.tts_args = {"max_instructions_length": 750}
mock_engine_client.stage_list = [mock_stage]
@@ -465,25 +466,25 @@ class TestTTSMethods:
server = OmniOpenAIServingSpeech(
engine_client=mock_engine_client,
models=mock_models,
request_logger=MagicMock(),
request_logger=mocker.MagicMock(),
)
# CLI value (2000) should override stage config (750)
assert server._max_instructions_length == 2000
def test_validate_instructions_length_uses_cached_value(self):
def test_validate_instructions_length_uses_cached_value(self, mocker: MockerFixture):
"""Test instructions length validation uses cached _max_instructions_length."""
mock_engine_client = MagicMock()
mock_engine_client = mocker.MagicMock()
mock_engine_client.errored = False
mock_engine_client.stage_list = None
# CLI override with max length of 10 characters
mock_engine_client.tts_max_instructions_length = 10
mock_models = MagicMock()
mock_models = mocker.MagicMock()
mock_models.is_base_model.return_value = True
server = OmniOpenAIServingSpeech(
engine_client=mock_engine_client,
models=mock_models,
request_logger=MagicMock(),
request_logger=mocker.MagicMock(),
)
# Verify cached value