|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# MIT license; Copyright (c) 2024 Damien P. George |
| 3 | + |
| 4 | +import sys |
| 5 | +import glob |
| 6 | +import os |
| 7 | +import subprocess |
| 8 | +import serial.tools.list_ports |
| 9 | +import datetime |
| 10 | +import time |
| 11 | +import vcprate |
| 12 | + |
| 13 | +sys.path.append("../tools/mpremote") |
| 14 | +from mpremote.transport_serial import SerialTransport |
| 15 | +import mpremote.commands |
| 16 | + |
| 17 | +WLAN_SSID = os.getenv("WLAN_SSID") |
| 18 | +WLAN_PASS = os.getenv("WLAN_PASS") |
| 19 | + |
| 20 | +if not WLAN_SSID or not WLAN_PASS: |
| 21 | + print("WLAN_SSID and/or WLAN_PASS not provided") |
| 22 | + |
| 23 | + |
| 24 | +class Target: |
| 25 | + def __init__(self, device, serial_number): |
| 26 | + self.device = device |
| 27 | + self.device_suffix = device.split("/")[-1] |
| 28 | + self.serial_number = serial_number |
| 29 | + self.port = None |
| 30 | + self.machine = None |
| 31 | + self.has_wlan_connected = False |
| 32 | + |
| 33 | + def info(self): |
| 34 | + s = f"{self.device} {self.port} {self.arch}" |
| 35 | + if self.can_import_mpy: |
| 36 | + s += " import-mpy" |
| 37 | + if self.has_wlan: |
| 38 | + s += " WLAN" |
| 39 | + if self.has_ble: |
| 40 | + s += " BLE" |
| 41 | + return s |
| 42 | + |
| 43 | + |
| 44 | +def map_device_name(d): |
| 45 | + if d[0] == "a" and d[1:].isdigit(): |
| 46 | + return "/dev/ttyACM" + d[1:] |
| 47 | + if d[0] == "u" and d[1:].isdigit(): |
| 48 | + return "/dev/ttyUSB" + d[1:] |
| 49 | + return d |
| 50 | + |
| 51 | + |
| 52 | +def list_ports(args): |
| 53 | + ports = [] |
| 54 | + for p in sorted(serial.tools.list_ports.comports()): |
| 55 | + if p.device.startswith("/dev/ttyS"): |
| 56 | + # Skip invalid ttySx ports. |
| 57 | + continue |
| 58 | + if not args or p.device in args: |
| 59 | + ports.append(Target(p.device, p.serial_number)) |
| 60 | + return ports |
| 61 | + |
| 62 | + |
| 63 | +NATMOD_LIBS = ("btree", "deflate", "framebuf", "heapq", "random", "re") |
| 64 | + |
| 65 | + |
| 66 | +def build_natmods(): |
| 67 | + for arch in ("armv6m", "armv7emsp", "armv7emdp", "xtensawin"): |
| 68 | + for lib in NATMOD_LIBS: |
| 69 | + subprocess.run(["make", "-C", f"examples/natmod/{lib}", "-B", f"ARCH={arch}"]) |
| 70 | + |
| 71 | + |
| 72 | +def try_import(target, module): |
| 73 | + return ( |
| 74 | + target.exec(f"try:\n import {module}\n print(True)\nexcept:\n print(False)").strip() |
| 75 | + == b"True" |
| 76 | + ) |
| 77 | + |
| 78 | + |
| 79 | +def rtc_set(target, serial): |
| 80 | + now = datetime.datetime.now() |
| 81 | + time.sleep(1 - now.microsecond * 1e-6) |
| 82 | + now = datetime.datetime.now() |
| 83 | + print(target.device, "set time to", now) |
| 84 | + timetuple = "({}, {}, {}, {}, {}, {}, {}, {})".format( |
| 85 | + now.year, |
| 86 | + now.month, |
| 87 | + now.day, |
| 88 | + now.weekday(), |
| 89 | + now.hour, |
| 90 | + now.minute, |
| 91 | + now.second, |
| 92 | + now.microsecond, |
| 93 | + ) |
| 94 | + serial.exec("import machine;machine.RTC().datetime({})".format(timetuple)) |
| 95 | + |
| 96 | + |
| 97 | +def put_file(target_serial, path, file): |
| 98 | + class state: |
| 99 | + transport = target_serial |
| 100 | + |
| 101 | + mpremote.commands.do_filesystem_cp(state, os.path.join(path, file), ":" + file, False, True) |
| 102 | + |
| 103 | + |
| 104 | +def wlan_connect(target, target_serial): |
| 105 | + print(f"{target.device} connecting to {WLAN_SSID}... ", end="") |
| 106 | + sys.stdout.flush() |
| 107 | + out = target_serial.exec(f""" |
| 108 | +import sys, machine, network, time |
| 109 | +wl = network.WLAN() |
| 110 | +wl.active(1) |
| 111 | +if not wl.isconnected(): |
| 112 | + wl.connect('{WLAN_SSID}', '{WLAN_PASS}') |
| 113 | + t0 = time.ticks_ms() |
| 114 | + print('connect') |
| 115 | + while not wl.isconnected() and time.ticks_diff(time.ticks_ms(), t0) < 10_000: |
| 116 | + machine.idle() |
| 117 | +""") |
| 118 | + isconnected, ifconfig = target_serial.eval("(wl.isconnected(), wl.ifconfig())") |
| 119 | + if isconnected: |
| 120 | + print(ifconfig[0]) |
| 121 | + else: |
| 122 | + print("FAIL") |
| 123 | + return isconnected |
| 124 | + |
| 125 | + |
| 126 | +def do_test(cmd): |
| 127 | + print("-" * 32) |
| 128 | + print("RUN", " ".join(cmd)) |
| 129 | + try: |
| 130 | + subprocess.run(cmd, check=True) |
| 131 | + print("PASS") |
| 132 | + except subprocess.CalledProcessError as er: |
| 133 | + print("ERROR", er) |
| 134 | + time.sleep(1) |
| 135 | + except KeyboardInterrupt: |
| 136 | + print("INTERRUPT") |
| 137 | + time.sleep(1) |
| 138 | + |
| 139 | + |
| 140 | +def run_multitests_on_one_target(targets, tests): |
| 141 | + for target in targets: |
| 142 | + do_test(["./run-multitests.py", "-i", f"pyb:{target.device}", "-p2"] + tests) |
| 143 | + |
| 144 | + |
| 145 | +def run_multitests_on_two_targets(targets, tests): |
| 146 | + if len(targets) == 1: |
| 147 | + return |
| 148 | + for i in range(len(targets)): |
| 149 | + target0 = targets[i] |
| 150 | + target1 = targets[(i + 1) % len(targets)] |
| 151 | + do_test( |
| 152 | + ["./run-multitests.py", "-i", f"pyb:{target0.device}", "-i", f"pyb:{target1.device}"] |
| 153 | + + tests |
| 154 | + ) |
| 155 | + |
| 156 | + |
| 157 | +def main(): |
| 158 | + # build_natmods() |
| 159 | + |
| 160 | + with open("feature_check/target_info.py", "rb") as f: |
| 161 | + target_info_check = f.read() |
| 162 | + |
| 163 | + selected_devices = [map_device_name(d) for d in sys.argv[1:]] |
| 164 | + targets = list_ports(selected_devices) |
| 165 | + |
| 166 | + targets_ble = [] |
| 167 | + for target in targets: |
| 168 | + t = SerialTransport(target.device) |
| 169 | + t.enter_raw_repl() |
| 170 | + t.exec("import sys") |
| 171 | + sys_info = t.eval("(sys.platform, sys.implementation._machine, sys.version)") |
| 172 | + target.port, target.machine, target.version = sys_info |
| 173 | + target.can_import_mpy = t.eval("hasattr(sys.implementation, '_mpy')") |
| 174 | + target.arch = str(t.exec(target_info_check), "ascii").strip() |
| 175 | + if target.arch == "None": |
| 176 | + target.arch = None |
| 177 | + target.has_vfs = try_import(t, "vfs") |
| 178 | + target.has_wlan = try_import(t, "network") and t.eval("hasattr(network, 'WLAN')") |
| 179 | + target.has_ble = try_import(t, "bluetooth") and t.eval("hasattr(bluetooth, 'BLE')") |
| 180 | + t.close() |
| 181 | + |
| 182 | + for target in targets: |
| 183 | + print(target.info()) |
| 184 | + |
| 185 | + print("=" * 64) |
| 186 | + print("=" * 64) |
| 187 | + |
| 188 | + tests_natmod = [ |
| 189 | + file for file in glob.iglob("extmod/*.py") if file.split("/")[1].startswith(NATMOD_LIBS) |
| 190 | + ] |
| 191 | + tests_multi_net = [file for file in glob.iglob("multi_net/*.py")] |
| 192 | + tests_multi_bluetooth = [ |
| 193 | + file for file in glob.iglob("multi_bluetooth/*.py") if "/ble_" in file |
| 194 | + ] |
| 195 | + |
| 196 | + if True: |
| 197 | + for target in targets: |
| 198 | + print("=" * 64) |
| 199 | + print(target.info()) |
| 200 | + print(target.machine) |
| 201 | + print(target.version) |
| 202 | + run_tests_cmd = [ |
| 203 | + "./run-tests.py", |
| 204 | + "--target", |
| 205 | + target.port, |
| 206 | + "--device", |
| 207 | + target.device, |
| 208 | + "--result-dir", |
| 209 | + f"results_{target.device_suffix}", |
| 210 | + ] |
| 211 | + do_test(run_tests_cmd + ["--clean-failures"]) |
| 212 | + |
| 213 | + try: |
| 214 | + vcprate.do_test(target.device) |
| 215 | + except KeyboardInterrupt: |
| 216 | + print("INTERRUPT") |
| 217 | + time.sleep(1) |
| 218 | + |
| 219 | + do_test(run_tests_cmd) |
| 220 | + do_test(run_tests_cmd + ["-d", "extmod_hardware"]) |
| 221 | + if target.port == "pyboard": |
| 222 | + do_test(run_tests_cmd + ["-d", "ports/stm32_hardware"]) |
| 223 | + |
| 224 | + if target.can_import_mpy: |
| 225 | + port_specific = [] |
| 226 | + if target.port == "esp8266": |
| 227 | + port_specific.extend(("-d", "basics", "extmod", "float")) |
| 228 | + do_test(run_tests_cmd + ["--via-mpy"] + port_specific) |
| 229 | + |
| 230 | + if target.arch is not None: |
| 231 | + do_test(run_tests_cmd + ["--via-mpy", "--emit", "native"]) |
| 232 | + do_test( |
| 233 | + ["./run-natmodtests.py", "-p", "-d", target.device, "-a", target.arch] |
| 234 | + + tests_natmod |
| 235 | + ) |
| 236 | + |
| 237 | + if target.has_wlan: |
| 238 | + t = SerialTransport(target.device) |
| 239 | + t.enter_raw_repl() |
| 240 | + rtc_set(target, t) |
| 241 | + if target.has_vfs: |
| 242 | + for file in glob.glob("tests/net_inet/*.der") + glob.glob( |
| 243 | + "tests/multi_net/*.der" |
| 244 | + ): |
| 245 | + put_file(t, *file.rsplit("/", 1)) |
| 246 | + target.has_wlan_connected = wlan_connect(target, t) |
| 247 | + t.close() |
| 248 | + if target.has_wlan_connected: |
| 249 | + do_test(run_tests_cmd + ["-d", "net_hosted", "net_inet"]) |
| 250 | + |
| 251 | + targets_wlan = [t for t in targets if t.has_wlan_connected] |
| 252 | + if targets_wlan: |
| 253 | + print("=" * 64) |
| 254 | + run_multitests_on_one_target(targets_wlan, tests_multi_net) |
| 255 | + run_multitests_on_two_targets(targets_wlan, tests_multi_net) |
| 256 | + |
| 257 | + targets_ble = [t for t in targets if t.has_ble] |
| 258 | + if targets_ble: |
| 259 | + print("=" * 64) |
| 260 | + run_multitests_on_two_targets(targets_ble, tests_multi_bluetooth) |
| 261 | + |
| 262 | + |
| 263 | +if __name__ == "__main__": |
| 264 | + main() |
0 commit comments