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>
89 lines
2.9 KiB
Python
89 lines
2.9 KiB
Python
"""
|
|
Tests for ResultSerializer — JSON serialization of inference result objects.
|
|
"""
|
|
import dataclasses
|
|
import pytest
|
|
from unittest.mock import MagicMock
|
|
|
|
from core.functions.result_handler import ResultSerializer
|
|
|
|
|
|
# Minimal stand-ins for the SDK dataclasses (no kp import needed)
|
|
@dataclasses.dataclass
|
|
class FakeBoundingBox:
|
|
x1: int = 0
|
|
y1: int = 0
|
|
x2: int = 100
|
|
y2: int = 100
|
|
class_name: str = "fire"
|
|
score: float = 0.9
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class FakeObjectDetectionResult:
|
|
class_count: int = 1
|
|
box_count: int = 1
|
|
box_list: list = dataclasses.field(default_factory=list)
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class FakeClassificationResult:
|
|
probability: float = 0.85
|
|
class_name: str = "fire"
|
|
class_num: int = 0
|
|
|
|
|
|
class TestResultSerializerToJson:
|
|
def setup_method(self):
|
|
self.serializer = ResultSerializer()
|
|
|
|
def should_serialize_plain_dict(self):
|
|
data = {"fps": 30.0, "pipeline_id": "p1"}
|
|
result = self.serializer.to_json(data)
|
|
assert '"fps"' in result
|
|
assert "30.0" in result
|
|
|
|
def should_serialize_dict_containing_dataclass_object(self):
|
|
"""Bug reproduction: ObjectDetectionResult in result dict caused TypeError."""
|
|
det = FakeObjectDetectionResult(
|
|
class_count=1,
|
|
box_count=1,
|
|
box_list=[FakeBoundingBox()]
|
|
)
|
|
data = {"stage_results": {"stage_0": det}}
|
|
# Should NOT raise TypeError: Object of type FakeObjectDetectionResult is not JSON serializable
|
|
result = self.serializer.to_json(data)
|
|
assert result is not None
|
|
assert "stage_0" in result
|
|
|
|
def should_serialize_dict_containing_classification_result(self):
|
|
"""ClassificationResult must also be handled."""
|
|
clf = FakeClassificationResult(probability=0.85, class_name="fire")
|
|
data = {"stage_results": {"stage_0": clf}}
|
|
result = self.serializer.to_json(data)
|
|
assert "stage_0" in result
|
|
|
|
def should_serialize_nested_dataclass_in_list(self):
|
|
"""box_list inside ObjectDetectionResult contains BoundingBox dataclasses."""
|
|
det = FakeObjectDetectionResult(
|
|
box_count=1,
|
|
box_list=[FakeBoundingBox(x1=10, y1=20, x2=110, y2=120, class_name="fire")]
|
|
)
|
|
data = {"detections": det}
|
|
result = self.serializer.to_json(data)
|
|
assert "fire" in result
|
|
|
|
def should_preserve_primitive_values_unchanged(self):
|
|
data = {"fps": 45.2, "count": 3, "name": "test", "flag": True}
|
|
import json
|
|
result = json.loads(self.serializer.to_json(data))
|
|
assert result["fps"] == 45.2
|
|
assert result["count"] == 3
|
|
assert result["name"] == "test"
|
|
assert result["flag"] is True
|
|
|
|
def should_handle_none_values(self):
|
|
data = {"result": None, "stage": "stage_0"}
|
|
result = self.serializer.to_json(data)
|
|
assert "null" in result
|