mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-06 20:43:46 +08:00
Repair hosted display selection and migrated test fixtures
Select the real headless backend for hosted AOL and RELEASE tests. Isolate SpacePilot scripted clocks from unrelated UI readers. Update camera and replay fixtures for existing native APIs, pin the reviewed schema addition, and supply declared CAN batch and throttle inputs. Preserve production guards, acceptance assertions, selected cases, capability skips and receipt gating. Refresh the canonical protected inventory receipt.
This commit is contained in:
@@ -343,6 +343,8 @@ jobs:
|
||||
run: scons
|
||||
- name: Gate every assigned lifecycle module serially
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
RAYLIB_BACKEND: headless
|
||||
run: python3 tools/ci/run_vehicle_tests.py --suite aol-lifecycle --shard-index "${{ matrix.shard }}" --shard-count 16 --output "ci-results/aol-lifecycle-${{ matrix.shard }}"
|
||||
- name: Save every lifecycle outcome and native build
|
||||
if: always()
|
||||
@@ -453,6 +455,8 @@ jobs:
|
||||
run: scons
|
||||
- name: Run every assigned RELEASE target serially
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
RAYLIB_BACKEND: headless
|
||||
run: python3 tools/ci/run_vehicle_tests.py --suite safety-release --shard-index "${{ matrix.shard }}" --shard-count 4 --output "ci-results/safety-release-${{ matrix.shard }}"
|
||||
- name: Save every RELEASE outcome and native build
|
||||
if: always()
|
||||
|
||||
@@ -244,14 +244,14 @@ class TestIoniq6PrepublicationHandoff(unittest.TestCase):
|
||||
cp = CarInterface.get_params(model, fp, [], False, False, False)
|
||||
ci = CarInterface(cp)
|
||||
packer = CANPacker(DBC[model][Bus.pt])
|
||||
ci.update((1_000_000_000, []))
|
||||
ci.update([(1_000_000_000, [])])
|
||||
sources = [(parser, state) for parser in ci.can_parsers.values()
|
||||
for state in parser.message_states.values() if not state.ignore_alive and state.address != 0x2E0]
|
||||
for frame in range(120):
|
||||
packets = [packer.make_can_msg(state.name, parser.bus,
|
||||
{"COUNTER": frame % 256} if any(sig.name == "COUNTER" for sig in state.signals) else {})
|
||||
for parser, state in sources]
|
||||
state = ci.update((1_000_000_000 + frame * 100_000_000, packets))
|
||||
state = ci.update([(1_000_000_000 + frame * 100_000_000, packets)])
|
||||
self.assertTrue(state.canValid)
|
||||
self.assertFalse(cp.openpilotLongitudinalControl)
|
||||
self.assertTrue(cp.pcmCruise)
|
||||
@@ -261,7 +261,7 @@ class TestIoniq6PrepublicationHandoff(unittest.TestCase):
|
||||
packets = [packer.make_can_msg(msg.name, parser.bus,
|
||||
{"COUNTER": frame % 256} if any(sig.name == "COUNTER" for sig in msg.signals) else {})
|
||||
for parser, msg in sources if msg.name != "MDPS"]
|
||||
state = ci.update((1_000_000_000 + frame * 100_000_000, packets))
|
||||
state = ci.update([(1_000_000_000 + frame * 100_000_000, packets)])
|
||||
self.assertFalse(state.canValid)
|
||||
|
||||
def test_card_tags_aol_candidate_before_cp_publication_without_disabling_ecu(self):
|
||||
|
||||
@@ -98,6 +98,7 @@ class Plant:
|
||||
# always start at current speed
|
||||
model.modelV2.velocity.x = [float(self.speed)] + [float(self.speed + 0.5)] * (len(T_IDXS) - 1)
|
||||
model.modelV2.acceleration.x = [0.0] * len(T_IDXS)
|
||||
model.modelV2.meta.disengagePredictions.gasPressProbs = [float(prob_throttle)] * 6
|
||||
|
||||
control.controlsState.longControlState = LongCtrlState.pid if self.enabled else LongCtrlState.off
|
||||
ss.selfdriveState.experimentalMode = self.e2e
|
||||
|
||||
+27
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"reviewed_schema_commit": "cd0f46b9d65a9c5e47002f57e5ad6625c9757c63",
|
||||
"reviewed_schema_sha256": "5effa2eb1fbbada3d1bbaae0acd3d31d780d8ec0e6ee1e214fedbc38ae400182",
|
||||
"reviewed_schema_commit": "63beafc6af294d0759b47dc3bc546f24082a2a5d",
|
||||
"reviewed_schema_sha256": "ca01ef4f6d2b15c5fac59ce237ec6e49f1167e2408903c9088ee322ca490de31",
|
||||
"incumbent_fixture_sha256": "a36ab54c00b5bbe61d520873d24aadfb8f33babd70c31a07046516d5545838d9",
|
||||
"types": {
|
||||
"0xf35cc4560bbf6ec2": {
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"new_storage": {
|
||||
"dataWordCount": 3,
|
||||
"pointerCount": 6,
|
||||
"pointerCount": 7,
|
||||
"preferredListEncoding": "inlineComposite",
|
||||
"isGroup": false,
|
||||
"discriminantCount": 0,
|
||||
@@ -59,11 +59,31 @@
|
||||
"hadExplicitDefault": false
|
||||
},
|
||||
"name": "toyotaCruise",
|
||||
"codeOrder": 38,
|
||||
"codeOrder": 39,
|
||||
"discriminantValue": 65535,
|
||||
"ordinal": {
|
||||
"explicit": 38
|
||||
}
|
||||
},
|
||||
{
|
||||
"slot": {
|
||||
"offset": 6,
|
||||
"type": {
|
||||
"struct": {
|
||||
"typeId": 15172299076723568810
|
||||
}
|
||||
},
|
||||
"defaultValue": {
|
||||
"schemaValueHex": "00000000040000000000000002000100100000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"hadExplicitDefault": false
|
||||
},
|
||||
"name": "steeringLimitInfo",
|
||||
"codeOrder": 38,
|
||||
"discriminantValue": 65535,
|
||||
"ordinal": {
|
||||
"explicit": 39
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -656,7 +676,8 @@
|
||||
},
|
||||
"published_addition_commits": {
|
||||
"slc": "ab8368f287b78b42f8590722878c7d5834609e19",
|
||||
"flm": "b151ca11cb4d88f32d7adfdbcff50baf29f4fbbc"
|
||||
"flm": "b151ca11cb4d88f32d7adfdbcff50baf29f4fbbc",
|
||||
"tesla_cooperative_steering": "63beafc6af294d0759b47dc3bc546f24082a2a5d"
|
||||
},
|
||||
"review_basis": "Published SLC and FLM append-only changes. Every incumbent compiled field descriptor and enum prefix was independently checked unchanged; only the exact seven typed deltas below are permitted."
|
||||
"review_basis": "Published SLC, FLM, and Tesla cooperative-steering additions. Every incumbent wire field descriptor and enum prefix was independently checked unchanged; only the exact typed deltas listed here are permitted. Tesla adds steeringLimitInfo @39 at pointer slot 6 using the existing SteeringLimitInfo type. ToyotaCruise @38 retains pointer slot 5, type, and default; only its declaration codeOrder changes from 38 to 39."
|
||||
}
|
||||
|
||||
@@ -115,6 +115,47 @@ def test_additive_feedback_survives_incumbent_reader_relay(historical):
|
||||
assert recovered.feedbackPublication.to_dict() == expected_feedback
|
||||
|
||||
|
||||
def test_additive_steering_limits_preserve_defaults_and_survive_incumbent_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:
|
||||
assert not decoded.steeringLimitInfo.valid
|
||||
assert decoded.steeringLimitInfo.monoTime == 0
|
||||
current = decoded.as_builder()
|
||||
current.feedbackPublication = {'version': 1, 'publicationSequence': 27, 'reportedTorque': 0.25}
|
||||
current.toyotaCruise = {'version': 1, 'status': 'active', 'targetKph': 72., 'sessionId': 'incumbent'}
|
||||
current.steeringLimitInfo = {
|
||||
'valid': True, 'modelLimitErrorDeg': 1.25, 'resumeLimitErrorDeg': 2.5,
|
||||
'cooperativeLimitErrorDeg': 3.75, 'cooperativeOffsetDeg': -0.5,
|
||||
'monoTime': 123456789, 'combinedLimitErrorDeg': 4.0}
|
||||
expected = current.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 recovered.to_dict() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(('before', 'after'), [
|
||||
('dashboardSpeedLimit @3 :Float32;', 'dashboardSpeedLimit @3 :Float64;'),
|
||||
('toyotaCruise @38 :ToyotaCruise;', 'toyotaCruise @38 :StarPilotCarControl.SteeringLimitInfo;'),
|
||||
('steeringLimitInfo @39 :StarPilotCarControl.SteeringLimitInfo;', 'steeringLimitInfo @39 :StarPilotCarState.Gps;'),
|
||||
('steeringLimitInfo @39 :StarPilotCarControl.SteeringLimitInfo;',
|
||||
'steeringLimitInfo @39 :StarPilotCarControl.SteeringLimitInfo; unreviewed @40 :Bool;'),
|
||||
])
|
||||
def test_reviewed_steering_addition_does_not_allow_other_schema_changes(historical, tmp_path, before, after):
|
||||
source = Path(CEREAL_PATH, 'custom.capnp').read_text()
|
||||
assert source.count(before) == 1
|
||||
path = tmp_path / 'unreviewed.capnp'
|
||||
path.write_text(source.replace(before, after))
|
||||
parser = capnp.SchemaParser()
|
||||
changed = parser.load(str(path), imports=[CEREAL_PATH, str(files('opendbc') / 'car')])
|
||||
with pytest.raises(AssertionError):
|
||||
compare_node(historical.StarPilotCarState, changed.StarPilotCarState)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(('fields', 'compatible'), [
|
||||
('value @0 :UInt32; label @1 :Text;', True),
|
||||
('value @0 :UInt32; label @1 :Text; extra @2 :Bool;', False),
|
||||
|
||||
@@ -574,11 +574,11 @@ def test_published_readiness_survives_buffering_but_rejects_seeks_and_old_epochs
|
||||
|
||||
|
||||
@pytest.mark.parametrize('matching_frame', (False, True))
|
||||
def test_epoch_reset_preserves_native_camera_stream_and_eligible_paused_frame(monkeypatch, matching_frame):
|
||||
@pytest.mark.parametrize('streams,expected_streams', (({0, 2}, [0, 2]), ([2, 0], [2, 0])))
|
||||
def test_epoch_reset_preserves_native_camera_stream_and_eligible_paused_frame(monkeypatch, matching_frame, streams, expected_streams):
|
||||
monkeypatch.setattr('openpilot.starpilot.ui.runtime_snapshot.RuntimeSnapshotAdapter', Mock())
|
||||
monkeypatch.setattr('openpilot.starpilot.ui.pip_render.PiPRenderer', Mock())
|
||||
core: Any = object.__new__(Compositor)
|
||||
streams = {0, 2}
|
||||
client = Mock(frame_id=5 if matching_frame else 4, timestamp_sof=9_000_000_000, timestamp_eof=9_010_000_000)
|
||||
client.is_connected.return_value = True
|
||||
frame = SimpleNamespace(frame_id=5)
|
||||
@@ -610,7 +610,7 @@ def test_epoch_reset_preserves_native_camera_stream_and_eligible_paused_frame(mo
|
||||
callback()
|
||||
assert core.render_receipts == {}
|
||||
assert core.camera.client is client
|
||||
assert core.camera.available_streams == streams
|
||||
assert core.camera.available_streams == expected_streams
|
||||
assert core.camera.available_streams is not streams
|
||||
core.camera._initialize_textures.assert_called_once()
|
||||
assert core.camera.frame is (frame if matching_frame else None)
|
||||
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
|
||||
from openpilot.selfdrive.ui.mici.layouts import main
|
||||
from openpilot.selfdrive.ui.mici.onroad import cameraview
|
||||
from openpilot.selfdrive.ui.onroad import cameraview as base_cameraview
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -152,7 +153,8 @@ def camera(monkeypatch):
|
||||
|
||||
monkeypatch.setattr(cameraview, 'VisionIpcClient', new_client)
|
||||
monkeypatch.setattr(cameraview, 'COMMA_HARDWARE', True)
|
||||
monkeypatch.setattr(cameraview, 'destroy_egl_image', lambda image: events.append(('egl', image)))
|
||||
monkeypatch.setattr(base_cameraview, 'COMMA_HARDWARE', True)
|
||||
monkeypatch.setattr(base_cameraview, 'destroy_egl_image', lambda image: events.append(('egl', image)))
|
||||
monkeypatch.setattr(cameraview.rl, 'unload_texture', lambda texture: events.append(('texture', texture.id)))
|
||||
monkeypatch.setattr(cameraview.rl, 'unload_shader', lambda shader: events.append(('shader', shader.id)))
|
||||
monkeypatch.setattr(cameraview.rl, 'get_time', lambda: 1.0)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
"""Cold native entry and preempted clock reads from the recorded d5 exit paths."""
|
||||
|
||||
import time
|
||||
from types import SimpleNamespace as NS
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.starpilot.ui.spacepilot import ignition, native, session
|
||||
from openpilot.starpilot.ui.spacepilot import guard, ignition, native, session
|
||||
from openpilot.starpilot.ui.tests.test_spacepilot_session import Clock, Vehicle, game_ui, offroad, setup as setup
|
||||
from openpilot.starpilot.ui.tests.test_spacepilot_state import mature_gate, read
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
@@ -20,11 +21,25 @@ def clock_reads(monkeypatch, *, delayed=1, backwards=False, offset=2_000_000_000
|
||||
boots.append(now + offset)
|
||||
now += max(delta, 0)
|
||||
mono = Mock(side_effect=monos)
|
||||
monkeypatch.setattr(session.time, 'monotonic_ns', mono)
|
||||
monkeypatch.setattr(session.time, 'clock_gettime_ns', Mock(side_effect=boots))
|
||||
# Keep background UI readers from consuming the scripted clock pairs.
|
||||
monkeypatch.setattr(guard, 'time', NS(monotonic_ns=mono, clock_gettime_ns=Mock(side_effect=boots), CLOCK_BOOTTIME=time.CLOCK_BOOTTIME))
|
||||
return mono
|
||||
|
||||
|
||||
def test_clock_reads_leave_unrelated_process_clocks_untouched(monkeypatch):
|
||||
process_mono, process_boot = time.monotonic_ns, time.clock_gettime_ns
|
||||
mono = clock_reads(monkeypatch, delayed=3)
|
||||
assert time.monotonic_ns is process_mono
|
||||
assert time.clock_gettime_ns is process_boot
|
||||
time.monotonic_ns()
|
||||
time.clock_gettime_ns(time.CLOCK_BOOTTIME)
|
||||
assert mono.call_count == guard.time.clock_gettime_ns.call_count == 0
|
||||
with pytest.raises(ValueError, match='stable device clock'):
|
||||
guard.sample_clocks()
|
||||
assert mono.call_count == 6
|
||||
assert guard.time.clock_gettime_ns.call_count == 3
|
||||
|
||||
|
||||
def witness_ui(monkeypatch):
|
||||
ui = game_ui()
|
||||
Clock().refresh(ui, 0)
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "b2b3de9483ae6ab96b75644afda03011e55dba114cb05ec6646ff9d85bee14bb", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "e0d27b06417496a48ff1a04a246384c056d93f34d2f33f31dc026b9010612af2", "schema_version": 1}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Hosted collection uses the real display-free Raylib backend, like unit tests."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from tools.ci.vehicle_pytest import serial_environment
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
RUNNERS = (
|
||||
('unit_tests', 'Run unit tests'),
|
||||
('custom_pytest', 'Test every assigned custom pytest node serially'),
|
||||
('aol_lifecycles', 'Gate every assigned lifecycle module serially'),
|
||||
('safety_release_contracts', 'Run every assigned RELEASE target serially'),
|
||||
)
|
||||
|
||||
|
||||
class TestHeadlessCiEnvironment(unittest.TestCase):
|
||||
def runner_step(self, workflow, job_name, step_name):
|
||||
jobs = dict(re.findall(r'^ ([a-z_]+):\n(.*?)(?=^ [a-z_]+:|\Z)', workflow, re.M | re.S))
|
||||
steps = dict(re.findall(r'^ - name: ([^\n]+)\n(.*?)(?=^ - |\Z)', jobs[job_name], re.M | re.S))
|
||||
return steps[step_name]
|
||||
|
||||
def assert_headless_step(self, step):
|
||||
self.assertRegex(step, r'(?m)^ env:\n(?: .*\n)*? RAYLIB_BACKEND: headless\n')
|
||||
|
||||
def test_hosted_collection_selects_native_headless_backend(self):
|
||||
workflow = (ROOT / '.github/workflows/tests.yaml').read_text()
|
||||
for job_name, step_name in RUNNERS:
|
||||
with self.subTest(job=job_name):
|
||||
self.assert_headless_step(self.runner_step(workflow, job_name, step_name))
|
||||
|
||||
def test_missing_or_desktop_backend_is_rejected(self):
|
||||
workflow = (ROOT / '.github/workflows/tests.yaml').read_text()
|
||||
for job_name, step_name in RUNNERS:
|
||||
step = self.runner_step(workflow, job_name, step_name)
|
||||
for replacement in ('', ' RAYLIB_BACKEND: desktop\n'):
|
||||
with self.subTest(job=job_name, replacement=replacement), self.assertRaises(AssertionError):
|
||||
self.assert_headless_step(step.replace(' RAYLIB_BACKEND: headless\n', replacement))
|
||||
|
||||
def test_serial_vehicle_pytest_preserves_backend(self):
|
||||
with patch.dict(os.environ, {'RAYLIB_BACKEND': 'headless'}):
|
||||
with serial_environment():
|
||||
self.assertEqual(os.environ['RAYLIB_BACKEND'], 'headless')
|
||||
self.assertEqual(os.environ['RAYLIB_BACKEND'], 'headless')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user