openpilot v0.11.1 release

date: 2026-06-04T09:49:56
master commit: c0ab3550eca2e9daf197c46b7e4b24aa9637cf2e
This commit is contained in:
Vehicle Researcher
2026-06-04 09:50:05 -07:00
commit 6adb63b915
3381 changed files with 1044370 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
import os
import time
import pytest
from panda import Panda, PandaDFU, McuType, BASEDIR
def check_signature(p):
assert not p.bootstub, "Flashed firmware not booting. Stuck in bootstub."
assert p.up_to_date()
def test_dfu(p):
dfu_serial = p.get_dfu_serial()
p.reset(enter_bootstub=True)
p.reset(enter_bootloader=True)
assert Panda.wait_for_dfu(dfu_serial, timeout=19), "failed to enter DFU"
dfu = PandaDFU(dfu_serial)
assert dfu.get_mcu_type() == McuType.H7
assert dfu_serial in PandaDFU.list()
dfu._handle.clear_status()
dfu.reset()
p.reconnect()
# TODO: make more comprehensive bootstub tests and run on a few production ones + current
# TODO: also test release-signed app
@pytest.mark.timeout(30)
def test_known_bootstub(p):
"""
Test that compiled app can work with known production bootstub
"""
known_bootstubs = {
McuType.H7: ["bootstub.panda_h7.bin"],
}
for kb in known_bootstubs[McuType.H7]:
app_serial = p.get_usb_serial()
assert app_serial is not None
p.reset(enter_bootstub=True)
p.reset(enter_bootloader=True)
dfu_serial = p.get_dfu_serial()
assert Panda.wait_for_dfu(dfu_serial, timeout=30)
dfu = PandaDFU(dfu_serial)
with open(os.path.join(BASEDIR, "tests/hitl/known_bootstub", kb), "rb") as f:
code = f.read()
dfu.program_bootstub(code)
dfu.reset()
p.connect(claim=False, wait=True)
# check for serial mismatch
with Panda(p._serial, claim=False) as np:
assert np.get_usb_serial() == app_serial
# ensure we can flash app and it jumps to app
p.flash()
check_signature(p)
assert not p.bootstub
@pytest.mark.timeout(25)
def test_recover(p):
assert p.recover(timeout=30)
check_signature(p)
@pytest.mark.timeout(25)
def test_flash(p):
# test flash from bootstub
serial = p._serial
assert serial is not None
p.reset(enter_bootstub=True)
p.close()
time.sleep(2)
with Panda(serial) as np:
assert np.bootstub
assert np._serial == serial
np.flash()
p.reconnect()
p.reset()
check_signature(p)
# test flash from app
p.flash()
check_signature(p)
+62
View File
@@ -0,0 +1,62 @@
import time
from opendbc.car.hyundai.values import HyundaiSafetyFlags
from opendbc.car.structs import CarParams
from panda import Panda
def test_voltage(p):
for _ in range(10):
voltage = p.health()['voltage']
assert ((voltage > 11000) and (voltage < 13000))
time.sleep(0.1)
def test_hw_type(p):
"""
hw type should be same in bootstub as application
"""
hw_type = p.get_type()
app_uid = p.get_uid()
usb_serial = p.get_usb_serial()
assert app_uid == usb_serial
p.reset(enter_bootstub=True, reconnect=True)
p.close()
time.sleep(3)
with Panda(p.get_usb_serial()) as pp:
assert pp.bootstub
assert pp.get_type() == hw_type, "Bootstub and app hw type mismatch"
assert pp.get_uid() == app_uid
def test_heartbeat(p, panda_jungle):
panda_jungle.set_ignition(True)
# TODO: add more cases here once the tests aren't super slow
p.set_safety_mode(mode=CarParams.SafetyModel.hyundai, param=HyundaiSafetyFlags.LONG)
p.send_heartbeat()
assert p.health()['safety_mode'] == CarParams.SafetyModel.hyundai
assert p.health()['safety_param'] == HyundaiSafetyFlags.LONG
# shouldn't do anything once we're in a car safety mode
p.set_heartbeat_disabled()
time.sleep(6.)
h = p.health()
assert h['heartbeat_lost']
assert h['safety_mode'] == CarParams.SafetyModel.silent
assert h['safety_param'] == 0
assert h['controls_allowed'] == 0
def test_microsecond_timer(p):
start_time = p.get_microsecond_timer()
time.sleep(1)
end_time = p.get_microsecond_timer()
# account for uint32 overflow
if end_time < start_time:
end_time += 2**32
time_diff = (end_time - start_time) / 1e6
assert 0.98 < time_diff < 1.02, f"Timer not running at the correct speed! (got {time_diff:.2f}s instead of 1.0s)"
+92
View File
@@ -0,0 +1,92 @@
import time
import pytest
from flaky import flaky
from opendbc.car.structs import CarParams
from panda import Panda
from panda.tests.hitl.helpers import time_many_sends
pytestmark = [
pytest.mark.test_panda_types((Panda.HW_TYPE_RED_PANDA, ))
]
def test_can_loopback(p):
p.set_safety_mode(CarParams.SafetyModel.allOutput)
p.set_can_loopback(True)
for bus in (0, 1, 2):
# set bus 0 speed to 5000
p.set_can_speed_kbps(bus, 500)
# send a message on bus 0
p.can_send(0x1aa, b"message", bus)
# confirm receive both on loopback and send receipt
time.sleep(0.05)
r = p.can_recv()
sr = [x for x in r if x[2] == 0x80 | bus]
lb = [x for x in r if x[2] == bus]
assert len(sr) == 1
assert len(lb) == 1
# confirm data is correct
assert 0x1aa == sr[0][0] == lb[0][0]
assert b"message" == sr[0][1] == lb[0][1]
def test_reliability(p):
MSG_COUNT = 100
p.set_safety_mode(CarParams.SafetyModel.allOutput)
p.set_can_loopback(True)
p.set_can_speed_kbps(0, 1000)
addrs = list(range(100, 100 + MSG_COUNT))
ts = [(j, b"\xaa" * 8, 0) for j in addrs]
for _ in range(100):
st = time.monotonic()
p.can_send_many(ts)
r = []
while len(r) < 200 and (time.monotonic() - st) < 0.5:
r.extend(p.can_recv())
sent_echo = [x for x in r if x[2] == 0x80]
loopback_resp = [x for x in r if x[2] == 0]
assert sorted([x[0] for x in loopback_resp]) == addrs
assert sorted([x[0] for x in sent_echo]) == addrs
assert len(r) == 200
# take sub 20ms
et = (time.monotonic() - st) * 1000.0
assert et < 20
@flaky(max_runs=6, min_passes=1)
def test_throughput(p):
# enable output mode
p.set_safety_mode(CarParams.SafetyModel.allOutput)
# enable CAN loopback mode
p.set_can_loopback(True)
for speed in [10, 20, 50, 100, 125, 250, 500, 1000]:
# set bus 0 speed to speed
p.set_can_speed_kbps(0, speed)
time.sleep(0.05)
comp_kbps = time_many_sends(p, 0)
# bit count from https://en.wikipedia.org/wiki/CAN_bus
saturation_pct = (comp_kbps / speed) * 100.0
assert saturation_pct > 80
assert saturation_pct < 100
print("loopback 100 messages at speed %d, comp speed is %.2f, percent %.2f" % (speed, comp_kbps, saturation_pct))
# this will fail if you have hardware serial connected
def test_serial_debug(p):
_ = p.serial_read(Panda.SERIAL_DEBUG) # junk
p.call_control_api(0x01)
assert p.serial_read(Panda.SERIAL_DEBUG).startswith(b"NO HANDLER")
+200
View File
@@ -0,0 +1,200 @@
import os
import time
import pytest
import random
import threading
from flaky import flaky
from collections import defaultdict
from opendbc.car.structs import CarParams
from panda.tests.hitl.helpers import time_many_sends, get_random_can_messages, clear_can_buffers
@flaky(max_runs=3, min_passes=1)
@pytest.mark.timeout(35)
def test_send_recv(p, panda_jungle):
def test(p_send, p_recv):
for bus in (0, 1, 2):
for speed in (10, 20, 50, 100, 125, 250, 500, 1000):
clear_can_buffers(p_send, speed)
clear_can_buffers(p_recv, speed)
comp_kbps = time_many_sends(p_send, bus, p_recv, two_pandas=True)
saturation_pct = (comp_kbps / speed) * 100.0
assert 80 < saturation_pct < 100
print(f"two pandas bus {bus}, 100 messages at speed {speed:4d}, comp speed is {comp_kbps:7.2f}, {saturation_pct:6.2f}%")
# Run tests in both directions
p.set_safety_mode(CarParams.SafetyModel.allOutput)
test(p, panda_jungle)
test(panda_jungle, p)
@flaky(max_runs=6, min_passes=1)
@pytest.mark.timeout(30)
def test_latency(p, panda_jungle):
def test(p_send, p_recv):
for bus in (0, 1, 2):
for speed in (10, 20, 50, 100, 125, 250, 500, 1000):
clear_can_buffers(p_send, speed)
clear_can_buffers(p_recv, speed)
latencies = []
comp_kbps_list = []
saturation_pcts = []
num_messages = 100
for _ in range(num_messages):
st = time.monotonic()
p_send.can_send(0x1ab, b"message", bus)
r = []
while len(r) < 1 and (time.monotonic() - st) < 5:
r = p_recv.can_recv()
et = time.monotonic()
r_echo = []
while len(r_echo) < 1 and (time.monotonic() - st) < 10:
r_echo = p_send.can_recv()
if len(r) == 0 or len(r_echo) == 0:
print(f"r: {r}, r_echo: {r_echo}")
assert len(r) == 1
assert len(r_echo) == 1
et = (et - st) * 1000.0
comp_kbps = (1 + 11 + 1 + 1 + 1 + 4 + 8 * 8 + 15 + 1 + 1 + 1 + 7) / et
latency = et - ((1 + 11 + 1 + 1 + 1 + 4 + 8 * 8 + 15 + 1 + 1 + 1 + 7) / speed)
assert latency < 5.0
saturation_pct = (comp_kbps / speed) * 100.0
latencies.append(latency)
comp_kbps_list.append(comp_kbps)
saturation_pcts.append(saturation_pct)
average_latency = sum(latencies) / num_messages
assert average_latency < 1.0
average_comp_kbps = sum(comp_kbps_list) / num_messages
average_saturation_pct = sum(saturation_pcts) / num_messages
print("two pandas bus {}, {} message average at speed {:4d}, latency is {:5.3f}ms, comp speed is {:7.2f}, percent {:6.2f}"
.format(bus, num_messages, speed, average_latency, average_comp_kbps, average_saturation_pct))
# Run tests in both directions
p.set_safety_mode(CarParams.SafetyModel.allOutput)
test(p, panda_jungle)
test(panda_jungle, p)
@pytest.mark.panda_expect_can_error
def test_gen2_loopback(p, panda_jungle):
def test(p_send, p_recv, address=None):
for bus in range(4):
obd = False
if bus == 3:
obd = True
bus = 1
# Clear buses
clear_can_buffers(p_send)
clear_can_buffers(p_recv)
# Send a random string
addr = address if address else random.randint(1, 2000)
string = b"test" + os.urandom(4)
p_send.set_obd(obd)
p_recv.set_obd(obd)
time.sleep(0.2)
p_send.can_send(addr, string, bus)
time.sleep(0.2)
content = p_recv.can_recv()
# Check amount of messages
assert len(content) == 1
# Check content
assert content[0][0] == addr and content[0][1] == string
# Check bus
assert content[0][2] == bus
print("Bus:", bus, "address:", addr, "OBD:", obd, "OK")
# Run tests in both directions
p.set_safety_mode(CarParams.SafetyModel.allOutput)
test(p, panda_jungle)
test(panda_jungle, p)
# Test extended frame address with ELM327 mode
p.set_safety_mode(CarParams.SafetyModel.elm327)
test(p, panda_jungle, 0x18DB33F1)
test(panda_jungle, p, 0x18DB33F1)
# TODO: why it's not being reset by fixtures reinit?
p.set_obd(False)
panda_jungle.set_obd(False)
def test_bulk_write(p, panda_jungle):
# The TX buffers on pandas is 0x100 in length.
NUM_MESSAGES_PER_BUS = 10000
def flood_tx(panda):
print('Sending!')
msg = b"\xaa" * 8
packet = []
# start with many messages on a single bus (higher contention for single TX ring buffer)
packet += [[0xaa, msg, 0]] * NUM_MESSAGES_PER_BUS
# end with many messages on multiple buses
packet += [[0xaa, msg, 0], [0xaa, msg, 1], [0xaa, msg, 2]] * NUM_MESSAGES_PER_BUS
# Disable timeout
panda.set_safety_mode(CarParams.SafetyModel.allOutput)
panda.can_send_many(packet, timeout=0)
print(f"Done sending {4 * NUM_MESSAGES_PER_BUS} messages!", time.monotonic())
print(panda.health())
# Start transmisson
threading.Thread(target=flood_tx, args=(p,)).start()
# Receive as much as we can in a few second time period
rx = []
old_len = 0
start_time = time.monotonic()
while time.monotonic() - start_time < 5 or len(rx) > old_len:
old_len = len(rx)
rx.extend(panda_jungle.can_recv())
print(f"Received {len(rx)} messages", time.monotonic())
# All messages should have been received
if len(rx) != 4 * NUM_MESSAGES_PER_BUS:
raise Exception("Did not receive all messages!")
def test_message_integrity(p):
p.set_safety_mode(CarParams.SafetyModel.allOutput)
p.set_can_loopback(True)
for i in range(250):
sent_msgs = defaultdict(set)
for _ in range(random.randrange(10)):
to_send = get_random_can_messages(random.randrange(100))
for m in to_send:
sent_msgs[m[2]].add((m[0], m[1]))
p.can_send_many(to_send, timeout=0)
start_time = time.monotonic()
while time.monotonic() - start_time < 2 and any(len(sent_msgs[bus]) for bus in range(3)):
recvd = p.can_recv()
for msg in recvd:
if msg[2] >= 128:
k = (msg[0], bytes(msg[1]))
bus = msg[2]-128
assert k in sent_msgs[bus], f"message {k} was never sent on bus {bus}"
sent_msgs[msg[2]-128].discard(k)
# if a set isn't empty, messages got dropped
for bus in range(3):
assert not len(sent_msgs[bus]), f"loop {i}: bus {bus} missing {len(sent_msgs[bus])} messages"
print("Got all messages intact")
+81
View File
@@ -0,0 +1,81 @@
import binascii
import pytest
import random
from unittest.mock import patch
from panda import Panda
from panda.python.spi import PandaProtocolMismatch, PandaSpiNackResponse
class TestSpi:
def _ping(self, mocker, panda):
# should work with no retries
spy = mocker.spy(panda._handle, '_wait_for_ack')
panda.health()
assert spy.call_count == 2
mocker.stop(spy)
def test_protocol_version_check(self, p):
for bootstub in (False, True):
p.reset(enter_bootstub=bootstub)
with patch('panda.python.spi.PandaSpiHandle.PROTOCOL_VERSION', return_value="abc"):
# list should still work with wrong version
assert p._serial in Panda.list()
# connect but raise protocol error
with pytest.raises(PandaProtocolMismatch):
Panda(p._serial)
def test_protocol_version_data(self, p):
for bootstub in (False, True):
p.reset(enter_bootstub=bootstub)
v = p._handle.get_protocol_version()
uid = binascii.hexlify(v[:12]).decode()
assert uid == p.get_uid()
hwtype = v[12]
assert hwtype == ord(p.get_type())
bstub = v[13]
assert bstub == (0xEE if bootstub else 0xCC)
def test_all_comm_types(self, mocker, p):
spy = mocker.spy(p._handle, '_wait_for_ack')
# controlRead + controlWrite
p.health()
p.can_clear(0)
assert spy.call_count == 2*2
# bulkRead + bulkWrite
p.can_recv()
p.can_send(0x123, b"somedata", 0)
assert spy.call_count == 2*4
def test_bad_header(self, mocker, p):
with patch('panda.python.spi.SYNC', return_value=0):
with pytest.raises(PandaSpiNackResponse):
p._handle.controlRead(Panda.REQUEST_IN, 0xd2, 0, 0, p.HEALTH_STRUCT.size, timeout=50)
self._ping(mocker, p)
def test_bad_checksum(self, mocker, p):
cnt = p.health()['spi_error_count']
with patch('panda.python.spi.PandaSpiHandle._calc_checksum', return_value=0):
with pytest.raises(PandaSpiNackResponse):
p._handle.controlRead(Panda.REQUEST_IN, 0xd2, 0, 0, p.HEALTH_STRUCT.size, timeout=50)
self._ping(mocker, p)
assert (p.health()['spi_error_count'] - cnt) > 0
def test_non_existent_endpoint(self, mocker, p):
for _ in range(10):
ep = random.randint(4, 20)
with pytest.raises(PandaSpiNackResponse):
p._handle.bulkRead(ep, random.randint(1, 1000), timeout=50)
self._ping(mocker, p)
with pytest.raises(PandaSpiNackResponse):
p._handle.bulkWrite(ep, b"abc", timeout=50)
self._ping(mocker, p)
+18
View File
@@ -0,0 +1,18 @@
import time
from opendbc.car.structs import CarParams
def test_safety_nooutput(p):
p.set_safety_mode(CarParams.SafetyModel.silent)
p.set_can_loopback(True)
# send a message on bus 0
p.can_send(0x1aa, b"message", 0)
# confirm receive nothing
time.sleep(0.05)
r = p.can_recv()
# bus 192 is messages blocked by TX safety hook on bus 0
assert len([x for x in r if x[2] != 192]) == 0
assert len([x for x in r if x[2] == 192]) == 1
+41
View File
@@ -0,0 +1,41 @@
import time
import pytest
from panda import Panda
pytestmark = [
pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES),
]
MAX_RPM = 5000
@pytest.mark.timeout(2*60)
def test_fan_curve(p):
# ensure fan curve is (roughly) linear
rpms = []
for power in (30, 70, 100):
# wait until fan spins up
p.set_fan_power(power)
for _ in range(20):
time.sleep(1)
if p.get_fan_rpm() > 1000:
break
time.sleep(2) # wait for RPM to converge
rpms.append(p.get_fan_rpm())
print(rpms)
diffs = [b - a for a, b in zip(rpms, rpms[1:])]
assert all(x > 0 for x in diffs), f"Fan RPMs not strictly increasing: {rpms=}"
assert rpms[-1] > (0.75*MAX_RPM)
def test_fan_cooldown(p):
# if the fan cooldown doesn't work, we get high frequency noise on the tach line
# while the rotor spins down. this makes sure it never goes beyond the expected max RPM
p.set_fan_power(100)
time.sleep(3)
p.set_fan_power(0)
for _ in range(5):
assert p.get_fan_rpm() <= MAX_RPM*2
time.sleep(0.5)
+44
View File
@@ -0,0 +1,44 @@
import time
import pytest
from panda import Panda
def test_boot_time(p):
# boot time should be instant
st = time.monotonic()
p.reset(reconnect=False)
assert Panda.wait_for_panda(p.get_usb_serial(), timeout=3.0)
# USB enumeration is slow, so SPI is faster
assert time.monotonic() - st < (1.0 if p.spi else 5.0)
@pytest.mark.panda_expect_can_error
@pytest.mark.test_panda_types((Panda.HW_TYPE_CUATRO, ))
def test_stop_mode(p, panda_jungle):
serial = p.get_usb_serial()
panda_jungle.set_obd(True)
for orientation in (Panda.HARNESS_STATUS_FLIPPED, Panda.HARNESS_STATUS_NORMAL):
panda_jungle.set_harness_orientation(orientation)
time.sleep(0.25) # wait for orientation detection
for wakeup in ("ign", "0", "1", "2"):
panda_jungle.set_ignition(False)
print(f"orientation={orientation} wakeup={wakeup}")
p.enter_stop_mode()
p.close()
# wait for panda to enter stop mode
time.sleep(1.5)
# wake via ignition or CAN activity
if wakeup == "ign":
panda_jungle.set_ignition(True)
else:
panda_jungle.can_send(0x123, b'\x01\x02', int(wakeup))
# panda should reset and come back
assert Panda.wait_for_panda(serial, timeout=10)
p.reconnect()
assert p.health()['uptime'] < 3
+76
View File
@@ -0,0 +1,76 @@
import time
import pytest
import itertools
from opendbc.car.structs import CarParams
from panda import Panda
# TODO: test relay
@pytest.mark.panda_expect_can_error
def test_harness_status(p, panda_jungle):
# map from jungle orientations to panda orientations
orientation_map = {
Panda.HARNESS_STATUS_NC: Panda.HARNESS_STATUS_NC,
}
# this shouldn't be parameterized since we don't want the panda to be reset
# between the tests.
for ignition, orientation in itertools.product([True, False], [Panda.HARNESS_STATUS_NC, Panda.HARNESS_STATUS_NORMAL, Panda.HARNESS_STATUS_FLIPPED]):
print()
p.set_safety_mode(CarParams.SafetyModel.elm327)
panda_jungle.set_harness_orientation(orientation)
panda_jungle.set_ignition(ignition)
# wait for orientation detection
time.sleep(0.25)
health = p.health()
detected_orientation = health['car_harness_status']
print(f"orientation set: {orientation} detected: {detected_orientation}")
if detected_orientation not in orientation_map:
assert detected_orientation != Panda.HARNESS_STATUS_NC
other = {Panda.HARNESS_STATUS_NORMAL: Panda.HARNESS_STATUS_FLIPPED, Panda.HARNESS_STATUS_FLIPPED: Panda.HARNESS_STATUS_NORMAL}
orientation_map.update({
orientation: detected_orientation,
other[orientation]: other[detected_orientation],
})
# Orientation
assert orientation_map[detected_orientation] == orientation
# Line ignition
assert health['ignition_line'] == (False if orientation == Panda.HARNESS_STATUS_NC else ignition)
# CAN traffic
if orientation != Panda.HARNESS_STATUS_NC:
for bus in range(3):
panda_jungle.can_send(0x123, f"{bus}".encode(), bus)
time.sleep(0.5)
msgs = p.can_recv()
buses = {int(dat): bus for _, dat, bus in msgs if bus <= 3}
print(msgs)
# jungle doesn't actually switch buses when switching orientation
flipped = orientation == Panda.HARNESS_STATUS_FLIPPED
assert buses[0] == (2 if flipped else 0)
assert buses[2] == (0 if flipped else 2)
# SBU voltages
supply_voltage_mV = 1800 if p.get_type() in [Panda.HW_TYPE_TRES, Panda.HW_TYPE_CUATRO] else 3300
if orientation == Panda.HARNESS_STATUS_NC:
assert health['sbu1_voltage_mV'] > 0.9 * supply_voltage_mV
assert health['sbu2_voltage_mV'] > 0.9 * supply_voltage_mV
else:
relay_line = 'sbu1_voltage_mV' if (detected_orientation == Panda.HARNESS_STATUS_FLIPPED) else 'sbu2_voltage_mV'
ignition_line = 'sbu2_voltage_mV' if (detected_orientation == Panda.HARNESS_STATUS_FLIPPED) else 'sbu1_voltage_mV'
assert health[relay_line] < 0.1 * supply_voltage_mV
assert health[ignition_line] > health[relay_line]
if ignition:
assert health[ignition_line] < 0.3 * supply_voltage_mV
else:
assert health[ignition_line] > 0.9 * supply_voltage_mV
View File
+169
View File
@@ -0,0 +1,169 @@
import os
import pytest
from panda import Panda, PandaDFU, PandaJungle
from panda.tests.hitl.helpers import clear_can_buffers
SPEED_NORMAL = 500
BUS_SPEEDS = [(0, SPEED_NORMAL), (1, SPEED_NORMAL), (2, SPEED_NORMAL)]
# test options
NO_JUNGLE = os.environ.get("NO_JUNGLE", "0") == "1"
# Find all pandas connected
_panda_jungle = None
_panda_type = None
_panda_serial = None
def init_devices():
if not NO_JUNGLE:
global _panda_jungle
_panda_jungle = PandaJungle()
_panda_jungle.set_panda_power(True)
with Panda(serial=None, claim=False) as p:
global _panda_type
global _panda_serial
_panda_serial = p.get_usb_serial()
_panda_type = bytes(p.get_type())
assert _panda_serial is not None, "No panda found!"
init_devices()
def init_jungle():
if _panda_jungle is None:
return
clear_can_buffers(_panda_jungle)
_panda_jungle.set_panda_power(True)
_panda_jungle.set_can_loopback(False)
_panda_jungle.set_obd(False)
_panda_jungle.set_harness_orientation(PandaJungle.HARNESS_ORIENTATION_1)
for bus, speed in BUS_SPEEDS:
_panda_jungle.set_can_speed_kbps(bus, speed)
# ensure FW hasn't changed
assert _panda_jungle.up_to_date()
def pytest_configure(config):
config.addinivalue_line(
"markers", "test_panda_types(name): whitelist a test for specific panda types"
)
config.addinivalue_line(
"markers", "skip_panda_types(name): blacklist panda types from a test"
)
config.addinivalue_line(
"markers", "panda_expect_can_error: mark test to ignore CAN health errors"
)
config.addinivalue_line(
"markers", "timeout: set test timeout in seconds"
)
@pytest.hookimpl(tryfirst=True)
def pytest_collection_modifyitems(items):
for item in items:
if item.get_closest_marker('timeout') is None:
item.add_marker(pytest.mark.timeout(60))
needs_jungle = "panda_jungle" in item.fixturenames
if needs_jungle and NO_JUNGLE:
item.add_marker(pytest.mark.skip(reason="skipping tests that requires a jungle"))
@pytest.fixture(name='panda_jungle', scope='function')
def fixture_panda_jungle(request):
init_jungle()
return _panda_jungle
@pytest.fixture(name='p', scope='function')
def func_fixture_panda(request, module_panda):
# *** Setup ***
p = module_panda
# Check if test is applicable to this panda
mark = request.node.get_closest_marker('test_panda_types')
if mark:
assert len(mark.args) > 0, "Missing panda types argument in mark"
test_types = mark.args[0]
if _panda_type not in test_types:
pytest.skip(f"Not applicable, {test_types} pandas only")
mark = request.node.get_closest_marker('skip_panda_types')
if mark:
assert len(mark.args) > 0, "Missing panda types argument in mark"
skip_types = mark.args[0]
if _panda_type in skip_types:
pytest.skip(f"Not applicable to {skip_types}")
# this is 2+ seconds on USB pandas due to slow
# enumeration on the host side
p.reset()
# ensure FW hasn't changed
assert p.up_to_date()
# *** Run test ***
yield p
# *** Teardown ***
# reconnect
if p.get_dfu_serial() in PandaDFU.list():
PandaDFU(p.get_dfu_serial()).reset()
p.reconnect()
if not p.connected:
p.reconnect()
if p.bootstub:
p.reset()
assert not p.bootstub
# TODO: would be nice to make these common checks in the teardown
# show up as failed tests instead of "errors"
# Check for faults
assert p.health()['faults'] == 0
assert p.health()['fault_status'] == 0
# Check for SPI errors
#assert p.health()['spi_error_count'] == 0
# Check health of each CAN core after test, normal to fail for test_gen2_loopback on OBD bus, so skipping
mark = request.node.get_closest_marker('panda_expect_can_error')
expect_can_error = mark is not None
if not expect_can_error:
for i in range(3):
can_health = p.can_health(i)
assert can_health['bus_off_cnt'] == 0
assert can_health['receive_error_cnt'] < 127
assert can_health['transmit_error_cnt'] < 255
assert can_health['error_passive'] == 0
assert can_health['error_warning'] == 0
assert can_health['total_rx_lost_cnt'] == 0
assert can_health['total_tx_lost_cnt'] == 0
assert can_health['total_error_cnt'] == 0
assert can_health['total_tx_checksum_error_cnt'] == 0
@pytest.fixture(name='module_panda', scope='module')
def fixture_panda_setup(request):
"""
Clean up panda + jungle and return the panda under test.
"""
# init jungle
init_jungle()
# init panda
assert Panda.wait_for_panda(_panda_serial, timeout=10), "panda not found"
p = Panda(serial=_panda_serial)
p.reset(reconnect=True)
p.set_can_loopback(False)
p.set_power_save(False)
for bus, speed in BUS_SPEEDS:
p.set_can_speed_kbps(bus, speed)
clear_can_buffers(p)
p.set_power_save(False)
# run test
yield p
# teardown
p.close()
+71
View File
@@ -0,0 +1,71 @@
import time
import random
def get_random_can_messages(n):
m = []
for _ in range(n):
bus = random.randrange(3)
addr = random.randrange(1 << 29)
dat = bytes([random.getrandbits(8) for _ in range(random.randrange(1, 9))])
m.append([addr, dat, bus])
return m
def time_many_sends(p, bus, p_recv=None, msg_count=100, two_pandas=False, msg_len=8):
if p_recv is None:
p_recv = p
if p == p_recv and two_pandas:
raise ValueError("Cannot have two pandas that are the same panda")
msg_id = random.randint(0x100, 0x200)
to_send = [(msg_id, b"\xaa" * msg_len, bus)] * msg_count
start_time = time.monotonic()
p.can_send_many(to_send)
r = []
r_echo = []
r_len_expected = msg_count if two_pandas else msg_count * 2
r_echo_len_exected = msg_count if two_pandas else 0
while len(r) < r_len_expected and (time.monotonic() - start_time) < 5:
r.extend(p_recv.can_recv())
end_time = time.monotonic()
if two_pandas:
while len(r_echo) < r_echo_len_exected and (time.monotonic() - start_time) < 10:
r_echo.extend(p.can_recv())
sent_echo = [x for x in r if x[2] == 0x80 | bus and x[0] == msg_id]
sent_echo.extend([x for x in r_echo if x[2] == 0x80 | bus and x[0] == msg_id])
resp = [x for x in r if x[2] == bus and x[0] == msg_id]
leftovers = [x for x in r if (x[2] != 0x80 | bus and x[2] != bus) or x[0] != msg_id]
assert len(leftovers) == 0
assert len(resp) == msg_count
assert len(sent_echo) == msg_count
end_time = (end_time - start_time) * 1000.0
comp_kbps = (1 + 11 + 1 + 1 + 1 + 4 + (msg_len * 8) + 15 + 1 + 1 + 1 + 7) * msg_count / end_time
return comp_kbps
def clear_can_buffers(panda, speed: int | None = None):
if speed is not None:
for bus in range(3):
panda.set_can_speed_kbps(bus, speed)
# clear tx buffers
for i in range(4):
panda.can_clear(i)
# clear rx buffers
panda.can_clear(0xFFFF)
r = [1]
st = time.monotonic()
while len(r) > 0:
r = panda.can_recv()
time.sleep(0.05)
if (time.monotonic() - st) > 10:
raise Exception("Unable to clear can buffers for panda ", panda.get_serial())
Binary file not shown.
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
import concurrent.futures
from panda import PandaJungle, PandaJungleDFU, McuType
from panda.tests.libs.resetter import Resetter
SERIALS = {
'180019001451313236343430', # jungle v2
}
def recover(s):
with PandaJungleDFU(s) as pd:
pd.recover()
def flash(s):
with PandaJungle(s) as p:
p.flash()
# Reset + flash all CI hardware to get it into a consistent state
# * port 1: jungles-under-test
# * port 2: USB hubs
# * port 3: HITL pandas and their jungles
if __name__ == "__main__":
with Resetter() as r:
# everything off
for i in range(1, 4):
r.enable_power(i, 0)
r.cycle_power(ports=[1, 2], dfu=True)
for s in SERIALS:
assert PandaJungle.wait_for_dfu(PandaJungleDFU.st_serial_to_dfu_serial(s, McuType.H7), timeout=10)
dfu_serials = PandaJungleDFU.list()
assert len(dfu_serials) == len(SERIALS)
with concurrent.futures.ProcessPoolExecutor(max_workers=len(dfu_serials)) as exc:
list(exc.map(recover, dfu_serials, timeout=30))
# power cycle for H7 bootloader bug
r.cycle_power(ports=[1, 2])
# wait for them to come back up
for s in SERIALS:
assert PandaJungle.wait_for_panda(s, timeout=10)
assert set(PandaJungle.list()) >= SERIALS
list(exc.map(flash, SERIALS, timeout=20))
+15
View File
@@ -0,0 +1,15 @@
import opendbc
env = Environment(
CFLAGS=[
'-nostdlib',
'-fno-builtin',
'-std=gnu11',
'-Wfatal-errors',
'-Wno-pointer-to-int-cast',
],
CPPPATH=[".", "../../", "../../board/", opendbc.INCLUDE_PATH],
)
panda = env.SharedObject("panda.os", "panda.c")
libpanda = env.SharedLibrary("libpanda.so", [panda])
+87
View File
@@ -0,0 +1,87 @@
import os
from cffi import FFI
from typing import Any, Protocol
from panda import LEN_TO_DLC
libpanda_dir = os.path.dirname(os.path.abspath(__file__))
libpanda_fn = os.path.join(libpanda_dir, "libpanda.so")
ffi = FFI()
ffi.cdef("""
typedef struct {
unsigned char fd : 1;
unsigned char bus : 3;
unsigned char data_len_code : 4;
unsigned char rejected : 1;
unsigned char returned : 1;
unsigned char extended : 1;
unsigned int addr : 29;
unsigned char checksum;
unsigned char data[64];
} CANPacket_t;
""", packed=True)
ffi.cdef("""
int set_safety_hooks(uint16_t mode, uint16_t param);
""")
ffi.cdef("""
typedef struct {
volatile uint32_t w_ptr;
volatile uint32_t r_ptr;
uint32_t fifo_size;
CANPacket_t *elems;
} can_ring;
extern can_ring *rx_q;
extern can_ring *tx1_q;
extern can_ring *tx2_q;
extern can_ring *tx3_q;
bool can_pop(can_ring *q, CANPacket_t *elem);
bool can_push(can_ring *q, CANPacket_t *elem);
void can_set_checksum(CANPacket_t *packet);
int comms_can_read(uint8_t *data, uint32_t max_len);
void comms_can_write(uint8_t *data, uint32_t len);
void comms_can_reset(void);
uint32_t can_slots_empty(can_ring *q);
""")
class CANPacket:
reserved: int
bus: int
data_len_code: int
rejected: int
returned: int
extended: int
addr: int
data: list[int]
class Panda(Protocol):
# CAN
tx1_q: Any
tx2_q: Any
tx3_q: Any
def can_set_checksum(self, p: CANPacket) -> None: ...
# safety
def set_safety_hooks(self, mode: int, param: int) -> int: ...
libpanda: Panda = ffi.dlopen(libpanda_fn)
# helpers
def make_CANPacket(addr: int, bus: int, dat):
ret = ffi.new('CANPacket_t *')
ret[0].extended = 1 if addr >= 0x800 else 0
ret[0].addr = addr
ret[0].data_len_code = LEN_TO_DLC[len(dat)]
ret[0].bus = bus
ret[0].data = bytes(dat)
libpanda.can_set_checksum(ret)
return ret
+28
View File
@@ -0,0 +1,28 @@
#include "fake_stm.h"
#include "config.h"
#include "can.h"
bool can_init(uint8_t can_number) { return true; }
void process_can(uint8_t can_number) { }
//int safety_tx_hook(CANPacket_t *to_send) { return 1; }
typedef struct harness_configuration harness_configuration;
void refresh_can_tx_slots_available(void);
void can_tx_comms_resume_usb(void) { };
void can_tx_comms_resume_spi(void) { };
#include "health.h"
#include "sys/faults.h"
#include "libc.h"
#include "boards/board_declarations.h"
#include "opendbc/safety/safety.h"
#include "main_definitions.h"
#include "drivers/can_common.h"
can_ring *rx_q = &can_rx_q;
can_ring *tx1_q = &can_tx1_q;
can_ring *tx2_q = &can_tx2_q;
can_ring *tx3_q = &can_tx3_q;
#include "comms_definitions.h"
#include "can_comms.h"
+58
View File
@@ -0,0 +1,58 @@
import time
import usb1
class Resetter():
def __init__(self):
self._handle = None
self.connect()
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def close(self):
self._handle.close()
self._context.close()
self._handle = None
def connect(self):
if self._handle:
self.close()
self._handle = None
self._context = usb1.USBContext()
self._context.open()
for device in self._context.getDeviceList(skip_on_error=True):
if device.getVendorID() == 0xbbaa and device.getProductID() == 0xddc0:
try:
self._handle = device.open()
self._handle.claimInterface(0)
break
except Exception as e:
print(e)
assert self._handle
def enable_power(self, port, enabled):
self._handle.controlWrite((usb1.ENDPOINT_OUT | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE), 0xff, port, enabled, b'')
def enable_boot(self, enabled):
self._handle.controlWrite((usb1.ENDPOINT_OUT | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE), 0xff, 0, enabled, b'')
def cycle_power(self, dfu=False, ports=None):
if ports is None:
ports = [1, 2, 3]
self.enable_boot(dfu)
for port in ports:
self.enable_power(port, False)
time.sleep(0.05)
for port in ports:
self.enable_power(port, True)
time.sleep(0.05)
self.enable_boot(False)
time.sleep(0.12) # takes the kernel this long to detect the disconnect
+5
View File
@@ -0,0 +1,5 @@
*.pdf
*.txt
.output.log
new_table
cppcheck/
+456
View File
@@ -0,0 +1,456 @@
Cppcheck checkers list from test_misra.sh:
TEST variant options:
--enable=all --disable=unusedFunction --addon=misra -DSTM32H7 -DSTM32H725xx -I /board/stm32h7/inc/ /board/main.c
Critical errors
---------------
No critical errors encountered.
Note: There might still have been non-critical bailouts which might lead to false negatives.
Open source checkers
--------------------
Yes Check64BitPortability::pointerassignment
Yes CheckAssert::assertWithSideEffects
Yes CheckAutoVariables::assignFunctionArg
Yes CheckAutoVariables::autoVariables
Yes CheckAutoVariables::checkVarLifetime
No CheckBool::checkAssignBoolToFloat require:style,c++
Yes CheckBool::checkAssignBoolToPointer
No CheckBool::checkBitwiseOnBoolean require:style,inconclusive
Yes CheckBool::checkComparisonOfBoolExpressionWithInt
No CheckBool::checkComparisonOfBoolWithBool require:style,c++
No CheckBool::checkComparisonOfBoolWithInt require:warning,c++
No CheckBool::checkComparisonOfFuncReturningBool require:style,c++
Yes CheckBool::checkIncrementBoolean
Yes CheckBool::pointerArithBool
Yes CheckBool::returnValueOfFunctionReturningBool
No CheckBoost::checkBoostForeachModification
Yes CheckBufferOverrun::analyseWholeProgram
Yes CheckBufferOverrun::argumentSize
Yes CheckBufferOverrun::arrayIndex
Yes CheckBufferOverrun::arrayIndexThenCheck
Yes CheckBufferOverrun::bufferOverflow
Yes CheckBufferOverrun::negativeArraySize
Yes CheckBufferOverrun::objectIndex
Yes CheckBufferOverrun::pointerArithmetic
No CheckBufferOverrun::stringNotZeroTerminated require:warning,inconclusive
Yes CheckClass::analyseWholeProgram
No CheckClass::checkConst require:style,inconclusive
No CheckClass::checkConstructors require:style,warning
No CheckClass::checkCopyConstructors require:warning
No CheckClass::checkDuplInheritedMembers require:warning
No CheckClass::checkExplicitConstructors require:style
No CheckClass::checkMemset
No CheckClass::checkMissingOverride require:style,c++03
No CheckClass::checkReturnByReference require:performance
No CheckClass::checkSelfInitialization
No CheckClass::checkThisUseAfterFree require:warning
No CheckClass::checkUnsafeClassRefMember require:warning,safeChecks
No CheckClass::checkUselessOverride require:style
No CheckClass::checkVirtualFunctionCallInConstructor require:warning
No CheckClass::initializationListUsage require:performance
No CheckClass::initializerListOrder require:style,inconclusive
No CheckClass::operatorEqRetRefThis require:style
No CheckClass::operatorEqToSelf require:warning
No CheckClass::privateFunctions require:style
No CheckClass::thisSubtraction require:warning
No CheckClass::virtualDestructor
Yes CheckCondition::alwaysTrueFalse
Yes CheckCondition::assignIf
Yes CheckCondition::checkAssignmentInCondition
Yes CheckCondition::checkBadBitmaskCheck
Yes CheckCondition::checkCompareValueOutOfTypeRange
Yes CheckCondition::checkDuplicateConditionalAssign
Yes CheckCondition::checkIncorrectLogicOperator
Yes CheckCondition::checkInvalidTestForOverflow
Yes CheckCondition::checkModuloAlwaysTrueFalse
Yes CheckCondition::checkPointerAdditionResultNotNull
Yes CheckCondition::clarifyCondition
Yes CheckCondition::comparison
Yes CheckCondition::duplicateCondition
Yes CheckCondition::multiCondition
Yes CheckCondition::multiCondition2
No CheckExceptionSafety::checkCatchExceptionByValue require:style
No CheckExceptionSafety::checkRethrowCopy require:style
No CheckExceptionSafety::deallocThrow require:warning
No CheckExceptionSafety::destructors require:warning
No CheckExceptionSafety::nothrowThrows
No CheckExceptionSafety::rethrowNoCurrentException
No CheckExceptionSafety::unhandledExceptionSpecification require:style,inconclusive
Yes CheckFunctions::checkIgnoredReturnValue
Yes CheckFunctions::checkMathFunctions
Yes CheckFunctions::checkMissingReturn
Yes CheckFunctions::checkProhibitedFunctions
Yes CheckFunctions::invalidFunctionUsage
Yes CheckFunctions::memsetInvalid2ndParam
Yes CheckFunctions::memsetZeroBytes
No CheckFunctions::returnLocalStdMove require:performance,c++11
Yes CheckFunctions::useStandardLibrary
No CheckIO::checkCoutCerrMisusage require:c
Yes CheckIO::checkFileUsage
Yes CheckIO::checkWrongPrintfScanfArguments
Yes CheckIO::invalidScanf
Yes CheckLeakAutoVar::check
No CheckMemoryLeakInClass::check
Yes CheckMemoryLeakInFunction::checkReallocUsage
Yes CheckMemoryLeakNoVar::check
No CheckMemoryLeakNoVar::checkForUnsafeArgAlloc
Yes CheckMemoryLeakStructMember::check
Yes CheckNullPointer::analyseWholeProgram
Yes CheckNullPointer::arithmetic
Yes CheckNullPointer::nullConstantDereference
Yes CheckNullPointer::nullPointer
No CheckOther::checkAccessOfMovedVariable require:c++11,warning
Yes CheckOther::checkCastIntToCharAndBack
Yes CheckOther::checkCharVariable
Yes CheckOther::checkComparePointers
Yes CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse
Yes CheckOther::checkConstPointer
No CheckOther::checkConstVariable require:style,c++
No CheckOther::checkDuplicateBranch require:style,inconclusive
Yes CheckOther::checkDuplicateExpression
Yes CheckOther::checkEvaluationOrder
Yes CheckOther::checkFuncArgNamesDifferent
No CheckOther::checkIncompleteArrayFill require:warning,portability,inconclusive
Yes CheckOther::checkIncompleteStatement
No CheckOther::checkInterlockedDecrement require:windows-platform
Yes CheckOther::checkInvalidFree
Yes CheckOther::checkKnownArgument
Yes CheckOther::checkKnownPointerToBool
No CheckOther::checkMisusedScopedObject require:style,c++
Yes CheckOther::checkModuloOfOne
Yes CheckOther::checkNanInArithmeticExpression
Yes CheckOther::checkNegativeBitwiseShift
Yes CheckOther::checkOverlappingWrite
No CheckOther::checkPassByReference require:performance,c++
Yes CheckOther::checkRedundantAssignment
No CheckOther::checkRedundantCopy require:c++,performance,inconclusive
Yes CheckOther::checkRedundantPointerOp
Yes CheckOther::checkShadowVariables
Yes CheckOther::checkSignOfUnsignedVariable
No CheckOther::checkSuspiciousCaseInSwitch require:warning,inconclusive
No CheckOther::checkSuspiciousSemicolon require:warning,inconclusive
Yes CheckOther::checkUnreachableCode
Yes CheckOther::checkUnusedLabel
Yes CheckOther::checkVarFuncNullUB
Yes CheckOther::checkVariableScope
Yes CheckOther::checkZeroDivision
Yes CheckOther::clarifyCalculation
Yes CheckOther::clarifyStatement
Yes CheckOther::invalidPointerCast
Yes CheckOther::redundantBitwiseOperationInSwitch
Yes CheckOther::suspiciousFloatingPointCast
No CheckOther::warningOldStylePointerCast require:style,c++
No CheckPostfixOperator::postfixOperator require:performance
Yes CheckSizeof::checkSizeofForArrayParameter
Yes CheckSizeof::checkSizeofForNumericParameter
Yes CheckSizeof::checkSizeofForPointerSize
Yes CheckSizeof::sizeofCalculation
Yes CheckSizeof::sizeofFunction
Yes CheckSizeof::sizeofVoid
Yes CheckSizeof::sizeofsizeof
No CheckSizeof::suspiciousSizeofCalculation require:warning,inconclusive
No CheckStl::checkDereferenceInvalidIterator require:warning
No CheckStl::checkDereferenceInvalidIterator2
No CheckStl::checkFindInsert require:performance
No CheckStl::checkMutexes require:warning
No CheckStl::erase
No CheckStl::eraseIteratorOutOfBounds
No CheckStl::if_find require:warning,performance
No CheckStl::invalidContainer
No CheckStl::iterators
No CheckStl::knownEmptyContainer require:style
No CheckStl::misMatchingContainerIterator
No CheckStl::misMatchingContainers
No CheckStl::missingComparison require:warning
No CheckStl::negativeIndex
No CheckStl::outOfBounds
No CheckStl::outOfBoundsIndexExpression
No CheckStl::redundantCondition require:style
No CheckStl::size require:performance,c++03
No CheckStl::stlBoundaries
No CheckStl::stlOutOfBounds
No CheckStl::string_c_str
No CheckStl::useStlAlgorithm require:style
No CheckStl::uselessCalls require:performance,warning
Yes CheckString::checkAlwaysTrueOrFalseStringCompare
Yes CheckString::checkIncorrectStringCompare
Yes CheckString::checkSuspiciousStringCompare
Yes CheckString::overlappingStrcmp
Yes CheckString::sprintfOverlappingData
Yes CheckString::strPlusChar
Yes CheckString::stringLiteralWrite
Yes CheckType::checkFloatToIntegerOverflow
Yes CheckType::checkIntegerOverflow
Yes CheckType::checkLongCast
Yes CheckType::checkSignConversion
Yes CheckType::checkTooBigBitwiseShift
Yes CheckUninitVar::check
Yes CheckUninitVar::valueFlowUninit
No CheckUnusedFunctions::check require:unusedFunction
Yes CheckUnusedVar::checkFunctionVariableUsage
Yes CheckUnusedVar::checkStructMemberUsage
Yes CheckVaarg::va_list_usage
Yes CheckVaarg::va_start_argument
Premium checkers
----------------
Not available, Cppcheck Premium is not used
Autosar
-------
Not available, Cppcheck Premium is not used
Cert C
------
Not available, Cppcheck Premium is not used
Cert C++
--------
Not available, Cppcheck Premium is not used
Misra C 2012
------------
No Misra C 2012: Dir 1.1
No Misra C 2012: Dir 2.1
No Misra C 2012: Dir 3.1
No Misra C 2012: Dir 4.1
No Misra C 2012: Dir 4.2
No Misra C 2012: Dir 4.3
No Misra C 2012: Dir 4.4
No Misra C 2012: Dir 4.5
No Misra C 2012: Dir 4.6 amendment:3
No Misra C 2012: Dir 4.7
No Misra C 2012: Dir 4.8
No Misra C 2012: Dir 4.9 amendment:3
No Misra C 2012: Dir 4.10
No Misra C 2012: Dir 4.11 amendment:3
No Misra C 2012: Dir 4.12
No Misra C 2012: Dir 4.13
No Misra C 2012: Dir 4.14 amendment:2
No Misra C 2012: Dir 4.15 amendment:3
No Misra C 2012: Dir 5.1 amendment:4
No Misra C 2012: Dir 5.2 amendment:4
No Misra C 2012: Dir 5.3 amendment:4
Yes Misra C 2012: 1.1
Yes Misra C 2012: 1.2
Yes Misra C 2012: 1.3
Yes Misra C 2012: 1.4 amendment:2
No Misra C 2012: 1.5 amendment:3 require:premium
Yes Misra C 2012: 2.1
Yes Misra C 2012: 2.2
Yes Misra C 2012: 2.3
Yes Misra C 2012: 2.4
Yes Misra C 2012: 2.5
Yes Misra C 2012: 2.6
Yes Misra C 2012: 2.7
Yes Misra C 2012: 2.8
Yes Misra C 2012: 3.1
Yes Misra C 2012: 3.2
Yes Misra C 2012: 4.1
Yes Misra C 2012: 4.2
Yes Misra C 2012: 5.1
Yes Misra C 2012: 5.2
Yes Misra C 2012: 5.3
Yes Misra C 2012: 5.4
Yes Misra C 2012: 5.5
Yes Misra C 2012: 5.6
Yes Misra C 2012: 5.7
Yes Misra C 2012: 5.8
Yes Misra C 2012: 5.9
Yes Misra C 2012: 6.1
Yes Misra C 2012: 6.2
No Misra C 2012: 6.3
Yes Misra C 2012: 7.1
Yes Misra C 2012: 7.2
Yes Misra C 2012: 7.3
Yes Misra C 2012: 7.4
No Misra C 2012: 7.5
No Misra C 2012: 7.6
Yes Misra C 2012: 8.1
Yes Misra C 2012: 8.2
No Misra C 2012: 8.3
Yes Misra C 2012: 8.4
Yes Misra C 2012: 8.5
Yes Misra C 2012: 8.6
Yes Misra C 2012: 8.7
Yes Misra C 2012: 8.8
Yes Misra C 2012: 8.9
Yes Misra C 2012: 8.10
Yes Misra C 2012: 8.11
Yes Misra C 2012: 8.12
Yes Misra C 2012: 8.13
Yes Misra C 2012: 8.14
No Misra C 2012: 8.15
No Misra C 2012: 8.16
No Misra C 2012: 8.17
Yes Misra C 2012: 9.1
Yes Misra C 2012: 9.2
Yes Misra C 2012: 9.3
Yes Misra C 2012: 9.4
Yes Misra C 2012: 9.5
No Misra C 2012: 9.6
No Misra C 2012: 9.7
Yes Misra C 2012: 10.1
Yes Misra C 2012: 10.2
Yes Misra C 2012: 10.3
Yes Misra C 2012: 10.4
Yes Misra C 2012: 10.5
Yes Misra C 2012: 10.6
Yes Misra C 2012: 10.7
Yes Misra C 2012: 10.8
Yes Misra C 2012: 11.1
Yes Misra C 2012: 11.2
Yes Misra C 2012: 11.3
Yes Misra C 2012: 11.4
Yes Misra C 2012: 11.5
Yes Misra C 2012: 11.6
Yes Misra C 2012: 11.7
Yes Misra C 2012: 11.8
Yes Misra C 2012: 11.9
No Misra C 2012: 11.10
Yes Misra C 2012: 12.1
Yes Misra C 2012: 12.2
Yes Misra C 2012: 12.3
Yes Misra C 2012: 12.4
Yes Misra C 2012: 12.5 amendment:1
No Misra C 2012: 12.6 amendment:4 require:premium
Yes Misra C 2012: 13.1
No Misra C 2012: 13.2
Yes Misra C 2012: 13.3
Yes Misra C 2012: 13.4
Yes Misra C 2012: 13.5
Yes Misra C 2012: 13.6
Yes Misra C 2012: 14.1
Yes Misra C 2012: 14.2
Yes Misra C 2012: 14.3
Yes Misra C 2012: 14.4
Yes Misra C 2012: 15.1
Yes Misra C 2012: 15.2
Yes Misra C 2012: 15.3
Yes Misra C 2012: 15.4
Yes Misra C 2012: 15.5
Yes Misra C 2012: 15.6
Yes Misra C 2012: 15.7
Yes Misra C 2012: 16.1
Yes Misra C 2012: 16.2
Yes Misra C 2012: 16.3
Yes Misra C 2012: 16.4
Yes Misra C 2012: 16.5
Yes Misra C 2012: 16.6
Yes Misra C 2012: 16.7
Yes Misra C 2012: 17.1
Yes Misra C 2012: 17.2
Yes Misra C 2012: 17.3
No Misra C 2012: 17.4
Yes Misra C 2012: 17.5
Yes Misra C 2012: 17.6
Yes Misra C 2012: 17.7
Yes Misra C 2012: 17.8
No Misra C 2012: 17.9
No Misra C 2012: 17.10
No Misra C 2012: 17.11
No Misra C 2012: 17.12
No Misra C 2012: 17.13
Yes Misra C 2012: 18.1
Yes Misra C 2012: 18.2
Yes Misra C 2012: 18.3
Yes Misra C 2012: 18.4
Yes Misra C 2012: 18.5
Yes Misra C 2012: 18.6
Yes Misra C 2012: 18.7
Yes Misra C 2012: 18.8
No Misra C 2012: 18.9
No Misra C 2012: 18.10
Yes Misra C 2012: 19.1
Yes Misra C 2012: 19.2
Yes Misra C 2012: 20.1
Yes Misra C 2012: 20.2
Yes Misra C 2012: 20.3
Yes Misra C 2012: 20.4
Yes Misra C 2012: 20.5
Yes Misra C 2012: 20.6
Yes Misra C 2012: 20.7
Yes Misra C 2012: 20.8
Yes Misra C 2012: 20.9
Yes Misra C 2012: 20.10
Yes Misra C 2012: 20.11
Yes Misra C 2012: 20.12
Yes Misra C 2012: 20.13
Yes Misra C 2012: 20.14
Yes Misra C 2012: 21.1
Yes Misra C 2012: 21.2
Yes Misra C 2012: 21.3
Yes Misra C 2012: 21.4
Yes Misra C 2012: 21.5
Yes Misra C 2012: 21.6
Yes Misra C 2012: 21.7
Yes Misra C 2012: 21.8
Yes Misra C 2012: 21.9
Yes Misra C 2012: 21.10
Yes Misra C 2012: 21.11
Yes Misra C 2012: 21.12
Yes Misra C 2012: 21.13 amendment:1
Yes Misra C 2012: 21.14 amendment:1
Yes Misra C 2012: 21.15 amendment:1
Yes Misra C 2012: 21.16 amendment:1
Yes Misra C 2012: 21.17 amendment:1
Yes Misra C 2012: 21.18 amendment:1
Yes Misra C 2012: 21.19 amendment:1
Yes Misra C 2012: 21.20 amendment:1
Yes Misra C 2012: 21.21 amendment:3
No Misra C 2012: 21.22 amendment:3 require:premium
No Misra C 2012: 21.23 amendment:3 require:premium
No Misra C 2012: 21.24 amendment:3 require:premium
No Misra C 2012: 21.25 amendment:4 require:premium
No Misra C 2012: 21.26 amendment:4 require:premium
Yes Misra C 2012: 22.1
Yes Misra C 2012: 22.2
Yes Misra C 2012: 22.3
Yes Misra C 2012: 22.4
Yes Misra C 2012: 22.5
Yes Misra C 2012: 22.6
Yes Misra C 2012: 22.7 amendment:1
Yes Misra C 2012: 22.8 amendment:1
Yes Misra C 2012: 22.9 amendment:1
Yes Misra C 2012: 22.10 amendment:1
No Misra C 2012: 22.11 amendment:4 require:premium
No Misra C 2012: 22.12 amendment:4 require:premium
No Misra C 2012: 22.13 amendment:4 require:premium
No Misra C 2012: 22.14 amendment:4 require:premium
No Misra C 2012: 22.15 amendment:4 require:premium
No Misra C 2012: 22.16 amendment:4 require:premium
No Misra C 2012: 22.17 amendment:4 require:premium
No Misra C 2012: 22.18 amendment:4 require:premium
No Misra C 2012: 22.19 amendment:4 require:premium
No Misra C 2012: 22.20 amendment:4 require:premium
No Misra C 2012: 23.1 amendment:3 require:premium
No Misra C 2012: 23.2 amendment:3 require:premium
No Misra C 2012: 23.3 amendment:3 require:premium
No Misra C 2012: 23.4 amendment:3 require:premium
No Misra C 2012: 23.5 amendment:3 require:premium
No Misra C 2012: 23.6 amendment:3 require:premium
No Misra C 2012: 23.7 amendment:3 require:premium
No Misra C 2012: 23.8 amendment:3 require:premium
Misra C++ 2008
--------------
Not available, Cppcheck Premium is not used
Misra C++ 2023
--------------
Not available, Cppcheck Premium is not used
+156
View File
@@ -0,0 +1,156 @@
1.1
1.2 X (Addon)
1.3 X (Cppcheck)
2.1 X (Cppcheck)
2.2 X (Addon)
2.3 X (Addon)
2.4 X (Addon)
2.5 X (Addon)
2.6 X (Cppcheck)
2.7 X (Addon)
3.1 X (Addon)
3.2 X (Addon)
4.1 X (Addon)
4.2 X (Addon)
5.1 X (Addon)
5.2 X (Addon)
5.3 X (Cppcheck)
5.4 X (Addon)
5.5 X (Addon)
5.6 X (Addon)
5.7 X (Addon)
5.8 X (Addon)
5.9 X (Addon)
6.1 X (Addon)
6.2 X (Addon)
7.1 X (Addon)
7.2 X (Addon)
7.3 X (Addon)
7.4 X (Addon)
8.1 X (Addon)
8.2 X (Addon)
8.3 X (Cppcheck)
8.4 X (Addon)
8.5 X (Addon)
8.6 X (Addon)
8.7 X (Addon)
8.8 X (Addon)
8.9 X (Addon)
8.10 X (Addon)
8.11 X (Addon)
8.12 X (Addon)
8.13 X (Cppcheck)
8.14 X (Addon)
9.1 X (Cppcheck)
9.2 X (Addon)
9.3 X (Addon)
9.4 X (Addon)
9.5 X (Addon)
10.1 X (Addon)
10.2 X (Addon)
10.3 X (Addon)
10.4 X (Addon)
10.5 X (Addon)
10.6 X (Addon)
10.7 X (Addon)
10.8 X (Addon)
11.1 X (Addon)
11.2 X (Addon)
11.3 X (Addon)
11.4 X (Addon)
11.5 X (Addon)
11.6 X (Addon)
11.7 X (Addon)
11.8 X (Addon)
11.9 X (Addon)
12.1 X (Addon)
12.2 X (Addon)
12.3 X (Addon)
12.4 X (Addon)
13.1 X (Addon)
13.2 X (Cppcheck)
13.3 X (Addon)
13.4 X (Addon)
13.5 X (Addon)
13.6 X (Addon)
14.1 X (Addon)
14.2 X (Addon)
14.3 X (Cppcheck)
14.4 X (Addon)
15.1 X (Addon)
15.2 X (Addon)
15.3 X (Addon)
15.4 X (Addon)
15.5 X (Addon)
15.6 X (Addon)
15.7 X (Addon)
16.1 X (Addon)
16.2 X (Addon)
16.3 X (Addon)
16.4 X (Addon)
16.5 X (Addon)
16.6 X (Addon)
16.7 X (Addon)
17.1 X (Addon)
17.2 X (Addon)
17.3 X (Addon)
17.4 X (Cppcheck)
17.5 X (Cppcheck)
17.6 X (Addon)
17.7 X (Addon)
17.8 X (Addon)
18.1 X (Cppcheck)
18.2 X (Cppcheck)
18.3 X (Cppcheck)
18.4 X (Addon)
18.5 X (Addon)
18.6 X (Cppcheck)
18.7 X (Addon)
18.8 X (Addon)
19.1 X (Cppcheck)
19.2 X (Addon)
20.1 X (Addon)
20.2 X (Addon)
20.3 X (Addon)
20.4 X (Addon)
20.5 X (Addon)
20.6 X (Cppcheck)
20.7 X (Addon)
20.8 X (Addon)
20.9 X (Addon)
20.10 X (Addon)
20.11 X (Addon)
20.12 X (Addon)
20.13 X (Addon)
20.14 X (Addon)
21.1 X (Addon)
21.2 X (Addon)
21.3 X (Addon)
21.4 X (Addon)
21.5 X (Addon)
21.6 X (Addon)
21.7 X (Addon)
21.8 X (Addon)
21.9 X (Addon)
21.10 X (Addon)
21.11 X (Addon)
21.12 X (Addon)
21.13 X (Cppcheck)
21.14 X (Addon)
21.15 X (Addon)
21.16 X (Addon)
21.17 X (Cppcheck)
21.18 X (Cppcheck)
21.19 X (Addon)
21.20 X (Addon)
21.21 X (Addon)
22.1 X (Cppcheck)
22.2 X (Cppcheck)
22.3 X (Cppcheck)
22.4 X (Cppcheck)
22.5 X (Addon)
22.6 X (Cppcheck)
22.7 X (Addon)
22.8 X (Addon)
22.9 X (Addon)
22.10 X (Addon)
+21
View File
@@ -0,0 +1,21 @@
# Advisory: casting from void pointer to type pointer is ok. Done by STM libraries as well
misra-c2012-11.4
# Advisory: casting from void pointer to type pointer is ok. Done by STM libraries as well
misra-c2012-11.5
# Advisory: as stated in the Misra document, use of goto statements in accordance to 15.2 and 15.3 is ok
misra-c2012-15.1
# Advisory: union types can be used
misra-c2012-19.2
# Advisory: The # and ## preprocessor operators should not be used
misra-c2012-20.10
# needed since not all of these suppressions are applicable to all builds
unmatchedSuppression
# All interrupt handlers are defined, including ones we don't use
unusedFunction:*/interrupt_handlers*.h
# all of the below suppressions are from new checks introduced after updating
# cppcheck from 2.5 -> 2.13. they are listed here to separate the update from
# fixing the violations and all are intended to be removed soon after
misra-c2012-2.5 # unused macros. a few legit, rest aren't common between F4/H7 builds. should we do this in the unusedFunction pass?
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env bash
set -e
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
PANDA_DIR=$(realpath $DIR/../../)
OPENDBC_ROOT=$(python3 -c "import opendbc; print(opendbc.INCLUDE_PATH)")
GREEN="\e[1;32m"
YELLOW="\e[1;33m"
RED="\e[1;31m"
NC='\033[0m'
: "${CPPCHECK_DIR:=$(python3 -c "import cppcheck; print(cppcheck.DIR)")}"
# ensure checked in coverage table is up to date
cd $DIR
if [ -z "$SKIP_TABLES_DIFF" ]; then
python3 $CPPCHECK_DIR/addons/misra.py -generate-table > coverage_table
if ! git diff --quiet coverage_table; then
echo -e "${YELLOW}MISRA coverage table doesn't match. Update and commit:${NC}"
exit 3
fi
fi
cd $PANDA_DIR
if [ -z "${SKIP_BUILD}" ]; then
scons
fi
CHECKLIST=$DIR/checkers.txt
echo "Cppcheck checkers list from test_misra.sh:" > $CHECKLIST
cppcheck() {
# get all gcc defines: arm-none-eabi-gcc -dM -E - < /dev/null
COMMON_DEFINES="-D__GNUC__=9 -UCMSIS_NVIC_VIRTUAL -UCMSIS_VECTAB_VIRTUAL -UPANDA_JUNGLE -UBOOTSTUB"
# note that cppcheck build cache results in inconsistent results as of v2.13.0
OUTPUT=$DIR/.output.log
echo -e "\n\n\n\n\nTEST variant options:" >> $CHECKLIST
echo -e ""${@//$PANDA_DIR/}"\n\n" >> $CHECKLIST # (absolute path removed)
$CPPCHECK_DIR/cppcheck --inline-suppr \
-I $PANDA_DIR \
-I "$(arm-none-eabi-gcc -print-file-name=include)" \
-I $OPENDBC_ROOT \
--suppressions-list=$DIR/suppressions.txt --suppress=*:*inc/* \
--suppress=*:*include/* --error-exitcode=2 --check-level=exhaustive --safety \
--platform=arm32-wchar_t4 $COMMON_DEFINES --checkers-report=$CHECKLIST.tmp \
--std=c11 "$@" 2>&1 | tee $OUTPUT
cat $CHECKLIST.tmp >> $CHECKLIST
rm $CHECKLIST.tmp
# cppcheck bug: some MISRA errors won't result in the error exit code,
# so check the output (https://trac.cppcheck.net/ticket/12440#no1)
if grep -e "misra violation" -e "error" -e "style: " $OUTPUT > /dev/null; then
printf "${RED}** FAILED: MISRA violations found!${NC}\n"
exit 1
fi
}
PANDA_OPTS="--enable=all --disable=unusedFunction --addon=misra"
printf "\n${GREEN}** PANDA H7 CODE **${NC}\n"
cppcheck $PANDA_OPTS -DSTM32H7 -DSTM32H725xx -I $PANDA_DIR/board/stm32h7/inc/ $PANDA_DIR/board/main.c
# unused needs to run globally
#printf "\n${GREEN}** UNUSED ALL CODE **${NC}\n"
#cppcheck --enable=unusedFunction --quiet $PANDA_DIR/board/
printf "\n${GREEN}Success!${NC} took $SECONDS seconds\n"
# ensure list of checkers is up to date
cd $DIR
if [ -z "$SKIP_TABLES_DIFF" ] && ! git diff --quiet $CHECKLIST; then
echo -e "\n${YELLOW}WARNING: Cppcheck checkers.txt report has changed. Review and commit...${NC}"
exit 4
fi
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
import os
import glob
import pytest
import shutil
import subprocess
import tempfile
import random
HERE = os.path.abspath(os.path.dirname(__file__))
ROOT = os.path.join(HERE, "../../")
# skip mutating these paths
IGNORED_PATHS = (
'board/obj',
'board/jungle',
'board/body',
'board/stm32h7/inc',
'board/fake_stm.h',
# bootstub only files
'board/flasher.h',
'board/bootstub.c',
'board/bootstub_declarations.h',
'board/stm32h7/llflash.h',
)
mutations = [
(None, None, False), # no mods, should pass
("board/stm32h7/llfdcan.h", "s/return ret;/if (true) { return ret; } else { return false; }/g", True),
]
patterns = [
# misra-c2012-13.3
"$a void test(int tmp) { int tmp2 = tmp++ + 2; if (tmp2) {;}}",
# misra-c2012-13.4
"$a int test(int x, int y) { return (x=2) && (y=2); }",
# misra-c2012-13.5
"$a void test(int tmp) { if (true && tmp++) {;} }",
# misra-c2012-13.6
"$a void test(int tmp) { if (sizeof(tmp++)) {;} }",
# misra-c2012-14.1
"$a void test(float len) { for (float j = 0; j < len; j++) {;} }",
# misra-c2012-14.4
"$a void test(int len) { if (len - 8) {;} }",
# misra-c2012-16.4
r"$a void test(int temp) {switch (temp) { case 1: ; }}\n",
# misra-c2012-17.8
"$a void test(int cnt) { for (cnt=0;;cnt++) {;} }",
# misra-c2012-20.4
r"$a #define auto 1\n",
# misra-c2012-20.5
r"$a #define TEST 1\n#undef TEST\n",
]
all_files = glob.glob('board/**', root_dir=ROOT, recursive=True)
files = sorted(f for f in all_files if f.endswith(('.c', '.h')) and not f.startswith(IGNORED_PATHS))
assert len(files) > 50, all(d in files for d in ('board/main.c', 'board/stm32h7/llfdcan.h'))
# fixed seed so every xdist worker collects the same test params
rng = random.Random(len(files))
for p in patterns:
mutations.append((rng.choice(files), p, True))
# sample to keep CI fast, but always include the no-mutation case
mutations = [mutations[0]] + rng.sample(mutations[1:], min(2, len(mutations) - 1))
@pytest.mark.parametrize("fn, patch, should_fail", mutations)
def test_misra_mutation(fn, patch, should_fail):
with tempfile.TemporaryDirectory() as tmp:
shutil.copytree(ROOT, tmp + "/panda", dirs_exist_ok=True)
# apply patch
if fn is not None:
fpath = os.path.join(tmp, "panda", fn)
with open(fpath) as f:
content = f.read()
if patch.startswith("s/"):
old, new = patch[2:].rsplit("/g", 1)[0].split("/", 1)
content = content.replace(old, new)
elif patch.startswith("$a "):
content += patch[3:].replace(r"\n", "\n")
with open(fpath, "w") as f:
f.write(content)
# run test
r = subprocess.run("SKIP_TABLES_DIFF=1 panda/tests/misra/test_misra.sh", cwd=tmp, shell=True)
failed = r.returncode != 0
assert failed == should_fail
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
set -e
if [ -z "$SOURCE_DIR" ]; then
echo "SOURCE_DIR must be set"
exit 1
fi
if [ -z "$GIT_COMMIT" ]; then
echo "GIT_COMMIT must be set"
exit 1
fi
if [ -z "$TEST_DIR" ]; then
echo "TEST_DIR must be set"
exit 1
fi
CONTINUE_PATH="/data/continue.sh"
tee $CONTINUE_PATH << EOF
#!/usr/bin/env bash
sudo abctl --set_success
# patch sshd config
sudo mount -o rw,remount /
sudo sed -i "s,/data/params/d/GithubSshKeys,/usr/comma/setup_keys," /etc/ssh/sshd_config
sudo systemctl daemon-reload
sudo systemctl restart ssh
sudo systemctl disable ssh-param-watcher.path
sudo systemctl disable ssh-param-watcher.service
sudo mount -o ro,remount /
sudo systemctl stop power_monitor
while true; do
if ! sudo systemctl is-active -q ssh; then
sudo systemctl start ssh
fi
sleep 5s
done
sleep infinity
EOF
chmod +x $CONTINUE_PATH
# set up environment
if [ ! -d "$SOURCE_DIR" ]; then
git clone https://github.com/commaai/panda.git $SOURCE_DIR
fi
# setup device/SOM state
SOM_ST_IO=49
echo $SOM_ST_IO > /sys/class/gpio/export || true
echo out > /sys/class/gpio/gpio${SOM_ST_IO}/direction
echo 1 > /sys/class/gpio/gpio${SOM_ST_IO}/value
# checkout panda commit
cd $SOURCE_DIR
rm -f .git/index.lock
git reset --hard
git fetch --no-tags --no-recurse-submodules -j4 --verbose --depth 1 origin $GIT_COMMIT
find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \;
git reset --hard $GIT_COMMIT
git checkout $GIT_COMMIT
git clean -xdff
echo "git checkout done, t=$SECONDS"
du -hs $SOURCE_DIR $SOURCE_DIR/.git
rsync -a --delete $SOURCE_DIR $TEST_DIR
echo "$TEST_DIR synced with $GIT_COMMIT, t=$SECONDS"
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
import random
import unittest
from opendbc.car.structs import CarParams
from panda import DLC_TO_LEN, USBPACKET_MAX_SIZE, pack_can_buffer, unpack_can_buffer
from panda.tests.libpanda import libpanda_py
lpp = libpanda_py.libpanda
CHUNK_SIZE = USBPACKET_MAX_SIZE
TX_QUEUES = (lpp.tx1_q, lpp.tx2_q, lpp.tx3_q)
def unpackage_can_msg(pkt):
dat_len = DLC_TO_LEN[pkt[0].data_len_code]
dat = bytes(pkt[0].data[0:dat_len])
return pkt[0].addr, dat, pkt[0].bus
def random_can_messages(n, bus=None):
msgs = []
for _ in range(n):
if bus is None:
bus = random.randint(0, 3)
address = random.randint(1, (1 << 29) - 1)
data = bytes([random.getrandbits(8) for _ in range(DLC_TO_LEN[random.randrange(0, len(DLC_TO_LEN))])])
msgs.append((address, data, bus))
return msgs
class TestPandaComms(unittest.TestCase):
def setUp(self):
lpp.set_safety_hooks(CarParams.SafetyModel.allOutput, 0)
lpp.comms_can_reset()
def test_tx_queues(self):
for bus in range(len(TX_QUEUES)):
message = (0x100, b"test", bus)
can_pkt_tx = libpanda_py.make_CANPacket(message[0], message[2], message[1])
can_pkt_rx = libpanda_py.ffi.new('CANPacket_t *')
assert lpp.can_push(TX_QUEUES[bus], can_pkt_tx), "CAN push failed"
assert lpp.can_pop(TX_QUEUES[bus], can_pkt_rx), "CAN pop failed"
assert unpackage_can_msg(can_pkt_rx) == message
def test_comms_reset_rx(self):
# store some test messages in the queue
test_msg = (0x100, b"test", 0)
for _ in range(100):
can_pkt_tx = libpanda_py.make_CANPacket(test_msg[0], test_msg[2], test_msg[1])
lpp.can_push(lpp.rx_q, can_pkt_tx)
# read a small chunk such that we have some overflow
TINY_CHUNK_SIZE = 6
dat = libpanda_py.ffi.new(f"uint8_t[{TINY_CHUNK_SIZE}]")
rx_len = lpp.comms_can_read(dat, TINY_CHUNK_SIZE)
assert rx_len == TINY_CHUNK_SIZE, "comms_can_read returned too little data"
_, overflow = unpack_can_buffer(bytes(dat))
assert len(overflow) > 0, "overflow buffer should not be empty"
# reset the comms to clear the overflow buffer on the panda side
lpp.comms_can_reset()
# read a large chunk, which should now contain valid messages
LARGE_CHUNK_SIZE = 512
dat = libpanda_py.ffi.new(f"uint8_t[{LARGE_CHUNK_SIZE}]")
rx_len = lpp.comms_can_read(dat, LARGE_CHUNK_SIZE)
assert rx_len == LARGE_CHUNK_SIZE, "comms_can_read returned too little data"
msgs, _ = unpack_can_buffer(bytes(dat))
assert len(msgs) > 0, "message buffer should not be empty"
for m in msgs:
assert m == test_msg, "message buffer should contain valid test messages"
def test_comms_reset_tx(self):
# store some test messages in the queue
test_msg = (0x100, b"test", 0)
packed = pack_can_buffer([test_msg for _ in range(100)], chunk=True)
# write a small chunk such that we have some overflow
TINY_CHUNK_SIZE = 6
lpp.comms_can_write(bytes(packed[0][:TINY_CHUNK_SIZE]), TINY_CHUNK_SIZE)
# reset the comms to clear the overflow buffer on the panda side
lpp.comms_can_reset()
# write a full valid chunk, which should now contain valid messages
lpp.comms_can_write(bytes(packed[1]), len(packed[1]))
# read the messages from the queue and make sure they're valid
queue_msgs = []
pkt = libpanda_py.ffi.new('CANPacket_t *')
while lpp.can_pop(TX_QUEUES[0], pkt):
queue_msgs.append(unpackage_can_msg(pkt))
assert len(queue_msgs) > 0, "message buffer should not be empty"
for m in queue_msgs:
assert m == test_msg, "message buffer should contain valid test messages"
def test_can_send_usb(self):
for bus in range(3):
with self.subTest(bus=bus):
for _ in range(100):
msgs = random_can_messages(200, bus=bus)
packed = pack_can_buffer(msgs)
# Simulate USB bulk chunks
for buf in packed:
for i in range(0, len(buf), CHUNK_SIZE):
chunk_len = min(CHUNK_SIZE, len(buf) - i)
lpp.comms_can_write(bytes(buf[i:i+chunk_len]), chunk_len)
# Check that they ended up in the right buffers
queue_msgs = []
pkt = libpanda_py.ffi.new('CANPacket_t *')
while lpp.can_pop(TX_QUEUES[bus], pkt):
queue_msgs.append(unpackage_can_msg(pkt))
self.assertEqual(len(queue_msgs), len(msgs))
self.assertEqual(queue_msgs, msgs)
def test_can_receive_usb(self):
msgs = random_can_messages(50000)
packets = [libpanda_py.make_CANPacket(m[0], m[2], m[1]) for m in msgs]
rx_msgs = []
overflow_buf = b""
while len(packets) > 0:
# Push into queue
while lpp.can_slots_empty(lpp.rx_q) > 0 and len(packets) > 0:
lpp.can_push(lpp.rx_q, packets.pop(0))
# Simulate USB bulk IN chunks
MAX_TRANSFER_SIZE = 16384
dat = libpanda_py.ffi.new(f"uint8_t[{CHUNK_SIZE}]")
while True:
buf = b""
while len(buf) < MAX_TRANSFER_SIZE:
max_size = min(CHUNK_SIZE, MAX_TRANSFER_SIZE - len(buf))
rx_len = lpp.comms_can_read(dat, max_size)
buf += bytes(dat[0:rx_len])
if rx_len < max_size:
break
if len(buf) == 0:
break
unpacked_msgs, overflow_buf = unpack_can_buffer(overflow_buf + buf)
rx_msgs.extend(unpacked_msgs)
self.assertEqual(len(rx_msgs), len(msgs))
self.assertEqual(rx_msgs, msgs)
if __name__ == "__main__":
unittest.main()
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env python3
import random
import unittest
from panda import pack_can_buffer, unpack_can_buffer, DLC_TO_LEN
class PandaTestPackUnpack(unittest.TestCase):
def test_panda_lib_pack_unpack(self):
overflow_buf = b''
to_pack = []
for _ in range(10000):
address = random.randint(1, (1 << 29) - 1)
data = bytes([random.getrandbits(8) for _ in range(DLC_TO_LEN[random.randrange(0, len(DLC_TO_LEN))])])
to_pack.append((address, data, 0))
packed = pack_can_buffer(to_pack)
unpacked = []
for dat in packed:
msgs, overflow_buf = unpack_can_buffer(overflow_buf + dat)
unpacked.extend(msgs)
self.assertEqual(unpacked, to_pack)
if __name__ == "__main__":
unittest.main()