Blame tests/generate.py

Packit ae9e2a
#!/usr/bin/env python
Packit ae9e2a
#
Packit ae9e2a
# Copyright (c) Vicent Marti. All rights reserved.
Packit ae9e2a
#
Packit ae9e2a
# This file is part of clar, distributed under the ISC license.
Packit ae9e2a
# For full terms see the included COPYING file.
Packit ae9e2a
#
Packit ae9e2a
Packit ae9e2a
from __future__ import with_statement
Packit ae9e2a
from string import Template
Packit ae9e2a
import re, fnmatch, os, codecs, pickle
Packit ae9e2a
Packit ae9e2a
class Module(object):
Packit ae9e2a
    class Template(object):
Packit ae9e2a
        def __init__(self, module):
Packit ae9e2a
            self.module = module
Packit ae9e2a
Packit ae9e2a
        def _render_callback(self, cb):
Packit ae9e2a
            if not cb:
Packit ae9e2a
                return '    { NULL, NULL }'
Packit ae9e2a
            return '    { "%s", &%s }' % (cb['short_name'], cb['symbol'])
Packit ae9e2a
Packit ae9e2a
    class DeclarationTemplate(Template):
Packit ae9e2a
        def render(self):
Packit ae9e2a
            out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
Packit ae9e2a
Packit ae9e2a
            if self.module.initialize:
Packit ae9e2a
                out += "extern %s;\n" % self.module.initialize['declaration']
Packit ae9e2a
Packit ae9e2a
            if self.module.cleanup:
Packit ae9e2a
                out += "extern %s;\n" % self.module.cleanup['declaration']
Packit ae9e2a
Packit ae9e2a
            return out
Packit ae9e2a
Packit ae9e2a
    class CallbacksTemplate(Template):
Packit ae9e2a
        def render(self):
Packit ae9e2a
            out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
Packit ae9e2a
            out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
Packit ae9e2a
            out += "\n};\n"
Packit ae9e2a
            return out
Packit ae9e2a
Packit ae9e2a
    class InfoTemplate(Template):
Packit ae9e2a
        def render(self):
Packit ae9e2a
            return Template(
Packit ae9e2a
            r"""
Packit ae9e2a
    {
Packit ae9e2a
        "${clean_name}",
Packit ae9e2a
    ${initialize},
Packit ae9e2a
    ${cleanup},
Packit ae9e2a
        ${cb_ptr}, ${cb_count}, ${enabled}
Packit ae9e2a
    }"""
Packit ae9e2a
            ).substitute(
Packit ae9e2a
                clean_name = self.module.clean_name(),
Packit ae9e2a
                initialize = self._render_callback(self.module.initialize),
Packit ae9e2a
                cleanup = self._render_callback(self.module.cleanup),
Packit ae9e2a
                cb_ptr = "_clar_cb_%s" % self.module.name,
Packit ae9e2a
                cb_count = len(self.module.callbacks),
Packit ae9e2a
                enabled = int(self.module.enabled)
Packit ae9e2a
            )
Packit ae9e2a
Packit ae9e2a
    def __init__(self, name):
Packit ae9e2a
        self.name = name
Packit ae9e2a
Packit ae9e2a
        self.mtime = 0
Packit ae9e2a
        self.enabled = True
Packit ae9e2a
        self.modified = False
Packit ae9e2a
Packit ae9e2a
    def clean_name(self):
Packit ae9e2a
        return self.name.replace("_", "::")
Packit ae9e2a
Packit ae9e2a
    def _skip_comments(self, text):
Packit ae9e2a
        SKIP_COMMENTS_REGEX = re.compile(
Packit ae9e2a
            r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
Packit ae9e2a
            re.DOTALL | re.MULTILINE)
Packit ae9e2a
Packit ae9e2a
        def _replacer(match):
Packit ae9e2a
            s = match.group(0)
Packit ae9e2a
            return "" if s.startswith('/') else s
Packit ae9e2a
Packit ae9e2a
        return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
Packit ae9e2a
Packit ae9e2a
    def parse(self, contents):
Packit ae9e2a
        TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
Packit ae9e2a
Packit ae9e2a
        contents = self._skip_comments(contents)
Packit ae9e2a
        regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
Packit ae9e2a
Packit ae9e2a
        self.callbacks = []
Packit ae9e2a
        self.initialize = None
Packit ae9e2a
        self.cleanup = None
Packit ae9e2a
Packit ae9e2a
        for (declaration, symbol, short_name) in regex.findall(contents):
Packit ae9e2a
            data = {
Packit ae9e2a
                "short_name" : short_name,
Packit ae9e2a
                "declaration" : declaration,
Packit ae9e2a
                "symbol" : symbol
Packit ae9e2a
            }
Packit ae9e2a
Packit ae9e2a
            if short_name == 'initialize':
Packit ae9e2a
                self.initialize = data
Packit ae9e2a
            elif short_name == 'cleanup':
Packit ae9e2a
                self.cleanup = data
Packit ae9e2a
            else:
Packit ae9e2a
                self.callbacks.append(data)
Packit ae9e2a
Packit ae9e2a
        return self.callbacks != []
Packit ae9e2a
Packit ae9e2a
    def refresh(self, path):
Packit ae9e2a
        self.modified = False
Packit ae9e2a
Packit ae9e2a
        try:
Packit ae9e2a
            st = os.stat(path)
Packit ae9e2a
Packit ae9e2a
            # Not modified
Packit ae9e2a
            if st.st_mtime == self.mtime:
Packit ae9e2a
                return True
Packit ae9e2a
Packit ae9e2a
            self.modified = True
Packit ae9e2a
            self.mtime = st.st_mtime
