From 59e28a635a471ace3bf90a7338fe065f4689d8d3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Oct 2024 16:43:58 +1100 Subject: [PATCH 01/43] tests/run-tests.py: Don't print info for tests that pass. Signed-off-by: Damien George --- tests/run-tests.py | 48 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index aeea0bad5e779..fdcc662fd7031 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -674,6 +674,42 @@ def run_script_on_remote_target(self, args, test_file, is_special): return had_crash, output_mupy +stdout_isatty = sys.stdout.isatty() +end_erase = " \r" + + +def print_output_skip(test): + if stdout_isatty: + print("skip ", test, end=end_erase) + else: + print("skip ", test) + + +def print_output_start(test): + if stdout_isatty: + print(".... ", test, end=end_erase) + + +def print_output_end_skip(test, reason): + if stdout_isatty: + return + print(reason, test, end=end_erase) + else: + print(reason, test) + + +def print_output_end_pass(test, extra_info): + if stdout_isatty: + return + print("pass ", test, extra_info, end=end_erase) + else: + print("pass ", test, extra_info) + + +def print_output_end_fail(test, extra_info): + print("FAIL ", test, extra_info) + + def run_tests(pyb, tests, args, result_dir, num_threads=1): testcase_count = ThreadSafeCounter() test_results = ThreadSafeCounter([]) @@ -930,10 +966,12 @@ def run_one_test(test_file): skip_it |= skip_inlineasm and is_inlineasm if skip_it: - print("skip ", test_file) + print_output_skip(test_file) test_results.append((test_file, "skip", "")) return + print_output_start(test_file) + # Run the test on the MicroPython target. output_mupy = run_micropython(pyb, args, test_file, test_file_abspath) @@ -945,11 +983,11 @@ def run_one_test(test_file): # reset. Wait for the soft reset to finish, so we don't interrupt the # start-up code (eg boot.py) when preparing to run the next test. pyb.read_until(1, b"raw REPL; CTRL-B to exit\r\n") - print("skip ", test_file) + print_output_end_skip(test_file, "skip") test_results.append((test_file, "skip", "")) return elif output_mupy == b"SKIP-TOO-LARGE\n": - print("lrge ", test_file) + print_output_end_skip(test_file, "lrge") test_results.append((test_file, "skip", "too large")) return @@ -1036,12 +1074,12 @@ def run_one_test(test_file): # Print test summary, update counters, and save .exp/.out files if needed. if test_passed: - print("pass ", test_file, extra_info) + print_output_end_pass(test_file, extra_info) test_results.append((test_file, "pass", "")) rm_f(filename_expected) rm_f(filename_mupy) else: - print("FAIL ", test_file, extra_info) + print_output_end_fail(test_file, extra_info) if output_expected is not None: with open(filename_expected, "wb") as f: f.write(output_expected) From 9029c3102d25866845d270ea1b721705d7ce6139 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 25 Oct 2024 23:50:36 +1100 Subject: [PATCH 02/43] tests/run-multitests.py: Suppress diff. TODO: make it a CLI option Signed-off-by: Damien George --- tests/run-multitests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run-multitests.py b/tests/run-multitests.py index 4412d0fde7fda..16702cf4b4581 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -538,7 +538,7 @@ def run_tests(test_files, instances_truth, instances_test): else: print("FAIL") test_results.append((test_file, "fail", "")) - if not cmd_args.show_output: + if False and not cmd_args.show_output: print("### TEST ###") print(output_test, end="") print("### TRUTH ###") From 0611f96c721f1adf1a7b34ea4b0182b6ad0f4dac Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 31 Oct 2024 12:38:27 +1100 Subject: [PATCH 03/43] tests/run-tests.py: Capture KeyboardInterrupt for nicer exit. Signed-off-by: Damien George --- tests/run-tests.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index fdcc662fd7031..1f5876f4242a5 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -1444,4 +1444,9 @@ def main(): if __name__ == "__main__": - main() + try: + main() + except KeyboardInterrupt: + print() + print("INTERRUPT") + sys.exit(1) From 66e14bc4d1869b2cf31dcacb93ce33c40bf80dc7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Oct 2024 14:07:16 +1100 Subject: [PATCH 04/43] tests/vcprate.py: Add a test for serial data rate and integrity. Signed-off-by: Damien George --- tests/vcprate.py | 279 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100755 tests/vcprate.py diff --git a/tests/vcprate.py b/tests/vcprate.py new file mode 100755 index 0000000000000..66c064fd54198 --- /dev/null +++ b/tests/vcprate.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python +# +# Performance and reliability test for USB CDC. +# +# Usage: +# vcprate.py [cdc-device] +# +# The `cdc-device` will default to /dev/ttyACM0. + +import sys +import time +import argparse +import serial + + +def drain_input(ser): + time.sleep(0.1) + while ser.inWaiting() > 0: + data = ser.read(ser.inWaiting()) + time.sleep(0.1) + + +read_test_script = """ +vcp_id = %u +led = None +try: + import pyb + pyb.LED(1).on() + led = pyb.LED(2) + assert pyb.USB_VCP(vcp_id).isconnected() + wr=pyb.USB_VCP(vcp_id).send +except: + import sys + wr=sys.stdout.buffer.write +b=bytearray(%u) +for i in range(len(b)): + b[i] = i & 0xff +for _ in range(%d): + if led: + led.toggle() + n = wr(b) +""" + + +def read_test(ser_repl, ser_data, usb_vcp_id, bufsize, nbuf): + assert bufsize % 256 == 0 # for verify to work + + # Load and run the read_test_script. + ser_repl.write(b"\x03\x01\x04") # break, raw-repl, soft-reboot + drain_input(ser_repl) + ser_repl.write(bytes(read_test_script % (usb_vcp_id, bufsize, nbuf), "ascii")) + ser_repl.write(b"\x04") # eof + ser_repl.flush() + response = ser_repl.read(2) + assert response == b"OK", response + + # Read data from the device, check it is correct, and measure throughput. + n = 0 + last_byte = None + t_start = time.time() + remain = nbuf * bufsize + total_data = bytearray(remain) + while remain: + t0 = time.monotonic_ns() + while ser_data.inWaiting() == 0: + if time.monotonic_ns() - t0 > 1e9: + # timeout waiting for data from device + break + time.sleep(0.0001) + if not ser_data.inWaiting(): + print("ERROR: timeout waiting for data") + return 0 + to_read = min(ser_data.inWaiting(), remain) + data = ser_data.read(to_read) + + # verify bytes coming in are in sequence + # if last_byte is not None: + # if data[0] != (last_byte + 1) & 0xff: + # print('ERROR: first byte is not in sequence:', last_byte, data[0]) + # last_byte = data[-1] + # for i in range(1, len(data)): + # if data[i] != (data[i - 1] + 1) & 0xff: + # print('ERROR: data not in sequence at position %d:' % i, data[i - 1], data[i]) + + remain -= len(data) + print(n, nbuf * bufsize, end="\r") + total_data[n : n + len(data)] = data + n += len(data) + t_end = time.time() + for i in range(0, len(total_data)): + if total_data[i] != i & 0xFF: + print("fail", i, i & 0xFF, total_data[i]) + ser_repl.write(b"\x03") # break + t = t_end - t_start + + # Print results. + print( + "DATA IN: bufsize=%u, read %u bytes in %.2f msec = %.2f kibytes/sec = %.2f MBits/sec" + % (bufsize, n, t * 1000, n / 1024 / t, n * 8 / 1000000 / t) + ) + + return t + + +write_test_script_verified = """ +import sys +vcp_id = %u +led=None +try: + import pyb + pyb.LED(1).on() + led=pyb.LED(2) + assert pyb.USB_VCP(vcp_id).isconnected() + rd=pyb.USB_VCP(vcp_id).recv +except: + rd=sys.stdin.readinto +b=bytearray(%u) +for _ in range(%u): + if led: + led.toggle() + n = rd(b) + fail = 0 + for i in range(n): + if b[i] != 32 + (i & 0x3f): + fail += 1 + if fail: + sys.stdout.write(b'ER%%04u' %% fail) + else: + sys.stdout.write(b'OK%%04u' %% n) +""" + +write_test_script_unverified = """ +import sys +vcp_id = %u +led=None +try: + import pyb + pyb.LED(1).on() + led=pyb.LED(2) + assert pyb.USB_VCP(vcp_id).isconnected() + rd=pyb.USB_VCP(vcp_id).recv +except: + rd=sys.stdin.readinto +b=bytearray(%u) +for _ in range(%u): + if led: + led.toggle() + n = rd(b) + if n != len(b): + sys.stdout.write(b'ER%%04u' %% n) + else: + sys.stdout.write(b'OK%%04u' %% n) +""" + + +def write_test(ser_repl, ser_data, usb_vcp_id, bufsize, nbuf, verified): + # Load and run the write_test_script. + ser_repl.write(b"\x03\x01\x04") # break, raw-repl, soft-reboot + drain_input(ser_repl) + if verified: + script = write_test_script_verified + else: + script = write_test_script_unverified + ser_repl.write(bytes(script % (usb_vcp_id, bufsize, nbuf), "ascii")) + ser_repl.write(b"\x04") # eof + ser_repl.flush() + drain_input(ser_repl) + + # Write data to the device, check it is correct, and measure throughput. + n = 0 + t_start = time.time() + buf = bytearray(bufsize) + for i in range(len(buf)): + buf[i] = 32 + (i & 0x3F) # don't want to send ctrl chars! + for i in range(nbuf): + ser_data.write(buf) + n += len(buf) + print(n, nbuf * bufsize, end="\r") + # while ser_data.inWaiting() == 0: + # time.sleep(0.001) + # response = ser_data.read(ser_data.inWaiting()) + response = ser_repl.read(6) + if response != b"OK%04u" % bufsize: + print("bad response, expecting OK%04u, got %r" % (bufsize, response)) + t_end = time.time() + ser_repl.write(b"\x03") # break + t = t_end - t_start + + # Print results. + print( + "DATA OUT: verify=%d, bufsize=%u, wrote %u bytes in %.2f msec = %.2f kibytes/sec = %.2f MBits/sec" + % (verified, bufsize, n, t * 1000, n / 1024 / t, n * 8 / 1000000 / t) + ) + + return t + + +def do_test(dev_repl, dev_data=None, usb_vcp_id=None, fast=False): + if dev_data is None: + print("REPL and data on", dev_repl) + ser_repl = serial.Serial(dev_repl, baudrate=115200) + ser_data = ser_repl + usb_vcp_id = 0 + else: + print("REPL on", dev_repl) + print("data on", dev_data) + print("USB VCP", usb_vcp_id) + ser_repl = serial.Serial(dev_repl, baudrate=115200) + ser_data = serial.Serial(dev_data, baudrate=115200) + + if 0: + for i in range(1000): + print("======== TEST %04u ========" % i) + read_test(ser_repl, ser_data, usb_vcp_id, 8000, 32) + write_test(ser_repl, ser_data, usb_vcp_id, 8000, 32, True) + return + + read_test_params = [ + (256, 128), + (512, 64), + (1024, 64), + (2048, 64), + (4096, 64), + (8192, 64), + (16384, 64), + ] + write_test_params = [ + (128, 32), + (256, 16), + (512, 16), + (1024, 16), + (2048, 16), + (4096, 16), + (8192, 32), + (9999, 64), + ] + + if fast: + read_test_params = (read_test_params[-1],) + write_test_params = (write_test_params[-1],) + + for bufsize, nbuf in read_test_params: + t = read_test(ser_repl, ser_data, usb_vcp_id, bufsize, nbuf) + if t > 4: + break + + for bufsize, nbuf in write_test_params: + t = write_test(ser_repl, ser_data, usb_vcp_id, bufsize, nbuf, True) + if t > 4: + break + + for bufsize, nbuf in write_test_params: + t = write_test(ser_repl, ser_data, usb_vcp_id, bufsize, nbuf, False) + if t > 4: + break + + ser_repl.close() + ser_data.close() + + +def main(): + dev_repl = "/dev/ttyACM0" + dev_data = None + usb_vcp_id = None + fast = False + if len(sys.argv) >= 2 and sys.argv[1] == "--fast": + fast = True + sys.argv.pop(1) + if len(sys.argv) >= 2: + dev_repl = sys.argv[1] + if len(sys.argv) >= 3: + assert len(sys.argv) >= 4 + dev_data = sys.argv[2] + usb_vcp_id = int(sys.argv[3]) + do_test(dev_repl, dev_data, usb_vcp_id, fast) + + +if __name__ == "__main__": + main() From b62871661f8a045e94eb4cd88480811d2f767d72 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Oct 2024 14:07:31 +1100 Subject: [PATCH 05/43] tests/hwtest.py: Add a full, automatic test runner. Signed-off-by: Damien George --- tests/hwtest.py | 272 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100755 tests/hwtest.py diff --git a/tests/hwtest.py b/tests/hwtest.py new file mode 100755 index 0000000000000..9a189b5d9057f --- /dev/null +++ b/tests/hwtest.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +# MIT license; Copyright (c) 2024 Damien P. George + +import sys +import glob +import os +import subprocess +import serial.tools.list_ports +import datetime +import time +import vcprate + +sys.path.append("../tools/mpremote") +from mpremote.transport_serial import SerialTransport +import mpremote.commands + +WLAN_SSID = os.getenv("WLAN_SSID") +WLAN_PASS = os.getenv("WLAN_PASS") + +if not WLAN_SSID or not WLAN_PASS: + print("WLAN_SSID and/or WLAN_PASS not provided") + + +class Target: + def __init__(self, device, serial_number): + self.device = device + self.device_suffix = device.split("/")[-1] + self.serial_number = serial_number + self.port = None + self.machine = None + self.has_wlan_connected = False + + def info(self): + features = "" + if self.can_import_mpy: + features += " import-mpy" + if self.has_wlan: + features += " WLAN" + if self.has_ble: + features += " BLE" + return f"{self.device} {self.port} {self.arch}{features} {self.version}" + + +def map_device_name(d): + if d[0] == "a" and d[1:].isdigit(): + return "/dev/ttyACM" + d[1:] + if d[0] == "u" and d[1:].isdigit(): + return "/dev/ttyUSB" + d[1:] + return d + + +def list_ports(args): + ports = [] + for p in sorted(serial.tools.list_ports.comports()): + if p.device.startswith("/dev/ttyS"): + # Skip invalid ttySx ports. + continue + if not args or p.device in args: + ports.append(Target(p.device, p.serial_number)) + return ports + + +NATMOD_LIBS = ("btree", "deflate", "framebuf", "heapq", "random", "re") + + +def build_natmods(): + for arch in ("armv6m", "armv7emsp", "armv7emdp", "xtensawin"): + for lib in NATMOD_LIBS: + subprocess.run(["make", "-C", f"examples/natmod/{lib}", "-B", f"ARCH={arch}"]) + + +def try_import(target, module): + return ( + target.exec(f"try:\n import {module}\n print(True)\nexcept:\n print(False)").strip() + == b"True" + ) + + +def rtc_set(target, serial): + now = datetime.datetime.now() + time.sleep(1 - now.microsecond * 1e-6) + now = datetime.datetime.now() + print(target.device, "set time to", now) + timetuple = "({}, {}, {}, {}, {}, {}, {}, {})".format( + now.year, + now.month, + now.day, + now.weekday(), + now.hour, + now.minute, + now.second, + now.microsecond, + ) + serial.exec("import machine;machine.RTC().datetime({})".format(timetuple)) + + +def put_file(target_serial, path, file): + class state: + transport = target_serial + + mpremote.commands.do_filesystem_cp(state, os.path.join(path, file), ":" + file, False, True) + + +def wlan_connect(target, target_serial): + print(f"{target.device} connecting to {WLAN_SSID}... ", end="") + sys.stdout.flush() + out = target_serial.exec(f""" +import sys, machine, network, time +wl = network.WLAN() +wl.active(1) +if not wl.isconnected(): + wl.connect('{WLAN_SSID}', '{WLAN_PASS}') + t0 = time.ticks_ms() + print('connect') + while not wl.isconnected() and time.ticks_diff(time.ticks_ms(), t0) < 10_000: + machine.idle() +""") + isconnected, ifconfig = target_serial.eval("(wl.isconnected(), wl.ifconfig())") + if isconnected: + print(ifconfig[0]) + else: + print("FAIL") + return isconnected + + +def do_test(cmd): + print("-" * 32) + print("RUN", " ".join(cmd)) + try: + subprocess.run(cmd, check=True) + print("PASS") + except subprocess.CalledProcessError as er: + print("ERROR", er) + time.sleep(1) + except KeyboardInterrupt: + print("INTERRUPT") + time.sleep(1) + + +def run_multitests_on_one_target(targets, tests): + for target in targets: + do_test(["./run-multitests.py", "-i", f"pyb:{target.device}", "-p2"] + tests) + + +def run_multitests_on_two_targets(targets, tests): + if len(targets) == 1: + return + for i in range(len(targets)): + target0 = targets[i] + target1 = targets[(i + 1) % len(targets)] + do_test( + ["./run-multitests.py", "-i", f"pyb:{target0.device}", "-i", f"pyb:{target1.device}"] + + tests + ) + + +def main(): + # build_natmods() + + with open("feature_check/target_info.py", "rb") as f: + target_info_check = f.read() + + selected_devices = [map_device_name(d) for d in sys.argv[1:]] + targets = list_ports(selected_devices) + + select_vcprate = True + select_python = True + select_hardware = True + select_via_mpy = True + select_via_mpy_native = True + select_wlan = True + select_ble = True + + targets_ble = [] + for target in targets: + t = SerialTransport(target.device) + t.enter_raw_repl() + t.exec("import sys") + sys_info = t.eval("(sys.platform, sys.implementation._machine, sys.version)") + target.port, target.machine, target.version = sys_info + target.can_import_mpy = t.eval("hasattr(sys.implementation, '_mpy')") + _, target.arch = str(t.exec(target_info_check), "ascii").strip().split() + if target.arch == "None": + target.arch = None + target.has_vfs = try_import(t, "vfs") + target.has_wlan = try_import(t, "network") and t.eval("hasattr(network, 'WLAN')") + target.has_ble = try_import(t, "bluetooth") and t.eval("hasattr(bluetooth, 'BLE')") + t.close() + + for target in targets: + print(target.info()) + + print("=" * 64) + print("=" * 64) + + tests_natmod = [ + file for file in glob.iglob("extmod/*.py") if file.split("/")[1].startswith(NATMOD_LIBS) + ] + tests_multi_net = [file for file in glob.iglob("multi_net/*.py")] + tests_multi_bluetooth = [ + file for file in glob.iglob("multi_bluetooth/*.py") if "/ble_" in file + ] + + if True: + for target in targets: + print("=" * 64) + print(target.info()) + print(target.machine) + print(target.version) + run_tests_cmd = [ + "./run-tests.py", + "--test-instance", + target.device, + "--result-dir", + f"results_{target.device_suffix}", + ] + do_test(run_tests_cmd + ["--clean-failures"]) + + if select_vcprate: + try: + vcprate.do_test(target.device) + except KeyboardInterrupt: + print("INTERRUPT") + time.sleep(1) + + if select_python: + do_test(run_tests_cmd) + + if select_hardware: + do_test(run_tests_cmd + ["-d", "extmod_hardware"]) + if target.port == "pyboard": + do_test(run_tests_cmd + ["-d", "ports/stm32_hardware"]) + + if select_via_mpy and target.can_import_mpy: + port_specific = [] + if target.port == "esp8266": + port_specific.extend(("-d", "basics", "extmod", "float")) + do_test(run_tests_cmd + ["--via-mpy"] + port_specific) + + if select_via_mpy_native and target.arch is not None: + do_test(run_tests_cmd + ["--via-mpy", "--emit", "native"]) + do_test( + ["./run-natmodtests.py", "-p", "-d", target.device] + + tests_natmod + ) + + if select_wlan and target.has_wlan: + t = SerialTransport(target.device) + t.enter_raw_repl() + rtc_set(target, t) + if target.has_vfs: + for file in glob.glob("net_inet/*.der") + glob.glob("multi_net/*.der"): + put_file(t, *file.rsplit("/", 1)) + target.has_wlan_connected = wlan_connect(target, t) + t.close() + if target.has_wlan_connected: + do_test(run_tests_cmd + ["-d", "net_hosted", "net_inet"]) + + targets_wlan = [t for t in targets if t.has_wlan_connected] + if select_wlan and targets_wlan: + print("=" * 64) + run_multitests_on_one_target(targets_wlan, tests_multi_net) + run_multitests_on_two_targets(targets_wlan, tests_multi_net) + + targets_ble = [t for t in targets if t.has_ble] + if select_ble and targets_ble: + print("=" * 64) + run_multitests_on_two_targets(targets_ble, tests_multi_bluetooth) + + +if __name__ == "__main__": + main() From b2cd326c8543f7c8e35e119264eb0a799e19a5ad Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 10 Apr 2025 16:00:43 +1000 Subject: [PATCH 06/43] tests/hwtest.py: Make sure unittest is installed. Signed-off-by: Damien George --- tests/hwtest.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index 9a189b5d9057f..450acb038d126 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -13,6 +13,7 @@ sys.path.append("../tools/mpremote") from mpremote.transport_serial import SerialTransport import mpremote.commands +import mpremote.mip WLAN_SSID = os.getenv("WLAN_SSID") WLAN_PASS = os.getenv("WLAN_PASS") @@ -76,6 +77,22 @@ def try_import(target, module): ) +def mip_install(target, target_serial, package): + class args: + command = ("install",) + packages = (package,) + index = None + target = None + mpy = True + + class state: + transport = target_serial + did_action = lambda: None + ensure_raw_repl = lambda: target_serial.enter_raw_repl() + + mpremote.mip.do_mip(state, args) + + def rtc_set(target, serial): now = datetime.datetime.now() time.sleep(1 - now.microsecond * 1e-6) @@ -183,6 +200,7 @@ def main(): if target.arch == "None": target.arch = None target.has_vfs = try_import(t, "vfs") + target.has_unittest = try_import(t, "unittest") target.has_wlan = try_import(t, "network") and t.eval("hasattr(network, 'WLAN')") target.has_ble = try_import(t, "bluetooth") and t.eval("hasattr(bluetooth, 'BLE')") t.close() @@ -207,6 +225,13 @@ def main(): print(target.info()) print(target.machine) print(target.version) + + # Install unittest if needed. + if target.has_vfs and not target.has_unittest: + t = SerialTransport(target.device) + mip_install(target, t, "unittest") + t.close() + run_tests_cmd = [ "./run-tests.py", "--test-instance", @@ -239,10 +264,7 @@ def main(): if select_via_mpy_native and target.arch is not None: do_test(run_tests_cmd + ["--via-mpy", "--emit", "native"]) - do_test( - ["./run-natmodtests.py", "-p", "-d", target.device] - + tests_natmod - ) + do_test(["./run-natmodtests.py", "-p", "-d", target.device] + tests_natmod) if select_wlan and target.has_wlan: t = SerialTransport(target.device) From edef636bc3c29bdf0dd6225f20620f60490891d2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 11 Apr 2025 00:33:50 +1000 Subject: [PATCH 07/43] tests/hwtest.py: Add command to build all natmods. Signed-off-by: Damien George --- tests/hwtest.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index 450acb038d126..7167051e77b19 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -61,13 +61,14 @@ def list_ports(args): return ports +NATMOD_ARCHS = ("x86", "x64", "armv6", "armv6m", "armv7m", "armv7em", "armv7emsp", "armv7emdp", "xtensa", "xtensawin", "rv32imc") NATMOD_LIBS = ("btree", "deflate", "framebuf", "heapq", "random", "re") def build_natmods(): - for arch in ("armv6m", "armv7emsp", "armv7emdp", "xtensawin"): + for arch in NATMOD_ARCHS: for lib in NATMOD_LIBS: - subprocess.run(["make", "-C", f"examples/natmod/{lib}", "-B", f"ARCH={arch}"]) + subprocess.run(["make", "-C", f"../examples/natmod/{lib}", "-j", "-B", f"ARCH={arch}"]) def try_import(target, module): @@ -172,7 +173,9 @@ def run_multitests_on_two_targets(targets, tests): def main(): - # build_natmods() + if len(sys.argv) > 1 and sys.argv[1] == "build-natmod": + build_natmods() + return with open("feature_check/target_info.py", "rb") as f: target_info_check = f.read() From f85f6ecd48f115afcb7f9505d2301fabc08fd649 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Apr 2025 11:12:40 +1000 Subject: [PATCH 08/43] tests/run-tests.py: Add option to not print list of skipped tests. To reduce output clutter. Signed-off-by: Damien George --- tests/run-tests.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 1f5876f4242a5..377e8ba73675b 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -22,6 +22,8 @@ # are guaranteed to always work, this one should though. BASEPATH = os.path.dirname(os.path.abspath(inspect.getsourcefile(lambda: None))) +VERBOSE = False + def base_path(*p): return os.path.abspath(os.path.join(BASEPATH, *p)).replace("\\", "/") @@ -1138,17 +1140,14 @@ def create_test_report(args, test_results, testcase_count=None): print("{} tests passed".format(len(passed_tests))) if len(skipped_tests) > 0: - print( - "{} tests skipped: {}".format( - len(skipped_tests), " ".join(test[0] for test in skipped_tests) - ) - ) + details = ": " + " ".join(test[0] for test in skipped_tests) if VERBOSE else "" + print("{} tests skipped".format(len(skipped_tests)) + details) if len(skipped_tests_too_large) > 0: + details = ": " + " ".join(test[0] for test in skipped_tests_too_large) if VERBOSE else "" print( - "{} tests skipped because they are too large: {}".format( - len(skipped_tests_too_large), " ".join(test[0] for test in skipped_tests_too_large) - ) + "{} tests skipped because they are too large".format(len(skipped_tests_too_large)) + + details ) if len(failed_tests) > 0: From c21ea4b0c27f79437169308622f4b30da1fb92ab Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 14 Apr 2025 23:55:37 +1000 Subject: [PATCH 09/43] tests/hwtest.py: Improve CLI options. Signed-off-by: Damien George --- tests/hwtest.py | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index 7167051e77b19..4b6955a7f18b4 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -28,6 +28,7 @@ def __init__(self, device, serial_number): self.device_suffix = device.split("/")[-1] self.serial_number = serial_number self.port = None + self.build = None self.machine = None self.has_wlan_connected = False @@ -39,7 +40,7 @@ def info(self): features += " WLAN" if self.has_ble: features += " BLE" - return f"{self.device} {self.port} {self.arch}{features} {self.version}" + return f"{self.device} {self.port} {self.build} {self.arch}{features} {self.version}" def map_device_name(d): @@ -173,31 +174,42 @@ def run_multitests_on_two_targets(targets, tests): def main(): - if len(sys.argv) > 1 and sys.argv[1] == "build-natmod": + args = sys.argv[1:] + + if len(args) > 0 and args[0] == "build-natmod": build_natmods() return + if len(args) > 0 and args[0] == "-s": + # Select certain tests. + args.pop(0) + selected_tests = args.pop(0) + else: + selected_tests = "vphmnwb" + with open("feature_check/target_info.py", "rb") as f: target_info_check = f.read() - selected_devices = [map_device_name(d) for d in sys.argv[1:]] + print("Selected tests:", selected_tests) + + selected_devices = [map_device_name(d) for d in args] targets = list_ports(selected_devices) - select_vcprate = True - select_python = True - select_hardware = True - select_via_mpy = True - select_via_mpy_native = True - select_wlan = True - select_ble = True + select_vcprate = "v" in selected_tests + select_python = "p" in selected_tests + select_hardware = "h" in selected_tests + select_via_mpy = "m" in selected_tests + select_native = "n" in selected_tests + select_wlan = "w" in selected_tests + select_ble = "b" in selected_tests targets_ble = [] for target in targets: t = SerialTransport(target.device) t.enter_raw_repl() t.exec("import sys") - sys_info = t.eval("(sys.platform, sys.implementation._machine, sys.version)") - target.port, target.machine, target.version = sys_info + sys_info = t.eval("(sys.platform, sys.implementation._build, sys.implementation._machine, sys.version)") + target.port, target.build, target.machine, target.version = sys_info target.can_import_mpy = t.eval("hasattr(sys.implementation, '_mpy')") _, target.arch = str(t.exec(target_info_check), "ascii").strip().split() if target.arch == "None": @@ -226,8 +238,7 @@ def main(): for target in targets: print("=" * 64) print(target.info()) - print(target.machine) - print(target.version) + print(target.machine, "--", target.version) # Install unittest if needed. if target.has_vfs and not target.has_unittest: @@ -265,7 +276,7 @@ def main(): port_specific.extend(("-d", "basics", "extmod", "float")) do_test(run_tests_cmd + ["--via-mpy"] + port_specific) - if select_via_mpy_native and target.arch is not None: + if select_native and target.arch is not None: do_test(run_tests_cmd + ["--via-mpy", "--emit", "native"]) do_test(["./run-natmodtests.py", "-p", "-d", target.device] + tests_natmod) From 73e4b0dba02f2cd12ab2588995bd370de05c942a Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 15 Apr 2025 14:00:21 +1000 Subject: [PATCH 10/43] tests: Increase buffer to 249 bytes. Signed-off-by: Damien George --- tests/run-natmodtests.py | 2 ++ tests/run-tests.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index 55adb6049a3e8..1250c36f21e4f 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -56,6 +56,8 @@ def __init__(self): def ioctl(self, request, arg): if request == 4: # MP_STREAM_CLOSE return 0 + if request == 11: # MP_STREAM_GET_BUFFER_SIZE + return 249 return -1 def readinto(self, buf): buf[:] = memoryview(__buf)[self.off:self.off + len(buf)] diff --git a/tests/run-tests.py b/tests/run-tests.py index 377e8ba73675b..13f52881d3a56 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -74,6 +74,8 @@ def __init__(self): def ioctl(self, request, arg): if request == 4: # MP_STREAM_CLOSE return 0 + if request == 11: # MP_STREAM_GET_BUFFER_SIZE + return 249 return -1 def readinto(self, buf): buf[:] = memoryview(__buf)[self.off:self.off + len(buf)] From e7509020ddc166d8ec98639e2c87d5ebf8e188b3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 24 Apr 2025 12:17:30 +1000 Subject: [PATCH 11/43] tests/vcprate.py: Support targets without sys.stdout.buffer. Signed-off-by: Damien George --- tests/vcprate.py | 52 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/tests/vcprate.py b/tests/vcprate.py index 66c064fd54198..e0cb9bb137214 100755 --- a/tests/vcprate.py +++ b/tests/vcprate.py @@ -19,9 +19,19 @@ def drain_input(ser): data = ser.read(ser.inWaiting()) time.sleep(0.1) +def send_script(ser, script): + chunk_size = 32 + for i in range(0, len(script), chunk_size): + ser.write(script[i : i + chunk_size]) + time.sleep(0.01) + ser.write(b"\x04") # eof + ser.flush() + response = ser.read(2) + assert response == b"OK", response read_test_script = """ vcp_id = %u +bin = True led = None try: import pyb @@ -31,14 +41,24 @@ def drain_input(ser): wr=pyb.USB_VCP(vcp_id).send except: import sys - wr=sys.stdout.buffer.write + if hasattr(sys.stdout,'buffer'): + wr=sys.stdout.buffer.write + else: + wr=sys.stdout.write + bin = False b=bytearray(%u) -for i in range(len(b)): - b[i] = i & 0xff +if bin: + wr('BIN') + for i in range(len(b)): + b[i] = i & 0xff +else: + wr('TXT') + for i in range(len(b)): + b[i] = 0x20 + (i & 0x3f) for _ in range(%d): if led: led.toggle() - n = wr(b) + wr(b) """ @@ -48,11 +68,11 @@ def read_test(ser_repl, ser_data, usb_vcp_id, bufsize, nbuf): # Load and run the read_test_script. ser_repl.write(b"\x03\x01\x04") # break, raw-repl, soft-reboot drain_input(ser_repl) - ser_repl.write(bytes(read_test_script % (usb_vcp_id, bufsize, nbuf), "ascii")) - ser_repl.write(b"\x04") # eof - ser_repl.flush() - response = ser_repl.read(2) - assert response == b"OK", response + script = bytes(read_test_script % (usb_vcp_id, bufsize, nbuf), "ascii") + send_script(ser_repl, script) + + # Read from the device the type of data that it will send (BIN or TXT). + data_type = ser_data.read(3) # Read data from the device, check it is correct, and measure throughput. n = 0 @@ -69,6 +89,7 @@ def read_test(ser_repl, ser_data, usb_vcp_id, bufsize, nbuf): time.sleep(0.0001) if not ser_data.inWaiting(): print("ERROR: timeout waiting for data") + print(total_data[:n]) return 0 to_read = min(ser_data.inWaiting(), remain) data = ser_data.read(to_read) @@ -88,8 +109,12 @@ def read_test(ser_repl, ser_data, usb_vcp_id, bufsize, nbuf): n += len(data) t_end = time.time() for i in range(0, len(total_data)): - if total_data[i] != i & 0xFF: - print("fail", i, i & 0xFF, total_data[i]) + if data_type == b"BIN": + wanted = i & 0xFF + else: + wanted = 0x20 + (i & 0x3F) + if total_data[i] != wanted: + print("fail", i, wanted, total_data[i]) ser_repl.write(b"\x03") # break t = t_end - t_start @@ -161,9 +186,8 @@ def write_test(ser_repl, ser_data, usb_vcp_id, bufsize, nbuf, verified): script = write_test_script_verified else: script = write_test_script_unverified - ser_repl.write(bytes(script % (usb_vcp_id, bufsize, nbuf), "ascii")) - ser_repl.write(b"\x04") # eof - ser_repl.flush() + script = bytes(script % (usb_vcp_id, bufsize, nbuf), "ascii") + send_script(ser_repl, script) drain_input(ser_repl) # Write data to the device, check it is correct, and measure throughput. From 97cecf48b0bb3be507d59522fbd7d5689ef66f8e Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Apr 2025 11:14:00 +1000 Subject: [PATCH 12/43] tests/hwtest.py: Just print returncode on subprocess fail. Signed-off-by: Damien George --- tests/hwtest.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index 4b6955a7f18b4..973bfcadb437c 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -146,10 +146,11 @@ def do_test(cmd): print("-" * 32) print("RUN", " ".join(cmd)) try: - subprocess.run(cmd, check=True) - print("PASS") - except subprocess.CalledProcessError as er: - print("ERROR", er) + result = subprocess.run(cmd) + if result.returncode == 0: + print("PASS") + else: + print(f"ERROR returncode={result.returncode}") time.sleep(1) except KeyboardInterrupt: print("INTERRUPT") From 8ffe151612c3cb611a4370df0d2a776d7cb901e2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Apr 2025 11:14:28 +1000 Subject: [PATCH 13/43] tests/hwtest.py: Change args to selection then devices. Signed-off-by: Damien George --- tests/hwtest.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index 973bfcadb437c..289971c5bf414 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -181,12 +181,11 @@ def main(): build_natmods() return - if len(args) > 0 and args[0] == "-s": + selected_tests = "vphmnwb" + if len(args) > 0: # Select certain tests. - args.pop(0) - selected_tests = args.pop(0) - else: - selected_tests = "vphmnwb" + if (a := args.pop(0)) != "all": + selected_tests = a with open("feature_check/target_info.py", "rb") as f: target_info_check = f.read() From d4d3bcce6c0d334989b98759d9f8a84e25a0814e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 29 Apr 2025 10:39:35 +1000 Subject: [PATCH 14/43] tests/hwtest.py: Run hardware tests in native mode if selected. Signed-off-by: Damien George --- tests/hwtest.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index 289971c5bf414..1c3a8f5c9b5a6 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -277,7 +277,12 @@ def main(): do_test(run_tests_cmd + ["--via-mpy"] + port_specific) if select_native and target.arch is not None: - do_test(run_tests_cmd + ["--via-mpy", "--emit", "native"]) + run_native = ["--via-mpy", "--emit", "native"] + do_test(run_tests_cmd + run_native) + if select_hardware: + do_test(run_tests_cmd + run_native + ["-d", "extmod_hardware"]) + if target.port == "pyboard": + do_test(run_tests_cmd + run_native + ["-d", "ports/stm32_hardware"]) do_test(["./run-natmodtests.py", "-p", "-d", target.device] + tests_natmod) if select_wlan and target.has_wlan: From 138109fdbf865be30fbf4909c4f9d50367eb4c22 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Apr 2025 00:37:29 +1000 Subject: [PATCH 15/43] tests/hwtest.py: Support targets without a filesystem. Signed-off-by: Damien George --- tests/hwtest.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index 1c3a8f5c9b5a6..b2023e5917ce5 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -208,13 +208,16 @@ def main(): t = SerialTransport(target.device) t.enter_raw_repl() t.exec("import sys") - sys_info = t.eval("(sys.platform, sys.implementation._build, sys.implementation._machine, sys.version)") + sys_info = t.eval( + "(sys.platform, getattr(sys.implementation, '_build', 'UNKNOWN'), sys.implementation._machine, sys.version)" + ) target.port, target.build, target.machine, target.version = sys_info target.can_import_mpy = t.eval("hasattr(sys.implementation, '_mpy')") _, target.arch = str(t.exec(target_info_check), "ascii").strip().split() if target.arch == "None": target.arch = None target.has_vfs = try_import(t, "vfs") + target.has_filesystem = target.has_vfs and t.eval("len(vfs.mount())") target.has_unittest = try_import(t, "unittest") target.has_wlan = try_import(t, "network") and t.eval("hasattr(network, 'WLAN')") target.has_ble = try_import(t, "bluetooth") and t.eval("hasattr(bluetooth, 'BLE')") @@ -241,7 +244,7 @@ def main(): print(target.machine, "--", target.version) # Install unittest if needed. - if target.has_vfs and not target.has_unittest: + if target.has_filesystem and not target.has_unittest: t = SerialTransport(target.device) mip_install(target, t, "unittest") t.close() @@ -289,7 +292,7 @@ def main(): t = SerialTransport(target.device) t.enter_raw_repl() rtc_set(target, t) - if target.has_vfs: + if target.has_filesystem: for file in glob.glob("net_inet/*.der") + glob.glob("multi_net/*.der"): put_file(t, *file.rsplit("/", 1)) target.has_wlan_connected = wlan_connect(target, t) From c3424f54d9a4b73df91219d0dcae7282084d0e35 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Apr 2025 00:39:22 +1000 Subject: [PATCH 16/43] tests/run-tests.py: Exit with code 2 on error. Signed-off-by: Damien George --- tests/run-tests.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 13f52881d3a56..48803f7f09d5b 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -1450,4 +1450,8 @@ def main(): except KeyboardInterrupt: print() print("INTERRUPT") - sys.exit(1) + sys.exit(2) + except Exception as er: + print("ERROR:") + print(er) + sys.exit(2) From c038e013802d58e1977457c52a1615adcec8c2c5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 12 May 2025 13:36:11 +1000 Subject: [PATCH 17/43] tests/hwtest.py: Run hardware tests after native tests. Signed-off-by: Damien George --- tests/hwtest.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index b2023e5917ce5..59157d4dbf4a3 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -256,6 +256,8 @@ def main(): "--result-dir", f"results_{target.device_suffix}", ] + run_tests_native_cmd = run_tests_cmd + ["--via-mpy", "--emit", "native"] + do_test(run_tests_cmd + ["--clean-failures"]) if select_vcprate: @@ -268,11 +270,6 @@ def main(): if select_python: do_test(run_tests_cmd) - if select_hardware: - do_test(run_tests_cmd + ["-d", "extmod_hardware"]) - if target.port == "pyboard": - do_test(run_tests_cmd + ["-d", "ports/stm32_hardware"]) - if select_via_mpy and target.can_import_mpy: port_specific = [] if target.port == "esp8266": @@ -280,14 +277,18 @@ def main(): do_test(run_tests_cmd + ["--via-mpy"] + port_specific) if select_native and target.arch is not None: - run_native = ["--via-mpy", "--emit", "native"] - do_test(run_tests_cmd + run_native) - if select_hardware: - do_test(run_tests_cmd + run_native + ["-d", "extmod_hardware"]) - if target.port == "pyboard": - do_test(run_tests_cmd + run_native + ["-d", "ports/stm32_hardware"]) + do_test(run_tests_native_cmd) do_test(["./run-natmodtests.py", "-p", "-d", target.device] + tests_natmod) + if select_hardware: + run_cmds = [run_tests_cmd] + if select_native and target.arch is not None: + run_cmds += run_tests_native_cmd + for run_cmd in run_cmds: + do_test(run_cmd + ["-d", "extmod_hardware"]) + if target.port == "pyboard": + do_test(run_cmd + ["-d", "ports/stm32_hardware"]) + if select_wlan and target.has_wlan: t = SerialTransport(target.device) t.enter_raw_repl() From 51a4383b23ddeaafe2c5117a17aa314f3bf22bca Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 May 2025 10:02:37 +1000 Subject: [PATCH 18/43] tests/hwtest.py: Increase sleep and fix list append bug. Signed-off-by: Damien George --- tests/hwtest.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index 59157d4dbf4a3..b374927f4d447 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -151,10 +151,10 @@ def do_test(cmd): print("PASS") else: print(f"ERROR returncode={result.returncode}") - time.sleep(1) + time.sleep(2) except KeyboardInterrupt: print("INTERRUPT") - time.sleep(1) + time.sleep(2) def run_multitests_on_one_target(targets, tests): @@ -265,7 +265,7 @@ def main(): vcprate.do_test(target.device) except KeyboardInterrupt: print("INTERRUPT") - time.sleep(1) + time.sleep(2) if select_python: do_test(run_tests_cmd) @@ -283,7 +283,7 @@ def main(): if select_hardware: run_cmds = [run_tests_cmd] if select_native and target.arch is not None: - run_cmds += run_tests_native_cmd + run_cmds.append(run_tests_native_cmd) for run_cmd in run_cmds: do_test(run_cmd + ["-d", "extmod_hardware"]) if target.port == "pyboard": From e38505fdb9c6ee0aaf33d6623562794c6af43cb5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 21 May 2025 13:57:24 +1000 Subject: [PATCH 19/43] tests/hwtest.py: Add multi_wlan tests. Signed-off-by: Damien George --- tests/hwtest.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index b374927f4d447..4285c8cc41945 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -233,11 +233,12 @@ def main(): file for file in glob.iglob("extmod/*.py") if file.split("/")[1].startswith(NATMOD_LIBS) ] tests_multi_net = [file for file in glob.iglob("multi_net/*.py")] + tests_multi_wlan = [file for file in glob.iglob("multi_wlan/*.py")] tests_multi_bluetooth = [ file for file in glob.iglob("multi_bluetooth/*.py") if "/ble_" in file ] - if True: + try: for target in targets: print("=" * 64) print(target.info()) @@ -301,16 +302,20 @@ def main(): if target.has_wlan_connected: do_test(run_tests_cmd + ["-d", "net_hosted", "net_inet"]) - targets_wlan = [t for t in targets if t.has_wlan_connected] - if select_wlan and targets_wlan: - print("=" * 64) - run_multitests_on_one_target(targets_wlan, tests_multi_net) - run_multitests_on_two_targets(targets_wlan, tests_multi_net) + targets_wlan = [t for t in targets if t.has_wlan_connected] + if select_wlan and targets_wlan: + print("=" * 64) + run_multitests_on_one_target(targets_wlan, tests_multi_net) + run_multitests_on_two_targets(targets_wlan, tests_multi_net) + run_multitests_on_two_targets(targets_wlan, tests_multi_wlan) + + targets_ble = [t for t in targets if t.has_ble] + if select_ble and targets_ble: + print("=" * 64) + run_multitests_on_two_targets(targets_ble, tests_multi_bluetooth) - targets_ble = [t for t in targets if t.has_ble] - if select_ble and targets_ble: - print("=" * 64) - run_multitests_on_two_targets(targets_ble, tests_multi_bluetooth) + except KeyboardInterrupt: + print("INTERRUPT") if __name__ == "__main__": From 5b3afc1f40523f0a0a2a0b798fbec9cc09f0a193 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 11 Apr 2025 00:34:03 +1000 Subject: [PATCH 20/43] tests/micropython/extreme_exc.py: Skip on ESP32 with PSRAM. Signed-off-by: Damien George --- tests/micropython/extreme_exc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/micropython/extreme_exc.py b/tests/micropython/extreme_exc.py index ad819e408fd82..c57d7f2bedf46 100644 --- a/tests/micropython/extreme_exc.py +++ b/tests/micropython/extreme_exc.py @@ -16,6 +16,12 @@ def stackless(): print("SKIP") raise SystemExit +# ESP32 boards with PSRAM can't run this test, too difficult to use up all heap. +import sys, gc +if sys.platform == "esp32" and gc.mem_free() > 1024 * 1024: + print("SKIP") + raise SystemExit + # some ports need to allocate heap for the emergency exception try: micropython.alloc_emergency_exception_buf(256) From ea4d25d730275be6c769f55dafce6e15054b82e7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 12 May 2025 13:38:06 +1000 Subject: [PATCH 21/43] tests/extmod_hardware: Add pins for UM FeatherS2. TODO: really need a set of local board_test.py files that are copied to the device before testing. Signed-off-by: Damien George --- tests/extmod_hardware/machine_pwm.py | 3 +++ tests/extmod_hardware/machine_uart_irq_rx.py | 4 ++++ tests/extmod_hardware/machine_uart_irq_rxidle.py | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/tests/extmod_hardware/machine_pwm.py b/tests/extmod_hardware/machine_pwm.py index e27da32548659..826ef22338334 100644 --- a/tests/extmod_hardware/machine_pwm.py +++ b/tests/extmod_hardware/machine_pwm.py @@ -22,6 +22,9 @@ # Configure pins based on the target. if "esp32" in sys.platform: pwm_pulse_pins = ((4, 5),) + if "ESP32S2" in sys.implementation._machine: + # For UM FeatherS2 + pwm_pulse_pins = ((6, 5),) freq_margin_per_thousand = 2 duty_margin_per_thousand = 1 timing_margin_us = 20 diff --git a/tests/extmod_hardware/machine_uart_irq_rx.py b/tests/extmod_hardware/machine_uart_irq_rx.py index 3602c260e396d..346cb1bcf4739 100644 --- a/tests/extmod_hardware/machine_uart_irq_rx.py +++ b/tests/extmod_hardware/machine_uart_irq_rx.py @@ -23,6 +23,10 @@ uart_id = 1 tx_pin = 4 rx_pin = 5 + if "ESP32S2" in sys.implementation._machine: + # For UM FeatherS2 + tx_pin = 6 + rx_pin = 5 elif "pyboard" in sys.platform: if "STM32WB" in sys.implementation._machine: # LPUART(1) is on PA2/PA3 diff --git a/tests/extmod_hardware/machine_uart_irq_rxidle.py b/tests/extmod_hardware/machine_uart_irq_rxidle.py index 3c743c9e0c1c0..8d2a9b9b2cefe 100644 --- a/tests/extmod_hardware/machine_uart_irq_rxidle.py +++ b/tests/extmod_hardware/machine_uart_irq_rxidle.py @@ -25,6 +25,10 @@ uart_id = 1 tx_pin = 4 rx_pin = 5 + if "ESP32S2" in sys.implementation._machine: + # For UM FeatherS2 + tx_pin = 6 + rx_pin = 5 elif "mimxrt" in sys.platform: uart_id = 1 tx_pin = None From 8ac59929a288f9709bcf4ab27cb68639e81d22f5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 21 May 2025 13:57:01 +1000 Subject: [PATCH 22/43] tests/extmod/machine_i2s_rate.py: Add I2S instance for ESP32C3. Signed-off-by: Damien George --- tests/extmod/machine_i2s_rate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/extmod/machine_i2s_rate.py b/tests/extmod/machine_i2s_rate.py index c8fa11514c96b..5f1d6ebceac3a 100644 --- a/tests/extmod/machine_i2s_rate.py +++ b/tests/extmod/machine_i2s_rate.py @@ -26,7 +26,10 @@ (2, Pin("D4"), Pin("D3"), Pin("D2"), None), ) elif "esp32" in sys.platform: - i2s_instances = ((0, Pin(18), Pin(19), Pin(21), Pin(14)),) + if "ESP32C3" in sys.implementation._machine: + i2s_instances = ((0, Pin(5), Pin(6), Pin(7), Pin(4)),) + else: + i2s_instances = ((0, Pin(18), Pin(19), Pin(21), Pin(14)),) # Allow for small additional RTOS overhead MAX_DELTA_MS = 8 From 46c0d0ac0ac9e0edae3fd38b457d5758337bb042 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 4 Jun 2025 14:17:15 +1000 Subject: [PATCH 23/43] tests/extmod/machine_uart_tx.py: Add zephyr-frdm_k64f config. Not really working, but a start. Signed-off-by: Damien George --- tests/extmod/machine_uart_tx.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/extmod/machine_uart_tx.py b/tests/extmod/machine_uart_tx.py index 85bf7e9fb878a..e94acb131580e 100644 --- a/tests/extmod/machine_uart_tx.py +++ b/tests/extmod/machine_uart_tx.py @@ -44,6 +44,9 @@ pins = {"tx": "D1", "rx": "D0"} timing_margin_us = 300 bit_margin = 1 +elif "zephyr-frdm_k64f" in sys.implementation._machine: + uart_id = "uart3" + pins = {} # TX/RX=PTC17/PTC16 else: print("SKIP") raise SystemExit From a0e0fbe2b7b3a707b33f7ceac913b1bb0b41924c Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 24 Jun 2025 12:37:06 +1000 Subject: [PATCH 24/43] tests/vcprate.py: Add read timeout and debugging when script isn't sent. Signed-off-by: Damien George --- tests/vcprate.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/tests/vcprate.py b/tests/vcprate.py index e0cb9bb137214..e5d05f33d81c2 100755 --- a/tests/vcprate.py +++ b/tests/vcprate.py @@ -27,7 +27,9 @@ def send_script(ser, script): ser.write(b"\x04") # eof ser.flush() response = ser.read(2) - assert response == b"OK", response + if response != b"OK": + response += ser.read(ser.inWaiting()) + raise Exception("could not send script", response) read_test_script = """ vcp_id = %u @@ -93,16 +95,6 @@ def read_test(ser_repl, ser_data, usb_vcp_id, bufsize, nbuf): return 0 to_read = min(ser_data.inWaiting(), remain) data = ser_data.read(to_read) - - # verify bytes coming in are in sequence - # if last_byte is not None: - # if data[0] != (last_byte + 1) & 0xff: - # print('ERROR: first byte is not in sequence:', last_byte, data[0]) - # last_byte = data[-1] - # for i in range(1, len(data)): - # if data[i] != (data[i - 1] + 1) & 0xff: - # print('ERROR: data not in sequence at position %d:' % i, data[i - 1], data[i]) - remain -= len(data) print(n, nbuf * bufsize, end="\r") total_data[n : n + len(data)] = data @@ -222,15 +214,15 @@ def write_test(ser_repl, ser_data, usb_vcp_id, bufsize, nbuf, verified): def do_test(dev_repl, dev_data=None, usb_vcp_id=None, fast=False): if dev_data is None: print("REPL and data on", dev_repl) - ser_repl = serial.Serial(dev_repl, baudrate=115200) + ser_repl = serial.Serial(dev_repl, baudrate=115200, timeout=1) ser_data = ser_repl usb_vcp_id = 0 else: print("REPL on", dev_repl) print("data on", dev_data) print("USB VCP", usb_vcp_id) - ser_repl = serial.Serial(dev_repl, baudrate=115200) - ser_data = serial.Serial(dev_data, baudrate=115200) + ser_repl = serial.Serial(dev_repl, baudrate=115200, timeout=1) + ser_data = serial.Serial(dev_data, baudrate=115200, timeout=1) if 0: for i in range(1000): From 16ec0c54b4db2221abfa6e1e18720eb7c2689b96 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 8 Jul 2025 00:32:20 +1000 Subject: [PATCH 25/43] tests/extmod/machine_uart_tx.py: Add settings for frdm, nucleo. They work. --- tests/extmod/machine_uart_tx.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/extmod/machine_uart_tx.py b/tests/extmod/machine_uart_tx.py index e94acb131580e..9919e43c7d4f1 100644 --- a/tests/extmod/machine_uart_tx.py +++ b/tests/extmod/machine_uart_tx.py @@ -47,6 +47,13 @@ elif "zephyr-frdm_k64f" in sys.implementation._machine: uart_id = "uart3" pins = {} # TX/RX=PTC17/PTC16 + bit_margin = 1 + timing_margin_us = 1200 +elif "zephyr-nucleo_wb55rg" in sys.implementation._machine: + uart_id = "lpuart1" + pins = {} # TX/RX=PC0/PC1 + bit_margin = 1 + timing_margin_us = 400 else: print("SKIP") raise SystemExit From b6e5375a284110695bc305964b2a5c0722989a45 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 8 Jul 2025 00:32:57 +1000 Subject: [PATCH 26/43] tests/hwtest.py: Use -j8 for building natmods. --- tests/hwtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index 4285c8cc41945..2db6e4cc3853e 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -69,7 +69,7 @@ def list_ports(args): def build_natmods(): for arch in NATMOD_ARCHS: for lib in NATMOD_LIBS: - subprocess.run(["make", "-C", f"../examples/natmod/{lib}", "-j", "-B", f"ARCH={arch}"]) + subprocess.run(["make", "-C", f"../examples/natmod/{lib}", "-j8", "-B", f"ARCH={arch}"]) def try_import(target, module): From 38396aef5317ec221324505fd145858eea3ec814 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 8 Jul 2025 00:41:54 +1000 Subject: [PATCH 27/43] tests/extmod_hardware/machine_pwm.py: Add comments about zephyr. Signed-off-by: Damien George --- tests/extmod_hardware/machine_pwm.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/extmod_hardware/machine_pwm.py b/tests/extmod_hardware/machine_pwm.py index 826ef22338334..7d56203afe0b9 100644 --- a/tests/extmod_hardware/machine_pwm.py +++ b/tests/extmod_hardware/machine_pwm.py @@ -50,6 +50,9 @@ if "SAMD21" in sys.implementation._machine: # MCU is too slow to capture short pulses. pwm_freq_limit = 2_000 + # zephyr + # enable machine.time_pulse_us() + # frdm_k64f board: work out which physical pin ("ftm0", 0) and ("ftm3", 0) are... else: print("Please add support for this test on this platform.") raise SystemExit From a1410eb46d39c770bdbf4376ff7c928b045cc9ea Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Jul 2025 14:38:30 +1000 Subject: [PATCH 28/43] tests/hwtest.py: Remove armv6 and armv7em. Signed-off-by: Damien George --- tests/hwtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index 2db6e4cc3853e..5c2132c65178e 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -62,7 +62,7 @@ def list_ports(args): return ports -NATMOD_ARCHS = ("x86", "x64", "armv6", "armv6m", "armv7m", "armv7em", "armv7emsp", "armv7emdp", "xtensa", "xtensawin", "rv32imc") +NATMOD_ARCHS = ("x86", "x64", "armv6m", "armv7m", "armv7emsp", "armv7emdp", "xtensa", "xtensawin", "rv32imc") NATMOD_LIBS = ("btree", "deflate", "framebuf", "heapq", "random", "re") From 3b7e48fdc9fbfb3adf47a4056f499398dbcc375f Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 26 Jul 2025 08:54:30 +1000 Subject: [PATCH 29/43] tests/hwtest.py: Fix target check. Signed-off-by: Damien George --- tests/hwtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index 5c2132c65178e..ff60980e68f4f 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -213,7 +213,7 @@ def main(): ) target.port, target.build, target.machine, target.version = sys_info target.can_import_mpy = t.eval("hasattr(sys.implementation, '_mpy')") - _, target.arch = str(t.exec(target_info_check), "ascii").strip().split() + _, target.arch, _ = str(t.exec(target_info_check), "ascii").strip().split() if target.arch == "None": target.arch = None target.has_vfs = try_import(t, "vfs") From 7aa7d67db2ae1d2d40c587f211e2e8ea89d0e3a7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 26 Jul 2025 17:29:15 +1000 Subject: [PATCH 30/43] tests/hwtest.py: Skip Octoprobe infrastructure. Signed-off-by: Damien George --- tests/hwtest.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/hwtest.py b/tests/hwtest.py index ff60980e68f4f..bb9ab3e56ddef 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -58,6 +58,17 @@ def list_ports(args): # Skip invalid ttySx ports. continue if not args or p.device in args: + t = SerialTransport(p.device) + t.enter_raw_repl() + t.exec("import os") + is_octoprobe_infra = "OCTOPROBE" in t.eval("os.listdir()") + t.close() + if is_octoprobe_infra: + if not args: + # If no devices specified, skip Octoprobe infrastructure. + print(f"{p.device} Octoprobe infrastructure device, skipping") + continue + print(f"{p.device} Octoprobe infrastructure device") ports.append(Target(p.device, p.serial_number)) return ports From ed2b04ffe2b456550316c986d4bc6a7c7c5b14cc Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Jul 2025 10:18:15 +1000 Subject: [PATCH 31/43] tests/extmod/machine_spi_rate.py: Support ADAFRUIT_ITSYBITSY_M0_EXPRESS. Signed-off-by: Damien George --- tests/extmod/machine_spi_rate.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/extmod/machine_spi_rate.py b/tests/extmod/machine_spi_rate.py index fe15b66fe648a..856b614a8b606 100644 --- a/tests/extmod/machine_spi_rate.py +++ b/tests/extmod/machine_spi_rate.py @@ -16,6 +16,9 @@ if "alif" in sys.platform: MAX_DELTA_MS = 20 spi_instances = ((0, None, None, None),) +elif "samd" in sys.platform: + # Use default SPI instance (tested working on ADAFRUIT_ITSYBITSY_M0_EXPRESS). + spi_instances = ((None, None, None, None),) elif "pyboard" in sys.platform: spi_instances = ( (1, None, None, None), # "explicit choice of sck/mosi/miso is not implemented" @@ -111,8 +114,10 @@ def test_spi(spi_id, sck, mosi, miso, baudrate, polarity, phase, print_results): polarity=polarity, phase=phase, ) - else: + elif spi_id is not None: s = SPI(spi_id, baudrate=baudrate, polarity=polarity, phase=phase) + else: + s = SPI(baudrate=baudrate, polarity=polarity, phase=phase) # to keep the test runtime down, use shorter buffer for lower baud rate wr_buf = wr_long if baudrate > 500_000 else wr_short From 038b98b7c2e575e484f00d3883322c2a7afe241c Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 3 Aug 2025 00:03:32 +1000 Subject: [PATCH 32/43] tests/hwtest.py: Allow selecting natmods to build. Signed-off-by: Damien George --- tests/hwtest.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index bb9ab3e56ddef..ddf43d318268b 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -77,8 +77,10 @@ def list_ports(args): NATMOD_LIBS = ("btree", "deflate", "framebuf", "heapq", "random", "re") -def build_natmods(): - for arch in NATMOD_ARCHS: +def build_natmods(archs): + if not archs: + archs = NATMOD_ARCHS + for arch in archs: for lib in NATMOD_LIBS: subprocess.run(["make", "-C", f"../examples/natmod/{lib}", "-j8", "-B", f"ARCH={arch}"]) @@ -189,7 +191,7 @@ def main(): args = sys.argv[1:] if len(args) > 0 and args[0] == "build-natmod": - build_natmods() + build_natmods(args[1:]) return selected_tests = "vphmnwb" From f1c8165104468267ae8748b7bb690d825a74ee0a Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 3 Aug 2025 00:04:02 +1000 Subject: [PATCH 33/43] tests/multi_extmod: Update pins for I2CTarget test using octoprobe. Signed-off-by: Damien George --- tests/multi_extmod/machine_i2c_target_irq.py | 10 ++++++++-- tests/multi_extmod/machine_i2c_target_memory.py | 14 ++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/multi_extmod/machine_i2c_target_irq.py b/tests/multi_extmod/machine_i2c_target_irq.py index eafd9dfdca838..1be6180f2a323 100644 --- a/tests/multi_extmod/machine_i2c_target_irq.py +++ b/tests/multi_extmod/machine_i2c_target_irq.py @@ -27,8 +27,14 @@ i2c_kwargs = {} clock_stretch_us = 50 # mimxrt cannot delay too long in the IRQ handler elif sys.platform == "rp2": - i2c_args = (0,) - i2c_kwargs = {"scl": 9, "sda": 8} + import os + + if "OCTOPROBE" in os.listdir(): + i2c_args = (1,) + i2c_kwargs = {"scl": 11, "sda": 10} + else: + i2c_args = (1,) + i2c_kwargs = {"scl": 7, "sda": 6} elif sys.platform == "pyboard": i2c_args = ("Y",) i2c_kwargs = {} diff --git a/tests/multi_extmod/machine_i2c_target_memory.py b/tests/multi_extmod/machine_i2c_target_memory.py index 6b3f0d03eb7fe..d20614d4706b0 100644 --- a/tests/multi_extmod/machine_i2c_target_memory.py +++ b/tests/multi_extmod/machine_i2c_target_memory.py @@ -17,14 +17,20 @@ i2c_args = (1,) # pins P3_7/P3_6 i2c_kwargs = {} elif sys.platform == "esp32": - i2c_args = (1,) # on pins 9/8 - i2c_kwargs = {} + i2c_args = (0,) # on pins 9/8 + i2c_kwargs = {"scl": 9, "sda": 8} elif sys.platform == "mimxrt": i2c_args = (0,) # pins 19/18 on Teensy 4.x i2c_kwargs = {} elif sys.platform == "rp2": - i2c_args = (0,) - i2c_kwargs = {"scl": 9, "sda": 8} + import os + + if "OCTOPROBE" in os.listdir(): + i2c_args = (1,) + i2c_kwargs = {"scl": 11, "sda": 10} + else: + i2c_args = (1,) + i2c_kwargs = {"scl": 7, "sda": 6} elif sys.platform == "pyboard": i2c_args = ("Y",) i2c_kwargs = {} From 7d745cfeadf3049b643f4896162e8a0689cf95f1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Aug 2025 10:45:27 +1000 Subject: [PATCH 34/43] tests: Improve test support for renesas-ra boards. Signed-off-by: Damien George --- tests/extmod/machine_spi_rate.py | 3 +++ tests/ports/renesas-ra/spi.py | 2 +- tests/ports/renesas-ra/uart1.py | 10 ++++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/extmod/machine_spi_rate.py b/tests/extmod/machine_spi_rate.py index 856b614a8b606..998eb9d0a5c5d 100644 --- a/tests/extmod/machine_spi_rate.py +++ b/tests/extmod/machine_spi_rate.py @@ -24,6 +24,9 @@ (1, None, None, None), # "explicit choice of sck/mosi/miso is not implemented" (2, None, None, None), ) +elif "renesas-ra" in sys.platform: + MAX_DELTA_MS = 15 + spi_instances = ((0, None, None, None),) elif "rp2" in sys.platform: spi_instances = ((0, Pin(18), Pin(19), Pin(16)),) elif "esp32" in sys.platform: diff --git a/tests/ports/renesas-ra/spi.py b/tests/ports/renesas-ra/spi.py index 52b908efbe5ca..9da4eb20152a5 100644 --- a/tests/ports/renesas-ra/spi.py +++ b/tests/ports/renesas-ra/spi.py @@ -20,7 +20,7 @@ except ValueError: print("ValueError", bus) -spi = SPI(0) +spi = SPI(0, baudrate=500000, polarity=0, phase=0, bits=8) print(spi) spi = SPI(0) diff --git a/tests/ports/renesas-ra/uart1.py b/tests/ports/renesas-ra/uart1.py index ba329fb85f63f..5caf2c4d0292b 100644 --- a/tests/ports/renesas-ra/uart1.py +++ b/tests/ports/renesas-ra/uart1.py @@ -3,8 +3,10 @@ machine = os.uname().machine if "EK-RA6M2" in machine: - # 0, 7, 9 - uart_ids = (0, 7, 9) + # 7, 9 + # skip 0 because that's the UART REPL + # TODO really should work out why the REPL breaks when just constructing UART(0) + uart_ids = (7, 9) try_id = 7 try_s = "UART(7, baudrate=115200, bits=8, parity=None, stop=1, tx=P401, rx=P402, flow=0, rxbuf=259, timeout=0, timeout_char=2)" elif "RA4M1 CLICKER" in machine: @@ -26,8 +28,8 @@ try_s = "UART(9, baudrate=115200, bits=8, parity=None, stop=1, tx=P109, rx=P110, flow=0, rxbuf=259, timeout=0, timeout_char=2)" elif "EK-RA6M1" in machine: # 0, 1, 2, 3, 4, 8, 9 - # 1/3/4/9 are disabled - uart_ids = (0, 2, 8) + # 1/3/4/9 are disabled, and skip 0 because that's the UART REPL + uart_ids = (2, 8) try_id = 8 try_s = "UART(8, baudrate=115200, bits=8, parity=None, stop=1, tx=P105, rx=P104, flow=0, rxbuf=259, timeout=0, timeout_char=2)" else: From dddfe9bacf85f8f54d808f56ed4c8a0260ae89b7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Aug 2025 10:46:04 +1000 Subject: [PATCH 35/43] tests/extmod_hardware/machine_i2c_target.py: Improve pyboard support. Signed-off-by: Damien George --- tests/extmod_hardware/machine_i2c_target.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/extmod_hardware/machine_i2c_target.py b/tests/extmod_hardware/machine_i2c_target.py index 763e6f4771e0f..bc59989623940 100644 --- a/tests/extmod_hardware/machine_i2c_target.py +++ b/tests/extmod_hardware/machine_i2c_target.py @@ -30,11 +30,14 @@ args_target = (1,) elif sys.platform == "pyboard": if sys.implementation._build == "NUCLEO_WB55": - args_controller = {"scl": "B8", "sda": "B9"} - args_target = (3,) + args_controller = {"scl": "B8", "sda": "B9"} # Arduino header D15/D14 + args_target = (3,) # PC0/PC1, Arduino header A0/A1 else: args_controller = {"scl": "X1", "sda": "X2"} args_target = ("X",) + if hasattr(Pin.board, "PULL_SCL"): + Pin("PULL_SCL", Pin.OUT, value=1) + Pin("PULL_SDA", Pin.OUT, value=1) elif "zephyr-nucleo_wb55rg" in sys.implementation._machine: # PB8=I2C1_SCL, PB9=I2C1_SDA (on Arduino header D15/D14) # PC0=I2C3_SCL, PC1=I2C3_SDA (on Arduino header A0/A1) From c07c5730301df5ef995980dc8bb3a138769ef5e0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Aug 2025 10:46:26 +1000 Subject: [PATCH 36/43] tests/extmod_hardware/machine_pwm.py: Improve support for Seeed Xiao. Signed-off-by: Damien George --- tests/extmod_hardware/machine_pwm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/extmod_hardware/machine_pwm.py b/tests/extmod_hardware/machine_pwm.py index 7d56203afe0b9..9cae0fa8b8ab8 100644 --- a/tests/extmod_hardware/machine_pwm.py +++ b/tests/extmod_hardware/machine_pwm.py @@ -47,6 +47,8 @@ pwm_pulse_pins = (("GPIO0", "GPIO1"),) elif "samd" in sys.platform: pwm_pulse_pins = (("D0", "D1"),) + if "Seeed Xiao" in sys.implementation._machine: + pwm_pulse_pins = (("A1_D1", "A0_D0"),) if "SAMD21" in sys.implementation._machine: # MCU is too slow to capture short pulses. pwm_freq_limit = 2_000 From a75b6ec5f128da36df6cfb5fc37780950b480d93 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 5 Aug 2025 00:02:29 +1000 Subject: [PATCH 37/43] tests/multi_net: Skip tests on axTLS when needed. Signed-off-by: Damien George --- tests/multi_net/asyncio_tls_server_client.py | 3 +++ .../asyncio_tls_server_client_cert_required_error.py | 4 ++++ tests/multi_net/asyncio_tls_server_client_readline.py | 3 +++ tests/multi_net/asyncio_tls_server_client_verify_error.py | 3 +++ tests/multi_net/ssl_cert_ec.py | 3 +++ tests/multi_net/ssl_cert_rsa.py | 3 +++ tests/multi_net/sslcontext_check_hostname_error.py | 3 +++ tests/multi_net/sslcontext_getpeercert.py | 3 +++ tests/multi_net/sslcontext_server_client.py | 4 ++++ tests/multi_net/sslcontext_server_client_ciphers.py | 4 ++++ tests/multi_net/sslcontext_server_client_files.py | 4 ++++ tests/multi_net/sslcontext_verify_callback.py | 4 ++++ tests/multi_net/sslcontext_verify_error.py | 4 ++++ tests/multi_net/sslcontext_verify_time_error.py | 4 ++++ tests/multi_net/tls_dtls_server_client.py | 4 ++++ 15 files changed, 53 insertions(+) diff --git a/tests/multi_net/asyncio_tls_server_client.py b/tests/multi_net/asyncio_tls_server_client.py index 016f57970c092..335d50bcd0807 100644 --- a/tests/multi_net/asyncio_tls_server_client.py +++ b/tests/multi_net/asyncio_tls_server_client.py @@ -62,5 +62,8 @@ def instance0(): def instance1(): + if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit multitest.next() asyncio.run(tcp_client(b"client data")) diff --git a/tests/multi_net/asyncio_tls_server_client_cert_required_error.py b/tests/multi_net/asyncio_tls_server_client_cert_required_error.py index eac9a98beae6c..5d66d9c1bd95d 100644 --- a/tests/multi_net/asyncio_tls_server_client_cert_required_error.py +++ b/tests/multi_net/asyncio_tls_server_client_cert_required_error.py @@ -8,6 +8,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/asyncio_tls_server_client_readline.py b/tests/multi_net/asyncio_tls_server_client_readline.py index 6093628ce5b7b..44797de95ad3a 100644 --- a/tests/multi_net/asyncio_tls_server_client_readline.py +++ b/tests/multi_net/asyncio_tls_server_client_readline.py @@ -66,5 +66,8 @@ def instance0(): def instance1(): + if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit multitest.next() asyncio.run(tcp_client(b"client data\nclient data2\n")) diff --git a/tests/multi_net/asyncio_tls_server_client_verify_error.py b/tests/multi_net/asyncio_tls_server_client_verify_error.py index 6dbff0482c8ae..1ce17585341c9 100644 --- a/tests/multi_net/asyncio_tls_server_client_verify_error.py +++ b/tests/multi_net/asyncio_tls_server_client_verify_error.py @@ -66,5 +66,8 @@ def instance0(): def instance1(): + if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit multitest.next() asyncio.run(tcp_client(b"client data")) diff --git a/tests/multi_net/ssl_cert_ec.py b/tests/multi_net/ssl_cert_ec.py index 8ecbd4d34f6e8..8b487ca367ace 100644 --- a/tests/multi_net/ssl_cert_ec.py +++ b/tests/multi_net/ssl_cert_ec.py @@ -38,6 +38,9 @@ def instance0(): # Client def instance1(): + if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit multitest.next() s = socket.socket() s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) diff --git a/tests/multi_net/ssl_cert_rsa.py b/tests/multi_net/ssl_cert_rsa.py index b99493b0ae5d8..7d3cc08808fb6 100644 --- a/tests/multi_net/ssl_cert_rsa.py +++ b/tests/multi_net/ssl_cert_rsa.py @@ -38,6 +38,9 @@ def instance0(): # Client def instance1(): + if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit multitest.next() s = socket.socket() s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) diff --git a/tests/multi_net/sslcontext_check_hostname_error.py b/tests/multi_net/sslcontext_check_hostname_error.py index 6bd911ddfd7cc..34beba8b66211 100644 --- a/tests/multi_net/sslcontext_check_hostname_error.py +++ b/tests/multi_net/sslcontext_check_hostname_error.py @@ -36,6 +36,9 @@ def instance0(): # Client def instance1(): + if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit multitest.next() s = socket.socket() s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) diff --git a/tests/multi_net/sslcontext_getpeercert.py b/tests/multi_net/sslcontext_getpeercert.py index e2d2a4251163b..82c3b7bafd4c7 100644 --- a/tests/multi_net/sslcontext_getpeercert.py +++ b/tests/multi_net/sslcontext_getpeercert.py @@ -18,6 +18,9 @@ # Server def instance0(): + if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit multitest.globals(IP=multitest.get_network_ip()) s = socket.socket() s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) diff --git a/tests/multi_net/sslcontext_server_client.py b/tests/multi_net/sslcontext_server_client.py index 6516de53f7dfb..10b0abea8f16c 100644 --- a/tests/multi_net/sslcontext_server_client.py +++ b/tests/multi_net/sslcontext_server_client.py @@ -44,6 +44,10 @@ def instance0(): # Client def instance1(): + if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + multitest.next() s = socket.socket() s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) diff --git a/tests/multi_net/sslcontext_server_client_ciphers.py b/tests/multi_net/sslcontext_server_client_ciphers.py index 3334d9d9e4c17..931638c1d5977 100644 --- a/tests/multi_net/sslcontext_server_client_ciphers.py +++ b/tests/multi_net/sslcontext_server_client_ciphers.py @@ -45,6 +45,10 @@ def instance0(): # Client def instance1(): + if not hasattr(tls, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + multitest.next() s = socket.socket() s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) diff --git a/tests/multi_net/sslcontext_server_client_files.py b/tests/multi_net/sslcontext_server_client_files.py index 48ff6376fad9b..1267f43618a98 100644 --- a/tests/multi_net/sslcontext_server_client_files.py +++ b/tests/multi_net/sslcontext_server_client_files.py @@ -35,6 +35,10 @@ def instance0(): # Client def instance1(): + if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + multitest.next() s = socket.socket() s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) diff --git a/tests/multi_net/sslcontext_verify_callback.py b/tests/multi_net/sslcontext_verify_callback.py index 74b97382453bd..168c5bf63a1e6 100644 --- a/tests/multi_net/sslcontext_verify_callback.py +++ b/tests/multi_net/sslcontext_verify_callback.py @@ -32,6 +32,10 @@ def verify_callback(cert, depth): # Server def instance0(): + if not hasattr(tls, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + multitest.globals(IP=multitest.get_network_ip()) s = socket.socket() s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) diff --git a/tests/multi_net/sslcontext_verify_error.py b/tests/multi_net/sslcontext_verify_error.py index 07dcc690b7ccc..a14d3c3b697bb 100644 --- a/tests/multi_net/sslcontext_verify_error.py +++ b/tests/multi_net/sslcontext_verify_error.py @@ -36,6 +36,10 @@ def instance0(): # Client def instance1(): + if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + multitest.next() s = socket.socket() s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) diff --git a/tests/multi_net/sslcontext_verify_time_error.py b/tests/multi_net/sslcontext_verify_time_error.py index 7b986322d15bd..a3d4ae1d1ef10 100644 --- a/tests/multi_net/sslcontext_verify_time_error.py +++ b/tests/multi_net/sslcontext_verify_time_error.py @@ -36,6 +36,10 @@ def instance0(): # Client def instance1(): + if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + multitest.next() s = socket.socket() s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) diff --git a/tests/multi_net/tls_dtls_server_client.py b/tests/multi_net/tls_dtls_server_client.py index a81c4cb28230c..e9ad0ab254004 100644 --- a/tests/multi_net/tls_dtls_server_client.py +++ b/tests/multi_net/tls_dtls_server_client.py @@ -7,6 +7,10 @@ print("SKIP") raise SystemExit +if not hasattr(tls, "PROTOCOL_DTLS_SERVER"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. From a01028459e86ca0f573a0d4c84efff1cc0720a27 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 31 Jul 2025 12:24:51 +1000 Subject: [PATCH 38/43] tests/net_inet: Skip tests on axTLS when necessary. Signed-off-by: Damien George --- tests/net_inet/asyncio_tls_open_connection_readline.py | 4 ++++ tests/net_inet/ssl_cert.py | 3 +++ tests/net_inet/ssl_errors.py | 4 ++++ tests/net_inet/test_sslcontext_client.py | 4 ++++ tests/net_inet/test_tls_nonblock.py | 7 +++++++ 5 files changed, 22 insertions(+) diff --git a/tests/net_inet/asyncio_tls_open_connection_readline.py b/tests/net_inet/asyncio_tls_open_connection_readline.py index a0df88be4af1b..492b85a5dbd24 100644 --- a/tests/net_inet/asyncio_tls_open_connection_readline.py +++ b/tests/net_inet/asyncio_tls_open_connection_readline.py @@ -2,6 +2,10 @@ import os import asyncio +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + # This certificate was obtained from micropython.org using openssl: # $ openssl s_client -showcerts -connect micropython.org:443 /dev/null # The certificate is from Let's Encrypt: diff --git a/tests/net_inet/ssl_cert.py b/tests/net_inet/ssl_cert.py index 7bb270d5fda7a..4f065c5657ee2 100644 --- a/tests/net_inet/ssl_cert.py +++ b/tests/net_inet/ssl_cert.py @@ -1,6 +1,9 @@ import socket import ssl +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit # This certificate was obtained from micropython.org using openssl: # $ openssl s_client -showcerts -connect micropython.org:443 /dev/null diff --git a/tests/net_inet/ssl_errors.py b/tests/net_inet/ssl_errors.py index bc4e5910bccd8..c23f736b09b08 100644 --- a/tests/net_inet/ssl_errors.py +++ b/tests/net_inet/ssl_errors.py @@ -3,6 +3,10 @@ import sys, errno, select, socket, ssl +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + def test(addr, hostname, block=True): print("---", hostname) diff --git a/tests/net_inet/test_sslcontext_client.py b/tests/net_inet/test_sslcontext_client.py index 77f68da496a77..bc06dc3c5832a 100644 --- a/tests/net_inet/test_sslcontext_client.py +++ b/tests/net_inet/test_sslcontext_client.py @@ -2,6 +2,10 @@ import socket import ssl +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + # This certificate was obtained from micropython.org using openssl: # $ openssl s_client -showcerts -connect micropython.org:443 /dev/null # The certificate is from Let's Encrypt: diff --git a/tests/net_inet/test_tls_nonblock.py b/tests/net_inet/test_tls_nonblock.py index 60af858b1f146..3f9a61413f9cf 100644 --- a/tests/net_inet/test_tls_nonblock.py +++ b/tests/net_inet/test_tls_nonblock.py @@ -1,5 +1,12 @@ import socket, ssl, errno, sys, time, select +# Although this test doesn't need ssl.CERT_REQUIRED, it does require the ssl module +# to support modern ciphers. So exclude the test on axTLS which doesn't have +# CERT_REQUIRED. +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + def test_one(site, opts): ai = socket.getaddrinfo(site, 443, socket.AF_INET) From 36b8550d0b44ae21578d26d8fdeeb7100f4b49e2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 31 Jul 2025 13:49:16 +1000 Subject: [PATCH 39/43] tests/net_hosted/ssl_verify_callback.py: Skip if necessary. Signed-off-by: Damien George --- tests/net_hosted/ssl_verify_callback.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/net_hosted/ssl_verify_callback.py b/tests/net_hosted/ssl_verify_callback.py index 0dba4e4fddcf6..cd03ff49d77f1 100644 --- a/tests/net_hosted/ssl_verify_callback.py +++ b/tests/net_hosted/ssl_verify_callback.py @@ -4,6 +4,12 @@ import socket import tls +context = tls.SSLContext(tls.PROTOCOL_TLS_CLIENT) + +if not hasattr(context, "verify_callback"): + print("SKIP") + raise SystemExit + def verify_callback(cert, depth): print("verify_callback:", type(cert), len(cert) > 100, depth) @@ -16,7 +22,6 @@ def verify_callback_fail(cert, depth): def test(peer_addr): - context = tls.SSLContext(tls.PROTOCOL_TLS_CLIENT) context.verify_mode = tls.CERT_OPTIONAL context.verify_callback = verify_callback s = socket.socket() From 3c87f14a4ec78aad4fe7f36518e0be6a20157bc5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Aug 2025 16:35:42 +1000 Subject: [PATCH 40/43] tests/run-tests.py: Decrease MP_STREAM_GET_BUFFER_SIZE to 32-7. If it's too big PYBLITEV10 will run out of memory when running `extmod/deflate_decompress.py`. Actually, 32-7 is the default... so this ioctl could be removed. Really need to benchmark it to see if it makes any difference having it large. Signed-off-by: Damien George --- tests/run-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 48803f7f09d5b..9bda02822e83c 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -75,7 +75,7 @@ def ioctl(self, request, arg): if request == 4: # MP_STREAM_CLOSE return 0 if request == 11: # MP_STREAM_GET_BUFFER_SIZE - return 249 + return 32 - 7 return -1 def readinto(self, buf): buf[:] = memoryview(__buf)[self.off:self.off + len(buf)] From 3cb3d7b78e6b64c94d6cc0ac57ea9f1750afa65a Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Aug 2025 16:39:23 +1000 Subject: [PATCH 41/43] tests: Support PYBLITE. Signed-off-by: Damien George --- tests/extmod/machine_uart_tx.py | 2 ++ tests/extmod_hardware/machine_uart_irq_rx.py | 2 ++ tests/extmod_hardware/machine_uart_irq_rxidle.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/tests/extmod/machine_uart_tx.py b/tests/extmod/machine_uart_tx.py index 9919e43c7d4f1..8e25cf4a6f7b8 100644 --- a/tests/extmod/machine_uart_tx.py +++ b/tests/extmod/machine_uart_tx.py @@ -30,6 +30,8 @@ elif "pyboard" in sys.platform: if "STM32WB" in sys.implementation._machine: uart_id = "LP1" + elif "PYBLITE" in sys.implementation._machine: + uart_id = "XA" else: uart_id = 4 pins = {} diff --git a/tests/extmod_hardware/machine_uart_irq_rx.py b/tests/extmod_hardware/machine_uart_irq_rx.py index 346cb1bcf4739..fc09be6ca66d2 100644 --- a/tests/extmod_hardware/machine_uart_irq_rx.py +++ b/tests/extmod_hardware/machine_uart_irq_rx.py @@ -31,6 +31,8 @@ if "STM32WB" in sys.implementation._machine: # LPUART(1) is on PA2/PA3 uart_id = "LP1" + elif "PYBLITE" in sys.implementation._machine: + uart_id = "XA" else: # UART(4) is on PA0/PA1 uart_id = 4 diff --git a/tests/extmod_hardware/machine_uart_irq_rxidle.py b/tests/extmod_hardware/machine_uart_irq_rxidle.py index 8d2a9b9b2cefe..6369b65cc3686 100644 --- a/tests/extmod_hardware/machine_uart_irq_rxidle.py +++ b/tests/extmod_hardware/machine_uart_irq_rxidle.py @@ -37,6 +37,8 @@ if "STM32WB" in sys.implementation._machine: # LPUART(1) is on PA2/PA3 uart_id = "LP1" + elif "PYBLITE" in sys.implementation._machine: + uart_id = "XA" else: # UART(4) is on PA0/PA1 uart_id = 4 From 51e0d42a3971d87bb0c93c259b3167b42264e0b6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Aug 2025 16:40:05 +1000 Subject: [PATCH 42/43] tests/hwtest.py: Add argparse and support a reference board. Signed-off-by: Damien George --- tests/hwtest.py | 209 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 142 insertions(+), 67 deletions(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index ddf43d318268b..befef0305db21 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # MIT license; Copyright (c) 2024 Damien P. George +import argparse import sys import glob import os @@ -21,6 +22,9 @@ if not WLAN_SSID or not WLAN_PASS: print("WLAN_SSID and/or WLAN_PASS not provided") +with open("feature_check/target_info.py", "rb") as f: + TARGET_INFO_CHECK = f.read() + class Target: def __init__(self, device, serial_number): @@ -30,7 +34,13 @@ def __init__(self, device, serial_number): self.port = None self.build = None self.machine = None + self.can_import_mpy = None + self.has_vfs = None + self.has_filesystem = None + self.has_unittest = None + self.has_wlan = None self.has_wlan_connected = False + self.has_ble = None def info(self): features = "" @@ -42,6 +52,35 @@ def info(self): features += " BLE" return f"{self.device} {self.port} {self.build} {self.arch}{features} {self.version}" + def detect_features(self): + t = SerialTransport(self.device) + t.enter_raw_repl() + t.exec("import sys") + sys_info = t.eval( + "(sys.platform, getattr(sys.implementation, '_build', 'UNKNOWN'), sys.implementation._machine, sys.version)" + ) + self.port, self.build, self.machine, self.version = sys_info + self.can_import_mpy = t.eval("hasattr(sys.implementation, '_mpy')") + _, self.arch, _ = str(t.exec(TARGET_INFO_CHECK), "ascii").strip().split() + if self.arch == "None": + self.arch = None + self.has_vfs = try_import(t, "vfs") + self.has_filesystem = self.has_vfs and t.eval("len(vfs.mount())") + self.has_unittest = try_import(t, "unittest") + self.has_wlan = try_import(t, "network") and t.eval("hasattr(network, 'WLAN')") + self.has_ble = try_import(t, "bluetooth") and t.eval("hasattr(bluetooth, 'BLE')") + t.close() + + def setup_wlan(self): + t = SerialTransport(self.device) + t.enter_raw_repl() + rtc_set(self, t) + if self.has_filesystem: + for file in glob.glob("net_inet/*.der") + glob.glob("multi_net/*.der"): + put_file(t, *file.rsplit("/", 1)) + self.has_wlan_connected = wlan_connect(self, t) + t.close() + def map_device_name(d): if d[0] == "a" and d[1:].isdigit(): @@ -51,29 +90,55 @@ def map_device_name(d): return d -def list_ports(args): - ports = [] - for p in sorted(serial.tools.list_ports.comports()): - if p.device.startswith("/dev/ttyS"): - # Skip invalid ttySx ports. - continue - if not args or p.device in args: - t = SerialTransport(p.device) - t.enter_raw_repl() - t.exec("import os") - is_octoprobe_infra = "OCTOPROBE" in t.eval("os.listdir()") - t.close() - if is_octoprobe_infra: - if not args: - # If no devices specified, skip Octoprobe infrastructure. - print(f"{p.device} Octoprobe infrastructure device, skipping") - continue - print(f"{p.device} Octoprobe infrastructure device") - ports.append(Target(p.device, p.serial_number)) - return ports - - -NATMOD_ARCHS = ("x86", "x64", "armv6m", "armv7m", "armv7emsp", "armv7emdp", "xtensa", "xtensawin", "rv32imc") +def detect_octoprobe(device): + t = SerialTransport(device) + t.enter_raw_repl() + t.exec("import os") + is_octoprobe_infra = "OCTOPROBE" in t.eval("os.listdir()") + t.close() + return is_octoprobe_infra + + +def list_targets(args, exclude_devices): + targets = [] + valid_serial_ports = sorted(serial.tools.list_ports.comports()) + if args: + for device in args: + for p in valid_serial_ports: + if device == p.device: + if detect_octoprobe(device): + print(f"{device} Octoprobe infrastructure device") + targets.append(Target(device, p.serial_number)) + break + else: + print(f"{device} not found") + sys.exit(1) + else: + for p in valid_serial_ports: + if p.device.startswith("/dev/ttyS"): + # Skip invalid ttySx ports. + continue + if p.device in exclude_devices: + continue + if detect_octoprobe(p.device): + # If no devices specified, skip Octoprobe infrastructure. + print(f"{p.device} Octoprobe infrastructure device, skipping") + continue + targets.append(Target(p.device, p.serial_number)) + return targets + + +NATMOD_ARCHS = ( + "x86", + "x64", + "armv6m", + "armv7m", + "armv7emsp", + "armv7emdp", + "xtensa", + "xtensawin", + "rv32imc", +) NATMOD_LIBS = ("btree", "deflate", "framebuf", "heapq", "random", "re") @@ -82,7 +147,9 @@ def build_natmods(archs): archs = NATMOD_ARCHS for arch in archs: for lib in NATMOD_LIBS: - subprocess.run(["make", "-C", f"../examples/natmod/{lib}", "-j8", "-B", f"ARCH={arch}"]) + subprocess.run( + ["make", "-C", f"../examples/natmod/{lib}", "-j8", "-B", f"ARCH={arch}"] + ) def try_import(target, module): @@ -170,9 +237,11 @@ def do_test(cmd): time.sleep(2) -def run_multitests_on_one_target(targets, tests): - for target in targets: - do_test(["./run-multitests.py", "-i", f"pyb:{target.device}", "-p2"] + tests) +def run_multitests_p2(instances, tests): + cmd = ["./run-multitests.py", "-p2"] + for instance in instances: + cmd.extend(["-i", f"pyb:{instance}"]) + do_test(cmd + tests) def run_multitests_on_two_targets(targets, tests): @@ -187,26 +256,41 @@ def run_multitests_on_two_targets(targets, tests): ) +ALL_TEST_CODES = "vphmnwb" + + def main(): - args = sys.argv[1:] + cmd_parser = argparse.ArgumentParser(description="Run all tests on hardware targets") + cmd_parser.add_argument( + "--build-natmods", action="store_true", help="build all/selected natmods" + ) + cmd_parser.add_argument("-s", "--tests", default=ALL_TEST_CODES, help="tests to run") + cmd_parser.add_argument( + "-r", "--reference", default=None, help="reference device for WLAN and BLE tests" + ) + cmd_parser.add_argument("test_instances", nargs="*", help="target devices to test") + args = cmd_parser.parse_args() - if len(args) > 0 and args[0] == "build-natmod": - build_natmods(args[1:]) + if args.build_natmods: + build_natmods(args.test_instances) return - selected_tests = "vphmnwb" - if len(args) > 0: - # Select certain tests. - if (a := args.pop(0)) != "all": - selected_tests = a + selected_tests = args.tests + for test_code in selected_tests: + if test_code not in ALL_TEST_CODES: + print(f"{test_code} is not a valid test code") + sys.exit(1) - with open("feature_check/target_info.py", "rb") as f: - target_info_check = f.read() + ref_target = None + if args.reference: + ref_device = map_device_name(args.reference) + ref_target = Target(ref_device, "") + ref_target.detect_features() print("Selected tests:", selected_tests) - selected_devices = [map_device_name(d) for d in args] - targets = list_ports(selected_devices) + selected_devices = [map_device_name(d) for d in args.test_instances] + targets = list_targets(selected_devices, [ref_target.device] if ref_target else []) select_vcprate = "v" in selected_tests select_python = "p" in selected_tests @@ -216,29 +300,19 @@ def main(): select_wlan = "w" in selected_tests select_ble = "b" in selected_tests - targets_ble = [] for target in targets: - t = SerialTransport(target.device) - t.enter_raw_repl() - t.exec("import sys") - sys_info = t.eval( - "(sys.platform, getattr(sys.implementation, '_build', 'UNKNOWN'), sys.implementation._machine, sys.version)" - ) - target.port, target.build, target.machine, target.version = sys_info - target.can_import_mpy = t.eval("hasattr(sys.implementation, '_mpy')") - _, target.arch, _ = str(t.exec(target_info_check), "ascii").strip().split() - if target.arch == "None": - target.arch = None - target.has_vfs = try_import(t, "vfs") - target.has_filesystem = target.has_vfs and t.eval("len(vfs.mount())") - target.has_unittest = try_import(t, "unittest") - target.has_wlan = try_import(t, "network") and t.eval("hasattr(network, 'WLAN')") - target.has_ble = try_import(t, "bluetooth") and t.eval("hasattr(bluetooth, 'BLE')") - t.close() + target.detect_features() for target in targets: print(target.info()) + if ref_target: + print("=" * 64) + print("REFERENCE") + print(ref_target.info()) + if select_wlan and ref_target.has_wlan: + ref_target.setup_wlan() + print("=" * 64) print("=" * 64) @@ -287,7 +361,8 @@ def main(): if select_via_mpy and target.can_import_mpy: port_specific = [] if target.port == "esp8266": - port_specific.extend(("-d", "basics", "extmod", "float")) + # extreme_exc has a stack overflow and crashes the device + port_specific.extend(("--exclude", "extreme_exc")) do_test(run_tests_cmd + ["--via-mpy"] + port_specific) if select_native and target.arch is not None: @@ -304,21 +379,21 @@ def main(): do_test(run_cmd + ["-d", "ports/stm32_hardware"]) if select_wlan and target.has_wlan: - t = SerialTransport(target.device) - t.enter_raw_repl() - rtc_set(target, t) - if target.has_filesystem: - for file in glob.glob("net_inet/*.der") + glob.glob("multi_net/*.der"): - put_file(t, *file.rsplit("/", 1)) - target.has_wlan_connected = wlan_connect(target, t) - t.close() + target.setup_wlan() if target.has_wlan_connected: do_test(run_tests_cmd + ["-d", "net_hosted", "net_inet"]) + run_multitests_p2([target.device], tests_multi_net) + if ref_target and ref_target.has_wlan_connected: + run_multitests_p2([target.device, ref_target.device], tests_multi_net) + run_multitests_p2([target.device, ref_target.device], tests_multi_wlan) + + if select_ble and target.has_ble: + if ref_target and ref_target.has_ble: + run_multitests_p2([target.device, ref_target.device], tests_multi_bluetooth) targets_wlan = [t for t in targets if t.has_wlan_connected] if select_wlan and targets_wlan: print("=" * 64) - run_multitests_on_one_target(targets_wlan, tests_multi_net) run_multitests_on_two_targets(targets_wlan, tests_multi_net) run_multitests_on_two_targets(targets_wlan, tests_multi_wlan) From d668cbec64c7758d6164cd75cba058705f7d5070 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Aug 2025 12:41:35 +1000 Subject: [PATCH 43/43] tests/hwtest.py: Support any number of return values from target_info. Signed-off-by: Damien George --- tests/hwtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hwtest.py b/tests/hwtest.py index befef0305db21..aa71839045bea 100755 --- a/tests/hwtest.py +++ b/tests/hwtest.py @@ -61,7 +61,7 @@ def detect_features(self): ) self.port, self.build, self.machine, self.version = sys_info self.can_import_mpy = t.eval("hasattr(sys.implementation, '_mpy')") - _, self.arch, _ = str(t.exec(TARGET_INFO_CHECK), "ascii").strip().split() + _, self.arch, *_ = str(t.exec(TARGET_INFO_CHECK), "ascii").strip().split() if self.arch == "None": self.arch = None self.has_vfs = try_import(t, "vfs")