Files
firestar5683 5deb059c54 Learning Workers
Process drive evidence and fit bounded steering profiles off the controls loop.
2026-10-14 20:02:39 -05:00

98 lines
4.0 KiB
Python

"""No partial FLM segment success after corrupt compression or Cereal data."""
import bz2
from unittest import mock
import pytest
import zstandard as zstd
from openpilot.cereal import messaging
from openpilot.starpilot.flm import log_decode
def _events() -> bytes:
first = messaging.new_message("deviceState", valid=True)
first.logMonoTime = 101
first.deviceState.started = True
second = messaging.new_message("carState", valid=False)
second.logMonoTime = 202
return first.to_bytes() + second.to_bytes()
def _compressed(raw: bytes, codec: str) -> bytes:
return zstd.ZstdCompressor().compress(raw) if codec == "zst" else bz2.compress(raw)
@pytest.mark.parametrize("codec", ["zst", "bz2"])
def test_complete_segment_retains_all_event_envelopes_and_timestamps(codec):
decoded = log_decode.decode_segment(_compressed(_events(), codec), codec)
assert len(decoded) == 2
assert [(event.which(), event.logMonoTime, event.valid) for event in decoded] == [
("deviceState", 101, True), ("carState", 202, False),
]
assert decoded[0].deviceState.started
@pytest.mark.parametrize("codec", ["zst", "bz2"])
def test_truncated_trailing_and_wrong_codec_reject_entire_segment(codec):
source = _compressed(_events(), codec)
for candidate in (source[:-1], source + _compressed(_events(), codec), source + b"trailing"):
with pytest.raises(log_decode.LogDecodeError):
log_decode.decode_segment(candidate, codec)
other = "bz2" if codec == "zst" else "zst"
with pytest.raises(log_decode.LogDecodeError):
log_decode.decode_segment(source, other)
def test_expansion_compressed_count_and_cereal_corruption_caps(monkeypatch):
raw = _events()
encoded = _compressed(raw, "zst")
monkeypatch.setattr(log_decode, "MAX_COMPRESSED_BYTES", len(encoded) - 1)
with pytest.raises(log_decode.LogDecodeError):
log_decode.decode_segment(encoded, "zst")
monkeypatch.setattr(log_decode, "MAX_COMPRESSED_BYTES", 32 * 1024 * 1024)
monkeypatch.setattr(log_decode, "MAX_EXPANDED_BYTES", len(raw) - 1)
with pytest.raises(log_decode.LogDecodeError):
log_decode.decode_segment(encoded, "zst")
monkeypatch.setattr(log_decode, "MAX_EXPANDED_BYTES", 64 * 1024 * 1024)
monkeypatch.setattr(log_decode, "MAX_MESSAGES", 1)
with pytest.raises(log_decode.LogDecodeError):
log_decode.decode_segment(encoded, "zst")
monkeypatch.setattr(log_decode, "MAX_MESSAGES", 250_000)
with pytest.raises(log_decode.LogDecodeError):
log_decode.decode_segment(_compressed(raw + b"not an Event", "zst"), "zst")
def test_cancelled_and_wrong_input_never_return_prefix(monkeypatch):
encoded = _compressed(_events(), "zst")
monkeypatch.setattr(log_decode, "CHUNK_BYTES", 8)
calls = 0
def cancel():
nonlocal calls
calls += 1
return calls > 3
with pytest.raises(log_decode.LogDecodeError, match="cancelled"):
log_decode.decode_segment(encoded, "zst", cancelled=cancel)
decode = mock.Mock(wraps=log_decode.decode_segment)
with pytest.raises(log_decode.LogDecodeError):
decode(bytearray(encoded), "zst")
with pytest.raises(log_decode.LogDecodeError):
log_decode.decode_segment(encoded, "raw")
def test_event_traversal_limit_rejects_whole_segment(monkeypatch):
monkeypatch.setattr(log_decode, "EVENT_TRAVERSAL_WORDS", 1)
with pytest.raises(log_decode.LogDecodeError, match="Cereal"):
log_decode.decode_segment(_compressed(_events(), "zst"), "zst")
@pytest.mark.parametrize("codec", ["zst", "bz2"])
def test_filtered_retention_still_validates_entire_segment(codec):
encoded = _compressed(_events(), codec)
selected = log_decode.decode_segment(encoded, codec, retain=frozenset(("carState",)))
assert [(event.which(), event.logMonoTime) for event in selected] == [("carState", 202)]
with mock.patch.object(log_decode, "MAX_MESSAGES", 1), pytest.raises(log_decode.LogDecodeError, match="message count"):
log_decode.decode_segment(encoded, codec, retain=frozenset())
with pytest.raises(log_decode.LogDecodeError):
log_decode.decode_segment(_compressed(_events() + b"not an Event", codec), codec, retain=frozenset())