Files
StarPilot/openpilot/starpilot/galaxy/tests/test_segment_summary.py
T
firestar5683 80fbb4f6db Feature registration
Register vehicle controls, planner modes, UI actions and Galaxy services after their providers exist. Wire the shared runtime entrypoints and cover their combined behavior.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Kacper Rączy <gfw.kra@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: haraschax <6804392+haraschax@users.noreply.github.com>
Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

142 lines
6.5 KiB
Python

"""Measured local-segment coverage never becomes a fabricated route total."""
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from openpilot.cereal import messaging
import zstandard as zstd
from openpilot.starpilot.galaxy.segment_summary import SegmentSummary, SegmentSummaryChanged, SegmentSummaryUnavailable
NAME = '00000042--abcdef1234--0'
def car(stamp: int, speed: float, *, valid: bool = True):
event = messaging.new_message('carState', valid=valid)
event.logMonoTime = stamp
event.carState.vEgo = speed
return event
def control(stamp: int, lat: bool, long: bool, *, valid: bool = True):
event = messaging.new_message('carControl', valid=valid)
event.logMonoTime = stamp
event.carControl.latActive = lat
event.carControl.longActive = long
return event
def write(root: Path, events) -> Path:
segment = root / NAME
segment.mkdir(parents=True, exist_ok=True)
source = segment / 'rlog.zst'
source.write_bytes(zstd.compress(b''.join(event.to_bytes() for event in events)))
return source
class SegmentSummaryTest(unittest.TestCase):
def test_two_axes_and_speed_distance_from_serialized_events(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
write(root, [car(1_000_000_000, 0), control(1_000_000_000, True, False),
car(2_000_000_000, 10), control(2_000_000_000, True, True),
car(3_000_000_000, 10), control(3_000_000_000, False, True)])
result = SegmentSummary(root).snapshot(NAME, permitted=lambda: True)
self.assertEqual(result['source'], 'closed_local_rlog')
self.assertEqual(result['segmentName'], NAME)
self.assertEqual(len(result['sourceSha256']), 64)
self.assertEqual(result['observedCarSpanSeconds'], 2)
self.assertEqual(result['estimatedDistanceMeters'], 15)
self.assertEqual(result['observedLatActiveSeconds'], 2)
self.assertEqual(result['observedLongActiveSeconds'], 1)
self.assertTrue(result['sampleCoverageComplete'])
def test_missing_axis_gap_and_invalid_speed_are_unknown_not_zero(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
source = write(root, [car(1_000_000_000, 5)])
result = SegmentSummary(root).snapshot(NAME, permitted=lambda: True)
self.assertIsNone(result['observedCarSpanSeconds'])
self.assertIsNone(result['estimatedDistanceMeters'])
self.assertIsNone(result['observedLatActiveSeconds'])
self.assertIsNone(result['observedLongActiveSeconds'])
self.assertFalse(result['sampleCoverageComplete'])
source.write_bytes(zstd.compress(b''.join(event.to_bytes() for event in
[car(1_000_000_000, 5), car(4_000_000_000, 5), car(4_100_000_000, float('nan')),
control(1_000_000_000, False, True), control(2_000_000_000, False, True)])))
partial = SegmentSummary(root).snapshot(NAME, permitted=lambda: True)
self.assertEqual(partial['observedCarSpanSeconds'], 3)
self.assertIsNone(partial['estimatedDistanceMeters'])
self.assertGreaterEqual(partial['gaps']['carState'], 2)
self.assertEqual(partial['observedLatActiveSeconds'], 0)
self.assertEqual(partial['observedLongActiveSeconds'], 1)
self.assertFalse(partial['sampleCoverageComplete'])
def test_short_control_window_does_not_claim_complete_segment_coverage(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
write(root, [car(1_000_000_000, 5), car(2_000_000_000, 5),
control(1_500_000_000, True, False), control(1_750_000_000, True, False)])
result = SegmentSummary(root).snapshot(NAME, permitted=lambda: True)
self.assertEqual(result['observedLatActiveSeconds'], .25)
self.assertFalse(result['sampleCoverageComplete'])
def test_out_of_order_duplicates_and_invalid_reset_do_not_recount_intervals(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
stamps = [1_000_000_000, 2_000_000_000, 1_500_000_000, 1_600_000_000,
2_100_000_000, 2_200_000_000]
events = []
for stamp in stamps:
events.extend([car(stamp, 10), control(stamp, True, True)])
events.extend([car(2_200_000_000, 10), control(2_200_000_000, True, True),
car(2_300_000_000, float('nan')), control(2_300_000_000, True, True, valid=False)])
events.extend([car(2_250_000_000, 10), control(2_250_000_000, True, True),
car(2_260_000_000, 10), control(2_260_000_000, True, True)])
write(root, events)
result = SegmentSummary(root).snapshot(NAME, permitted=lambda: True)
self.assertEqual(result['estimatedDistanceMeters'], 11)
self.assertEqual(result['observedLatActiveSeconds'], 1.1)
self.assertEqual(result['observedLongActiveSeconds'], 1.1)
self.assertGreaterEqual(result['gaps']['carState'], 6)
self.assertGreaterEqual(result['gaps']['carControl'], 6)
self.assertFalse(result['sampleCoverageComplete'])
def test_active_symlink_and_post_decode_in_place_change(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
source = write(root, [car(1_000_000_000, 5), car(2_000_000_000, 5)])
lock = source.parent / 'rlog.lock'
lock.touch()
with self.assertRaises(SegmentSummaryChanged):
SegmentSummary(root).snapshot(NAME, permitted=lambda: True)
lock.unlink()
from openpilot.starpilot.galaxy import segment_summary
decode = segment_summary.decode_segment
def mutate(data, codec, *, cancelled):
events = decode(data, codec, cancelled=cancelled)
with source.open('ab') as output:
output.write(b'changed')
return events
with patch.object(segment_summary, 'decode_segment', mutate), self.assertRaises(SegmentSummaryChanged):
SegmentSummary(root).snapshot(NAME, permitted=lambda: True)
source.unlink()
source.symlink_to('missing')
with self.assertRaises(SegmentSummaryUnavailable):
SegmentSummary(root).snapshot(NAME, permitted=lambda: True)
def test_permission_loss_withholds_result(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
write(root, [car(1_000_000_000, 5), car(2_000_000_000, 5)])
calls = 0
def permit():
nonlocal calls
calls += 1
return calls < 4
with self.assertRaises(SegmentSummaryUnavailable):
SegmentSummary(root).snapshot(NAME, permitted=permit)