#!/usr/bin/env python from __future__ import with_statement from string import Template import re, fnmatch, os VERSION = "0.10.0" TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\(\s*void\s*\))\s*\{" EVENT_CB_REGEX = re.compile( r"^(void\s+clar_on_(\w+)\(\s*void\s*\))\s*\{", re.MULTILINE) SKIP_COMMENTS_REGEX = re.compile( r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', re.DOTALL | re.MULTILINE) CLAR_HEADER = """ /* * Clar v%s * * This is an autogenerated file. Do not modify. * To add new unit tests or suites, regenerate the whole * file with `./clar` */ """ % VERSION CLAR_EVENTS = [ 'init', 'shutdown', 'test', 'suite' ] def main(): from optparse import OptionParser parser = OptionParser() parser.add_option('-c', '--clar-path', dest='clar_path') parser.add_option('-v', '--report-to', dest='print_mode', default='default') options, args = parser.parse_args() for folder in args or ['.']: builder = ClarTestBuilder(folder, clar_path = options.clar_path, print_mode = options.print_mode) builder.render() class ClarTestBuilder: def __init__(self, path, clar_path = None, print_mode = 'default'): self.declarations = [] self.suite_names = [] self.callback_data = {} self.suite_data = {} self.event_callbacks = [] self.clar_path = os.path.abspath(clar_path) if clar_path else None self.path = os.path.abspath(path) self.modules = [ "clar_sandbox.c", "clar_fixtures.c", "clar_fs.c" ] self.modules.append("clar_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"' % path) def render(self): main_file = os.path.join(self.path, 'clar_main.c') with open(main_file, "w") as out: out.write(self._render_main()) header_file = os.path.join(self.path, 'clar.h') with open(header_file, "w") as out: out.write(self._render_header()) print ('Written Clar 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, index): template = Template( r""" { ${suite_index}, "${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( suite_index = index, clean_name = suite['name'].replace("_", "::"), initialize = callbacks['initialize'], cleanup = callbacks['cleanup'], cb_ptr = "_clar_cb_%s" % suite['name'], cb_count = suite['cb_count'] ).strip() def _render_callbacks(self, suite_name, callbacks): template = Template( r""" static const struct clar_func _clar_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_event_overrides(self): overrides = [] for event in CLAR_EVENTS: if event in self.event_callbacks: continue overrides.append( "#define clar_on_%s() /* nop */" % event ) return '\n'.join(overrides) def _render_header(self): template = Template(self._load_file('clar.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('clar.c')) suite_names = sorted(self.suite_names) suite_data = [ self._render_suite(self.suite_data[s], i) for i, s in enumerate(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( clar_modules = self._get_modules(), clar_callbacks = "\n".join(callbacks), clar_suites = ",\n\t".join(suite_data), clar_suite_count = len(suite_data), clar_callback_count = callback_count, clar_event_overrides = self._render_event_overrides(), ) def _load_file(self, filename): if self.clar_path: filename = os.path.join(self.clar_path, filename) with open(filename) as cfile: return cfile.read() else: import zlib, base64, sys content = CLAR_FILES[filename] if sys.version_info >= (3, 0): content = bytearray(content, 'utf_8') content = base64.b64decode(content) content = zlib.decompress(content) return str(content, 'utf-8') 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 _skip_comments(self, text): def _replacer(match): s = match.group(0) return "" if s.startswith('/') else s return re.sub(SKIP_COMMENTS_REGEX, _replacer, text) def _process_test_file(self, suite_name, contents): contents = self._skip_comments(contents) self._process_events(contents) self._process_declarations(suite_name, contents) def _process_events(self, contents): for (decl, event) in EVENT_CB_REGEX.findall(contents): if event not in CLAR_EVENTS: continue self.declarations.append(decl) self.event_callbacks.append(event) def _process_declarations(self, suite_name, contents): callbacks = [] initialize = cleanup = None regex_string = TEST_FUNC_REGEX % suite_name regex = re.compile(regex_string, re.MULTILINE) 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)) CLAR_FILES = { "clar.c" : r"""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""", "clar_print_default.c" : r"""eJyFU8Fu2zAMPdtfwQUwIgVuenew9tZTsMuwU1sYqiW3AhzJkOhswNB/n0Q5rRws6Ukmxff4RD6XHgXqDo5WS+gG4drRaYOtNhpZ+ABUHtvOTgZriLGfNKpTorPGI3RvwsEmXRhxUJ6Xf8uCRUr+Cd+VBVH3bLW3QioJlUxsvoHKP5lVDbEjX3TIWTOGnygcKhlAIftelhde4d8mlPa3+folMaGcsy4lLr0gpTLkRy4D78pPoU8maSxIlVOjddhSrWdXpVMN6TbT4TRpj27qMJVRAWzoILmnlhAGy+FB6GFyqqG5Bgqeq6p801QeWOU5PIagks/weIPhiOVlURDrzR09NIvjLGK4Mhak8p3TI2q7gPR6yBGDNmF90+FFuTOeObvQBScjzHVpqAf/SlW6BzZfZM3h23f48Wu/54H+Ek9Wzpfbue4fa6JSlts8SQ9+TJ7JXpISfZi7kuf+iYDdMkOYzNJVF/QmNNzD+mENDay36y/00YbY///D3ObaSPWHVN1uwFg7wuZ2aWeqOLN4kn2tv3gJhl70D9uqYbvdUrOjaAcdroR7HXcU+vjnshjXkBZbHPt5Bh5lWBjla4LwhFFGsjl8L/8BsUiTTQ==""", "clar_print_tap.c" : r"""eJyNVE1vnDAQPcOvmGWFBAiQot6yaqr2HFU9tLdKyAGzscLayDbbVlX+e8cDJPbuJtsTzPObmTcfdmwss6KFoxIdtAPTzaiFtI2Qwmb4A5Yb27RqkrYEZ5tJWL4CrZLGQvvINBTzgWQHbvL4bxxlLmT+6r5bIY94gq08ktBnyffP3+DItRFKws2HnzLJd/FzHL8h2TxOtlO/5HXZDuBaKz0D/yM3xDznXRxHoodsEwSMXmrYwsiM4R2wYYC0I2GZybGY0hOJhUV8MDxw7JkY0BGd2EHJ/am3l7BEvyiMtoa5qeu0O8/2dhspLPVQTod1xMbqqbUzjQhQ0MdrHbJdL9a8AFVVzSPzMJy5YXsOt5Ca1yKqu7mWg9mHdMNx/ML+uaVenEWj0QCcRSM8pLri4QLV4SGzx6ZfYjo8ZA5CrszOZzq8wXY8cJ2v67Ecddy0WozWbfTmI3z9cX/vLwuARzgV4B3lYafrur52OZSk1fEvLO2Du4bzhZhNUj0D8/rRhNdUqXFLWC3CUPiyop8gkcqCekqwGQl+3Jkf8MXEdHFE8kmc5qPSy86Z7EoFNNbs8pvj33IhO/470L2FoihQNWTbtMudQY313X3X92WwB5QcyMC9Ld0QKOeRNYPAI6b3445MjIQOzi5hWfF+UWbwxZrwRUq+YCMBfzdAO348JVAKFyKfY3LZZYv5HP8D5Mbj9w==""", "clar_sandbox.c" : r"""eJydVWtP4kAU/dz+iism0gpKfWQ3G9YPm+gasioEMJgomdR2KhPplMwM7KLxv++dTqEP0DVrTKjcO+eec+6cKpWvWADBxBdAgqkvyMxXk/tT79uXcdu2pSkzrmwmycKfspCoeJY2OUHCpTJH9/UXrv1qW4PhjyEZglR42mIROBrC0eUm7Enlws4ZeK5tWYKqueDgrfp2BqQzOO/08cChVCROQupW+7Jnxw8CKmWGOiLdXy6cadi2/VbiHDFe5JsyfZxHERVNkOyFEgVTyp8M9V0W8ZBGQEadm5Nj28pwjMqse4EGBcmcKziD03alx+BTvkCjhLwfYw8aYtWG1z3UVWuCfko/Lszn7eCi3+t3f3auLmo2WG8oEaxsEtN6o0SAwxDHawOD7/n4NjQazE3hK7Ox+YkqfHDWRNgYjbGMyfilNlWfUozPqZ6SVjbXq1vNCJQpeDBbOivvsNRcOaehC0uyrDcbf22rtQ+dCNSE6m4mEh5TtC1MqOR19NNfgs+XasL4UxOUWIJKYC4ptHA+7Lfsd0jVdL2W8arSMsUSswIxJLVLp5Ia6EuqhjSe9TSocz7q9s9dc6wJBq5y+XYpD1lkdA0nTIJcSkXjtaApe6YooKRFiw/mQqTCmaCBSrD4gbjDd5UdfiRr9efBUTEAi4SFkEZ6zqXPw8fkj6O/S2OqCRTy7o11gOoPXj1XjVcDI1FMRDBBFcgSaRYMiSQRcQGsmkL0k01DklEwStc8CrdXF4jy2TRNTi3F09bcpT81nbZ1ZFcvjXLAcw4m3klUpOVigIpvHu2WbSEYTkO/8aEsoqr+FXD1PBExLu2FpnT1onvdQecOMKm/fRGCnPpyQmW65EKUrY0oaxF5iKv7YNk+HtJ9WFalBPVWfR219SIqGFrZARyN9RsX+82gcr3RyMH0PVpdu7wLGpppM1/ONmdxDDZllgF6xjgNHUKuOzeXo5NjQtyMXPyMkZmVjqLMm9urq4296P74Wd+34la9r5638S9EH8BkF0enKytPJfKf92ML7v8QWb1i8NQn5a5XmOe6HKEU4fMhhr29banbngCNYpJdJLrVixK9v7GvgW8=""", "clar_fixtures.c" : r"""eJyFUV1LwzAUfW5+xZU9rLUVJ4ggZQ9DFAUfZEwQSglZmrBAl5Qkk6n43236tWbKfMvNOfecc+81llhBgSppLNAN0XCOuNjbnWa4InYTjpE1MSzxuD1Vki2L0BcKTKfn0EYgu57d3uRpjYhPhi1opSwumUwRCvo3zMFYXT9C5xA5stWSVh9hI5FAa+wUFG//osgJCA5tmQ1SF3CVw9kcppfTCAWBj8ZxDg3UN4/zZ7MaHBrHSBw7vpcJ4mGS5Ijtai9qnannNqk1q7myXU+KvhGaCF4wDnfPiyV+eHpbvS7v8cti9YjGq6Yl7lzCkxfo1L0j/lJOwOtrUrwrUcDBBRsii7Xan3bjBlNVL2WUzuMkgGlJdLuIP21oyYjcVf/a6G3ozXTQPRqmsZkwWQiOfgAVGffP""", "clar_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/jjp40U6bpmA46t0vgVzZpVS7TLApg3lOwe55A6ivMqE04hwcsgtCB7tJK0KxdH0pdLWlUpXylii3IVZuLm9mphsPXg6gsrqeXECtwH+Kl7jF96sLj4m6z1i773cGw1VLYCb5dEqoIKodnzgvmDVLQGtLl4B5/t7c+Q40ZwFL66bgLNmUfvmSKHr0Onsg5eT4LFp/c0vyWm1uPFwBTdBd9lTGGwvjCAF7b+Ad4b9mq9HP05TubJaXIxJ/b8f3DZU2lNU9Ivi+G2VNcL1dopLh3dt17IuC0LpHVDwuvA9TLtT21LrHm1EXlo9ly/s/4rwC5C1z00g6MvrDnK22DovCYoOJz1jpPFpsaN6412udkJndTNwdtF/zdiFF6vpMJxlNKIfD12hjQj7MiwD4qD7jkovbfcSEvtlVlTfOH3uxX+rKg3NL3B0dvFrh6I+rselNtN6F68oxk/+2araVBLuv3SZ6RvZL5q3BVi9r52bTgeUfZNwUr/G9kaoSs=""", "clar.h" : r"""eJy9VU1P4zAQPZNfYZo9JJUFlCMLSAi1AqlCKyjavVmO4xBrEyfYztLVav874yRtmq922QOX1pnxzHvOe+O4IpIhjxAht8ubR7KaP63IHSGOC0EheS/uuEKypAg5utQmTERwEl87zq9MhIglVBFCtebKeM6RkAaxTIbCiExi5wjWGiIxVWgaiYTjaksCKJ0sVypTnVjINVMir3vZQh1nRRISGmTK+F8HOBD+WtCEaG+3Dx5/gKa9ADQe6ys8WzBUNNRl04ZobghLOJVF7pUxb1o/+tXz1MeoWmQ5fS14Q4FEulVq27oisvKVIi3uf6yeH+fk283qztnlYEvF2hSKe20VyhiRNG2h1GFNZRhk64+UbNjtKXE5WCJynNPp1EFTdFO+UlAVpZSpTKM3YWLE13kimDCotAJKudb0hcP+060xATUttCE5iEI8KFAYWZP4bR+WGR9dX6EzDGZe3C/nhNjV8v6hXE0WhWQlAUaTBEUUrBleoAlym54YzfwesN15GPhyFHe+zjkzPERRi4DJSg4HGNROPAh/PH5uwFfwXi2w0EhmBhlV8CHcjVa3MWc//0MnZus+Sagzv4/8yUoNUfgEoc78A0Mls38cp5rS0IQ9PC+Xw6PQKdp9572i+ujbirabq+3jpjt0jsZuDULfgj1SjVe6ZXvPUm7pVgyeZJEpZk0E3eA+PH2jSgr50mVfEhjwyZg7Vhxu2moYTibDl0WN9JGu36sSFBbK/hkLwtecFdZVF5MBz61+53A42nFe93SdL7OeYX3eprTNQdLHHqTxluGW4OTJlLxSoVNqWFwOg57BL8yRXZ6PXJjbT/cMi2Fg4UESgMUgsCsaELEfJPCCGQ7GQI6PIe1j+zcMFDRAwX6g3MtnOD/fmSQPIj66ukIehHcksiqm3MRZCPpZWtRKVYn05Q9fG64k2c38dTbf63eIKlZw""" } if __name__ == '__main__': main()