mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 07:01:14 +00:00
feat(02-04): decorate 7 test files with @pytest.mark.ac markers
- test_acceptance.py: 8 decorators (AC-1.1, AC-2.1a, AC-3.3, AC-3.4, AC-4.1, AC-4.4, AC-1.4) - test_accuracy.py: 11 decorators (AC-1.1, AC-1.2, AC-1.3, AC-2.1a, AC-2.2, AC-4.1) - test_processor_pipe.py: 2 decorators (AC-4.4, AC-1.4) - test_gps_input_encoding.py: module-level pytestmark for AC-4.3 (13 tests) - test_sitl_integration.py: 9 decorators (AC-4.3, AC-4.4, AC-NEW-2, AC-5.2, AC-3.4) - test_mavlink.py: 5 decorators (AC-4.3, AC-5.2, AC-3.4) - test_schemas.py: 2 class-level decorators (AC-6.3, AC-6.3) - 14 non-deferred ACs covered; 45 tests collected under -m ac
This commit is contained in:
@@ -61,6 +61,8 @@ def _random_frame(h=200, w=200):
|
||||
# ---------------------------------------------------------------
|
||||
# AC-1: Normal flight — 20 consecutive frames
|
||||
# ---------------------------------------------------------------
|
||||
@pytest.mark.ac("AC-1.1")
|
||||
@pytest.mark.ac("AC-4.4")
|
||||
@pytest.mark.asyncio
|
||||
async def test_ac1_normal_flight(wired_processor):
|
||||
"""Twenty frames processed without crash; SSE events emitted for each."""
|
||||
@@ -76,6 +78,8 @@ async def test_ac1_normal_flight(wired_processor):
|
||||
# ---------------------------------------------------------------
|
||||
# AC-2: Tracking loss → recovery cycle
|
||||
# ---------------------------------------------------------------
|
||||
@pytest.mark.ac("AC-2.1a")
|
||||
@pytest.mark.ac("AC-3.4")
|
||||
@pytest.mark.asyncio
|
||||
async def test_ac2_tracking_loss_and_recovery(wired_processor, monkeypatch):
|
||||
"""
|
||||
@@ -122,6 +126,7 @@ async def test_ac2_tracking_loss_and_recovery(wired_processor, monkeypatch):
|
||||
# ---------------------------------------------------------------
|
||||
# AC-3: Performance — < 5 s per frame
|
||||
# ---------------------------------------------------------------
|
||||
@pytest.mark.ac("AC-4.1")
|
||||
@pytest.mark.asyncio
|
||||
async def test_ac3_performance_per_frame(wired_processor):
|
||||
"""Each process_frame call must complete in < 5 seconds (mock pipeline)."""
|
||||
@@ -144,6 +149,8 @@ async def test_ac3_performance_per_frame(wired_processor):
|
||||
# ---------------------------------------------------------------
|
||||
# AC-4: User anchor fix
|
||||
# ---------------------------------------------------------------
|
||||
@pytest.mark.ac("AC-4.4")
|
||||
@pytest.mark.ac("AC-1.4")
|
||||
@pytest.mark.asyncio
|
||||
async def test_ac4_user_anchor_fix(wired_processor):
|
||||
"""
|
||||
@@ -185,6 +192,8 @@ async def test_ac4_user_anchor_fix(wired_processor):
|
||||
# ---------------------------------------------------------------
|
||||
# AC-5: Sustained throughput — 50 frames
|
||||
# ---------------------------------------------------------------
|
||||
@pytest.mark.ac("AC-1.1")
|
||||
@pytest.mark.ac("AC-4.4")
|
||||
@pytest.mark.asyncio
|
||||
async def test_ac5_sustained_throughput(wired_processor):
|
||||
"""Process 50 frames back-to-back; no crashes, total < 30 seconds."""
|
||||
@@ -201,6 +210,7 @@ async def test_ac5_sustained_throughput(wired_processor):
|
||||
# ---------------------------------------------------------------
|
||||
# AC-6: Factor graph optimization converges
|
||||
# ---------------------------------------------------------------
|
||||
@pytest.mark.ac("AC-3.3")
|
||||
@pytest.mark.asyncio
|
||||
async def test_ac6_graph_optimization_convergence(wired_processor):
|
||||
"""After N frames the graph should report convergence."""
|
||||
|
||||
@@ -162,6 +162,7 @@ def test_benchmark_summary_non_empty():
|
||||
# AC-PERF-3: Latency < 400ms
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-4.1")
|
||||
def test_per_frame_latency_under_400ms():
|
||||
"""AC-PERF-3: p95 per-frame latency < 400ms on synthetic trajectory."""
|
||||
result = _run_benchmark(num_frames=20)
|
||||
@@ -182,6 +183,7 @@ def test_median_error_with_sat_corrections():
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.ac("AC-1.1")
|
||||
def test_pct_within_50m_with_sat_corrections():
|
||||
"""AC-PERF-1: ≥80% frames within 50m when satellite corrections are active."""
|
||||
result = _run_benchmark(num_frames=40, with_sat=True)
|
||||
@@ -191,6 +193,7 @@ def test_pct_within_50m_with_sat_corrections():
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.ac("AC-1.2")
|
||||
def test_pct_within_20m_with_sat_corrections():
|
||||
"""AC-PERF-2: ≥60% frames within 20m when satellite corrections are active."""
|
||||
result = _run_benchmark(num_frames=40, with_sat=True)
|
||||
@@ -239,6 +242,7 @@ def test_waypoint_steering_changes_direction():
|
||||
# AC-PERF-4: VO drift over 1 km straight segment
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-1.3")
|
||||
def test_vo_drift_under_100m_over_1km():
|
||||
"""AC-PERF-4: VO drift (no sat correction) over 1 km < 100 m."""
|
||||
bench = AccuracyBenchmark()
|
||||
@@ -252,6 +256,7 @@ def test_vo_drift_under_100m_over_1km():
|
||||
# AC-PERF-6: Covariance shrinks after satellite update
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-2.2")
|
||||
def test_covariance_shrinks_after_satellite_update():
|
||||
"""AC-PERF-6: ESKF position covariance trace decreases after satellite correction."""
|
||||
from gps_denied.core.eskf import ESKF
|
||||
@@ -274,6 +279,7 @@ def test_covariance_shrinks_after_satellite_update():
|
||||
# AC-PERF-5: Confidence tier transitions
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-2.1a")
|
||||
def test_confidence_high_after_fresh_satellite():
|
||||
"""AC-PERF-5: HIGH confidence when satellite correction is recent + covariance small."""
|
||||
from gps_denied.core.eskf import ESKF
|
||||
@@ -323,6 +329,8 @@ def test_confidence_failed_after_3_consecutive():
|
||||
# passes_acceptance_criteria integration
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-1.1")
|
||||
@pytest.mark.ac("AC-1.2")
|
||||
def test_passes_acceptance_criteria_full_pass():
|
||||
"""passes_acceptance_criteria returns (True, all-True) for ideal data."""
|
||||
result = BenchmarkResult(
|
||||
@@ -336,6 +344,7 @@ def test_passes_acceptance_criteria_full_pass():
|
||||
assert all(checks.values())
|
||||
|
||||
|
||||
@pytest.mark.ac("AC-4.1")
|
||||
def test_passes_acceptance_criteria_latency_fail():
|
||||
"""passes_acceptance_criteria fails when latency exceeds 400ms."""
|
||||
result = BenchmarkResult(
|
||||
@@ -349,6 +358,7 @@ def test_passes_acceptance_criteria_latency_fail():
|
||||
assert checks["AC-PERF-3: p95 latency < 400ms"] is False
|
||||
|
||||
|
||||
@pytest.mark.ac("AC-1.1")
|
||||
def test_passes_acceptance_criteria_accuracy_fail():
|
||||
"""passes_acceptance_criteria fails when less than 80% within 50m."""
|
||||
result = BenchmarkResult(
|
||||
|
||||
@@ -17,7 +17,7 @@ import time
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
pytestmark = [pytest.mark.blackbox]
|
||||
pytestmark = [pytest.mark.blackbox, pytest.mark.ac("AC-4.3")]
|
||||
|
||||
from gps_denied.core.mavlink import _confidence_to_fix_type, _eskf_to_gps_input
|
||||
from gps_denied.schemas import GPSPoint
|
||||
|
||||
@@ -75,6 +75,7 @@ def test_unix_to_gps_time_recent():
|
||||
# MAV-02: ESKF → GPS_INPUT field mapping
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-4.3")
|
||||
def test_confidence_to_fix_type():
|
||||
"""MAV-02: confidence tier → fix_type mapping."""
|
||||
assert _confidence_to_fix_type(ConfidenceTier.HIGH) == 3
|
||||
@@ -83,6 +84,7 @@ def test_confidence_to_fix_type():
|
||||
assert _confidence_to_fix_type(ConfidenceTier.FAILED) == 0
|
||||
|
||||
|
||||
@pytest.mark.ac("AC-4.3")
|
||||
def test_eskf_to_gps_input_position():
|
||||
"""MAV-02: ENU position → degE7 lat/lon."""
|
||||
# 1° lat ≈ 111319.5 m; move 111319.5 m North → lat + 1°
|
||||
@@ -93,6 +95,7 @@ def test_eskf_to_gps_input_position():
|
||||
assert abs(msg.lat - expected_lat) <= 10 # within 1 µ-degree tolerance
|
||||
|
||||
|
||||
@pytest.mark.ac("AC-4.3")
|
||||
def test_eskf_to_gps_input_lon():
|
||||
"""MAV-02: East displacement → longitude shift."""
|
||||
cos_lat = math.cos(math.radians(ORIGIN.lat))
|
||||
@@ -212,6 +215,8 @@ def test_consecutive_failure_counter_increments_on_low(bridge):
|
||||
assert bridge._consecutive_failures == 2
|
||||
|
||||
|
||||
@pytest.mark.ac("AC-5.2")
|
||||
@pytest.mark.ac("AC-3.4")
|
||||
def test_reloc_request_triggered_after_3_failures(bridge):
|
||||
"""MAV-04: after 3 failures the re-localisation callback is called."""
|
||||
received: list[RelocalizationRequest] = []
|
||||
|
||||
@@ -211,6 +211,7 @@ async def test_failure_counter_resets_on_recovery():
|
||||
# PIPE-07: ESKF state pushed to MAVLink
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-4.4")
|
||||
@pytest.mark.asyncio
|
||||
async def test_mavlink_state_pushed_per_frame():
|
||||
"""PIPE-07: MAVLinkBridge.update_state called on every frame with ESKF."""
|
||||
@@ -283,6 +284,7 @@ async def test_convert_object_to_gps_fallback_without_coord():
|
||||
# ESKF initialization via create_flight
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-1.4")
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_flight_initialises_eskf():
|
||||
"""create_flight should seed ESKF for the new flight."""
|
||||
|
||||
@@ -22,6 +22,7 @@ from gps_denied.schemas.flight import (
|
||||
|
||||
# ── GPSPoint ──────────────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.ac("AC-6.3")
|
||||
class TestGPSPoint:
|
||||
def test_valid(self):
|
||||
p = GPSPoint(lat=48.275, lon=37.385)
|
||||
@@ -102,6 +103,7 @@ class TestFlightCreateRequest:
|
||||
|
||||
# ── Waypoint ──────────────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.ac("AC-6.3")
|
||||
class TestWaypoint:
|
||||
def test_valid(self):
|
||||
wp = Waypoint(
|
||||
|
||||
@@ -90,6 +90,7 @@ def _wait_for_tcp(host: str, port: int, timeout: float = 30.0) -> bool:
|
||||
# SITL-01: Connection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-4.3")
|
||||
def test_sitl_tcp_port_reachable():
|
||||
"""SITL-01: ArduPilot SITL TCP port is reachable before running tests."""
|
||||
reachable = _wait_for_tcp(SITL_HOST, SITL_PORT, timeout=30.0)
|
||||
@@ -99,6 +100,7 @@ def test_sitl_tcp_port_reachable():
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.ac("AC-4.3")
|
||||
def test_pymavlink_connection_to_sitl():
|
||||
"""SITL-01: pymavlink connects to SITL without error."""
|
||||
pytest.importorskip("pymavlink", reason="pymavlink not installed")
|
||||
@@ -115,6 +117,8 @@ def test_pymavlink_connection_to_sitl():
|
||||
# SITL-02: GPS_INPUT accepted by SITL EKF
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-4.3")
|
||||
@pytest.mark.ac("AC-NEW-2")
|
||||
def test_gps_input_accepted_by_sitl():
|
||||
"""SITL-02: Sending GPS_INPUT produces GPS_RAW_INT with fix_type >= 3."""
|
||||
pytest.importorskip("pymavlink", reason="pymavlink not installed")
|
||||
@@ -173,6 +177,7 @@ def test_gps_input_accepted_by_sitl():
|
||||
# SITL-03: MAVLinkBridge lifecycle
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-4.3")
|
||||
@pytest.mark.asyncio
|
||||
async def test_mavlink_bridge_start_stop_with_sitl():
|
||||
"""SITL-03: MAVLinkBridge.start/stop with real SITL TCP connection."""
|
||||
@@ -228,6 +233,8 @@ async def test_imu_callback_fires_from_sitl():
|
||||
# SITL-05: GPS_INPUT rate ≥ 5 Hz
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-4.3")
|
||||
@pytest.mark.ac("AC-4.4")
|
||||
@pytest.mark.asyncio
|
||||
async def test_gps_input_rate_at_least_5hz():
|
||||
"""SITL-05: MAVLinkBridge delivers GPS_INPUT at ≥5 Hz over 1 second."""
|
||||
@@ -297,6 +304,8 @@ async def test_telemetry_reaches_sitl_at_1hz():
|
||||
# SITL-07: Reloc request after 3 consecutive failures
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.ac("AC-5.2")
|
||||
@pytest.mark.ac("AC-3.4")
|
||||
@pytest.mark.asyncio
|
||||
async def test_reloc_request_after_3_failures_with_sitl():
|
||||
"""SITL-07: After 3 FAILED-confidence updates, reloc callback fires."""
|
||||
|
||||
Reference in New Issue
Block a user