mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 17:13:54 +08:00
Authorize EV9 lateral intent from physical cruise SET release
This commit is contained in:
@@ -320,7 +320,7 @@ static uint8_t aol_ioniq6_request_mask(void) {
|
||||
aol_host_axis_mask = 0U;
|
||||
} else if (heartbeat_engaged) {
|
||||
aol_ioniq6_session_started = true;
|
||||
if (hyundai_canfd_ioniq6_long && controls_allowed && aol_ioniq6_enable_release &&
|
||||
if ((hyundai_canfd_ioniq6_long || hyundai_ev9_long_aol) && controls_allowed && aol_ioniq6_enable_release &&
|
||||
((aol_host_axis_mask & 0x3U) == 0x3U) &&
|
||||
(safety_get_ts_elapsed(now, aol_ioniq6_enable_release_ts) <= AOL_HOST_REQUEST_TIMEOUT_US)) {
|
||||
aol_ioniq6_lateral_latch = true;
|
||||
@@ -572,7 +572,7 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *msg) {
|
||||
const int main_policy = hyundai_canfd_ccnc_ev_long ? (hyundai_canfd_pe_long ? HYUNDAI_MAIN_WITHDRAW : HYUNDAI_MAIN_TRACK) : HYUNDAI_MAIN_NONE;
|
||||
const bool normal_main_button = hyundai_canfd_ccnc_ev_long && GET_BIT(msg, 21U);
|
||||
hyundai_common_cruise_buttons_check(cruise_button, main_button, normal_main_button, main_policy);
|
||||
if (hyundai_canfd_ioniq6_long && aol_ioniq6_long) {
|
||||
if ((hyundai_canfd_ioniq6_long || hyundai_ev9_long_aol) && aol_ioniq6_long) {
|
||||
static uint8_t aol_ioniq6_enable_counter = 0U;
|
||||
static uint32_t aol_ioniq6_enable_ts = 0U;
|
||||
const uint8_t counter = hyundai_canfd_get_counter(msg);
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
"""Same real pending owner, actual callers, C++ requests and native emitted frames."""
|
||||
import os
|
||||
from threading import Event
|
||||
from time import perf_counter
|
||||
from unittest.mock import patch
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.hyundai.radar_interface import RadarInterface
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
from opendbc.safety.tests import test_ev9_pending_native as native_fixture
|
||||
from openpilot.cereal import messaging
|
||||
from openpilot.cereal.services import SERVICE_LIST
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.car import card as card_module
|
||||
from openpilot.starpilot.aol.intent import AOL_TOGGLE, read_settings
|
||||
from openpilot.starpilot.aol.tests import test_ioniq_availability as caller_fixture
|
||||
from openpilot.starpilot.aol.tests.test_canfd_location_native_recovery import physical_frames
|
||||
from openpilot.starpilot.aol.tests.test_classic_location_stock_intent import host_heartbeat_bridge as host_heartbeat_bridge
|
||||
from openpilot.starpilot.aol.wire import PandaSlot, SafetyState, encode_safety
|
||||
|
||||
|
||||
def test_normal_set_release_after_expired_main_keeps_lateral_after_cancel(monkeypatch, host_heartbeat_bridge):
|
||||
case = native_fixture.TestEV9PendingControllerNativeDebug(methodName='runTest')
|
||||
callers = caller_fixture.IoniqAvailabilityTests(methodName='runTest')
|
||||
owner = card = None
|
||||
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '0', 'AOL_REPLAY_RUNTIME': '1'}):
|
||||
saved = Params()
|
||||
for key, value in (('OpenpilotEnabledToggle', True), ('AlwaysOnLateral', True),
|
||||
('AlphaLongitudinalEnabled', True), ('IsReleaseBranch', False), ('IsOffroad', False)):
|
||||
saved.put_bool(key, value, block=True)
|
||||
for key, value in (('LKASButtonControl', AOL_TOGGLE), ('MainCruiseButtonControl', 0),
|
||||
('CancelButtonControl', 0), ('LongCancelButtonControl', 0), ('VeryLongCancelButtonControl', 0),
|
||||
('PauseAOLOnBrake', 0.)):
|
||||
saved.put(key, value, block=True)
|
||||
keys = ('AlwaysOnLateral', 'AlphaLongitudinalEnabled', 'LKASButtonControl', 'MainCruiseButtonControl', 'PauseAOLOnBrake')
|
||||
original_settings = {key: saved.get(key) for key in keys}
|
||||
case.setUp()
|
||||
case.boot, case.seconds, case.now = 9_000_000_000, 9., 9_000_000
|
||||
native = case.safety
|
||||
native.set_aol_test_heartbeat(False)
|
||||
received, queued = {}, []
|
||||
local = monkeypatch.context()
|
||||
patcher = local.__enter__()
|
||||
try:
|
||||
patcher.setattr(card_module.time, 'monotonic_ns', lambda: case.boot)
|
||||
patcher.setattr(card_module.time, 'clock_gettime_ns', lambda clock: case.boot)
|
||||
patcher.setattr(card_module.time, 'monotonic', lambda: case.boot / 1e9)
|
||||
owner, ci, packer = case.instance(seal=False)
|
||||
card = card_module.Car(CI=ci, RI=RadarInterface(ci.CP), startup_owner=owner)
|
||||
assert card.CP.alternativeExperience == 32 and owner.prepared_for(card.CP)
|
||||
assert owner.owner.published and not owner.takeover_attempted
|
||||
assert case.queries == []
|
||||
patcher.setattr(card.sm, 'update', lambda *args: None)
|
||||
patcher.setattr(card_module, 'REPLAY', False)
|
||||
patcher.setattr(card_module.messaging, 'drain_sock_raw', lambda *args, **kwargs: list(queued))
|
||||
original_send = card.pm.send
|
||||
|
||||
def capture(service, event):
|
||||
raw = event if isinstance(event, bytes) else event.to_bytes()
|
||||
received[service] = messaging.log_from_bytes(raw).as_builder()
|
||||
return original_send(service, raw)
|
||||
|
||||
patcher.setattr(card.pm, 'send', capture)
|
||||
|
||||
def pandas_message(diagnostic=False):
|
||||
message = messaging.new_message('pandaStates', 1, valid=True, logMonoTime=case.boot)
|
||||
panda = message.pandaStates[0]
|
||||
panda.safetyModel = 'elm327' if diagnostic else card.CP.safetyConfigs[0].safetyModel
|
||||
panda.safetyParam = 1 if diagnostic else 0x5c95
|
||||
panda.alternativeExperience = 0 if diagnostic else 32
|
||||
panda.controlsAllowed = native.get_controls_allowed()
|
||||
panda.safetyRxChecksInvalid = False if diagnostic else bool(native.safety_test_toyota_rx_summary_invalid())
|
||||
panda.ignitionLine = True
|
||||
return message
|
||||
|
||||
def inactive_control():
|
||||
control = messaging.new_message('carControl', valid=True, logMonoTime=case.boot)
|
||||
return control
|
||||
|
||||
for _ in range(12):
|
||||
case.physical_tick(ci, packer, native_hyundai=False)
|
||||
initialized = messaging.new_message('onroadEvents', 0, valid=True, logMonoTime=case.boot)
|
||||
card.sm.update_msgs(case.boot / 1e9, [pandas_message(True).as_reader(), inactive_control().as_reader(), initialized.as_reader()])
|
||||
owner.diagnostic_admission = card.startup_diagnostic_admission
|
||||
card.step()
|
||||
assert card.ci_initialized and not owner.pending_target and owner.takeover_attempted
|
||||
deadline = perf_counter() + 1.
|
||||
while not saved.get_bool('ControlsReady') and perf_counter() < deadline:
|
||||
Event().wait(.001)
|
||||
assert saved.get_bool('ControlsReady') and not owner.handed_off
|
||||
native.set_alternative_experience(32)
|
||||
assert native.set_safety_hooks(structs.CarParams.SafetyModel.hyundaiCanfd, 0x5c95) == 0
|
||||
for _ in range(12):
|
||||
case.physical_tick(ci, packer)
|
||||
panda = pandas_message()
|
||||
card.sm.update_msgs(case.boot / 1e9, [panda.as_reader(), inactive_control().as_reader()])
|
||||
card.step()
|
||||
assert owner.handed_off, 'Only the actual serialized send acknowledgment may transfer this owner'
|
||||
first = received['sendcan']
|
||||
assert first.valid
|
||||
first_cb = next(frame.dat for frame in first.sendcan if frame.address == 0xcb)
|
||||
first_scc = next(frame.dat for frame in first.sendcan if frame.address == 0x1a0)
|
||||
assert (first_cb[3] >> 4) & 15 == 1 and (first_scc[8] >> 4) & 7 == 0
|
||||
for frame in first.sendcan:
|
||||
assert native.safety_tx_hook(libsafety_py.make_CANPacket(frame.address, frame.src, frame.dat))
|
||||
|
||||
card.slc_producer_session = 'authority-card'
|
||||
sd, controls, drive = callers._authority_callers(False, cp=card.CP.as_reader().as_builder(), genuine_events=True)
|
||||
sd.aol_settings = read_settings(saved)
|
||||
# The common caller fixture controls its own clock; restore this test's shared time source afterward.
|
||||
patcher.setattr(card_module.time, 'monotonic_ns', lambda: case.boot)
|
||||
patcher.setattr(card_module.time, 'clock_gettime_ns', lambda clock: case.boot)
|
||||
patcher.setattr(card_module.time, 'monotonic', lambda: case.boot / 1e9)
|
||||
original_update = card.sm.update_msgs
|
||||
panda_due = [0.]
|
||||
|
||||
def panda_cadence(timestamp, messages):
|
||||
delivered = []
|
||||
for message in messages:
|
||||
if message.which() == 'pandaStates':
|
||||
if timestamp + 1e-9 < panda_due[0]:
|
||||
continue
|
||||
panda_due[0] = timestamp + .1
|
||||
delivered.append(message)
|
||||
return original_update(timestamp, delivered)
|
||||
|
||||
patcher.setattr(card.sm, 'update_msgs', panda_cadence)
|
||||
previous = {}
|
||||
auxiliary_due = {}
|
||||
host_heartbeat_bridge.reset_host()
|
||||
tick = case.tick
|
||||
emitted_active = 0
|
||||
emitted_longitudinal = 0
|
||||
native_rejections = 0
|
||||
last = None
|
||||
probe = False
|
||||
trace = []
|
||||
|
||||
def step(*, main=0, lkas=0, button=0, gas=False, brake=False):
|
||||
nonlocal tick, emitted_active, emitted_longitudinal, native_rejections, last
|
||||
tick += 1
|
||||
source_ns = 10_000_000_000 + tick * 10_000_000
|
||||
case.boot, case.seconds = source_ns, source_ns / 1e9
|
||||
native.set_timer(source_ns // 1000)
|
||||
if tick % 10 == 0:
|
||||
axis = previous.get('aolAxisState')
|
||||
intent = received.get('aolIntentWire')
|
||||
axis_bytes = axis.as_builder().to_bytes() if axis is not None else b''
|
||||
intent_bytes = intent.to_bytes() if intent is not None else b''
|
||||
state = previous.get('selfdriveState')
|
||||
plan = host_heartbeat_bridge.plan_host(28, 0x5c95, native.aol_get_request_mask(), native.aol_get_permission_mask(),
|
||||
source_ns, axis_bytes, len(axis_bytes), intent_bytes, len(intent_bytes),
|
||||
state is not None and state.selfdriveState.enabled)
|
||||
native.aol_set_host_request(plan & 0xff)
|
||||
native.set_aol_test_heartbeat(bool(plan & 0x100))
|
||||
host_heartbeat_bridge.complete_host(native.aol_get_request_mask(), native.aol_get_permission_mask())
|
||||
frames = physical_frames(ci, tick, lkas=lkas)
|
||||
pack = CANPacker('hyundai_canfd_generated')
|
||||
replacement: dict[int, tuple[str, dict[str, float]]] = {
|
||||
0x35: ('ACCELERATOR', {'GEAR': 5, 'ACCELERATOR_PEDAL': 4 if gas else 0, 'COUNTER': tick % 256}),
|
||||
0x175: ('TCS', {'ACCEnable': 0, 'ACC_REQ': 1, 'DriverBraking': int(brake), 'COUNTER': tick // 2 % 256}),
|
||||
0x1cf: ('CRUISE_BUTTONS', {'LDA_BTN': lkas, 'ADAPTIVE_CRUISE_MAIN_BTN': main,
|
||||
'CRUISE_BUTTONS': button, 'COUNTER': tick // 2 % 16}),
|
||||
}
|
||||
frames = [pack.make_can_msg(replacement[address][0], bus, replacement[address][1])
|
||||
if address in replacement and bus == 1 else (address, data, bus) for address, data, bus in frames]
|
||||
assert not any(address == 0x1a0 and bus == 1 for address, _, bus in frames)
|
||||
for address, data, bus in frames:
|
||||
assert native.safety_rx_hook(libsafety_py.make_CANPacket(address, bus, data)), (hex(address), bus)
|
||||
if tick % 100 == 0:
|
||||
native.safety_tick()
|
||||
mask, requested = native.aol_get_permission_mask(), native.aol_get_request_mask()
|
||||
rx_invalid = bool(native.safety_test_toyota_rx_summary_invalid())
|
||||
assert not native.get_relay_malfunction()
|
||||
batch = messaging.log_from_bytes(card_module.can_list_to_can_capnp(frames)).as_builder()
|
||||
batch.logMonoTime = source_ns
|
||||
queued[:] = [batch.to_bytes()]
|
||||
device = messaging.new_message('deviceState', valid=True, logMonoTime=source_ns)
|
||||
device.deviceState.started, device.deviceState.startedMonoTime = True, 9_000_000_000
|
||||
pandas = pandas_message()
|
||||
slot = PandaSlot(0, 'authority-panda', 28, 0x5c95, 32, native.get_controls_allowed(), rx_invalid)
|
||||
wire = messaging.new_message('aolSafetyWire', 0, valid=True, logMonoTime=source_ns)
|
||||
wire.aolSafetyWire = encode_safety(SafetyState(1, True, source_ns, source_ns + 30_000_000,
|
||||
28, 0x5c95, bool(mask & 1), bool(mask & 2), bool(requested & 1), bool(requested & 2),
|
||||
'authority-panda', card.aol_axis_session(), source_ns, (slot,)))
|
||||
messages = [device.as_reader(), pandas.as_reader(), wire.as_reader()]
|
||||
messages += [previous[name] for name in ('selfdriveState', 'aolAxisState', 'onroadEvents') if name in previous]
|
||||
if 'onroadEvents' not in previous:
|
||||
messages.append(messaging.new_message('onroadEvents', 0, valid=True, logMonoTime=source_ns).as_reader())
|
||||
card.sm.update_msgs(source_ns / 1e9, messages)
|
||||
state, _ = card.state_update()
|
||||
card.state_publish(state, None)
|
||||
auxiliary = []
|
||||
for name in ('alertDebug', 'lateralManeuverPlan', 'laneChangeAssistWire', 'starpilotLongitudinalPlan', 'slcState', 'gpsLocation'):
|
||||
if source_ns >= auxiliary_due.get(name, 0):
|
||||
frequency = SERVICE_LIST[name].frequency
|
||||
auxiliary_due[name] = source_ns + (round(1e9 / min(frequency, 100)) if frequency > 0 else 0)
|
||||
auxiliary.append(messaging.new_message(name, 0 if name == 'laneChangeAssistWire' else None, valid=True, logMonoTime=source_ns).as_reader())
|
||||
sd.sm.update_msgs(source_ns / 1e9, auxiliary)
|
||||
axis, host, command = drive(tick, intent_message=received['aolIntentWire'],
|
||||
car_state_message=received['carState'], car_output_message=received['carOutput'], companion=False,
|
||||
authority_message=received.get('starpilotCarState'), allowed=bool(mask & 1), long_allowed=bool(mask & 2),
|
||||
native_request_lat=bool(requested & 1), native_request_long=bool(requested & 2),
|
||||
panda_controls_allowed=native.get_controls_allowed(), panda_fault=rx_invalid,
|
||||
panda_state_message=pandas, safety_message=wire)
|
||||
previous.update(sd.pm.messages)
|
||||
case.boot += 1
|
||||
card.sm.update_msgs(case.boot / 1e9, [controls.pm.messages['carControl']])
|
||||
received.pop('sendcan', None)
|
||||
card.controls_update(state, card.sm['carControl'])
|
||||
if 'sendcan' in received:
|
||||
outgoing = received['sendcan']
|
||||
for frame in outgoing.sendcan:
|
||||
accepted = bool(native.safety_tx_hook(libsafety_py.make_CANPacket(frame.address, frame.src, frame.dat)))
|
||||
native_rejections += not accepted
|
||||
if not accepted:
|
||||
print('EV9_NATIVE_REJECT', tick, hex(frame.address), bytes(frame.dat).hex(),
|
||||
native.get_controls_allowed(), native.aol_get_request_mask(), native.aol_get_permission_mask(),
|
||||
'axis', str(axis.mode), axis.desiredLateral, axis.lateralActive,
|
||||
axis.desiredLongitudinal, axis.longitudinalActive,
|
||||
'cc', command.latActive, command.longActive, 'latch', card.aol_card_intent.allowed_latch,
|
||||
'rx', rx_invalid, 'host', host.enabled, flush=True)
|
||||
assert accepted, (tick, hex(frame.address), frame.src, 'activeMode', (frame.dat[3] >> 4) & 15 if frame.address == 0xcb else 0, 'native',
|
||||
native.get_controls_allowed(), native.aol_get_request_mask(), native.aol_get_permission_mask(), 'latch',
|
||||
card.aol_card_intent.allowed_latch, 'axis', str(axis.mode), str(axis.faultReason), axis.desiredLateral, axis.lateralActive,
|
||||
axis.desiredLongitudinal, axis.longitudinalActive, 'CC', command.latActive, command.longActive)
|
||||
if frame.address == 0x1a0:
|
||||
active_long = ((frame.dat[8] >> 4) & 7) in (1, 2)
|
||||
raw_accel = (((frame.dat[17] & 7) << 8) | frame.dat[16]) - 1023
|
||||
value_accel = ((frame.dat[18] << 4) | (frame.dat[17] >> 4)) - 1023
|
||||
assert command.longActive or (raw_accel == 0 and value_accel == 0)
|
||||
emitted_longitudinal += active_long
|
||||
if frame.address == 0xcb:
|
||||
active = ((frame.dat[3] >> 4) & 15) == 2
|
||||
assert not active or command.latActive
|
||||
emitted_active += active
|
||||
if probe:
|
||||
row = (native.aol_get_request_mask(), native.aol_get_permission_mask(), native.get_controls_allowed(),
|
||||
card.aol_card_intent.allowed_latch, axis.lateralActive, axis.longitudinalActive, str(axis.mode),
|
||||
str(axis.faultReason), host.enabled, str(host.state), command.latActive, command.longActive,
|
||||
tuple(str(event) for event in sd.events.names), bool(state.cruiseState.enabled),
|
||||
bool(state.cruiseState.available), card.aol_card_intent.latch_clear_reason,
|
||||
card.aol_transport_feedback.longitudinal_cancel_ns)
|
||||
if not trace or trace[-1][1] != row:
|
||||
trace.append((tick, row))
|
||||
print('EV9_FULL_TRANSITION', tick, row, flush=True)
|
||||
last = (state, axis, host, command)
|
||||
return last
|
||||
|
||||
# Toggle MAIN with a physical momentary press, then release it and
|
||||
# wait past its unclaimed native gesture deadline. No LKAS press occurs.
|
||||
for _ in range(120):
|
||||
step()
|
||||
for _ in range(2):
|
||||
step(main=1)
|
||||
for _ in range(240):
|
||||
step()
|
||||
assert last is not None
|
||||
probe = True
|
||||
before_long = emitted_longitudinal
|
||||
for _ in range(2):
|
||||
step(button=2)
|
||||
for _ in range(40):
|
||||
state, axis, host, command = step()
|
||||
assert native.get_controls_allowed() and native.aol_get_permission_mask() == 3
|
||||
assert host.enabled and axis.longitudinalActive and command.longActive
|
||||
assert emitted_longitudinal > before_long
|
||||
for _ in range(2):
|
||||
step(button=4)
|
||||
before_lat = emitted_active
|
||||
for _ in range(40):
|
||||
state, axis, host, command = step()
|
||||
assert not native.get_controls_allowed()
|
||||
assert native.aol_get_permission_mask() == 1, 'Physical SET/release followed by Cancel must retain the lateral grant'
|
||||
assert not host.enabled and not axis.longitudinalActive and not command.longActive
|
||||
assert card.aol_card_intent.allowed_latch and axis.lateralActive and command.latActive
|
||||
assert emitted_active > before_lat and native_rejections == 0
|
||||
for _ in range(2):
|
||||
step(lkas=1)
|
||||
for _ in range(40):
|
||||
state, axis, host, command = step()
|
||||
assert not card.aol_card_intent.allowed_latch
|
||||
assert not axis.lateralActive and not command.latActive and not command.longActive
|
||||
assert native.aol_get_permission_mask() == 0
|
||||
assert {key: saved.get(key) for key in keys} == original_settings
|
||||
finally:
|
||||
try:
|
||||
if owner is not None and card is not None:
|
||||
native.set_alternative_experience(0)
|
||||
native.set_safety_hooks(structs.CarParams.SafetyModel.elm327, 1)
|
||||
# Restoration is a new native telemetry sample, independent of the operating 10Hz decimator.
|
||||
type(card.sm).update_msgs(card.sm, case.boot / 1e9, [pandas_message(True).as_reader()])
|
||||
# The production restoration timeout must advance even if the diagnostic setup fails.
|
||||
patcher.setattr(card_module.time, 'monotonic', perf_counter)
|
||||
assert card.prepare_pe_restoration()
|
||||
owner.close()
|
||||
finally:
|
||||
callers.doCleanups()
|
||||
case.doCleanups()
|
||||
local.__exit__(None, None, None)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "908f150c49255e1a0b0eb3318d185ce0202711e928db4583eba0ea4b02aa1391", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "b87a6f4531540070168f2a9a05d26a62f5300e67b190b25833104c1f0bf74b52", "schema_version": 1}
|
||||
|
||||
@@ -24,6 +24,7 @@ from tools.ci import vehicle_pytest
|
||||
# Representative production callers plus their source/native refusal cases.
|
||||
# The emitted selected_test_ids record exact parametrized configurations.
|
||||
AOL_LIFECYCLE_TARGETS = (
|
||||
'opendbc_repo/opendbc/safety/tests/test_ev9_set_release_callers.py',
|
||||
'openpilot/starpilot/aol/tests/test_ev6_source_deadline.py',
|
||||
"openpilot/starpilot/car/rivian/tests/test_aol_policy.py",
|
||||
"openpilot/starpilot/car/rivian/tests/test_aol_native.py",
|
||||
|
||||
@@ -17,6 +17,8 @@ from tools.ci.record_firmware import compile_commands
|
||||
|
||||
class TestVehicleCollection(unittest.TestCase):
|
||||
def test_ev9_joined_main_cancel_off_and_vetoes_run_in_lifecycle_ci(self):
|
||||
self.assertIn('opendbc_repo/opendbc/safety/tests/test_ev9_set_release_callers.py', targets_for('safety-debug'))
|
||||
self.assertIn('opendbc_repo/opendbc/safety/tests/test_ev9_set_release_callers.py', targets_for('aol-lifecycle'))
|
||||
self.assertLessEqual({
|
||||
'opendbc_repo/opendbc/safety/tests/test_ev9_combined_callers.py',
|
||||
'openpilot/starpilot/aol/tests/test_ev9_main_withdrawal_guards.py',
|
||||
|
||||
Reference in New Issue
Block a user