mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-10 14:33:42 +08:00
138 lines
6.5 KiB
Python
138 lines
6.5 KiB
Python
"""Compiled incumbent IDs/layout and wire regressions for the nested startup type."""
|
|
from importlib.resources import files
|
|
from pathlib import Path
|
|
import hashlib
|
|
import json
|
|
import re
|
|
|
|
import capnp
|
|
import pytest
|
|
from openpilot.cereal import custom, CEREAL_PATH
|
|
from openpilot.starpilot.car.tesla.tests.schema_fixture import layout
|
|
from openpilot.starpilot.car.tesla.tests.test_radar_schema_evolution import populated, assert_occupied_equal
|
|
|
|
FIXTURE = Path(__file__).with_name('fixtures') / 'custom_before_preap_pedal.capnp'
|
|
REVIEW = json.loads(FIXTURE.with_name('custom_reviewed_schema_additions.json').read_text())
|
|
|
|
|
|
@pytest.fixture
|
|
def historical():
|
|
parser = capnp.SchemaParser()
|
|
yield parser.load(str(FIXTURE), imports=[CEREAL_PATH, str(files('opendbc') / 'car')])
|
|
|
|
|
|
def compare_node(old, new):
|
|
assert old.schema.node.id == new.schema.node.id
|
|
kind = old.schema.node.which()
|
|
assert kind == new.schema.node.which()
|
|
approved = REVIEW['types'].get(hex(old.schema.node.id))
|
|
if kind == 'struct':
|
|
old_layout, new_layout = layout(old.schema), layout(new.schema)
|
|
if approved is None:
|
|
assert old_layout == new_layout
|
|
else:
|
|
assert approved['kind'] == 'struct'
|
|
old_fields = old_layout.pop('fields', [])
|
|
assert len(old_fields) == approved['old_field_count']
|
|
assert old_layout == approved['old_storage']
|
|
expected = {**approved['new_storage'], 'fields': old_fields + approved['added_fields']}
|
|
assert new_layout == expected
|
|
elif kind == 'enum':
|
|
old_layout, new_layout = old.schema.node.enum.to_dict(), new.schema.node.enum.to_dict()
|
|
if approved is None:
|
|
assert old_layout == new_layout
|
|
else:
|
|
assert approved['kind'] == 'enum'
|
|
old_values = old_layout.pop('enumerants')
|
|
assert len(old_values) == approved['old_field_count']
|
|
assert old_layout == approved['old_storage']
|
|
assert new_layout == {**approved['new_storage'], 'enumerants': old_values + approved['added_enumerants']}
|
|
for child in old.schema.node.nestedNodes:
|
|
compare_node(getattr(old, child.name), getattr(new, child.name))
|
|
|
|
|
|
def test_all_incumbent_custom_type_ids_fields_and_layouts_are_preserved(historical):
|
|
assert hashlib.sha256(FIXTURE.read_bytes()).hexdigest() == REVIEW['incumbent_fixture_sha256']
|
|
for name in re.findall(r'^(?:struct|enum) (\w+)', FIXTURE.read_text(), re.MULTILINE):
|
|
old = getattr(historical, name)
|
|
approved = REVIEW['types'].get(hex(old.schema.node.id))
|
|
current_name = approved['after_name'] if approved else name
|
|
compare_node(old, getattr(custom, current_name))
|
|
# Startup capsules have no parent slots; published phone GPS@3 stays occupied.
|
|
assert set(custom.StarPilotCarControl.schema.fields) == {
|
|
'hudControl', 'steeringLimitInfo', 'navigation', 'phoneGps'}
|
|
assert custom.StarPilotCarControl.PreapPedalStartup.schema.node.id not in {
|
|
getattr(historical, name).schema.node.id for name in
|
|
re.findall(r'^struct (\w+)', FIXTURE.read_text(), re.MULTILINE)}
|
|
|
|
|
|
def test_standalone_startup_does_not_modify_parent_and_old_relay_preserves_navigation(historical):
|
|
parent = populated(custom.StarPilotCarControl)
|
|
original = parent.to_bytes()
|
|
capsule = custom.StarPilotCarControl.PreapPedalStartup.new_message(
|
|
version=1, kind=5, carParamsSha256=bytes(range(32)), safetyModel=39,
|
|
safetyParam=7, factor=1.25, zero=-0.5, pedalBus=2, sourceMonoTime=1234)
|
|
with custom.StarPilotCarControl.PreapPedalStartup.from_bytes(capsule.to_bytes()) as recovered:
|
|
assert recovered.to_dict() == capsule.to_dict()
|
|
parent.clear_write_flag()
|
|
assert parent.to_bytes() == original
|
|
with historical.StarPilotCarControl.from_bytes(original) as old:
|
|
assert_occupied_equal(parent, old)
|
|
relayed = old.as_builder().to_bytes()
|
|
with custom.StarPilotCarControl.from_bytes(relayed) as recovered:
|
|
assert_occupied_equal(parent, recovered)
|
|
|
|
|
|
def test_old_gps_and_pedal_status_survive_current_reader_and_old_relay(historical):
|
|
old = historical.StarPilotCarState.new_message(pedalMaxRegen=True, pedalLongActive=True,
|
|
sourceCarStateMonoTime=123456, gps={'sourceMonoTime': 123456, 'latitude': 41.25,
|
|
'longitude': -87.5, 'altitude': 180., 'speed': 20., 'bearingDeg': 90., 'hasFix': True,
|
|
'unixTimestampMillis': 1700000000000, 'vNED': [1., 2., 3.]})
|
|
with custom.StarPilotCarState.from_bytes(old.to_bytes()) as current:
|
|
assert {name: current.to_dict()[name] for name in old.to_dict()} == old.to_dict()
|
|
relayed = current.as_builder().to_bytes()
|
|
with historical.StarPilotCarState.from_bytes(relayed) as recovered:
|
|
assert recovered.to_dict() == old.to_dict()
|
|
|
|
|
|
def test_additive_feedback_survives_incumbent_reader_relay(historical):
|
|
original = historical.StarPilotCarState.new_message(
|
|
pedalMaxRegen=True, pedalLongActive=True, sourceCarStateMonoTime=123456,
|
|
gps={'latitude': 41.25, 'longitude': -87.5, 'hasFix': True})
|
|
incumbent = original.to_dict()
|
|
with custom.StarPilotCarState.from_bytes(original.to_bytes()) as decoded:
|
|
current = decoded.as_builder()
|
|
feedback = {'version': 1, 'cardSession': bytes(range(16)), 'publicationSequence': 27,
|
|
'sourceCarStateMonoTime': 123456, 'outputKnown': True,
|
|
'reportedTorque': 0.25, 'coBodySha256': bytes(range(32))}
|
|
current.feedbackPublication = feedback
|
|
expected_feedback = current.feedbackPublication.to_dict()
|
|
with historical.StarPilotCarState.from_bytes(current.to_bytes()) as old:
|
|
assert old.to_dict() == incumbent
|
|
relayed = old.as_builder().to_bytes()
|
|
with custom.StarPilotCarState.from_bytes(relayed) as recovered:
|
|
assert {name: recovered.to_dict()[name] for name in incumbent} == incumbent
|
|
assert recovered.feedbackPublication.to_dict() == expected_feedback
|
|
|
|
|
|
@pytest.mark.parametrize(('fields', 'compatible'), [
|
|
('value @0 :UInt32; label @1 :Text;', True),
|
|
('value @0 :UInt32; label @1 :Text; extra @2 :Bool;', False),
|
|
('value @0 :UInt64; label @1 :Text;', False),
|
|
('value @0 :UInt32 = 42; label @1 :Text;', False),
|
|
('value @1 :UInt32; label @0 :Text;', False),
|
|
('value @0 :UInt32;', False),
|
|
])
|
|
def test_compiled_compatibility_check_rejects_unreviewed_changes(tmp_path, fields, compatible):
|
|
header = '@0x9db79da1a8afbd10; struct Sample @0x9713dcf4d2386e50 {'
|
|
old_path, new_path = tmp_path / 'old.capnp', tmp_path / 'new.capnp'
|
|
old_path.write_text(header + 'value @0 :UInt32; label @1 :Text; }')
|
|
new_path.write_text(header + fields + '}')
|
|
old_parser, new_parser = capnp.SchemaParser(), capnp.SchemaParser()
|
|
old, new = old_parser.load(str(old_path)).Sample, new_parser.load(str(new_path)).Sample
|
|
if compatible:
|
|
compare_node(old, new)
|
|
else:
|
|
with pytest.raises(AssertionError):
|
|
compare_node(old, new)
|