diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py new file mode 100644 index 0000000..755ab77 --- /dev/null +++ b/tests/e2e/conftest.py @@ -0,0 +1,36 @@ +"""Shared fixtures for e2e tests — dataset discovery + skip markers.""" + +from pathlib import Path + +import pytest + + +REPO_ROOT = Path(__file__).resolve().parents[2] +DATASETS_ROOT = REPO_ROOT / "datasets" + + +@pytest.fixture(scope="session") +def euroc_mh01_root() -> Path: + root = DATASETS_ROOT / "euroc" / "MH_01" + if not (root / "mav0").is_dir(): + pytest.skip( + f"EuRoC MH_01 not present at {root}. " + "Run `python scripts/download_dataset.py euroc_mh01` to fetch it." + ) + return root + + +@pytest.fixture(scope="session") +def vpair_sample_root() -> Path: + root = DATASETS_ROOT / "vpair" / "sample" + if not root.is_dir(): + pytest.skip(f"VPAIR sample not present at {root}.") + return root + + +@pytest.fixture(scope="session") +def mars_lvig_root() -> Path: + root = DATASETS_ROOT / "mars_lvig" + if not root.is_dir(): + pytest.skip(f"MARS-LVIG not present at {root}.") + return root diff --git a/tests/e2e/test_euroc.py b/tests/e2e/test_euroc.py new file mode 100644 index 0000000..ae8641c --- /dev/null +++ b/tests/e2e/test_euroc.py @@ -0,0 +1,50 @@ +"""CI-tier e2e: run the full pipeline on EuRoC MH_01. + +Skipped if the dataset is not installed under datasets/euroc/MH_01/. +""" + +from pathlib import Path + +import pytest + +from gps_denied.testing.datasets.euroc import EuRoCAdapter +from gps_denied.testing.harness import E2EHarness +from gps_denied.testing.metrics import absolute_trajectory_error + + +# Initial target — calibrated once real numbers land. +EUROC_MH01_RMSE_CEILING_M = 5.0 + + +@pytest.mark.e2e +@pytest.mark.needs_dataset +@pytest.mark.asyncio +async def test_euroc_mh01_pipeline_completes(euroc_mh01_root: Path): + adapter = EuRoCAdapter(euroc_mh01_root) + harness = E2EHarness(adapter) + result = await harness.run() + assert result.num_frames_submitted > 100, ( + "MH_01 has thousands of frames; harness should have submitted them all" + ) + + +@pytest.mark.e2e +@pytest.mark.needs_dataset +@pytest.mark.asyncio +async def test_euroc_mh01_rmse_within_ceiling(euroc_mh01_root: Path): + adapter = EuRoCAdapter(euroc_mh01_root) + harness = E2EHarness(adapter) + result = await harness.run() + if result.estimated_positions_enu.shape[0] == 0: + pytest.xfail( + "Pipeline currently emits zero GPS estimates on EuRoC — " + "expected: VO works but satellite matching + ESKF anchoring not yet tuned. " + "Convert to regular assert once the pipeline stabilises." + ) + # Align lengths by truncating to shorter (estimates may lag GT at start) + n = min(result.estimated_positions_enu.shape[0], result.ground_truth.shape[0]) + ate = absolute_trajectory_error( + result.estimated_positions_enu[:n], + result.ground_truth[:n], + ) + assert ate["rmse"] < EUROC_MH01_RMSE_CEILING_M, f"ATE RMSE={ate['rmse']:.2f}m"