forked from micropython/micropython
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrun-tests
executable file
·280 lines (243 loc) · 11.5 KB
/
run-tests
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#! /usr/bin/env python3
import os
import subprocess
import sys
import platform
import argparse
import re
from glob import glob
# Tests require at least CPython 3.3. If your default python3 executable
# is of lower version, you can point MICROPY_CPYTHON3 environment var
# to the correct executable.
if os.name == 'nt':
CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3.exe')
MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', '../windows/micropython.exe')
else:
CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3')
MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', '../unix/micropython')
# Set PYTHONIOENCODING so that CPython will use utf-8 on systems which set another encoding in the locale
os.environ['PYTHONIOENCODING'] = 'utf-8'
os.environ['MICROPYPATH'] = ''
def rm_f(fname):
if os.path.exists(fname):
os.remove(fname)
def run_micropython(pyb, args, test_file):
if pyb is None:
# run on PC
if test_file.startswith('cmdline/'):
# special handling for tests of the unix cmdline program
# check for any cmdline options needed for this test
args = [MICROPYTHON]
with open(test_file, 'rb') as f:
line = f.readline()
if line.startswith(b'# cmdline:'):
args += line[10:].strip().split()
# run the test, possibly with redirected input
try:
if test_file.startswith('cmdline/repl_'):
f = open(test_file, 'rb')
output_mupy = subprocess.check_output(args, stdin=f)
f.close()
else:
output_mupy = subprocess.check_output(args + [test_file])
except subprocess.CalledProcessError:
output_mupy = b'CRASH'
# unescape wanted regex chars and escape unwanted ones
def convert_regex_escapes(line):
cs = []
escape = False
for c in str(line, 'utf8'):
if escape:
escape = False
cs.append(c)
elif c == '\\':
escape = True
elif c in ('(', ')', '[', ']', '{', '}', '.', '*', '+', '^', '$'):
cs.append('\\' + c)
else:
cs.append(c)
return bytes(''.join(cs), 'utf8')
# convert parts of the output that are not stable across runs
with open(test_file + '.exp', 'rb') as f:
lines_exp = []
for line in f.readlines():
if line == b'########\n':
line = (line,)
else:
line = (line, re.compile(convert_regex_escapes(line)))
lines_exp.append(line)
lines_mupy = [line + b'\n' for line in output_mupy.split(b'\n')]
if output_mupy.endswith(b'\n'):
lines_mupy = lines_mupy[:-1] # remove erroneous last empty line
i_mupy = 0
for i in range(len(lines_exp)):
if lines_exp[i][0] == b'########\n':
# 8x #'s means match 0 or more whole lines
line_exp = lines_exp[i + 1]
skip = 0
while i_mupy + skip < len(lines_mupy) and not line_exp[1].match(lines_mupy[i_mupy + skip]):
skip += 1
if i_mupy + skip >= len(lines_mupy):
lines_mupy[i_mupy] = b'######## FAIL\n'
break
del lines_mupy[i_mupy:i_mupy + skip]
lines_mupy.insert(i_mupy, b'########\n')
i_mupy += 1
else:
# a regex
if lines_exp[i][1].match(lines_mupy[i_mupy]):
lines_mupy[i_mupy] = lines_exp[i][0]
i_mupy += 1
if i_mupy >= len(lines_mupy):
break
output_mupy = b''.join(lines_mupy)
else:
# a standard test
try:
output_mupy = subprocess.check_output([MICROPYTHON, '-X', 'emit=' + args.emit, test_file])
except subprocess.CalledProcessError:
output_mupy = b'CRASH'
else:
# run on pyboard
import pyboard
pyb.enter_raw_repl()
try:
output_mupy = pyb.execfile(test_file).replace(b'\r\n', b'\n')
except pyboard.PyboardError:
output_mupy = b'CRASH'
return output_mupy
def run_tests(pyb, tests, args):
test_count = 0
testcase_count = 0
passed_count = 0
failed_tests = []
skipped_tests = []
skip_tests = set()
skip_native = False
# Check if micropython.native is supported, and skip such tests if it's not
native = run_micropython(pyb, args, 'micropython/native_check.py')
if native == b'CRASH':
skip_native = True
# Some tests shouldn't be run under Travis CI
if os.getenv('TRAVIS') == 'true':
skip_tests.add('basics/memoryerror.py')
# Some tests shouldn't be run on pyboard
if pyb is not None:
skip_tests.add('float/float_divmod.py') # tested by float/float_divmod_relaxed.py instead
skip_tests.add('float/float2int_doubleprec.py') # requires double precision floating point to work
# Some tests are known to fail on 64-bit machines
if pyb is None and platform.architecture()[0] == '64bit':
pass
# Some tests use unsupported features on Windows
if os.name == 'nt':
skip_tests.add('import\\import_file.py') #works but CPython prints forward slashes
# Some tests are known to fail with native emitter
# Remove them from the below when they work
if args.emit == 'native':
skip_tests.update({'basics/%s.py' % t for t in 'bytes_gen class_store_class class_super class_super_object closure1 closure2 closure_defargs del_deref del_local fun3 fun_calldblstar fun_callstar fun_callstardblstar fun_defargs fun_defargs2 fun_kwargs fun_kwonly fun_kwonlydef fun_kwvarargs fun_varargs gen_yield_from gen_yield_from_close gen_yield_from_ducktype gen_yield_from_exc gen_yield_from_iter gen_yield_from_send gen_yield_from_throw generator1 generator2 generator_args generator_close generator_closure generator_exc generator_return generator_send globals_del string_format string_join subclass_native2_list subclass_native2_tuple try_finally_loops try_finally_return try_reraise try_reraise2 unboundlocal with1 with_break with_continue with_return'.split()})
skip_tests.add('basics/boundmeth1.py') # requires support for many args
skip_tests.add('float/string_format.py')
skip_tests.add('float/cmath_fun.py') # requires f(*args) support
skip_tests.add('import/gen_context.py')
skip_tests.add('io/file_with.py')
skip_tests.add('micropython/heapalloc.py')
skip_tests.add('misc/features.py')
skip_tests.add('misc/recursion.py')
skip_tests.add('misc/rge_sm.py')
skip_tests.add('misc/print_exception.py') # because native doesn't have proper traceback info
for test_file in tests:
test_basename = os.path.basename(test_file)
test_name = os.path.splitext(test_basename)[0]
is_native = test_name.startswith("native_") or test_name.startswith("viper_")
if test_file in skip_tests or (skip_native and is_native):
print("skip ", test_file)
skipped_tests.append(test_name)
continue
# get expected output
test_file_expected = test_file + '.exp'
if os.path.isfile(test_file_expected):
# expected output given by a file, so read that in
with open(test_file_expected, 'rb') as f:
output_expected = f.read()
if os.name == 'nt':
output_expected = output_expected.replace(b'\n', b'\r\n')
else:
# run CPython to work out expected output
try:
output_expected = subprocess.check_output([CPYTHON3, '-B', test_file])
if args.write_exp:
with open(test_file_expected, 'wb') as f:
f.write(output_expected)
except subprocess.CalledProcessError:
output_expected = b'CPYTHON3 CRASH'
if args.write_exp:
continue
# run Micro Python
output_mupy = run_micropython(pyb, args, test_file)
if os.name != 'nt':
# It may be the case that we run Windows build under Linux
# (using Wine).
output_mupy = output_mupy.replace(b'\r\n', b'\n')
if output_mupy == b'SKIP\n' or output_mupy == b'SKIP\r\n':
print("skip ", test_file)
skipped_tests.append(test_name)
continue
testcase_count += len(output_expected.splitlines())
filename_expected = test_basename + ".exp"
filename_mupy = test_basename + ".out"
if output_expected == output_mupy:
print("pass ", test_file)
passed_count += 1
rm_f(filename_expected)
rm_f(filename_mupy)
else:
with open(filename_expected, "wb") as f:
f.write(output_expected)
with open(filename_mupy, "wb") as f:
f.write(output_mupy)
print("FAIL ", test_file)
failed_tests.append(test_name)
test_count += 1
print("{} tests performed ({} individual testcases)".format(test_count, testcase_count))
print("{} tests passed".format(passed_count))
if len(skipped_tests) > 0:
print("{} tests skipped: {}".format(len(skipped_tests), ' '.join(skipped_tests)))
if len(failed_tests) > 0:
print("{} tests failed: {}".format(len(failed_tests), ' '.join(failed_tests)))
return False
# all tests succeeded
return True
def main():
cmd_parser = argparse.ArgumentParser(description='Run tests for Micro Python.')
cmd_parser.add_argument('--pyboard', action='store_true', help='run the tests on the pyboard')
cmd_parser.add_argument('--device', default='/dev/ttyACM0', help='the serial device of the pyboard')
cmd_parser.add_argument('-d', '--test-dirs', nargs='*', help='input test directories (if no files given)')
cmd_parser.add_argument('--write-exp', action='store_true', help='save .exp files to run tests w/o CPython')
cmd_parser.add_argument('--emit', default='bytecode', help='Micro Python emitter to use (bytecode or native)')
cmd_parser.add_argument('files', nargs='*', help='input test files')
args = cmd_parser.parse_args()
if args.pyboard:
import pyboard
pyb = pyboard.Pyboard(args.device)
pyb.enter_raw_repl()
else:
pyb = None
if len(args.files) == 0:
if args.test_dirs is None:
if pyb is None:
# run PC tests
test_dirs = ('basics', 'micropython', 'float', 'import', 'io', 'misc', 'unicode', 'extmod', 'unix', 'cmdline')
else:
# run pyboard tests
test_dirs = ('basics', 'micropython', 'float', 'misc', 'extmod', 'pyb', 'pybnative', 'inlineasm')
else:
# run tests from these directories
test_dirs = args.test_dirs
tests = sorted(test_file for test_files in (glob('{}/*.py'.format(dir)) for dir in test_dirs) for test_file in test_files)
else:
# tests explicitly given
tests = args.files
if not run_tests(pyb, tests, args):
sys.exit(1)
if __name__ == "__main__":
main()