parent
3b52f1f5d8
commit
dfe8d0685c
3 changed files with 378 additions and 5 deletions
@ -0,0 +1,355 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
import capnp |
||||||
|
import os |
||||||
|
import importlib |
||||||
|
import pytest |
||||||
|
import random |
||||||
|
import unittest |
||||||
|
from collections import defaultdict, Counter |
||||||
|
from typing import List, Optional, Tuple |
||||||
|
from parameterized import parameterized_class |
||||||
|
import hypothesis.strategies as st |
||||||
|
from hypothesis.stateful import RuleBasedStateMachine, rule, precondition |
||||||
|
from hypothesis import HealthCheck, Phase, assume, given, settings |
||||||
|
|
||||||
|
from cereal import messaging, log, car |
||||||
|
from openpilot.common.basedir import BASEDIR |
||||||
|
from openpilot.common.params import Params |
||||||
|
from openpilot.common.realtime import DT_CTRL |
||||||
|
from openpilot.selfdrive.car.fingerprints import all_known_cars |
||||||
|
from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces |
||||||
|
from openpilot.selfdrive.car.gm.values import CAR as GM |
||||||
|
from openpilot.selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH |
||||||
|
from openpilot.selfdrive.car.hyundai.values import CAR as HYUNDAI |
||||||
|
from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute |
||||||
|
from openpilot.selfdrive.controls.controlsd import Controls |
||||||
|
from openpilot.selfdrive.test.openpilotci import get_url |
||||||
|
from openpilot.tools.lib.logreader import LogReader |
||||||
|
from openpilot.tools.lib.route import Route, SegmentName, RouteName |
||||||
|
|
||||||
|
from panda.tests.libpanda import libpanda_py |
||||||
|
|
||||||
|
EventName = car.CarEvent.EventName |
||||||
|
PandaType = log.PandaState.PandaType |
||||||
|
SafetyModel = car.CarParams.SafetyModel |
||||||
|
|
||||||
|
NUM_JOBS = int(os.environ.get("NUM_JOBS", "1")) |
||||||
|
JOB_ID = int(os.environ.get("JOB_ID", "0")) |
||||||
|
INTERNAL_SEG_LIST = os.environ.get("INTERNAL_SEG_LIST", "") |
||||||
|
INTERNAL_SEG_CNT = int(os.environ.get("INTERNAL_SEG_CNT", "0")) |
||||||
|
|
||||||
|
ignore_addr_checks_valid = [ |
||||||
|
GM.BUICK_REGAL, |
||||||
|
HYUNDAI.GENESIS_G70_2020, |
||||||
|
] |
||||||
|
|
||||||
|
|
||||||
|
def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: |
||||||
|
# build list of test cases |
||||||
|
test_cases = [] |
||||||
|
if not len(INTERNAL_SEG_LIST): |
||||||
|
routes_by_car = defaultdict(set) |
||||||
|
for r in routes: |
||||||
|
routes_by_car[r.car_model].add(r) |
||||||
|
|
||||||
|
for i, c in enumerate(sorted(all_known_cars())): |
||||||
|
if i % NUM_JOBS == JOB_ID: |
||||||
|
test_cases.extend(sorted((c, r) for r in routes_by_car.get(c, (None,)))) |
||||||
|
|
||||||
|
else: |
||||||
|
with open(os.path.join(BASEDIR, INTERNAL_SEG_LIST), "r") as f: |
||||||
|
seg_list = f.read().splitlines() |
||||||
|
|
||||||
|
cnt = INTERNAL_SEG_CNT or len(seg_list) |
||||||
|
seg_list_iter = iter(seg_list[:cnt]) |
||||||
|
|
||||||
|
for platform in seg_list_iter: |
||||||
|
platform = platform[2:] # get rid of comment |
||||||
|
segment_name = SegmentName(next(seg_list_iter)) |
||||||
|
test_cases.append((platform, CarTestRoute(segment_name.route_name.canonical_name, platform, |
||||||
|
segment=segment_name.segment_num))) |
||||||
|
return test_cases |
||||||
|
|
||||||
|
|
||||||
|
init_done = False |
||||||
|
|
||||||
|
@pytest.mark.slow |
||||||
|
class CarModelBase(RuleBasedStateMachine): |
||||||
|
car_model: Optional[str] = 'TOYOTA CAMRY 2021' |
||||||
|
test_route: Optional[CarTestRoute] = CarTestRoute("3456ad0cd7281b24|2020-12-13--17-45-56", 'TOYOTA CAMRY 2021') |
||||||
|
ci: bool = True |
||||||
|
|
||||||
|
can_msgs: List[capnp.lib.capnp._DynamicStructReader] |
||||||
|
elm_frame: Optional[int] |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
super().__init__() |
||||||
|
global init_done |
||||||
|
if init_done: |
||||||
|
return |
||||||
|
init_done = True |
||||||
|
# if self.__name__ == 'TestCarModel' or self.__name__.endswith('Base'): |
||||||
|
# raise unittest.SkipTest |
||||||
|
|
||||||
|
if 'FILTER' in os.environ: |
||||||
|
if not self.car_model.startswith(tuple(os.environ.get('FILTER').split(','))): |
||||||
|
raise unittest.SkipTest |
||||||
|
|
||||||
|
if self.test_route is None: |
||||||
|
if self.car_model in non_tested_cars: |
||||||
|
print(f"Skipping tests for {self.car_model}: missing route") |
||||||
|
raise unittest.SkipTest |
||||||
|
raise Exception(f"missing test route for {self.car_model}") |
||||||
|
|
||||||
|
test_segs = (2, 1, 0) |
||||||
|
if self.test_route.segment is not None: |
||||||
|
test_segs = (self.test_route.segment,) |
||||||
|
|
||||||
|
for seg in test_segs: |
||||||
|
try: |
||||||
|
if len(INTERNAL_SEG_LIST): |
||||||
|
route_name = RouteName(self.test_route.route) |
||||||
|
lr = LogReader(f"cd:/{route_name.dongle_id}/{route_name.time_str}/{seg}/rlog.bz2") |
||||||
|
elif self.ci: |
||||||
|
lr = LogReader(get_url(self.test_route.route, seg)) |
||||||
|
else: |
||||||
|
lr = LogReader(Route(self.test_route.route).log_paths()[seg]) |
||||||
|
except Exception: |
||||||
|
continue |
||||||
|
|
||||||
|
car_fw = [] |
||||||
|
can_msgs = [] |
||||||
|
self.elm_frame = None |
||||||
|
fingerprint = defaultdict(dict) |
||||||
|
experimental_long = False |
||||||
|
enabled_toggle = True |
||||||
|
dashcam_only = False |
||||||
|
for msg in lr: |
||||||
|
if msg.which() == "can": |
||||||
|
can_msgs.append(msg) |
||||||
|
if len(can_msgs) <= FRAME_FINGERPRINT: |
||||||
|
for m in msg.can: |
||||||
|
if m.src < 64: |
||||||
|
fingerprint[m.src][m.address] = len(m.dat) |
||||||
|
|
||||||
|
elif msg.which() == "carParams": |
||||||
|
car_fw = msg.carParams.carFw |
||||||
|
dashcam_only = msg.carParams.dashcamOnly |
||||||
|
if msg.carParams.openpilotLongitudinalControl: |
||||||
|
experimental_long = True |
||||||
|
if self.car_model is None and not self.ci: |
||||||
|
self.car_model = msg.carParams.carFingerprint |
||||||
|
|
||||||
|
elif msg.which() == 'initData': |
||||||
|
for param in msg.initData.params.entries: |
||||||
|
if param.key == 'OpenpilotEnabledToggle': |
||||||
|
enabled_toggle = param.value.strip(b'\x00') == b'1' |
||||||
|
|
||||||
|
# Log which can frame the panda safety mode left ELM327, for CAN validity checks |
||||||
|
if msg.which() == 'pandaStates': |
||||||
|
for ps in msg.pandaStates: |
||||||
|
if self.elm_frame is None and ps.safetyModel != SafetyModel.elm327: |
||||||
|
self.elm_frame = len(can_msgs) |
||||||
|
|
||||||
|
elif msg.which() == 'pandaStateDEPRECATED': |
||||||
|
if self.elm_frame is None and msg.pandaStateDEPRECATED.safetyModel != SafetyModel.elm327: |
||||||
|
self.elm_frame = len(can_msgs) |
||||||
|
|
||||||
|
if len(can_msgs) > int(50 / DT_CTRL): |
||||||
|
break |
||||||
|
else: |
||||||
|
raise Exception(f"Route: {repr(self.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded?") |
||||||
|
|
||||||
|
# if relay is expected to be open in the route |
||||||
|
self.openpilot_enabled = enabled_toggle and not dashcam_only |
||||||
|
|
||||||
|
self.can_msgs = sorted(can_msgs, key=lambda msg: msg.logMonoTime) |
||||||
|
|
||||||
|
self.CarInterface, self.CarController, self.CarState = interfaces[self.car_model] |
||||||
|
self.CP = self.CarInterface.get_params(self.car_model, fingerprint, car_fw, experimental_long, docs=False) |
||||||
|
assert self.CP |
||||||
|
assert self.CP.carFingerprint == self.car_model |
||||||
|
|
||||||
|
self.car_state_dict = {'gas_pressed': False} |
||||||
|
print("HERE!!!!!") |
||||||
|
|
||||||
|
# print('SETUP HEREHEREHEREHEREHEREHEREHEREHEREHEREHEREHEREHEREHEREHEREHEREHEREHERE') |
||||||
|
self.CI = self.CarInterface(self.CP.copy(), self.CarController, self.CarState) |
||||||
|
assert self.CI |
||||||
|
|
||||||
|
Params().put_bool("OpenpilotEnabledToggle", self.openpilot_enabled) |
||||||
|
|
||||||
|
# TODO: check safetyModel is in release panda build |
||||||
|
self.safety = libpanda_py.libpanda |
||||||
|
|
||||||
|
cfg = self.CP.safetyConfigs[-1] |
||||||
|
set_status = self.safety.set_safety_hooks(cfg.safetyModel.raw, cfg.safetyParam) |
||||||
|
assert 0 == set_status, f"failed to set safetyModel {cfg}" |
||||||
|
self.safety.init_tests() |
||||||
|
|
||||||
|
@rule(messages=st.lists(st.tuples( |
||||||
|
st.integers(min_value=0, max_value=2), |
||||||
|
st.integers(min_value=0, max_value=0x1FFFF), |
||||||
|
st.binary(min_size=8, max_size=8) |
||||||
|
))) |
||||||
|
def send_messages(self, messages): |
||||||
|
print(messages) |
||||||
|
for bus, address, dat in messages: |
||||||
|
to_send = libpanda_py.make_CANPacket(address, bus, dat) |
||||||
|
self.safety.safety_rx_hook(to_send) |
||||||
|
|
||||||
|
can = messaging.new_message('can', 1) |
||||||
|
can.can = [log.CanData(address=address, dat=dat, src=bus)] |
||||||
|
|
||||||
|
CC = car.CarControl.new_message() |
||||||
|
prev_car_state = self.car_state.copy() |
||||||
|
self.car_state = self.CI.update(CC, (can.to_bytes(),)) |
||||||
|
|
||||||
|
# Check for state change. |
||||||
|
self.check_state_change(prev_car_state, self.car_state) |
||||||
|
|
||||||
|
# Only start asserting once there's been a state change. |
||||||
|
if self.has_state_changed: |
||||||
|
# Assertions based on your criteria here |
||||||
|
# For example: |
||||||
|
assert self.car_state['gas_pressed'] == self.initial_car_state['gas_pressed'], \ |
||||||
|
"Mismatch in gas pressed state." |
||||||
|
|
||||||
|
# @settings(max_examples=100, deadline=None, |
||||||
|
# # phases=(Phase.reuse, Phase.generate, Phase.shrink), |
||||||
|
# suppress_health_check=[HealthCheck.filter_too_much, HealthCheck.too_slow], |
||||||
|
# ) |
||||||
|
# @given(data=st.data()) |
||||||
|
# def test_panda_safety_carstate_fuzzy(self, data): |
||||||
|
# state_has_changed = lambda prev_state, new_state: prev_state != new_state |
||||||
|
# # cfg = self.CP.safetyConfigs[-1] |
||||||
|
# # set_status = self.safety.set_safety_hooks(cfg.safetyModel.raw, cfg.safetyParam) |
||||||
|
# # self.assertEqual(0, set_status, f"failed to set safetyModel {cfg}") |
||||||
|
# # self.safety.init_tests() |
||||||
|
# |
||||||
|
# # bus = 0 # random.randint(0, 3) |
||||||
|
# # address = 0xaa # random.randint(0x200, 0x300) |
||||||
|
# |
||||||
|
# address = data.draw(st.integers(0x1ff, 0x250)) |
||||||
|
# bus = 0 |
||||||
|
# |
||||||
|
# # ORIG: |
||||||
|
# # msg_strategy = st.tuples(st.integers(min_value=0, max_value=0), st.integers(min_value=0x100, max_value=0x400), st.binary(min_size=8, max_size=8)) |
||||||
|
# |
||||||
|
# msg_strategy = st.binary(min_size=8, max_size=8) |
||||||
|
# msgs = data.draw(st.lists(msg_strategy, min_size=100))#, min_size=100, max_size=1000)) |
||||||
|
# print(len(msgs)) |
||||||
|
# |
||||||
|
# start_gas = self.safety.get_gas_pressed_prev() |
||||||
|
# start_gas_int_detected = self.safety.get_gas_interceptor_detected() |
||||||
|
# |
||||||
|
# # for bus, address, dat in msgs: |
||||||
|
# for dat in msgs: |
||||||
|
# to_send = libpanda_py.make_CANPacket(address, bus, dat) |
||||||
|
# did_rx = self.safety.safety_rx_hook(to_send) |
||||||
|
# |
||||||
|
# can = messaging.new_message('can', 1) |
||||||
|
# can.can = [log.CanData(address=address, dat=dat, src=bus)] |
||||||
|
# |
||||||
|
# CC = car.CarControl.new_message() |
||||||
|
# CS = self.CI.update(CC, (can.to_bytes(),)) |
||||||
|
# |
||||||
|
# if self.safety.get_gas_interceptor_detected():# and state_has_changed(start_gas, self.safety.get_gas_pressed_prev()): |
||||||
|
# print('get_gas_interceptor_detected!') |
||||||
|
# # self.assertEqual(CS.gasPressed, self.safety.get_gas_interceptor_prev()) |
||||||
|
# self.assertEqual(CS.gasPressed, self.safety.get_gas_pressed_prev()) |
||||||
|
# # self.assertFalse(True) |
||||||
|
# |
||||||
|
# |
||||||
|
# # if self.safety.get_gas_pressed_prev() and self.safety.get_cruise_engaged_prev(): |
||||||
|
# # self.assertFalse(True) |
||||||
|
# # self.assertFalse(self.safety.get_cruise_engaged_prev()) |
||||||
|
# |
||||||
|
# # print('gas_pressed', CS.gasPressed, self.safety.get_gas_pressed_prev()) |
||||||
|
# # print('wheel_speeds', CS.wheelSpeeds) |
||||||
|
# # print('standstill', CS.standstill, not self.safety.get_vehicle_moving()) |
||||||
|
# |
||||||
|
# # print('did_rx', did_rx) |
||||||
|
# # if did_rx: |
||||||
|
# # self.assertFalse(True, 'finally did rx: {}, {}'.format(i, dat)) |
||||||
|
# # self.assertTrue(CS.standstill, (not CS.standstill, self.safety.get_vehicle_moving(), CS.vEgoRaw, CS.wheelSpeeds)) |
||||||
|
# |
||||||
|
# |
||||||
|
# # self.assertEqual(CS.gasPressed, self.safety.get_gas_pressed_prev()) |
||||||
|
# # self.assertEqual(not CS.standstill, self.safety.get_vehicle_moving()) |
||||||
|
# # self.assertEqual(CS.brakePressed, self.safety.get_brake_pressed_prev()) |
||||||
|
# # self.assertEqual(CS.regenBraking, self.safety.get_regen_braking_prev()) |
||||||
|
# # |
||||||
|
# # if self.CP.pcmCruise: |
||||||
|
# # self.assertEqual(CS.cruiseState.enabled, self.safety.get_cruise_engaged_prev()) |
||||||
|
# # |
||||||
|
# # if self.CP.carName == "honda": |
||||||
|
# # self.assertEqual(CS.cruiseState.available, self.safety.get_acc_main_on()) |
||||||
|
# |
||||||
|
# |
||||||
|
# # if self.safety.get_gas_interceptor_detected(): |
||||||
|
# # print('get_gas_interceptor_detected!') |
||||||
|
# # # self.assertEqual(CS.gasPressed, self.safety.get_gas_interceptor_prev()) |
||||||
|
# # self.assertEqual(CS.gasPressed, self.safety.get_gas_pressed_prev()) |
||||||
|
# # # self.assertFalse(True) |
||||||
|
# |
||||||
|
# print(self.safety.get_gas_pressed_prev(), self.safety.get_brake_pressed_prev(), self.safety.get_vehicle_moving(), self.safety.get_cruise_engaged_prev()) |
||||||
|
# assume(state_has_changed(False, self.safety.get_gas_pressed_prev())) |
||||||
|
# # assume(state_has_changed(start_gas, self.safety.get_gas_pressed_prev())) # this just goes on forever |
||||||
|
# # assume(state_has_changed(start_gas_int_detected, self.safety.get_gas_interceptor_detected())) |
||||||
|
# # assume(state_has_changed(False, self.safety.get_brake_pressed_prev())) |
||||||
|
# # assume(state_has_changed(False, self.safety.get_vehicle_moving())) |
||||||
|
# # assume(state_has_changed(False, self.safety.get_cruise_engaged_prev())) |
||||||
|
# |
||||||
|
# # print(msgs) |
||||||
|
# # print('\nresults', self.safety.get_gas_pressed_prev(), self.safety.get_vehicle_moving(), self.safety.get_brake_pressed_prev(), self.safety.get_regen_braking_prev(), self.safety.get_cruise_engaged_prev(), self.safety.get_acc_main_on()) |
||||||
|
# return |
||||||
|
# |
||||||
|
# for i in range(1000): |
||||||
|
# # self.setUp() |
||||||
|
# dat = os.urandom(8) |
||||||
|
# to_send = libpanda_py.make_CANPacket(address, bus, dat) |
||||||
|
# did_rx = self.safety.safety_rx_hook(to_send) |
||||||
|
# |
||||||
|
# can = messaging.new_message('can', 1) |
||||||
|
# can.can = [log.CanData(address=address, dat=dat, src=bus)] |
||||||
|
# |
||||||
|
# CC = car.CarControl.new_message() |
||||||
|
# CS = self.CI.update(CC, (can.to_bytes(), )) |
||||||
|
# |
||||||
|
# print('gas_pressed', CS.gasPressed, self.safety.get_gas_pressed_prev()) |
||||||
|
# print('wheel_speeds', CS.wheelSpeeds) |
||||||
|
# print('standstill', CS.standstill, not self.safety.get_vehicle_moving()) |
||||||
|
# |
||||||
|
# print('did_rx', did_rx) |
||||||
|
# # if did_rx: |
||||||
|
# # self.assertFalse(True, 'finally did rx: {}, {}'.format(i, dat)) |
||||||
|
# self.assertEqual(not CS.standstill, self.safety.get_vehicle_moving()) |
||||||
|
# |
||||||
|
# print('\nresults', self.safety.get_gas_pressed_prev(), self.safety.get_vehicle_moving(), self.safety.get_brake_pressed_prev(), self.safety.get_regen_braking_prev(), self.safety.get_cruise_engaged_prev(), self.safety.get_acc_main_on()) |
||||||
|
# |
||||||
|
# # self.assertEqual(CS.gasPressed, self.safety.get_gas_pressed_prev()) |
||||||
|
# # self.assertEqual(not CS.standstill, self.safety.get_vehicle_moving()) |
||||||
|
# # self.assertEqual(CS.brakePressed, self.safety.get_brake_pressed_prev()) |
||||||
|
# # self.assertEqual(CS.regenBraking, self.safety.get_regen_braking_prev()) |
||||||
|
# # |
||||||
|
# # if self.CP.pcmCruise: |
||||||
|
# # self.assertEqual(CS.cruiseState.enabled, self.safety.get_cruise_engaged_prev()) |
||||||
|
# # |
||||||
|
# # if self.CP.carName == "honda": |
||||||
|
# # self.assertEqual(CS.cruiseState.available, self.safety.get_acc_main_on()) |
||||||
|
|
||||||
|
|
||||||
|
# @parameterized_class(('car_model', 'test_route'), get_test_cases()) |
||||||
|
# class TestCarModel(TestCarModelBase): |
||||||
|
# pass |
||||||
|
|
||||||
|
TestCarModelBase = CarModelBase.TestCase |
||||||
|
|
||||||
|
# TestCarModelBase().runTest() |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
unittest.main() |
||||||
|
|
||||||
|
|
Loading…
Reference in new issue