#!/usr/bin/env python from __future__ import with_statement from string import Template import re, fnmatch, os VERSION = "0.9.0" TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\(\s*void\s*\))\s*\{" CLAY_HEADER = """ /* * Clay v0.9.0 * * This is an autogenerated file. Do not modify. * To add new unit tests or suites, regenerate the whole * file with `./clay` */ """ def main(): from optparse import OptionParser parser = OptionParser() parser.add_option('-c', '--clay-path', dest='clay_path') parser.add_option('-v', '--report-to', dest='print_mode', default='default') options, args = parser.parse_args() for folder in args or ['.']: builder = ClayTestBuilder(folder, clay_path = options.clay_path, print_mode = options.print_mode) builder.render() class ClayTestBuilder: def __init__(self, path, clay_path = None, print_mode = 'default'): self.declarations = [] self.suite_names = [] self.callback_data = {} self.suite_data = {} self.clay_path = os.path.abspath(clay_path) if clay_path else None self.path = os.path.abspath(path) self.modules = [ "clay_sandbox.c", "clay_fixtures.c", "clay_fs.c" ] self.modules.append("clay_print_%s.c" % print_mode) print("Loading test suites...") for root, dirs, files in os.walk(self.path): module_root = root[len(self.path):] module_root = [c for c in module_root.split(os.sep) if c] tests_in_module = fnmatch.filter(files, "*.c") for test_file in tests_in_module: full_path = os.path.join(root, test_file) test_name = "_".join(module_root + [test_file[:-2]]) with open(full_path) as f: self._process_test_file(test_name, f.read()) if not self.suite_data: raise RuntimeError( 'No tests found under "%s"' % folder_name) def render(self): main_file = os.path.join(self.path, 'clay_main.c') with open(main_file, "w") as out: out.write(self._render_main()) header_file = os.path.join(self.path, 'clay.h') with open(header_file, "w") as out: out.write(self._render_header()) print ('Written Clay suite to "%s"' % self.path) ##################################################### # Internal methods ##################################################### def _render_cb(self, cb): return '{"%s", &%s}' % (cb['short_name'], cb['symbol']) def _render_suite(self, suite): template = Template( r""" { "${clean_name}", ${initialize}, ${cleanup}, ${cb_ptr}, ${cb_count} } """) callbacks = {} for cb in ['initialize', 'cleanup']: callbacks[cb] = (self._render_cb(suite[cb]) if suite[cb] else "{NULL, NULL}") return template.substitute( clean_name = suite['name'].replace("_", "::"), initialize = callbacks['initialize'], cleanup = callbacks['cleanup'], cb_ptr = "_clay_cb_%s" % suite['name'], cb_count = suite['cb_count'] ).strip() def _render_callbacks(self, suite_name, callbacks): template = Template( r""" static const struct clay_func _clay_cb_${suite_name}[] = { ${callbacks} }; """) callbacks = [ self._render_cb(cb) for cb in callbacks if cb['short_name'] not in ('initialize', 'cleanup') ] return template.substitute( suite_name = suite_name, callbacks = ",\n\t".join(callbacks) ).strip() def _render_header(self): template = Template(self._load_file('clay.h')) declarations = "\n".join( "extern %s;" % decl for decl in sorted(self.declarations) ) return template.substitute( extern_declarations = declarations, ) def _render_main(self): template = Template(self._load_file('clay.c')) suite_names = sorted(self.suite_names) suite_data = [ self._render_suite(self.suite_data[s]) for s in suite_names ] callbacks = [ self._render_callbacks(s, self.callback_data[s]) for s in suite_names ] callback_count = sum( len(cbs) for cbs in self.callback_data.values() ) return template.substitute( clay_modules = self._get_modules(), clay_callbacks = "\n".join(callbacks), clay_suites = ",\n\t".join(suite_data), clay_suite_count = len(suite_data), clay_callback_count = callback_count, ) def _load_file(self, filename): if self.clay_path: filename = os.path.join(self.clay_path, filename) with open(filename) as cfile: return cfile.read() else: import zlib, base64, sys content = CLAY_FILES[filename] if sys.version_info >= (3, 0): content = bytearray(content, 'utf_8') content = base64.b64decode(content) content = zlib.decompress(content) return str(content) else: content = base64.b64decode(content) return zlib.decompress(content) def _get_modules(self): return "\n".join(self._load_file(f) for f in self.modules) def _parse_comment(self, comment): comment = comment[2:-2] comment = comment.splitlines() comment = [line.strip() for line in comment] comment = "\n".join(comment) return comment def _process_test_file(self, suite_name, contents): regex_string = TEST_FUNC_REGEX % suite_name regex = re.compile(regex_string, re.MULTILINE) callbacks = [] initialize = cleanup = None for (declaration, symbol, short_name) in regex.findall(contents): data = { "short_name" : short_name, "declaration" : declaration, "symbol" : symbol } if short_name == 'initialize': initialize = data elif short_name == 'cleanup': cleanup = data else: callbacks.append(data) if not callbacks: return tests_in_suite = len(callbacks) suite = { "name" : suite_name, "initialize" : initialize, "cleanup" : cleanup, "cb_count" : tests_in_suite } if initialize: self.declarations.append(initialize['declaration']) if cleanup: self.declarations.append(cleanup['declaration']) self.declarations += [ callback['declaration'] for callback in callbacks ] callbacks.sort(key=lambda x: x['short_name']) self.callback_data[suite_name] = callbacks self.suite_data[suite_name] = suite self.suite_names.append(suite_name) print(" %s (%d tests)" % (suite_name, tests_in_suite)) CLAY_FILES = { "clay.c" : r"""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""", "clay_print_default.c" : r"""eJyFU01P4zAQPSe/YqgU1a5Cuafa3RunistqT4AiEztgKbUje9LVCvHfsccpOGhbTs48z3t+85HSo0DdwdFqCd0g/rWj0wZbbTSy8AGoPLadnQzWEGM/aVQnoLPGI3QvwsEmXRhxUJ6Xr2XBoiT/pO/KgqR7ttpbIZWESiY130DlH8yqhvgiX7yQq2YKv1E4VDKQAvpWlmeq8C8TSvvXfF9JBJRz1iXgXAUJypgfWEbelZ9GH0zyWJArp0brsKVczy5apxzybabDqdMe3dRhSqME2NBBdk9PQmgsh1uhh8mphvoaJHjuqvJNU3lgledwH4JKPsL9NYYjppdFQarXP6nQLI69iOHKWJDKd06PqO2C0ushZwzahPFNhyflvujM6MIXnBZhzktNPfhnytI9sPkiexyufsDdn/2eB/lzOlk6X07n8v5YE52yfM2T9bCPaWeyShLQh74r+XV/ImG3RIiTrXTVBb+JDb9gfbuGBtbb9Tf+aELs//8hmbjZgLF2hM3NcnuTo0vS4ins6kI6DKKG7XZLwkfRDjpcCfc87ij08adkMa4hzaw49nN5HmWYBeE1UXjiKCPZHL6V7yZUhjs=""", "clay_print_tap.c" : r"""eJyFU8Fu2zAMPUdfwXoIYBuxgWK3Btuwnotih/U2wFBtORXmSIEkZyiG/ntJylnkNFlOMh+fyMdnSvggg25hb3UH7SBfm53TJjTa6JDjBwTlQ9Pa0YQVUOxHHdQBaK3xAdoX6aCMCSO3yhfir1jkVLJI0PUc4xKIcb8+z35+/wF75by2Bm4//zJZkSRv63rZIbZK9GD+TYgL+v3LGDr7x1yfgQDlnHVT1aP247UL0iOWXF6Lo+Q4wWWFfI3lmXF7sNIHN7Yh0pgAJR+JKmSnbQCqqjpxCwDt9nKj4A6Wnm3jKtXXqHXrN3O6V+i8Dq930Es9fKjGUwN8qMb4nEqewRkq4XNmrwd1jkn4nDloc2B2KZPwBu14Vq4gS3QP+ZTqlG+d3gVappsv8Pj08FCIRVIzIZwKSFLF3Om6rq/9VWto0jx9GLxG9ALirsWQVUeALFcd/+FDq6XHUaGahKHwyIFvkBkbwP7O0IwMD8qlBf+F2E4sWD6Lc2pn3bRzPr8yAf/W/Pzbnsn8BGVZokg62MGE9/8W8hnlzFrgTq7IYG6wl82gMSXdZrfmECvhBYpXMK1vP8nw+NBHfMjZPZoE+HkDvL/7UwK3oBJFrKlMl0/hm3gHeFWmnA==""", "clay_sandbox.c" : r"""eJyNVV1P20AQfLZ/xRIkYpNATItaVSkPlaBVVEoiEgQSRJaxz+SEfY7uLmkD4r931+fEHwRahBST3Zudmb0xSgeahxDOAgl+mAQrfx7o2e2x9+XTtG/bypS50DZX/jJIeOTrdJ43OWEmlDZH9+kL1362rfHk28SfgNJ42uIxOAThULkLe0q7sHMCnmtblmR6IQV4676dsT8Ynw4u8cCh0n6aRcxt9hXPThCGTKkC9dof/nThhGD79kuNc8xFlW/O9H4Rx0x2QfEn5mtImHgw1Hd5LCIWg389uPj4wbYKHKOy6F4G0g+zhdBwAsf9Ro/BZ2KJRkl1O8UeNMRqTX6NUFerC/SUf5yZz6vx2eXocvh9cH7WssF6QYlgFZM46Y0zCQ5HHK8PHL6W4/vQ6XA3h2/MxuYHpvHB2RDhUzTGMibjl2QqndJcLBhNySuv10utZgTKlCKcr5y1d1jqrp0j6MqSLOvFxl/b6u3DIAY9Y9TNZSZShrZFGVOijX4GKwjESs+4eOiClivQGSwUgx7Oh/2e/QapFtVbBa8mLVOsMasQQ1K7LFHMQP9gesLS+YhAndPr4eWpa451wcA1Lt8uExGPja7JjCtQK6VZuhGU8EeGAmpaSHy4kDIXziULdYbFd8Qdvqns8D1Z6z8PjqoBWGY8gjzSC6ECEd1nfxz6Lo8pEajk3ZtSgNp3XrtUjVcDI1FNRDhDFcgSaVYMiZUv0wpYM4XoJ08iv6BglG54VG4vFXwd8CRPTivHI2tu8p8WpW0T2fVLox7wkoOJdxZXabkYoOqbh9yyLQTDaeg3PtRFNNU/A65eZDLFpT2xnC4tejQcD24Ak/o7kBGoJFAzpvIlV6JsvYoyiShD3NwHL/Zxl+/DsholaPfam6htFtHAIGUHcDSlNy72m0H1eqdTgtE9Wl+7sgs6xLRbLmebszgGm7ZYRozSR4zJ3Ff/3E7jH4NZj0Gga1c97n32vK0HKgHHUzS4xhM9vbg6P391qDCwTFX9AucI/x8h2Nvbdue33z9CMbmqEt3qRY3eX120XBI=""", "clay_fixtures.c" : r"""eJyFUV1LwzAUfW5+xZU9rLUVJ4ggZQ9DFAUfRCZMRglZmrBAl5Qkk03xv9v0a82U+Zabc+45595rLLGCAlXSWKBrouEccbGzW81wSew6HCIrYljicTuqJBsWoS8UmFbPobXA8npye5OlFSI+GbaglbK4YDJFKOjeMAVjdfUInUPkyFZLWu7DWiKBxtgpKN78RZETEByactlLXcBVBmdTGF+OIxQEPhrHGdRQ1zzMv5xUYN84ROLY8b1MEPeTJEdsV3tRq0wdt06tWcWVzXpS9I3QSPCccbh7nr3jh6fF/O31Hr/M5o9ouGpa4NYlPHmBVt074i/lBLy+OsWHEjkcXLAhMl+p3Wk3bjBV1VIG6TxOApgWZN8s4k8bWjAit+W/NnoTejMddI+GqW1GTOaCox8pOffr""", "clay_fs.c" : r"""eJylVdtu20YQfSa/YkAD8TKWY8dJX6L0wXDEVqgsBhINN7UFhiGX1qIkl9hd+dLG/57ZCynJUWEkfZE0s7NnZufMGe2xsqAlpJfj6ZsT399DgzUUojhKo8npb3Mg+ud8PBlNE/hq/NP4LJ5G49n5aTKOp71zNJvFs4vx06DzPz6MZ6HvS5UplkO+zAS89EtWUd7KtM3UkuS8kcqdGE/o/+t71tYm/ArTi8lk6HuS/UNTBRVtbtRyAGzo+x4rgaQ2zMaFvucJqlaicdd8z15AHKkE/rbxIQI6+DqrKp4TF3YAJ2GH/AxwTeu8fTBRA0jtl0Xp0K+sucAsx9suzPPauX2v5AIIMxYweO9AhnBwwELAbvTFXLGFrmf/aF+X4/Uu2L++3scEjwjmitRnQ/+x7/0tZ0XXecIaBTUv6AC22i/5SuRPnQWVynAy/z3CSYg/zpPZxVkCJQLp4m2YvYqVbJHrEHU7bJgG+y7IZNBQf1HBz2nNxQN5oeEHoDnnJdlOHYa2aa18dRetmlxziI8ZOl8bCV5ruk3u3ptw9OlUnaeMquxGorOfd/OcKs2kpEKlBFuMibHUuKUCm8gbW1aoOTge4HFwyZqC30l4EgdlhmYR+J4tVVBK1q0wpnv0U4JkKmqygxTDQEdfFKcfRpNRMsKx6zgzM7oLL+c4oz9A80aSs/jjp40U6bpmA46t0vgVzZpVS7TLApg3lOwe55A6ivMqe3AKCV4GoQXZo5WkXbk4kr5c0qpK+UoRW5SrMBM3t1cLg60HV19YSS0nVuA+wE/dY/zSg8XF32StX/S9h2OrobIVeLskUhVUCM2eF8wfpKI1oM3FO/hsb3+GHDeCo/DVdRNozjx6zxQ5fB06lXXwehIsPr2n+S0xtR4vBqboLvguYwqD9YUBvLD1D/DesFfr5ejPcTJPTpOLObHn/4PLnkprmpJ+WQy3pbpeqNZOcenovvVCxm1ZIK0bEl4Hrpdpf2pbYs2rjchDs+f6nfVfAXYRuu6hGRx9Yc1R3gZD5zVBweGsd5wsNjVuXG+0y81O6KRuDt4u+r8Ro/B6JRWOo5RG5OuxM6QZYUeGfVAcdM9B6b3lRlpqr8ya4gu/363wZ0W9oekNjt4udvVA1N/1oNxuQvfiHc342TdbTYNa0u2XPiN9I/NV464Qs/e1a8PxiLJvClb63wD3Q6FA""", "clay.h" : r"""eJy9Vctu2zAQPEdfwVo9WIIQp9c0DWAENmLACIrWQdsTQZOriKhMqiTVqCj67yUp+aGH46YHn0wtdzizu0M65KlgkCKM75bTb3g1+7zC9xgHoQ1yAb14EHJB85IButGG5Xx9md0GwU/JGaI5+YUx0RqUGQcXXBhEpWDccCmS4MKutY1kRKE45TkkdUpuWTq7oJRUnRgDTRUvmrMcUGeyzBkma6lM9H6nAWswmOZARFmMfWwcN59R/R1HCaoXsiA/SrDgLTbVLag7NuSp64/vwnzxdfX4aYY/Tlf3waE6B+WVKRWM22X6GBZk02JpwpoItpbVayBbdS9AQrA9T4NgEscBitHUz8O2DW0IVVKjZ24yBFWRc8oN8r1GG9CaPIHNn+wmb1k3pTa4sBPFYwtQCXJTiNqD9jsRuv2ArhLrlvliOcPYrZaLB78azUtBvQBK8hylxM6eXaMRCvdnJuhd1CN2maeJb47yzqoCqAGG0pYAI72GEwpqktP0b47XbfmV7asj5hoJaZBRJQzxbmd1lwH9/h9zog53pkFdRX3mM09qSMIZBnUVnbhUQv7jdWokDd2wh8flcvgqdECHPe+BmtJ3iLab6/TjpjtVx95ue4a+BXui9l7pwl6sxad0EYOVzKWizkT2NPseTp6JElw8ddV7AQM+OeaOFdiXtr4Ml6Phx6Jhes2pX2oIYqVyP8aRQAW0dK66Hg14zuvYgMkks5uWRBGXq319b39DZUAJfLjzJ9j+GfwFGCyeSg==""" } if __name__ == '__main__': main()