#!/usr/bin/env python3 import os, sys from glob import glob from re import sub import argparse def escape(s): s = s.decode() lookup = { "\0": "\\0", "\t": "\\t", "\n": '\\n"\n"', "\r": "\\r", "\\": "\\\\", '"': '\\"', } return '""\n"{}"'.format("".join([lookup[x] if x in lookup else x for x in s])) def chew_filename(t): return {"func": "test_{}_fn".format(sub(r"/|\.|-", "_", t)), "desc": t} def script_to_map(test_file): r = {"name": chew_filename(test_file)["func"]} with open(test_file, "rb") as f: r["script"] = escape(f.read()) with open(test_file + ".exp", "rb") as f: r["output"] = escape(f.read()) return r def load_profile(profile_file, test_dirs, exclude_tests): profile_globals = {"test_dirs": test_dirs, "exclude_tests": exclude_tests} exec(profile_file.read(), profile_globals) return profile_globals["test_dirs"], profile_globals["exclude_tests"] test_function = ( "void {name}(void* data) {{\n" " static const char pystr[] = {script};\n" " static const char exp[] = {output};\n" ' printf("\\n");\n' " upytest_set_expected_output(exp, sizeof(exp) - 1);\n" " upytest_execute_test(pystr);\n" ' printf("result: ");\n' "}}" ) testcase_struct = "struct testcase_t {name}_tests[] = {{\n{body}\n END_OF_TESTCASES\n}};" testcase_member = ' {{ "{desc}", {func}, TT_ENABLED_, 0, 0 }},' testgroup_struct = "struct testgroup_t groups[] = {{\n{body}\n END_OF_GROUPS\n}};" testgroup_member = ' {{ "{name}", {name}_tests }},' ## XXX: may be we could have `--without ` argument... test_dirs = set( ( "basics", "extmod", "float", "micropython", "misc", ) ) exclude_tests = set( ( # pattern matching in .exp "basics/bytes_compare3.py", "extmod/ticks_diff.py", "extmod/time_ms_us.py", # unicode char issue "extmod/json_loads.py", # doesn't output to python stdout "extmod/re_debug.py", "extmod/vfs_basic.py", "extmod/vfs_fat_ramdisk.py", "extmod/vfs_fat_fileio.py", "extmod/vfs_fat_fsusermount.py", "extmod/vfs_fat_oldproto.py", # rounding issues "float/float_divmod.py", # requires double precision floating point to work "float/float2int_doubleprec_intbig.py", "float/float_format_ints_doubleprec.py", "float/float_parse_doubleprec.py", # different filename in output "micropython/emg_exc.py", "micropython/heapalloc_traceback.py", # don't have emergency exception buffer "micropython/heapalloc_exc_compressed_emg_exc.py", # pattern matching in .exp "micropython/meminfo.py", # needs sys stdfiles "misc/print_exception.py", # settrace .exp files are too large "misc/sys_settrace_loop.py", "misc/sys_settrace_generator.py", "misc/sys_settrace_features.py", # don't have f-string "basics/string_fstring.py", "basics/string_fstring_debug.py", ) ) output = [] tests = [] argparser = argparse.ArgumentParser( description="Convert native MicroPython tests to tinytest/upytesthelper C code" ) argparser.add_argument("--stdin", action="store_true", help="read list of tests from stdin") argparser.add_argument("--exclude", action="append", help="exclude test by name") argparser.add_argument( "--profile", type=argparse.FileType("rt", encoding="utf-8"), help="optional profile file providing test directories and exclusion list", ) args = argparser.parse_args() if not args.stdin: if args.profile: test_dirs, exclude_tests = load_profile(args.profile, test_dirs, exclude_tests) if args.exclude: exclude_tests = exclude_tests.union(args.exclude) for group in test_dirs: tests += [test for test in glob("{}/*.py".format(group)) if test not in exclude_tests] else: for l in sys.stdin: tests.append(l.rstrip()) output.extend([test_function.format(**script_to_map(test)) for test in tests]) testcase_members = [testcase_member.format(**chew_filename(test)) for test in tests] output.append(testcase_struct.format(name="", body="\n".join(testcase_members))) testgroup_members = [testgroup_member.format(name=group) for group in [""]] output.append(testgroup_struct.format(body="\n".join(testgroup_members))) ## XXX: may be we could have `--output ` argument... # Don't depend on what system locale is set, use utf8 encoding. sys.stdout.buffer.write("\n\n".join(output).encode("utf8"))