Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
micropython/tests/run-natmodtests.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
executable file
205 lines (172 sloc)
5.85 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 | |
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", | |
} | |
# 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): | |
return 0 | |
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') | |
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 run_tests(target_truth, target, args, stats): | |
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)", args.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: | |
print("---- {} - 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 | |
stats["total"] += 1 | |
if result == "pass": | |
stats["pass"] += 1 | |
elif result == "SKIP": | |
stats["skip"] += 1 | |
else: | |
stats["fail"] += 1 | |
# Print result | |
print("{:4} {}{}".format(result, test_file, extra)) | |
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", default="x64", help="native architecture of the target" | |
) | |
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]) | |
stats = {"total": 0, "pass": 0, "fail": 0, "skip": 0} | |
run_tests(target_truth, target, args, stats) | |
target.close() | |
target_truth.close() | |
print("{} tests performed".format(stats["total"])) | |
print("{} tests passed".format(stats["pass"])) | |
if stats["fail"]: | |
print("{} tests failed".format(stats["fail"])) | |
if stats["skip"]: | |
print("{} tests skipped".format(stats["skip"])) | |
if stats["fail"]: | |
sys.exit(1) | |
if __name__ == "__main__": | |
main() |