Files
micropython/tests/run-natmodtests.py
Damien George 5676b45cbb
Some checks failed
JavaScript code lint and formatting with Biome / eslint (push) Has been cancelled
Check code formatting / code-formatting (push) Has been cancelled
Check spelling with codespell / codespell (push) Has been cancelled
Build docs / build (push) Has been cancelled
Check examples / embedding (push) Has been cancelled
Package mpremote / build (push) Has been cancelled
.mpy file format and tools / test (push) Has been cancelled
Build ports metadata / build (push) Has been cancelled
alif port / build_alif (alif_ae3_build) (push) Has been cancelled
cc3200 port / build (push) Has been cancelled
esp32 port / build_idf (esp32_build_cmod_spiram_s2) (push) Has been cancelled
esp32 port / build_idf (esp32_build_s3_c3) (push) Has been cancelled
esp8266 port / build (push) Has been cancelled
mimxrt port / build (push) Has been cancelled
nrf port / build (push) Has been cancelled
powerpc port / build (push) Has been cancelled
qemu port / build_and_test_arm (bigendian) (push) Has been cancelled
qemu port / build_and_test_arm (sabrelite) (push) Has been cancelled
qemu port / build_and_test_arm (thumb) (push) Has been cancelled
qemu port / build_and_test_rv32 (push) Has been cancelled
renesas-ra port / build_renesas_ra_board (push) Has been cancelled
rp2 port / build (push) Has been cancelled
samd port / build (push) Has been cancelled
stm32 port / build_stm32 (stm32_misc_build) (push) Has been cancelled
stm32 port / build_stm32 (stm32_nucleo_build) (push) Has been cancelled
stm32 port / build_stm32 (stm32_pyb_build) (push) Has been cancelled
unix port / minimal (push) Has been cancelled
unix port / reproducible (push) Has been cancelled
unix port / standard (push) Has been cancelled
unix port / standard_v2 (push) Has been cancelled
unix port / coverage (push) Has been cancelled
unix port / coverage_32bit (push) Has been cancelled
unix port / nanbox (push) Has been cancelled
unix port / float (push) Has been cancelled
unix port / stackless_clang (push) Has been cancelled
unix port / float_clang (push) Has been cancelled
unix port / settrace (push) Has been cancelled
unix port / settrace_stackless (push) Has been cancelled
unix port / macos (push) Has been cancelled
unix port / qemu_mips (push) Has been cancelled
unix port / qemu_arm (push) Has been cancelled
unix port / qemu_riscv64 (push) Has been cancelled
webassembly port / build (push) Has been cancelled
windows port / build-vs (Debug, x64, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Debug, x64, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Debug, x86, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Debug, x86, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x64, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, x64, dev, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, x64, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x64, standard, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, x64, standard, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, x64, standard, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x86, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, x86, dev, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, x86, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x86, standard, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, x86, standard, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, x86, standard, 2022, [17, 18)) (push) Has been cancelled
windows port / build-mingw (i686, mingw32, dev) (push) Has been cancelled
windows port / build-mingw (i686, mingw32, standard) (push) Has been cancelled
windows port / build-mingw (x86_64, mingw64, dev) (push) Has been cancelled
windows port / build-mingw (x86_64, mingw64, standard) (push) Has been cancelled
windows port / cross-build-on-linux (push) Has been cancelled
zephyr port / build (push) Has been cancelled
Python code lint and formatting with ruff / ruff (push) Has been cancelled
tests/run-natmodtests.py: Consider a test skipped if mpy doesn't exist.
This is different to a test not being run because there is no corresponding
natmod at all.

Signed-off-by: Damien George <damien@micropython.org>
2025-06-12 21:53:31 +10:00

278 lines
8.0 KiB
Python
Executable File

