Skip to content

Commit b8ce03c

Browse files
committed
tests/hwtest.py: Add a full, automatic test runner.
Signed-off-by: Damien George <damien@micropython.org>
1 parent d817524 commit b8ce03c

File tree

1 file changed

+264
-0
lines changed

1 file changed

+264
-0
lines changed

tests/hwtest.py

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy