feat(openvino): добавлена поддержка Intel GPU через OpenVINO
- Зачем: - OpenVINO backend хардкодил "CPU", хотя Intel Arc GPU доступен и даёт ~2x ускорение. - Что: - новые device modes: --device openvino-gpu, openvino-cpu; openvino — авто-детект GPU/CPU. - detect_device() проверяет Intel GPU через OpenVINO Core API (с fail-safe). - OpenVINOBackend передаёт "GPU"/"CPU" в WhisperPipeline вместо хардкода "CPU". - подсказка "Совет: --model large-v3" при наличии GPU и модели не large-v3. - .gitattributes для нормализации line endings (eol=lf). - 150 тестов, включая GPU detection, routing, fallback, CLI device info. - README, docs/gpu.md, docs/PRD.md обновлены для Intel GPU. - Проверка: - uv run pytest (150 passed). - uv run transcribe --device openvino-gpu file.mp4 на Intel Arc 140T GPU. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
dc59d2a620
commit
617181f2dc
@@ -104,17 +104,70 @@ def test_ensure_model_available_downloads(mock_download, tmp_path):
|
||||
# === create_model ===
|
||||
|
||||
|
||||
def test_create_model():
|
||||
def test_create_model_cpu():
|
||||
mock_ov = MagicMock()
|
||||
mock_pipeline = MagicMock()
|
||||
mock_ov.WhisperPipeline.return_value = mock_pipeline
|
||||
|
||||
backend = OpenVINOBackend()
|
||||
backend = OpenVINOBackend(ov_device="openvino-cpu")
|
||||
with patch.dict("sys.modules", {"openvino_genai": mock_ov}):
|
||||
model = backend.create_model("/path/to/model", "openvino", "int8")
|
||||
model = backend.create_model("/path/to/model", "openvino-cpu", "int8")
|
||||
|
||||
mock_ov.WhisperPipeline.assert_called_once_with("/path/to/model", "CPU")
|
||||
assert model is mock_pipeline
|
||||
assert backend.actual_ov_device == "CPU"
|
||||
|
||||
|
||||
def test_create_model_gpu():
|
||||
mock_ov = MagicMock()
|
||||
mock_pipeline = MagicMock()
|
||||
mock_ov.WhisperPipeline.return_value = mock_pipeline
|
||||
|
||||
backend = OpenVINOBackend(ov_device="openvino-gpu")
|
||||
with patch.dict("sys.modules", {"openvino_genai": mock_ov}):
|
||||
model = backend.create_model("/path/to/model", "openvino-gpu", "fp16")
|
||||
|
||||
mock_ov.WhisperPipeline.assert_called_once_with("/path/to/model", "GPU")
|
||||
assert model is mock_pipeline
|
||||
assert backend.actual_ov_device == "GPU"
|
||||
|
||||
|
||||
def test_create_model_openvino_auto_detects_gpu():
|
||||
"""ov_device='openvino' + GPU доступен → WhisperPipeline получает 'GPU'."""
|
||||
mock_ov = MagicMock()
|
||||
mock_pipeline = MagicMock()
|
||||
mock_ov.WhisperPipeline.return_value = mock_pipeline
|
||||
|
||||
mock_core = MagicMock()
|
||||
mock_core.return_value.available_devices = ["CPU", "GPU"]
|
||||
|
||||
backend = OpenVINOBackend(ov_device="openvino")
|
||||
with (
|
||||
patch.dict("sys.modules", {"openvino_genai": mock_ov, "openvino": MagicMock(Core=mock_core)}),
|
||||
):
|
||||
model = backend.create_model("/path/to/model", "openvino", "int8")
|
||||
|
||||
mock_ov.WhisperPipeline.assert_called_once_with("/path/to/model", "GPU")
|
||||
assert backend.actual_ov_device == "GPU"
|
||||
|
||||
|
||||
def test_create_model_openvino_auto_falls_back_to_cpu():
|
||||
"""ov_device='openvino' + нет GPU → WhisperPipeline получает 'CPU'."""
|
||||
mock_ov = MagicMock()
|
||||
mock_pipeline = MagicMock()
|
||||
mock_ov.WhisperPipeline.return_value = mock_pipeline
|
||||
|
||||
mock_core = MagicMock()
|
||||
mock_core.return_value.available_devices = ["CPU"]
|
||||
|
||||
backend = OpenVINOBackend(ov_device="openvino")
|
||||
with (
|
||||
patch.dict("sys.modules", {"openvino_genai": mock_ov, "openvino": MagicMock(Core=mock_core)}),
|
||||
):
|
||||
model = backend.create_model("/path/to/model", "openvino", "int8")
|
||||
|
||||
mock_ov.WhisperPipeline.assert_called_once_with("/path/to/model", "CPU")
|
||||
assert backend.actual_ov_device == "CPU"
|
||||
|
||||
|
||||
# === transcribe ===
|
||||
|
||||
+75
-1
@@ -4,7 +4,7 @@ from unittest.mock import MagicMock, patch
|
||||
import pytest
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from local_transcriber.cli import app
|
||||
from local_transcriber.cli import _format_device_info, app
|
||||
from local_transcriber.transcriber import Segment, TranscribeFileResult, TranscribeResult
|
||||
|
||||
runner = CliRunner()
|
||||
@@ -788,3 +788,77 @@ def test_cli_batch_model_loaded_once(tmp_path):
|
||||
|
||||
assert out.exit_code == 0
|
||||
mock_load_model.assert_called_once()
|
||||
|
||||
|
||||
# === _format_device_info tests ===
|
||||
|
||||
|
||||
def test_format_device_info_openvino_gpu():
|
||||
with patch("local_transcriber.cli.get_intel_gpu_name", return_value="Intel(R) Arc(TM) 140T GPU"):
|
||||
assert _format_device_info("openvino-gpu") == "OpenVINO (Intel(R) Arc(TM) 140T GPU)"
|
||||
|
||||
|
||||
def test_format_device_info_openvino_gpu_no_name():
|
||||
"""get_intel_gpu_name вернул None → fallback на 'Intel GPU'."""
|
||||
with patch("local_transcriber.cli.get_intel_gpu_name", return_value=None):
|
||||
assert _format_device_info("openvino-gpu") == "OpenVINO (Intel GPU)"
|
||||
|
||||
|
||||
def test_format_device_info_openvino_cpu():
|
||||
assert _format_device_info("openvino-cpu") == "OpenVINO (CPU)"
|
||||
|
||||
|
||||
def test_format_device_info_openvino_legacy():
|
||||
"""Обратная совместимость: 'openvino' → OpenVINO (CPU)."""
|
||||
assert _format_device_info("openvino") == "OpenVINO (CPU)"
|
||||
|
||||
|
||||
def test_format_device_info_cpu():
|
||||
assert _format_device_info("cpu") == "CPU"
|
||||
|
||||
|
||||
def test_format_device_info_cuda():
|
||||
with patch("local_transcriber.cli.get_gpu_name", return_value="RTX 4090"):
|
||||
assert _format_device_info("cuda") == "CUDA (RTX 4090)"
|
||||
|
||||
|
||||
# === CLI with --device openvino-gpu ===
|
||||
|
||||
|
||||
def test_cli_openvino_gpu_happy_path(tmp_path):
|
||||
audio = tmp_path / "test.mp3"
|
||||
audio.write_bytes(b"fake")
|
||||
result = _make_result(device_used="openvino-gpu")
|
||||
|
||||
patches = _single_patches(result=result, tmp_file=audio, actual_device="openvino-gpu")
|
||||
with patches[0], patches[1], patches[2], patches[3], patches[4], patches[5]:
|
||||
with patch("local_transcriber.cli.get_intel_gpu_name", return_value="Intel Arc 140T"):
|
||||
out = runner.invoke(app, [str(audio), "--device", "openvino-gpu"])
|
||||
|
||||
assert out.exit_code == 0
|
||||
|
||||
|
||||
def test_cli_openvino_alias_resolves_to_gpu(tmp_path):
|
||||
"""--device openvino резолвится через detect_device в openvino-gpu."""
|
||||
audio = tmp_path / "test.mp3"
|
||||
audio.write_bytes(b"fake")
|
||||
result = _make_result(device_used="openvino-gpu")
|
||||
model = _make_model()
|
||||
backend = _make_backend()
|
||||
tfr = _make_tfr(result=result, model=model, actual_device="openvino-gpu", backend=backend)
|
||||
mock_load_model = MagicMock(return_value=(model, "openvino-gpu", backend, "/models/medium"))
|
||||
|
||||
with (
|
||||
patch("local_transcriber.cli.load_config", return_value={}),
|
||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||
patch("local_transcriber.cli.detect_device", return_value="openvino-gpu"),
|
||||
patch("local_transcriber.cli.load_model", mock_load_model),
|
||||
patch("local_transcriber.cli._transcribe_file", return_value=tfr),
|
||||
patch("local_transcriber.cli.write_transcript"),
|
||||
patch("local_transcriber.cli.get_intel_gpu_name", return_value="Intel Arc 140T"),
|
||||
):
|
||||
out = runner.invoke(app, [str(audio), "--device", "openvino"])
|
||||
|
||||
assert out.exit_code == 0
|
||||
# detect_device("openvino") resolved to "openvino-gpu", load_model receives it
|
||||
assert mock_load_model.call_args[0][1] == "openvino-gpu"
|
||||
|
||||
@@ -137,9 +137,39 @@ def test_load_config_openvino_device(tmp_path):
|
||||
assert result == {"device": "openvino"}
|
||||
|
||||
|
||||
def test_load_config_openvino_gpu_device(tmp_path):
|
||||
config = tmp_path / "config.toml"
|
||||
config.write_text('device = "openvino-gpu"\n')
|
||||
result = load_config(config)
|
||||
assert result == {"device": "openvino-gpu"}
|
||||
|
||||
|
||||
def test_load_config_openvino_cpu_device(tmp_path):
|
||||
config = tmp_path / "config.toml"
|
||||
config.write_text('device = "openvino-cpu"\n')
|
||||
result = load_config(config)
|
||||
assert result == {"device": "openvino-cpu"}
|
||||
|
||||
|
||||
def test_apply_device_defaults_openvino():
|
||||
defaults = {"model": "medium", "language": "ru", "device": "auto", "compute_type": "float32"}
|
||||
cli = {"model": None, "language": None, "device": None, "compute_type": None}
|
||||
result = apply_device_defaults(defaults, "openvino", cli, {})
|
||||
assert result["model"] == "medium"
|
||||
assert result["compute_type"] == "int8"
|
||||
|
||||
|
||||
def test_apply_device_defaults_openvino_gpu():
|
||||
defaults = {"model": "medium", "language": "ru", "device": "auto", "compute_type": "float32"}
|
||||
cli = {"model": None, "language": None, "device": None, "compute_type": None}
|
||||
result = apply_device_defaults(defaults, "openvino-gpu", cli, {})
|
||||
assert result["model"] == "medium"
|
||||
assert result["compute_type"] == "int8"
|
||||
|
||||
|
||||
def test_apply_device_defaults_openvino_cpu():
|
||||
defaults = {"model": "medium", "language": "ru", "device": "auto", "compute_type": "float32"}
|
||||
cli = {"model": None, "language": None, "device": None, "compute_type": None}
|
||||
result = apply_device_defaults(defaults, "openvino-cpu", cli, {})
|
||||
assert result["model"] == "medium"
|
||||
assert result["compute_type"] == "int8"
|
||||
|
||||
+66
-17
@@ -372,7 +372,7 @@ def test_ensure_model_available_downloads_on_cache_miss(mock_snapshot_download,
|
||||
|
||||
result = ensure_model_available("large-v3", on_status=statuses.append)
|
||||
|
||||
assert result == "/downloaded/model"
|
||||
assert Path(result) == Path("/downloaded/model")
|
||||
assert mock_snapshot_download.call_args_list[0].kwargs["local_files_only"] is True
|
||||
assert mock_snapshot_download.call_args_list[1].kwargs["local_files_only"] is False
|
||||
assert "Проверяю кэш модели large-v3..." in statuses
|
||||
@@ -446,8 +446,8 @@ def test_ensure_model_available_rejects_incomplete_local_directory(tmp_path):
|
||||
|
||||
|
||||
@patch("local_transcriber.transcriber.get_backend")
|
||||
def test_load_model_openvino_fallback_to_cpu(mock_get_backend):
|
||||
"""OpenVINO ошибка при init → fallback на CPU (FasterWhisper)."""
|
||||
def test_load_model_openvino_gpu_fallback_to_cpu(mock_get_backend):
|
||||
"""OpenVINO GPU ошибка при init → fallback на CPU (FasterWhisper)."""
|
||||
ov_backend = _make_backend(
|
||||
create_model_error=RuntimeError("OpenVINO model load failed"),
|
||||
)
|
||||
@@ -455,13 +455,13 @@ def test_load_model_openvino_fallback_to_cpu(mock_get_backend):
|
||||
cpu_backend = _make_backend(model=cpu_model, model_path="/mock/cpu/model")
|
||||
|
||||
def backend_for_device(device, **kwargs):
|
||||
return ov_backend if device == "openvino" else cpu_backend
|
||||
return ov_backend if device == "openvino-gpu" else cpu_backend
|
||||
|
||||
mock_get_backend.side_effect = backend_for_device
|
||||
|
||||
with pytest.warns(UserWarning, match="Переключение на CPU"):
|
||||
model, actual_device, backend, model_path = load_model(
|
||||
"medium", "openvino", "int8",
|
||||
"medium", "openvino-gpu", "fp16",
|
||||
)
|
||||
|
||||
assert actual_device == "cpu"
|
||||
@@ -471,8 +471,33 @@ def test_load_model_openvino_fallback_to_cpu(mock_get_backend):
|
||||
|
||||
|
||||
@patch("local_transcriber.transcriber.get_backend")
|
||||
def test_transcribe_file_openvino_midstream_fallback(mock_get_backend):
|
||||
"""OpenVINO ошибка при транскрипции → fallback на CPU."""
|
||||
def test_load_model_openvino_cpu_fallback_to_cpu(mock_get_backend):
|
||||
"""OpenVINO CPU ошибка при init → fallback на CPU (FasterWhisper)."""
|
||||
ov_backend = _make_backend(
|
||||
create_model_error=RuntimeError("OpenVINO model load failed"),
|
||||
)
|
||||
cpu_model = MagicMock()
|
||||
cpu_backend = _make_backend(model=cpu_model, model_path="/mock/cpu/model")
|
||||
|
||||
def backend_for_device(device, **kwargs):
|
||||
return ov_backend if device == "openvino-cpu" else cpu_backend
|
||||
|
||||
mock_get_backend.side_effect = backend_for_device
|
||||
|
||||
with pytest.warns(UserWarning, match="Переключение на CPU"):
|
||||
model, actual_device, backend, model_path = load_model(
|
||||
"medium", "openvino-cpu", "int8",
|
||||
)
|
||||
|
||||
assert actual_device == "cpu"
|
||||
assert model is cpu_model
|
||||
assert backend is cpu_backend
|
||||
assert model_path == "/mock/cpu/model"
|
||||
|
||||
|
||||
@patch("local_transcriber.transcriber.get_backend")
|
||||
def test_transcribe_file_openvino_gpu_midstream_fallback(mock_get_backend):
|
||||
"""OpenVINO GPU ошибка при транскрипции → fallback на CPU."""
|
||||
ov_backend = _make_backend(
|
||||
transcribe_error=RuntimeError("OpenVINO inference error"),
|
||||
)
|
||||
@@ -482,19 +507,19 @@ def test_transcribe_file_openvino_midstream_fallback(mock_get_backend):
|
||||
)
|
||||
|
||||
def backend_for_device(device, **kwargs):
|
||||
return ov_backend if device == "openvino" else cpu_backend
|
||||
return ov_backend if device.startswith("openvino") else cpu_backend
|
||||
|
||||
mock_get_backend.side_effect = backend_for_device
|
||||
|
||||
with pytest.warns(UserWarning, match="Переключение на CPU"):
|
||||
tfr = _transcribe_file(
|
||||
model=MagicMock(),
|
||||
actual_device="openvino",
|
||||
actual_device="openvino-gpu",
|
||||
backend=ov_backend,
|
||||
model_path="/mock/ov/model",
|
||||
file_path=Path("test.mp3"),
|
||||
model_name="medium",
|
||||
compute_type="int8",
|
||||
compute_type="fp16",
|
||||
)
|
||||
|
||||
assert tfr.actual_device == "cpu"
|
||||
@@ -503,15 +528,15 @@ def test_transcribe_file_openvino_midstream_fallback(mock_get_backend):
|
||||
|
||||
|
||||
@patch("local_transcriber.transcriber.get_backend")
|
||||
def test_openvino_strict_device_no_fallback(mock_get_backend):
|
||||
"""strict_device=True + OpenVINO ошибка → raise."""
|
||||
def test_openvino_gpu_strict_device_no_fallback(mock_get_backend):
|
||||
"""strict_device=True + OpenVINO GPU ошибка → raise."""
|
||||
backend = _make_backend(
|
||||
create_model_error=RuntimeError("OpenVINO model load failed"),
|
||||
)
|
||||
mock_get_backend.return_value = backend
|
||||
|
||||
with pytest.raises(RuntimeError, match="OpenVINO"):
|
||||
load_model("medium", "openvino", "int8", strict_device=True)
|
||||
load_model("medium", "openvino-gpu", "fp16", strict_device=True)
|
||||
|
||||
|
||||
@patch("local_transcriber.transcriber.get_backend")
|
||||
@@ -523,21 +548,45 @@ def test_openvino_runtime_error_triggers_fallback(mock_get_backend):
|
||||
cpu_backend = _make_backend(model_path="/mock/cpu/model")
|
||||
|
||||
def backend_for_device(device, **kwargs):
|
||||
return ov_backend if device == "openvino" else cpu_backend
|
||||
return ov_backend if device.startswith("openvino") else cpu_backend
|
||||
|
||||
mock_get_backend.side_effect = backend_for_device
|
||||
|
||||
with pytest.warns(UserWarning, match="Переключение на CPU"):
|
||||
_, actual_device, _, _ = load_model("medium", "openvino", "int8")
|
||||
_, actual_device, _, _ = load_model("medium", "openvino-cpu", "int8")
|
||||
|
||||
assert actual_device == "cpu"
|
||||
|
||||
|
||||
@patch("local_transcriber.transcriber.get_backend")
|
||||
def test_load_model_resolves_openvino_gpu_device(mock_get_backend):
|
||||
"""load_model обновляет actual_device по backend.actual_ov_device."""
|
||||
backend = _make_backend()
|
||||
backend.actual_ov_device = "GPU"
|
||||
mock_get_backend.return_value = backend
|
||||
|
||||
_, actual_device, _, _ = load_model("medium", "openvino-cpu", "int8")
|
||||
|
||||
assert actual_device == "openvino-gpu"
|
||||
|
||||
|
||||
@patch("local_transcriber.transcriber.get_backend")
|
||||
def test_load_model_resolves_openvino_cpu_device(mock_get_backend):
|
||||
"""load_model обновляет actual_device по backend.actual_ov_device."""
|
||||
backend = _make_backend()
|
||||
backend.actual_ov_device = "CPU"
|
||||
mock_get_backend.return_value = backend
|
||||
|
||||
_, actual_device, _, _ = load_model("medium", "openvino-gpu", "fp16")
|
||||
|
||||
assert actual_device == "openvino-cpu"
|
||||
|
||||
|
||||
def test_ensure_model_available_openvino_default_compute_type():
|
||||
"""ensure_model_available(device='openvino') без compute_type не падает."""
|
||||
"""ensure_model_available(device='openvino-cpu') без compute_type не падает."""
|
||||
from local_transcriber.backends.openvino import OpenVINOBackend
|
||||
|
||||
backend = OpenVINOBackend(compute_type_explicit=True)
|
||||
backend = OpenVINOBackend(ov_device="openvino-cpu", compute_type_explicit=True)
|
||||
# Проверяем что _resolve_repo работает с дефолтным compute_type для openvino (int8)
|
||||
repo, ct = backend._resolve_repo("medium", "int8")
|
||||
assert repo == "OpenVINO/whisper-medium-int8-ov"
|
||||
|
||||
+90
-7
@@ -1,14 +1,16 @@
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from local_transcriber.utils import (
|
||||
_is_openvino_gpu_available,
|
||||
build_output_path,
|
||||
detect_device,
|
||||
expand_globs,
|
||||
get_gpu_name,
|
||||
get_intel_gpu_name,
|
||||
has_existing_transcript,
|
||||
validate_input_file,
|
||||
)
|
||||
@@ -55,26 +57,38 @@ def test_build_output_path_custom():
|
||||
assert result == custom
|
||||
|
||||
|
||||
def test_detect_device_explicit():
|
||||
def test_detect_device_explicit_passthrough():
|
||||
"""Явные device strings проходят без изменений."""
|
||||
assert detect_device("cpu") == "cpu"
|
||||
assert detect_device("cuda") == "cuda"
|
||||
assert detect_device("openvino") == "openvino"
|
||||
assert detect_device("openvino-gpu") == "openvino-gpu"
|
||||
assert detect_device("openvino-cpu") == "openvino-cpu"
|
||||
|
||||
|
||||
def test_detect_device_auto_openvino():
|
||||
"""Нет nvidia-smi, есть openvino_genai, x86_64 → openvino."""
|
||||
def test_detect_device_auto_openvino_gpu():
|
||||
"""auto + нет nvidia-smi + есть OpenVINO GPU → openvino-gpu."""
|
||||
with (
|
||||
patch("local_transcriber.utils.shutil.which", return_value=None),
|
||||
patch("local_transcriber.utils._is_openvino_gpu_available", return_value=True),
|
||||
):
|
||||
assert detect_device("auto") == "openvino-gpu"
|
||||
|
||||
|
||||
def test_detect_device_auto_openvino_cpu():
|
||||
"""auto + нет nvidia-smi + есть OpenVINO, нет GPU → openvino-cpu."""
|
||||
with (
|
||||
patch("local_transcriber.utils.shutil.which", return_value=None),
|
||||
patch("local_transcriber.utils._is_openvino_gpu_available", return_value=False),
|
||||
patch("local_transcriber.utils._is_openvino_available", return_value=True),
|
||||
):
|
||||
assert detect_device("auto") == "openvino"
|
||||
assert detect_device("auto") == "openvino-cpu"
|
||||
|
||||
|
||||
def test_detect_device_cuda_over_openvino():
|
||||
"""nvidia-smi доступен и openvino тоже → cuda побеждает."""
|
||||
with (
|
||||
patch("local_transcriber.utils.shutil.which", return_value="/usr/bin/nvidia-smi"),
|
||||
patch("local_transcriber.utils._is_openvino_available", return_value=True),
|
||||
patch("local_transcriber.utils._is_openvino_gpu_available", return_value=True),
|
||||
):
|
||||
assert detect_device("auto") == "cuda"
|
||||
|
||||
@@ -83,11 +97,80 @@ def test_detect_device_auto_cpu_fallback():
|
||||
"""Ни nvidia-smi, ни openvino → cpu."""
|
||||
with (
|
||||
patch("local_transcriber.utils.shutil.which", return_value=None),
|
||||
patch("local_transcriber.utils._is_openvino_gpu_available", return_value=False),
|
||||
patch("local_transcriber.utils._is_openvino_available", return_value=False),
|
||||
):
|
||||
assert detect_device("auto") == "cpu"
|
||||
|
||||
|
||||
def test_detect_device_openvino_resolves_to_gpu():
|
||||
"""--device openvino + GPU доступен → openvino-gpu."""
|
||||
with patch("local_transcriber.utils._is_openvino_gpu_available", return_value=True):
|
||||
assert detect_device("openvino") == "openvino-gpu"
|
||||
|
||||
|
||||
def test_detect_device_openvino_resolves_to_cpu():
|
||||
"""--device openvino + GPU недоступен → openvino-cpu."""
|
||||
with patch("local_transcriber.utils._is_openvino_gpu_available", return_value=False):
|
||||
assert detect_device("openvino") == "openvino-cpu"
|
||||
|
||||
|
||||
# === _is_openvino_gpu_available ===
|
||||
|
||||
|
||||
def test_is_openvino_gpu_available_true():
|
||||
mock_core = MagicMock()
|
||||
mock_core.return_value.available_devices = ["CPU", "GPU"]
|
||||
with (
|
||||
patch("local_transcriber.utils._is_openvino_available", return_value=True),
|
||||
patch("local_transcriber.utils.Core", mock_core, create=True),
|
||||
):
|
||||
# Need to patch the import inside the function
|
||||
with patch.dict("sys.modules", {"openvino": MagicMock(Core=mock_core)}):
|
||||
assert _is_openvino_gpu_available() is True
|
||||
|
||||
|
||||
def test_is_openvino_gpu_available_no_gpu():
|
||||
mock_core = MagicMock()
|
||||
mock_core.return_value.available_devices = ["CPU"]
|
||||
with (
|
||||
patch("local_transcriber.utils._is_openvino_available", return_value=True),
|
||||
patch.dict("sys.modules", {"openvino": MagicMock(Core=mock_core)}),
|
||||
):
|
||||
assert _is_openvino_gpu_available() is False
|
||||
|
||||
|
||||
def test_is_openvino_gpu_available_no_openvino():
|
||||
"""OpenVINO не установлен → False."""
|
||||
with patch("local_transcriber.utils._is_openvino_available", return_value=False):
|
||||
assert _is_openvino_gpu_available() is False
|
||||
|
||||
|
||||
def test_is_openvino_gpu_available_runtime_error():
|
||||
"""Runtime error от OpenVINO → False (fail-safe)."""
|
||||
with (
|
||||
patch("local_transcriber.utils._is_openvino_available", return_value=True),
|
||||
patch.dict("sys.modules", {"openvino": MagicMock(Core=MagicMock(side_effect=RuntimeError("broken")))}),
|
||||
):
|
||||
assert _is_openvino_gpu_available() is False
|
||||
|
||||
|
||||
# === get_intel_gpu_name ===
|
||||
|
||||
|
||||
def test_get_intel_gpu_name_success():
|
||||
mock_core = MagicMock()
|
||||
mock_core.return_value.get_property.return_value = "Intel(R) Arc(TM) 140T GPU (16GB) (iGPU)"
|
||||
with patch.dict("sys.modules", {"openvino": MagicMock(Core=mock_core)}):
|
||||
assert get_intel_gpu_name() == "Intel(R) Arc(TM) 140T GPU (16GB) (iGPU)"
|
||||
|
||||
|
||||
def test_get_intel_gpu_name_error():
|
||||
"""Ошибка → None (fail-safe)."""
|
||||
with patch.dict("sys.modules", {"openvino": MagicMock(Core=MagicMock(side_effect=Exception("no gpu")))}):
|
||||
assert get_intel_gpu_name() is None
|
||||
|
||||
|
||||
def test_get_gpu_name_no_nvidia_smi():
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("subprocess.run", side_effect=FileNotFoundError):
|
||||
|
||||
Reference in New Issue
Block a user