blob: 385c4e696df243d7ab75b26ba37ca97c11e80b9a [file] [log] [blame]
Damien George929a6752014-04-02 15:31:39 +01001#! /usr/bin/env python3
Damien39977a52013-12-29 22:34:42 +00002
Markus Siemens19ccc6b2014-01-27 22:53:28 +01003import os
4import subprocess
5import sys
Damien George612045f2014-09-17 22:56:34 +01006import platform
Damien George41f768f2014-05-03 16:43:27 +01007import argparse
Damien George10045352015-03-20 17:25:25 +00008import re
Markus Siemens19ccc6b2014-01-27 22:53:28 +01009from glob import glob
Damien39977a52013-12-29 22:34:42 +000010
Paul Sokolovskya7752a42014-04-04 17:28:34 +030011# Tests require at least CPython 3.3. If your default python3 executable
12# is of lower version, you can point MICROPY_CPYTHON3 environment var
13# to the correct executable.
Markus Siemens19ccc6b2014-01-27 22:53:28 +010014if os.name == 'nt':
Paul Sokolovskya7752a42014-04-04 17:28:34 +030015 CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3.exe')
Andrew Scheller57094532014-04-17 01:22:45 +010016 MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', '../windows/micropython.exe')
Markus Siemens19ccc6b2014-01-27 22:53:28 +010017else:
Paul Sokolovsky34e11992014-04-03 22:06:35 +030018 CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3')
Andrew Scheller57094532014-04-17 01:22:45 +010019 MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', '../unix/micropython')
Damien39977a52013-12-29 22:34:42 +000020
blmorris9d1ca652014-11-19 10:44:31 -050021# Set PYTHONIOENCODING so that CPython will use utf-8 on systems which set another encoding in the locale
Paul Sokolovskydbc78542014-12-12 00:51:39 +020022os.environ['PYTHONIOENCODING'] = 'utf-8'
Paul Sokolovsky1ca28bd2014-12-12 00:57:03 +020023os.environ['MICROPYPATH'] = ''
blmorris9d1ca652014-11-19 10:44:31 -050024
Paul Sokolovskyfd232c32014-03-23 01:07:30 +020025def rm_f(fname):
26 if os.path.exists(fname):
27 os.remove(fname)
28
Damien George5a04e2c2014-10-05 22:27:12 +010029def run_micropython(pyb, args, test_file):
30 if pyb is None:
31 # run on PC
Damien George143c3412015-03-13 10:58:34 +000032 if test_file.startswith('cmdline/'):
33 # special handling for tests of the unix cmdline program
34
35 # check for any cmdline options needed for this test
36 args = [MICROPYTHON]
37 with open(test_file, 'rb') as f:
38 line = f.readline()
39 if line.startswith(b'# cmdline:'):
40 args += line[10:].strip().split()
41
42 # run the test, possibly with redirected input
43 try:
44 if test_file.startswith('cmdline/repl_'):
45 f = open(test_file, 'rb')
46 output_mupy = subprocess.check_output(args, stdin=f)
47 f.close()
48 else:
49 output_mupy = subprocess.check_output(args + [test_file])
50 except subprocess.CalledProcessError:
51 output_mupy = b'CRASH'
52
Damien George10045352015-03-20 17:25:25 +000053 # unescape wanted regex chars and escape unwanted ones
54 def convert_regex_escapes(line):
55 cs = []
56 escape = False
57 for c in str(line, 'utf8'):
58 if escape:
59 escape = False
60 cs.append(c)
61 elif c == '\\':
62 escape = True
63 elif c in ('(', ')', '[', ']', '{', '}', '.', '*', '+', '^', '$'):
64 cs.append('\\' + c)
65 else:
66 cs.append(c)
67 return bytes(''.join(cs), 'utf8')
68
69 # convert parts of the output that are not stable across runs
Damien George143c3412015-03-13 10:58:34 +000070 with open(test_file + '.exp', 'rb') as f:
Damien George10045352015-03-20 17:25:25 +000071 lines_exp = []
72 for line in f.readlines():
73 if line == b'########\n':
74 line = (line,)
75 else:
76 line = (line, re.compile(convert_regex_escapes(line)))
77 lines_exp.append(line)
Damien George143c3412015-03-13 10:58:34 +000078 lines_mupy = [line + b'\n' for line in output_mupy.split(b'\n')]
79 if output_mupy.endswith(b'\n'):
80 lines_mupy = lines_mupy[:-1] # remove erroneous last empty line
Damien George10045352015-03-20 17:25:25 +000081 i_mupy = 0
82 for i in range(len(lines_exp)):
83 if lines_exp[i][0] == b'########\n':
84 # 8x #'s means match 0 or more whole lines
85 line_exp = lines_exp[i + 1]
86 skip = 0
87 while i_mupy + skip < len(lines_mupy) and not line_exp[1].match(lines_mupy[i_mupy + skip]):
88 skip += 1
89 if i_mupy + skip >= len(lines_mupy):
90 lines_mupy[i_mupy] = b'######## FAIL\n'
91 break
92 del lines_mupy[i_mupy:i_mupy + skip]
93 lines_mupy.insert(i_mupy, b'########\n')
94 i_mupy += 1
95 else:
96 # a regex
97 if lines_exp[i][1].match(lines_mupy[i_mupy]):
98 lines_mupy[i_mupy] = lines_exp[i][0]
99 i_mupy += 1
100 if i_mupy >= len(lines_mupy):
101 break
102 output_mupy = b''.join(lines_mupy)
Damien George143c3412015-03-13 10:58:34 +0000103
104 else:
105 # a standard test
106 try:
107 output_mupy = subprocess.check_output([MICROPYTHON, '-X', 'emit=' + args.emit, test_file])
108 except subprocess.CalledProcessError:
109 output_mupy = b'CRASH'
Damien George5a04e2c2014-10-05 22:27:12 +0100110 else:
111 # run on pyboard
112 import pyboard
113 pyb.enter_raw_repl()
114 try:
115 output_mupy = pyb.execfile(test_file).replace(b'\r\n', b'\n')
116 except pyboard.PyboardError:
117 output_mupy = b'CRASH'
118
119 return output_mupy
120
Paul Sokolovsky2cf38102014-07-12 16:34:51 +0300121def run_tests(pyb, tests, args):
Damien George41f768f2014-05-03 16:43:27 +0100122 test_count = 0
123 testcase_count = 0
124 passed_count = 0
125 failed_tests = []
Paul Sokolovsky43d4a6f2014-05-10 16:52:58 +0300126 skipped_tests = []
Damien39977a52013-12-29 22:34:42 +0000127
Chris Angelico047db222014-06-06 07:45:55 +1000128 skip_tests = set()
Paul Sokolovsky138562c2014-12-13 00:50:54 +0200129 skip_native = False
Damien39977a52013-12-29 22:34:42 +0000130
Damien George5a04e2c2014-10-05 22:27:12 +0100131 # Check if micropython.native is supported, and skip such tests if it's not
132 native = run_micropython(pyb, args, 'micropython/native_check.py')
133 if native == b'CRASH':
Paul Sokolovsky138562c2014-12-13 00:50:54 +0200134 skip_native = True
Damien George5a04e2c2014-10-05 22:27:12 +0100135
Chris Angelico047db222014-06-06 07:45:55 +1000136 # Some tests shouldn't be run under Travis CI
137 if os.getenv('TRAVIS') == 'true':
138 skip_tests.add('basics/memoryerror.py')
Damien George23093692014-04-03 22:44:51 +0100139
Damien George8594ce22014-09-13 18:43:09 +0100140 # Some tests shouldn't be run on pyboard
141 if pyb is not None:
142 skip_tests.add('float/float_divmod.py') # tested by float/float_divmod_relaxed.py instead
Damien Georgead2307c2015-01-07 12:10:47 +0000143 skip_tests.add('float/float2int_doubleprec.py') # requires double precision floating point to work
Damien George8594ce22014-09-13 18:43:09 +0100144
Damien George612045f2014-09-17 22:56:34 +0100145 # Some tests are known to fail on 64-bit machines
146 if pyb is None and platform.architecture()[0] == '64bit':
Damien George96e20c62014-09-23 12:09:26 +0000147 pass
Damien George612045f2014-09-17 22:56:34 +0100148
stijndc1ea112014-10-04 08:39:15 +0200149 # Some tests use unsupported features on Windows
150 if os.name == 'nt':
stijndc1ea112014-10-04 08:39:15 +0200151 skip_tests.add('import\\import_file.py') #works but CPython prints forward slashes
stijndc1ea112014-10-04 08:39:15 +0200152
Damien George15d2fe82014-08-29 19:47:10 +0100153 # Some tests are known to fail with native emitter
154 # Remove them from the below when they work
155 if args.emit == 'native':
Damien George4cd9ced2015-01-15 14:41:41 +0000156 skip_tests.update({'basics/%s.py' % t for t in 'bytes_gen class_store_class 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()})
Damien George23d7fd52015-03-25 23:33:48 +0000157 skip_tests.add('basics/array_construct2.py') # requires generators
158 skip_tests.add('basics/bool1.py') # seems to randomly fail
Damien George72ddcfd2015-03-03 18:06:45 +0000159 skip_tests.add('basics/boundmeth1.py') # requires support for many args
Damien George23d7fd52015-03-25 23:33:48 +0000160 skip_tests.add('basics/closure_manyvars.py') # requires closures
Damien George15d2fe82014-08-29 19:47:10 +0100161 skip_tests.add('float/string_format.py')
Damien Georgee8b877b2015-02-02 20:01:51 +0000162 skip_tests.add('float/cmath_fun.py') # requires f(*args) support
Damien George15d2fe82014-08-29 19:47:10 +0100163 skip_tests.add('import/gen_context.py')
Damien George15d2fe82014-08-29 19:47:10 +0100164 skip_tests.add('io/file_with.py')
165 skip_tests.add('micropython/heapalloc.py')
Damien George15d2fe82014-08-29 19:47:10 +0100166 skip_tests.add('misc/features.py')
167 skip_tests.add('misc/recursion.py')
168 skip_tests.add('misc/rge_sm.py')
Damien Georgef9051452014-12-11 17:34:55 +0000169 skip_tests.add('misc/print_exception.py') # because native doesn't have proper traceback info
Damien George15d2fe82014-08-29 19:47:10 +0100170
Damien George41f768f2014-05-03 16:43:27 +0100171 for test_file in tests:
Chris Angelico88b11b52014-06-06 07:41:30 +1000172 test_basename = os.path.basename(test_file)
173 test_name = os.path.splitext(test_basename)[0]
Paul Sokolovsky138562c2014-12-13 00:50:54 +0200174 is_native = test_name.startswith("native_") or test_name.startswith("viper_")
Chris Angelico88b11b52014-06-06 07:41:30 +1000175
Paul Sokolovsky138562c2014-12-13 00:50:54 +0200176 if test_file in skip_tests or (skip_native and is_native):
Damien George41f768f2014-05-03 16:43:27 +0100177 print("skip ", test_file)
Paul Sokolovsky43d4a6f2014-05-10 16:52:58 +0300178 skipped_tests.append(test_name)
Damien George41f768f2014-05-03 16:43:27 +0100179 continue
Andrew Scheller1b997d52014-04-16 03:28:40 +0100180
Damien George41f768f2014-05-03 16:43:27 +0100181 # get expected output
182 test_file_expected = test_file + '.exp'
183 if os.path.isfile(test_file_expected):
184 # expected output given by a file, so read that in
185 with open(test_file_expected, 'rb') as f:
186 output_expected = f.read()
stijna4dbc732014-05-11 12:45:02 +0200187 if os.name == 'nt':
188 output_expected = output_expected.replace(b'\n', b'\r\n')
Damien George41f768f2014-05-03 16:43:27 +0100189 else:
stijna4dbc732014-05-11 12:45:02 +0200190 # run CPython to work out expected output
Damien George41f768f2014-05-03 16:43:27 +0100191 try:
192 output_expected = subprocess.check_output([CPYTHON3, '-B', test_file])
Paul Sokolovsky2cf38102014-07-12 16:34:51 +0300193 if args.write_exp:
194 with open(test_file_expected, 'wb') as f:
195 f.write(output_expected)
Damien George41f768f2014-05-03 16:43:27 +0100196 except subprocess.CalledProcessError:
197 output_expected = b'CPYTHON3 CRASH'
Damien39977a52013-12-29 22:34:42 +0000198
Paul Sokolovsky2cf38102014-07-12 16:34:51 +0300199 if args.write_exp:
200 continue
201
Damien George41f768f2014-05-03 16:43:27 +0100202 # run Micro Python
Damien George5a04e2c2014-10-05 22:27:12 +0100203 output_mupy = run_micropython(pyb, args, test_file)
Paul Sokolovsky978f4ca2014-12-20 16:53:06 +0200204 if os.name != 'nt':
205 # It may be the case that we run Windows build under Linux
206 # (using Wine).
207 output_mupy = output_mupy.replace(b'\r\n', b'\n')
Damien George4b34c762014-04-03 23:51:16 +0100208
stijnf5efefd2014-12-18 16:53:46 +0100209 if output_mupy == b'SKIP\n' or output_mupy == b'SKIP\r\n':
Paul Sokolovsky43d4a6f2014-05-10 16:52:58 +0300210 print("skip ", test_file)
211 skipped_tests.append(test_name)
212 continue
213
214 testcase_count += len(output_expected.splitlines())
215
Damien George41f768f2014-05-03 16:43:27 +0100216 filename_expected = test_basename + ".exp"
217 filename_mupy = test_basename + ".out"
218
219 if output_expected == output_mupy:
220 print("pass ", test_file)
221 passed_count += 1
222 rm_f(filename_expected)
223 rm_f(filename_mupy)
224 else:
Damien George41736f82014-06-28 10:29:12 +0100225 with open(filename_expected, "wb") as f:
226 f.write(output_expected)
227 with open(filename_mupy, "wb") as f:
228 f.write(output_mupy)
Damien George41f768f2014-05-03 16:43:27 +0100229 print("FAIL ", test_file)
230 failed_tests.append(test_name)
231
232 test_count += 1
233
234 print("{} tests performed ({} individual testcases)".format(test_count, testcase_count))
235 print("{} tests passed".format(passed_count))
236
Paul Sokolovsky43d4a6f2014-05-10 16:52:58 +0300237 if len(skipped_tests) > 0:
238 print("{} tests skipped: {}".format(len(skipped_tests), ' '.join(skipped_tests)))
Damien George41f768f2014-05-03 16:43:27 +0100239 if len(failed_tests) > 0:
240 print("{} tests failed: {}".format(len(failed_tests), ' '.join(failed_tests)))
241 return False
242
243 # all tests succeeded
244 return True
245
246def main():
247 cmd_parser = argparse.ArgumentParser(description='Run tests for Micro Python.')
248 cmd_parser.add_argument('--pyboard', action='store_true', help='run the tests on the pyboard')
blmorris3b064372014-10-01 13:53:50 -0400249 cmd_parser.add_argument('--device', default='/dev/ttyACM0', help='the serial device of the pyboard')
Damien Georgea053e372014-05-31 18:11:01 +0100250 cmd_parser.add_argument('-d', '--test-dirs', nargs='*', help='input test directories (if no files given)')
Paul Sokolovsky2cf38102014-07-12 16:34:51 +0300251 cmd_parser.add_argument('--write-exp', action='store_true', help='save .exp files to run tests w/o CPython')
Damien George15d2fe82014-08-29 19:47:10 +0100252 cmd_parser.add_argument('--emit', default='bytecode', help='Micro Python emitter to use (bytecode or native)')
Damien George41f768f2014-05-03 16:43:27 +0100253 cmd_parser.add_argument('files', nargs='*', help='input test files')
254 args = cmd_parser.parse_args()
255
256 if args.pyboard:
257 import pyboard
blmorris3b064372014-10-01 13:53:50 -0400258 pyb = pyboard.Pyboard(args.device)
Damien Georgeb636d022014-04-13 13:48:33 +0100259 pyb.enter_raw_repl()
Damien Georgeb636d022014-04-13 13:48:33 +0100260 else:
Damien George41f768f2014-05-03 16:43:27 +0100261 pyb = None
Damien39977a52013-12-29 22:34:42 +0000262
Damien George41f768f2014-05-03 16:43:27 +0100263 if len(args.files) == 0:
stijn8ac3b572014-05-28 14:27:54 +0200264 if args.test_dirs is None:
265 if pyb is None:
266 # run PC tests
Damien George143c3412015-03-13 10:58:34 +0000267 test_dirs = ('basics', 'micropython', 'float', 'import', 'io', 'misc', 'unicode', 'extmod', 'unix', 'cmdline')
stijn8ac3b572014-05-28 14:27:54 +0200268 else:
269 # run pyboard tests
Damien George612045f2014-09-17 22:56:34 +0100270 test_dirs = ('basics', 'micropython', 'float', 'misc', 'extmod', 'pyb', 'pybnative', 'inlineasm')
Damien George41f768f2014-05-03 16:43:27 +0100271 else:
stijn8ac3b572014-05-28 14:27:54 +0200272 # run tests from these directories
273 test_dirs = args.test_dirs
Damien George41f768f2014-05-03 16:43:27 +0100274 tests = sorted(test_file for test_files in (glob('{}/*.py'.format(dir)) for dir in test_dirs) for test_file in test_files)
Markus Siemens19ccc6b2014-01-27 22:53:28 +0100275 else:
Damien George41f768f2014-05-03 16:43:27 +0100276 # tests explicitly given
277 tests = args.files
Markus Siemens19ccc6b2014-01-27 22:53:28 +0100278
Paul Sokolovsky2cf38102014-07-12 16:34:51 +0300279 if not run_tests(pyb, tests, args):
Damien George41f768f2014-05-03 16:43:27 +0100280 sys.exit(1)
Markus Siemens19ccc6b2014-01-27 22:53:28 +0100281
Damien George41f768f2014-05-03 16:43:27 +0100282if __name__ == "__main__":
283 main()