#!/usr/bin/env python3
# This file is part of the MicroPython project, http://micropython.org/
# The MIT License (MIT)
# Copyright (c) 2019 Damien P. George
import os
import subprocess
import sys
import argparse
run_tests_module = __import__("run-tests")
sys.path.append("../tools")
import pyboard
# Paths for host executables
CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3")
MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/unix/build-coverage/micropython")
NATMOD_EXAMPLE_DIR = "../examples/natmod/"
# Supported tests and their corresponding mpy module
TEST_MAPPINGS = {
"btree": "btree/btree_$(ARCH).mpy",
"deflate": "deflate/deflate_$(ARCH).mpy",
"framebuf": "framebuf/framebuf_$(ARCH).mpy",
"heapq": "heapq/heapq_$(ARCH).mpy",
"random": "random/random_$(ARCH).mpy",
"re": "re/re_$(ARCH).mpy",
}
# Supported architectures for native mpy modules
AVAILABLE_ARCHS = (
"x86",
"x64",
"armv6",
"armv6m",
"armv7m",
"armv7em",
"armv7emsp",
"armv7emdp",
"xtensa",
"xtensawin",
"rv32imc",
)
ARCH_MAPPINGS = {"armv7em": "armv7m"}
# Code to allow a target MicroPython to import an .mpy from RAM
injected_import_hook_code = """\
import sys, io, vfs
class __File(io.IOBase):
def __init__(self):
self.off = 0
def ioctl(self, request, arg):
if request == 4: # MP_STREAM_CLOSE
return 0
return -1
def readinto(self, buf):
buf[:] = memoryview(__buf)[self.off:self.off + len(buf)]
self.off += len(buf)
return len(buf)
class __FS:
def mount(self, readonly, mkfs):
pass
def chdir(self, path):
pass
def stat(self, path):
if path == '/__injected.mpy':
return tuple(0 for _ in range(10))
else:
raise OSError(-2) # ENOENT
def open(self, path, mode):
return __File()
vfs.mount(__FS(), '/__remote')
sys.path.insert(0, '/__remote')
{import_prelude}
sys.modules['{}'] = __import__('__injected')
"""
class TargetSubprocess:
def __init__(self, cmd):
self.cmd = cmd
def close(self):
pass
def run_script(self, script):
try:
p = subprocess.run(
self.cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, input=script
)
return p.stdout, None
except subprocess.CalledProcessError as er:
return b"", er
class TargetPyboard:
def __init__(self, pyb):
self.pyb = pyb
self.pyb.enter_raw_repl()
def close(self):
self.pyb.exit_raw_repl()
self.pyb.close()
def run_script(self, script):
try:
self.pyb.enter_raw_repl()
output = self.pyb.exec_(script)
output = output.replace(b"\r\n", b"\n")
return output, None
except pyboard.PyboardError as er:
return b"", er
def detect_architecture(target):
with open("./feature_check/target_info.py", "rb") as f:
target_info_data = f.read()
result_out, error = target.run_script(target_info_data)
if error is not None:
return None, None, error
info = result_out.split(b" ")
if len(info) < 2:
return None, None, "unexpected target info: {}".format(info)
platform = info[0].strip().decode()
arch = info[1].strip().decode()
if arch not in AVAILABLE_ARCHS:
if arch == "None":
return None, None, "the target does not support dynamic modules"
else:
return None, None, "{} is not a supported architecture".format(arch)
return platform, arch, None
def run_tests(target_truth, target, args, resolved_arch):
global injected_import_hook_code
prelude = ""
if args.begin:
prelude = args.begin.read()
injected_import_hook_code = injected_import_hook_code.replace("{import_prelude}", prelude)
test_results = []
for test_file in args.files:
# Find supported test
test_file_basename = os.path.basename(test_file)
for k, v in TEST_MAPPINGS.items():
if test_file_basename.startswith(k):
test_module = k
test_mpy = v.replace("$(ARCH)", resolved_arch)
break
else:
print("---- {} - no matching mpy".format(test_file))
continue
# Read test script
with open(test_file, "rb") as f:
test_file_data = f.read()
# Create full test with embedded .mpy
test_script = b"import sys\nsys.path.remove('')\n\n"
try:
with open(NATMOD_EXAMPLE_DIR + test_mpy, "rb") as f:
test_script += b"__buf=" + bytes(repr(f.read()), "ascii") + b"\n"
except OSError:
test_results.append((test_file, "skip", "mpy file not compiled"))
print("skip {} - mpy file not compiled".format(test_file))
continue
test_script += bytes(injected_import_hook_code.format(test_module), "ascii")
test_script += test_file_data
# Run test under MicroPython
result_out, error = target.run_script(test_script)
# Work out result of test
extra = ""
if error is None and result_out == b"SKIP\n":
result = "SKIP"
elif error is not None:
result = "FAIL"
extra = " - " + str(error)
else:
# Check result against truth
try:
with open(test_file + ".exp", "rb") as f:
result_exp = f.read()
error = None
except OSError:
result_exp, error = target_truth.run_script(test_file_data)
if error is not None:
result = "TRUTH FAIL"
elif result_out != result_exp:
result = "FAIL"
print(result_out)
else:
result = "pass"
# Accumulate statistics
if result == "pass":
test_results.append((test_file, "pass", ""))
elif result == "SKIP":
test_results.append((test_file, "skip", ""))
else:
test_results.append((test_file, "fail", ""))
# Print result
print("{:4} {}{}".format(result, test_file, extra))
return test_results
def main():
cmd_parser = argparse.ArgumentParser(
description="Run dynamic-native-module tests under MicroPython"
)
cmd_parser.add_argument(
"-p", "--pyboard", action="store_true", help="run tests via pyboard.py"
)
cmd_parser.add_argument(
"-d", "--device", default="/dev/ttyACM0", help="the device for pyboard.py"
)
cmd_parser.add_argument(
"-a", "--arch", choices=AVAILABLE_ARCHS, help="override native architecture of the target"
)
cmd_parser.add_argument(
"-b",
"--begin",
type=argparse.FileType("rt"),
default=None,
help="prologue python file to execute before module import",
)
cmd_parser.add_argument(
"-r",
"--result-dir",
default=run_tests_module.base_path("results"),
help="directory for test results",
)
cmd_parser.add_argument("files", nargs="*", help="input test files")
args = cmd_parser.parse_args()
target_truth = TargetSubprocess([CPYTHON3])
if args.pyboard:
target = TargetPyboard(pyboard.Pyboard(args.device))
else:
target = TargetSubprocess([MICROPYTHON])
if hasattr(args, "arch") and args.arch is not None:
target_arch = args.arch
target_platform = None
else:
target_platform, target_arch, error = detect_architecture(target)
if error:
print("Cannot run tests: {}".format(error))
sys.exit(1)
target_arch = ARCH_MAPPINGS.get(target_arch, target_arch)
if target_platform:
print("platform={} ".format(target_platform), end="")
print("arch={}".format(target_arch))
os.makedirs(args.result_dir, exist_ok=True)
test_results = run_tests(target_truth, target, args, target_arch)
res = run_tests_module.create_test_report(args, test_results)
target.close()
target_truth.close()
if not res:
sys.exit(1)
if __name__ == "__main__":
main()