From a0c5371835744e2f0abd5a97ab2e89086e918faf Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 13 Oct 2026 16:30:41 -0700 Subject: [PATCH] SPI: add more test cases (#2462) (cherry picked from commit 09d742cdf2b415ca6218cee2c5f58238069337fb) --- panda/python/__init__.py | 2 +- panda/tests/hitl/test_5_spi.py | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/panda/python/__init__.py b/panda/python/__init__.py index be86a23393..11a562afc1 100644 --- a/panda/python/__init__.py +++ b/panda/python/__init__.py @@ -710,7 +710,7 @@ class Panda: @ensure_can_packet_version def can_send_many(self, arr, *, fd=False, timeout=CAN_SEND_TIMEOUT_MS): - snds = pack_can_buffer(arr, chunk=(not self.spi), fd=fd) + snds = pack_can_buffer(arr, chunk=True, fd=fd) for tx in snds: while len(tx) > 0: bs = self._handle.bulkWrite(3, tx, timeout=timeout) diff --git a/panda/tests/hitl/test_5_spi.py b/panda/tests/hitl/test_5_spi.py index 83b13184a1..0e0eea1625 100644 --- a/panda/tests/hitl/test_5_spi.py +++ b/panda/tests/hitl/test_5_spi.py @@ -1,7 +1,12 @@ import binascii import random +import struct +import threading +import time +import unittest from unittest.mock import patch +from opendbc.car.structs import CarParams from panda.tests.hitl.base import PandaTestCase from panda import Panda from panda.python.spi import PandaProtocolMismatch, PandaSpiNackResponse @@ -84,3 +89,48 @@ class TestSpi(PandaTestCase): p._handle.bulkWrite(ep, b"abc", timeout=50) self._ping(p) + + def test_multiple_clients(self): + # every client's request runs, even when it's identical to another client's + self.p.set_safety_mode(CarParams.SafetyModel.allOutput) + self.p.set_can_loopback(True) + with Panda(self.p._serial) as p1, Panda(self.p._serial) as p2: + for _ in range(100): + p1.can_send(0x123, b"\x01" * 8, 0) + p2.can_send(0x123, b"\x01" * 8, 0) + time.sleep(0.1) + assert len([m for m in self.p.can_recv() if m[2] == 0]) == 200 + + def test_can_exactly_once(self): + # more than a full transfer each way, so writes and reads are split into chunks + self.p.set_safety_mode(CarParams.SafetyModel.allOutput) + self.p.set_can_loopback(True) + sent = [struct.pack(">Q", i) for i in range(600)] + self.p.can_send_many([(0x123, dat, 0) for dat in sent], timeout=0) + time.sleep(0.5) + got = [] + while len(r := self.p.can_recv()): + got += [m[1] for m in r if m[2] == 0] + assert sorted(got) == sent + + def test_thread_safe(self): + # one handle shared by threads, like pandad + errors = [] + def hammer(): + try: + for _ in range(200): + self.p.health() + except Exception as e: + errors.append(e) + threads = [threading.Thread(target=hammer) for _ in range(4)] + for t in threads: + t.start() + for t in threads: + t.join() + assert errors == [] + + @unittest.expectedFailure # SPI bulkRead ignores length on the old protocol + def test_max_sizes(self): + # around the first read size and the max transfer size + for n in (1, 1013, 1014, 4032): + assert len(self.p._handle.bulkRead(0xAB, n)) == n