1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
|
#!/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/micropython_coverage')
NATMOD_EXAMPLE_DIR = '../examples/natmod/'
# Supported tests and their corresponding mpy module
TEST_MAPPINGS = {
'btree': 'btree/btree_$(ARCH).mpy',
'framebuf': 'framebuf/framebuf_$(ARCH).mpy',
'uheapq': 'uheapq/uheapq_$(ARCH).mpy',
'urandom': 'urandom/urandom_$(ARCH).mpy',
'ure': 'ure/ure_$(ARCH).mpy',
'uzlib': 'uzlib/uzlib_$(ARCH).mpy',
}
# Code to allow a target MicroPython to import an .mpy from RAM
injected_import_hook_code = """\
import sys, uos, uio
class __File(uio.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()
uos.mount(__FS(), '/__remote')
uos.chdir('/__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
for k, v in TEST_MAPPINGS.items():
if test_file.find(k) != -1:
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
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()
|