Packit ae9e2a
Packit ae9e2a
            with codecs.open(path, encoding='utf-8') as fp:
Packit ae9e2a
                raw_content = fp.read()
Packit ae9e2a
Packit ae9e2a
        except IOError:
Packit ae9e2a
            return False
Packit ae9e2a
Packit ae9e2a
        return self.parse(raw_content)
Packit ae9e2a
Packit ae9e2a
class TestSuite(object):
Packit ae9e2a
Packit ae9e2a
    def __init__(self, path):
Packit ae9e2a
        self.path = path
Packit ae9e2a
Packit ae9e2a
    def should_generate(self, path):
Packit ae9e2a
        if not os.path.isfile(path):
Packit ae9e2a
            return True
Packit ae9e2a
Packit ae9e2a
        if any(module.modified for module in self.modules.values()):
Packit ae9e2a
            return True
Packit ae9e2a
Packit ae9e2a
        return False
Packit ae9e2a
Packit ae9e2a
    def find_modules(self):
Packit ae9e2a
        modules = []
Packit ae9e2a
        for root, _, files in os.walk(self.path):
Packit ae9e2a
            module_root = root[len(self.path):]
Packit ae9e2a
            module_root = [c for c in module_root.split(os.sep) if c]
Packit ae9e2a
Packit ae9e2a
            tests_in_module = fnmatch.filter(files, "*.c")
Packit ae9e2a
Packit ae9e2a
            for test_file in tests_in_module:
Packit ae9e2a
                full_path = os.path.join(root, test_file)
Packit ae9e2a
                module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
Packit ae9e2a
Packit ae9e2a
                modules.append((full_path, module_name))
Packit ae9e2a
Packit ae9e2a
        return modules
Packit ae9e2a
Packit ae9e2a
    def load_cache(self):
Packit ae9e2a
        path = os.path.join(self.path, '.clarcache')
Packit ae9e2a
        cache = {}
Packit ae9e2a
Packit ae9e2a
        try:
Packit ae9e2a
            fp = open(path, 'rb')
Packit ae9e2a
            cache = pickle.load(fp)
Packit ae9e2a
            fp.close()
Packit ae9e2a
        except (IOError, ValueError):
Packit ae9e2a
            pass
Packit ae9e2a
Packit ae9e2a
        return cache
Packit ae9e2a
Packit ae9e2a
    def save_cache(self):
Packit ae9e2a
        path = os.path.join(self.path, '.clarcache')
Packit ae9e2a
        with open(path, 'wb') as cache:
Packit ae9e2a
            pickle.dump(self.modules, cache)
Packit ae9e2a
Packit ae9e2a
    def load(self, force = False):
Packit ae9e2a
        module_data = self.find_modules()
Packit ae9e2a
        self.modules = {} if force else self.load_cache()
Packit ae9e2a
Packit ae9e2a
        for path, name in module_data:
Packit ae9e2a
            if name not in self.modules:
Packit ae9e2a
                self.modules[name] = Module(name)
Packit ae9e2a
Packit ae9e2a
            if not self.modules[name].refresh(path):
Packit ae9e2a
                del self.modules[name]
Packit ae9e2a
Packit ae9e2a
    def disable(self, excluded):
Packit ae9e2a
        for exclude in excluded:
Packit ae9e2a
            for module in self.modules.values():
Packit ae9e2a
                name = module.clean_name()
Packit ae9e2a
                if name.startswith(exclude):
Packit ae9e2a
                    module.enabled = False
Packit ae9e2a
                    module.modified = True
Packit ae9e2a
Packit ae9e2a
    def suite_count(self):
Packit ae9e2a
        return len(self.modules)
Packit ae9e2a
Packit ae9e2a
    def callback_count(self):
Packit ae9e2a
        return sum(len(module.callbacks) for module in self.modules.values())
Packit ae9e2a
Packit ae9e2a
    def write(self):
Packit ae9e2a
        output = os.path.join(self.path, 'clar.suite')
Packit ae9e2a
Packit ae9e2a
        if not self.should_generate(output):
Packit ae9e2a
            return False
Packit ae9e2a
Packit ae9e2a
        with open(output, 'w') as data:
Packit ae9e2a
            for module in self.modules.values():
Packit ae9e2a
                t = Module.DeclarationTemplate(module)
Packit ae9e2a
                data.write(t.render())
Packit ae9e2a
Packit ae9e2a
            for module in self.modules.values():
Packit ae9e2a
                t = Module.CallbacksTemplate(module)
Packit ae9e2a
                data.write(t.render())
Packit ae9e2a
Packit ae9e2a
            suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
Packit ae9e2a
                Module.InfoTemplate(module).render() for module in sorted(self.modules.values(), key=lambda module: module.name)
Packit ae9e2a
            ) + "\n};\n"
Packit ae9e2a
Packit ae9e2a
            data.write(suites)
Packit ae9e2a
Packit ae9e2a
            data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
Packit ae9e2a
            data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
Packit ae9e2a
Packit ae9e2a
        self.save_cache()
Packit ae9e2a
        return True
Packit ae9e2a
Packit ae9e2a
if __name__ == '__main__':
Packit ae9e2a
    from optparse import OptionParser
Packit ae9e2a
Packit ae9e2a
    parser = OptionParser()
Packit ae9e2a
    parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
Packit ae9e2a
    parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
Packit ae9e2a
Packit ae9e2a
    options, args = parser.parse_args()
Packit ae9e2a
Packit ae9e2a
    for path in args or ['.']:
Packit ae9e2a
        suite = TestSuite(path)
Packit ae9e2a
        suite.load(options.force)
Packit ae9e2a
        suite.disable(options.excluded)
Packit ae9e2a
        if suite.write():
Packit ae9e2a
            print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
Packit ae9e2a