test(e2e): add DatasetAdapter base interface + capability dataclass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yuzviak
2026-04-16 21:41:58 +03:00
parent a2620aee6c
commit 2f87621926
2 changed files with 137 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
"""DatasetAdapter — uniform interface for e2e harness over arbitrary UAV datasets."""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum
from typing import Iterator
class PlatformClass(str, Enum):
FIXED_WING = "fixed_wing"
ROTARY = "rotary"
INDOOR = "indoor"
SYNTHETIC = "synthetic"
class DatasetNotAvailableError(RuntimeError):
"""Raised when a dataset's expected files are missing. Tests should skip, not fail."""
@dataclass(frozen=True)
class DatasetCapabilities:
has_raw_imu: bool
has_rtk_gt: bool
has_loop_closures: bool
platform_class: PlatformClass
@dataclass(frozen=True)
class DatasetFrame:
frame_idx: int
timestamp_ns: int
image_path: str
@dataclass(frozen=True)
class DatasetIMU:
timestamp_ns: int
accel: tuple[float, float, float] # m/s^2 in body frame
gyro: tuple[float, float, float] # rad/s in body frame
@dataclass(frozen=True)
class DatasetPose:
timestamp_ns: int
lat: float
lon: float
alt: float
qx: float
qy: float
qz: float
qw: float
class DatasetAdapter(ABC):
"""Uniform read-only iteration over a UAV dataset."""
@property
@abstractmethod
def name(self) -> str: ...
@property
@abstractmethod
def capabilities(self) -> DatasetCapabilities: ...
@abstractmethod
def iter_frames(self) -> Iterator[DatasetFrame]: ...
@abstractmethod
def iter_imu(self) -> Iterator[DatasetIMU]: ...
@abstractmethod
def iter_ground_truth(self) -> Iterator[DatasetPose]: ...
+63
View File
@@ -0,0 +1,63 @@
"""Tests for DatasetAdapter base contract."""
import pytest
from gps_denied.testing.datasets.base import (
DatasetAdapter,
DatasetCapabilities,
DatasetFrame,
DatasetIMU,
DatasetPose,
DatasetNotAvailableError,
PlatformClass,
)
def test_capabilities_defaults():
cap = DatasetCapabilities(
has_raw_imu=False,
has_rtk_gt=False,
has_loop_closures=False,
platform_class=PlatformClass.FIXED_WING,
)
assert cap.has_raw_imu is False
assert cap.platform_class == PlatformClass.FIXED_WING
def test_adapter_is_abstract():
with pytest.raises(TypeError):
DatasetAdapter() # type: ignore[abstract]
def test_dataset_not_available_error_is_exception():
assert issubclass(DatasetNotAvailableError, Exception)
def test_dataset_frame_dataclass_fields():
frame = DatasetFrame(
frame_idx=0,
timestamp_ns=1_000_000_000,
image_path="/tmp/x.jpg",
)
assert frame.frame_idx == 0
assert frame.timestamp_ns == 1_000_000_000
def test_dataset_imu_dataclass_fields():
imu = DatasetIMU(
timestamp_ns=1_000_000_000,
accel=(0.0, 0.0, -9.81),
gyro=(0.0, 0.0, 0.0),
)
assert imu.accel == (0.0, 0.0, -9.81)
def test_dataset_pose_dataclass_fields():
pose = DatasetPose(
timestamp_ns=1_000_000_000,
lat=49.0,
lon=32.0,
alt=100.0,
qx=0.0, qy=0.0, qz=0.0, qw=1.0,
)
assert pose.lat == 49.0