forked from masonhuang/cluster4npu
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>
135 lines
6.0 KiB
Python
135 lines
6.0 KiB
Python
"""
|
||
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"
|