From 10dc86482f1bf3f8759e8bdcd16bf7261f92a4f1 Mon Sep 17 00:00:00 2001 From: Vladimir Smitka Date: Sat, 12 Sep 2026 05:27:18 +0000 Subject: [PATCH 1/2] Read test serial output in chunks instead of one byte at a time SerialSaver's reader thread read one byte per iteration and appended it to self.all_output. That store goes through STORE_ATTR, so CPython's in-place unicode concatenation does not apply and every byte copies the whole string. The bsim tests run the BabbleSim phy at -v=9 through the same reader, which is about 1.1 MB per test. Measured here, 993 KB of that costs 9.4 s in the reader against a test budget of 10 to 30 s of wall clock, and the phy stalls whenever the reader falls more than one pipe buffer behind. Read what is already buffered instead. The two bsim test files go from 144 s to 26 s on an idle host, and from 340 s with failures to 57 s green under CPU oversubscription. --- ports/zephyr-cp/tests/__init__.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ports/zephyr-cp/tests/__init__.py b/ports/zephyr-cp/tests/__init__.py index c042d744d7d..78b9a17a676 100644 --- a/ports/zephyr-cp/tests/__init__.py +++ b/ports/zephyr-cp/tests/__init__.py @@ -28,6 +28,14 @@ def close(self): self.stdin.close() self.stdout.close() + def read_some(self, limit=65536): + # read1() hands back whatever is already buffered and does at most one + # read on the pipe, so it blocks only when nothing has arrived yet. + data = self.stdout.read1(limit) + if data == b"": + raise EOFError("stdout closed") + return data + @property def in_waiting(self): if self.stdout is None: @@ -47,13 +55,22 @@ def __init__(self, serial_obj, name="serial"): self._stop = threading.Event() self._lock = threading.Lock() self._cv = threading.Condition(self._lock) + self._read_some = getattr(serial_obj, "read_some", None) self._reader = threading.Thread(target=self._reader_loop, daemon=True) self._reader.start() def _reader_loop(self): while not self._stop.is_set(): try: - read = self.serial.read(1) + if self._read_some is not None: + read = self._read_some() + else: + read = self.serial.read(1) + # in_waiting is a non-blocking check on a real serial port, + # so draining it here is free. + waiting = self.serial.in_waiting + if waiting: + read += self.serial.read(waiting) except Exception: # Serial port closed or device disconnected. break From b3f338e050a029974aaceb84521299aa70a83fec Mon Sep 17 00:00:00 2001 From: Vladimir Smitka Date: Sat, 12 Sep 2026 06:21:16 +0000 Subject: [PATCH 2/2] Give test_bsim_packet_buffer_packet_lengths the default timeout It was the only bsim test below the default 10 s, and it is the first one to miss its deadline when the host is busy: with 32 spinning processes on a 4 core machine it failed both runs at 5 s and passed all three at 10 s. The timeout is only an upper bound, wait_until_done() returns as soon as the device exits, so the larger budget costs nothing when the test passes. The full bsim suite runs in 106 s either way. --- ports/zephyr-cp/tests/bsim/test_bsim_ble_packet_buffer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/zephyr-cp/tests/bsim/test_bsim_ble_packet_buffer.py b/ports/zephyr-cp/tests/bsim/test_bsim_ble_packet_buffer.py index 27bb703f379..d5ff222ff5d 100644 --- a/ports/zephyr-cp/tests/bsim/test_bsim_ble_packet_buffer.py +++ b/ports/zephyr-cp/tests/bsim/test_bsim_ble_packet_buffer.py @@ -604,7 +604,6 @@ def test_bsim_packet_buffer_write_header(bsim_phy, circuitpython1, circuitpython """ -@pytest.mark.duration(5) @pytest.mark.circuitpy_drive({"code.py": BSIM_PB_LENGTHS_CODE}) def test_bsim_packet_buffer_packet_lengths(bsim_phy, circuitpython): """incoming_packet_length and outgoing_packet_length properties."""