From ae61e6c7e663ad8f2162d8a0c5cf0ce7c6e5f728 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Fri, 15 May 2026 00:45:49 +0200 Subject: fix test hung Signed-off-by: HiFiPhile --- test/hil/hil_test.py | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) (limited to 'test') diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index ed9ebbf1a..527679d46 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -134,6 +134,9 @@ class HilConfig(TypedDict): CMD_TIMEOUT = int(os.getenv('HIL_CMD_TIMEOUT', '180')) POOL_TIMEOUT = int(os.getenv('HIL_POOL_TIMEOUT', '3000')) +SERIAL_READ_TIMEOUT = float(os.getenv('HIL_SERIAL_READ_TIMEOUT', '5')) +SERIAL_WRITE_TIMEOUT = float(os.getenv('HIL_SERIAL_WRITE_TIMEOUT', '2')) +SERIAL_WRITE_DEADLINE = float(os.getenv('HIL_SERIAL_WRITE_DEADLINE', '10')) def cmd_stdout_text(out: Any) -> str: @@ -218,7 +221,8 @@ def open_serial_dev(port: str): while timeout > 0: if os.path.exists(port): try: - ser = serial.Serial(port, baudrate=115200, timeout=5) + ser = serial.Serial(port, baudrate=115200, timeout=SERIAL_READ_TIMEOUT, + write_timeout=SERIAL_WRITE_TIMEOUT) break except serial.SerialException: print(f'serial {port} not reaady {timeout} sec') @@ -231,6 +235,26 @@ def open_serial_dev(port: str): return ser +def serial_write_all(ser: serial.Serial, data: bytes, deadline: float = SERIAL_WRITE_DEADLINE): + total = 0 + end = time.monotonic() + deadline + + while total < len(data): + try: + written = ser.write(data[total:]) + except serial.SerialTimeoutException: + written = 0 + + if written: + total += written + continue + + if time.monotonic() >= end: + raise AssertionError(f'Serial write timeout after {deadline:.1f}s') + + time.sleep(0.01) + + def read_disk_file(uid: str, lun: int, fname: str) -> bytes: # open_fs("fat://{dev}) require 'pip install pyfatfs' dev = get_disk_dev(uid, 'TinyUSB', lun) @@ -698,8 +722,7 @@ def test_host_cdc_msc_hid(board): offset = 0 while offset < echo_len: chunk_size = min(random.randint(1, packet_size), echo_len - offset) - ser.write(echo_data[offset:offset + chunk_size]) - ser.flush() + serial_write_all(ser, echo_data[offset:offset + chunk_size]) # wait until this chunk is echoed back echo = b'' t_end = time.monotonic() + 1.0 @@ -748,8 +771,7 @@ def test_host_msc_file_explorer(board): time.sleep(1) ser.reset_input_buffer() for ch in 'cat README.TXT\r': - ser.write(ch.encode()) - ser.flush() + serial_write_all(ser, ch.encode()) time.sleep(0.002) resp = b'' @@ -771,8 +793,7 @@ def test_host_msc_file_explorer(board): time.sleep(0.5) ser.reset_input_buffer() for ch in 'dd 1024\r': - ser.write(ch.encode()) - ser.flush() + serial_write_all(ser, ch.encode()) time.sleep(0.002) # Read dd output until prompt @@ -827,8 +848,7 @@ def test_device_cdc_dual_ports(board): # Write in chunks of random 1-64 bytes (device has 64-byte buffer) while offset < payload_len: chunk_size = min(random.randint(1, 64), payload_len - offset) - ser[writer].write(payload[offset:offset + chunk_size]) - ser[writer].flush() + serial_write_all(ser[writer], payload[offset:offset + chunk_size]) rd0 += ser[0].read(chunk_size) rd1 += ser[1].read(chunk_size) offset += chunk_size @@ -862,8 +882,7 @@ def test_device_cdc_msc(board): # Write in chunks of random 1-64 bytes (device has 64-byte buffer) while offset < size: chunk_size = min(random.randint(1, 64), size - offset) - ser.write(test_str[offset:offset + chunk_size]) - ser.flush() + serial_write_all(ser, test_str[offset:offset + chunk_size]) rd_str += ser.read(chunk_size) offset += chunk_size assert rd_str == test_str, f'CDC wrong data ({size} bytes):\n expected: {test_str}\n received: {rd_str}' @@ -1124,8 +1143,7 @@ def test_device_printer_to_cdc(board): offset = 0 while offset < size: chunk_size = min(random.randint(1, 64), size - offset) - ser.write(test_data[offset:offset + chunk_size]) - ser.flush() + serial_write_all(ser, test_data[offset:offset + chunk_size]) time.sleep(0.01) offset += chunk_size -- cgit v1.3.1 From ff57edb3e5e8d36b2783971924bf7373b4fc39bc Mon Sep 17 00:00:00 2001 From: hathach Date: Sat, 13 Jun 2026 23:35:35 +0700 Subject: hil: make serial write timeout fatal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pyserial's posix write() raises SerialTimeoutException after partial progress with the byte count lost, so the retry loop re-sent from the same offset and could duplicate bytes on the wire — surfacing as bogus data-mismatch failures that look like device firmware bugs. write_timeout is already a total per-call deadline, so the loop added duplication risk without extending the budget: write once and treat a timeout as fatal. Default bumped 2 -> 10 s to keep the old overall bound; HIL_SERIAL_WRITE_DEADLINE removed. The per-character CLI loops keep their existing pacing (the 2 ms sleep between single-byte writes already spaces them on the wire); no unbounded ser.flush()/tcdrain is added. Review follow-up for #3643 (hil_test.py l.257/264 findings). Co-Authored-By: Claude Fable 5 --- test/hil/hil_test.py | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) (limited to 'test') diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index c2ba34f02..aee59e05a 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -145,8 +145,7 @@ class HilConfig(TypedDict): CMD_TIMEOUT = int(os.getenv('HIL_CMD_TIMEOUT', '180')) POOL_TIMEOUT = int(os.getenv('HIL_POOL_TIMEOUT', '3000')) SERIAL_READ_TIMEOUT = float(os.getenv('HIL_SERIAL_READ_TIMEOUT', '5')) -SERIAL_WRITE_TIMEOUT = float(os.getenv('HIL_SERIAL_WRITE_TIMEOUT', '2')) -SERIAL_WRITE_DEADLINE = float(os.getenv('HIL_SERIAL_WRITE_DEADLINE', '10')) +SERIAL_WRITE_TIMEOUT = float(os.getenv('HIL_SERIAL_WRITE_TIMEOUT', '10')) def cmd_stdout_text(out: Any) -> str: @@ -247,24 +246,14 @@ def open_serial_dev(port: str): return ser -def serial_write_all(ser: serial.Serial, data: bytes, deadline: float = SERIAL_WRITE_DEADLINE): - total = 0 - end = time.monotonic() + deadline - - while total < len(data): - try: - written = ser.write(data[total:]) - except serial.SerialTimeoutException: - written = 0 - - if written: - total += written - continue - - if time.monotonic() >= end: - raise AssertionError(f'Serial write timeout after {deadline:.1f}s') - - time.sleep(0.01) +def serial_write_all(ser: serial.Serial, data: bytes): + # write_timeout is a total deadline for the whole call (pyserial keeps partial progress + # internally). A timeout means the device stopped draining — treat it as fatal: pyserial + # loses the partial-write count on raise, so retrying would duplicate bytes on the wire. + try: + ser.write(data) + except serial.SerialTimeoutException: + raise AssertionError(f'Serial write timeout after {SERIAL_WRITE_TIMEOUT:.1f}s') def read_disk_file(uid: str, lun: int, fname: str) -> bytes: -- cgit v1.3.1