cluster4npu/tests/unit/test_benchmark_dialog.py
abin 55040733fe feat: implement Phase 1-4 performance visualization and device management
Phase 1 — Performance Benchmarking:
- PerformanceBenchmarker: sequential vs parallel benchmark with injectable runner
- PerformanceHistory: JSON-backed benchmark history with regression support
- PerformanceDashboard: real-time FPS/latency display widget
- BenchmarkDialog: one-click benchmark with 3-phase progress bar

Phase 2 — Device Management:
- DeviceManager: NPU dongle scan, assign/unassign, load balance recommendation
- DeviceManagementPanel: live device status cards with auto-refresh
- BottleneckAlert: dataclass for pipeline bottleneck detection

Phase 3 — Advanced Features:
- OptimizationEngine: 3 optimization rules (rebalance/adjust_queue/add_devices)
- TemplateManager: 3 built-in pipeline templates (YOLOv5, fire detection, dual-model)

Phase 4 — Report Export:
- ReportExporter: PDF (reportlab, optional) and CSV export
- ExportReportDialog: format selection + path picker UI

192 unit tests, all passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:32:05 +08:00

135 lines
6.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
BenchmarkDialog 的單元測試。
測試策略:
- PyQt5 在 CI 環境中不可用,透過 conftest.py 的 Stub 注入繞過 import。
- 測試驗證 BenchmarkDialog 的行為邏輯:
- 對話框可正常建立
- pipeline_config 為空時開始按鈕被禁用
- show_result 正確顯示加速倍數文字
- update_progress 更新進度條值
"""
import pytest
from unittest.mock import MagicMock
# ---------------------------------------------------------------------------
# 測試BenchmarkDialog 可以建立
# ---------------------------------------------------------------------------
class TestBenchmarkDialogInit:
def should_be_importable(self):
"""BenchmarkDialog 模組應可匯入(即使 PyQt5 被 Stub"""
from ui.dialogs.benchmark_dialog import BenchmarkDialog
assert BenchmarkDialog is not None
def should_instantiate_with_valid_config(self):
"""提供非空 pipeline_config 時BenchmarkDialog 應可正常建立。"""
from ui.dialogs.benchmark_dialog import BenchmarkDialog
stage_config = MagicMock()
dialog = BenchmarkDialog(parent=None, pipeline_config=[stage_config])
assert dialog is not None
def should_instantiate_with_empty_config(self):
"""pipeline_config 為空時BenchmarkDialog 應可建立(不應拋出例外)。"""
from ui.dialogs.benchmark_dialog import BenchmarkDialog
dialog = BenchmarkDialog(parent=None, pipeline_config=[])
assert dialog is not None
# ---------------------------------------------------------------------------
# 測試pipeline_config 為空時禁用開始按鈕
# ---------------------------------------------------------------------------
class TestStartButtonDisabledWhenEmptyConfig:
def should_disable_start_button_when_pipeline_config_is_empty(self):
"""pipeline_config 為空時start_button 應被禁用。"""
from ui.dialogs.benchmark_dialog import BenchmarkDialog
dialog = BenchmarkDialog(parent=None, pipeline_config=[])
assert dialog.start_button.isEnabled() is False
def should_enable_start_button_when_pipeline_config_has_stages(self):
"""pipeline_config 有 Stage 時start_button 應為啟用狀態。"""
from ui.dialogs.benchmark_dialog import BenchmarkDialog
stage_config = MagicMock()
dialog = BenchmarkDialog(parent=None, pipeline_config=[stage_config])
assert dialog.start_button.isEnabled() is True
def should_show_info_label_when_pipeline_config_is_empty(self):
"""pipeline_config 為空時,應有提示訊息 label 顯示。"""
from ui.dialogs.benchmark_dialog import BenchmarkDialog
dialog = BenchmarkDialog(parent=None, pipeline_config=[])
assert hasattr(dialog, "info_label")
# ---------------------------------------------------------------------------
# 測試show_result 顯示加速倍數
# ---------------------------------------------------------------------------
class TestShowResult:
def should_display_speedup_text_with_x_suffix(self):
"""show_result 後speedup_label 的文字應包含倍數數值與 'x'"""
from ui.dialogs.benchmark_dialog import BenchmarkDialog
stage = MagicMock()
dialog = BenchmarkDialog(parent=None, pipeline_config=[stage])
seq_result = MagicMock()
par_result = MagicMock()
dialog.show_result(seq_result, par_result, speedup=3.2)
call_arg = dialog.speedup_label.setText.call_args[0][0]
assert "3.2" in call_arg
assert "x" in call_arg.lower() or "X" in call_arg
def should_display_faster_in_speedup_text(self):
"""show_result 後speedup_label 文字應包含 'FASTER''faster'"""
from ui.dialogs.benchmark_dialog import BenchmarkDialog
stage = MagicMock()
dialog = BenchmarkDialog(parent=None, pipeline_config=[stage])
seq_result = MagicMock()
par_result = MagicMock()
dialog.show_result(seq_result, par_result, speedup=2.5)
call_arg = dialog.speedup_label.setText.call_args[0][0]
assert "FASTER" in call_arg or "faster" in call_arg
def should_store_seq_result(self):
"""show_result 後seq_result 應儲存在 dialog 上。"""
from ui.dialogs.benchmark_dialog import BenchmarkDialog
stage = MagicMock()
dialog = BenchmarkDialog(parent=None, pipeline_config=[stage])
seq_result = MagicMock()
par_result = MagicMock()
dialog.show_result(seq_result, par_result, speedup=1.8)
assert dialog.seq_result is seq_result
def should_store_par_result(self):
"""show_result 後par_result 應儲存在 dialog 上。"""
from ui.dialogs.benchmark_dialog import BenchmarkDialog
stage = MagicMock()
dialog = BenchmarkDialog(parent=None, pipeline_config=[stage])
seq_result = MagicMock()
par_result = MagicMock()
dialog.show_result(seq_result, par_result, speedup=1.8)
assert dialog.par_result is par_result
# ---------------------------------------------------------------------------
# 測試update_progress 更新進度條
# ---------------------------------------------------------------------------
class TestUpdateProgress:
def should_update_progress_bar_value(self):
"""update_progress 應將進度條值更新為傳入的 value。"""
from ui.dialogs.benchmark_dialog import BenchmarkDialog
stage = MagicMock()
dialog = BenchmarkDialog(parent=None, pipeline_config=[stage])
dialog.progress_bar.setValue.reset_mock()
dialog.update_progress("warmup", 42)
dialog.progress_bar.setValue.assert_called_once_with(42)
def should_store_current_phase(self):
"""update_progress 應儲存當前 phase 名稱。"""
from ui.dialogs.benchmark_dialog import BenchmarkDialog
stage = MagicMock()
dialog = BenchmarkDialog(parent=None, pipeline_config=[stage])
dialog.update_progress("sequential", 70)
assert dialog.current_phase == "sequential"