generate.py 7.24 KB
Newer Older
Vicent Marti committed
1
#!/usr/bin/env python
Russell Belfer committed
2 3 4 5 6 7
#
# Copyright (c) Vicent Marti. All rights reserved.
#
# This file is part of clar, distributed under the ISC license.
# For full terms see the included COPYING file.
#
Vicent Marti committed
8 9 10 11 12 13 14 15 16 17 18 19

from __future__ import with_statement
from string import Template
import re, fnmatch, os, codecs, pickle

class Module(object):
    class Template(object):
        def __init__(self, module):
            self.module = module

        def _render_callback(self, cb):
            if not cb:
Russell Belfer committed
20 21
                return '    { NULL, NULL }'
            return '    { "%s", &%s }' % (cb['short_name'], cb['symbol'])
Vicent Marti committed
22 23 24

    class DeclarationTemplate(Template):
        def render(self):
Russell Belfer committed
25
            out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
Vicent Marti committed
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

            if self.module.initialize:
                out += "extern %s;\n" % self.module.initialize['declaration']

            if self.module.cleanup:
                out += "extern %s;\n" % self.module.cleanup['declaration']

            return out

    class CallbacksTemplate(Template):
        def render(self):
            out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
            out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
            out += "\n};\n"
            return out

    class InfoTemplate(Template):
        def render(self):
            return Template(
Russell Belfer committed
45 46 47 48 49 50 51
            r"""
    {
        "${clean_name}",
    ${initialize},
    ${cleanup},
        ${cb_ptr}, ${cb_count}, ${enabled}
    }"""
Vicent Marti committed
52 53 54 55 56 57 58 59 60 61 62
            ).substitute(
                clean_name = self.module.clean_name(),
                initialize = self._render_callback(self.module.initialize),
                cleanup = self._render_callback(self.module.cleanup),
                cb_ptr = "_clar_cb_%s" % self.module.name,
                cb_count = len(self.module.callbacks),
                enabled = int(self.module.enabled)
            )

    def __init__(self, name):
        self.name = name
Russell Belfer committed
63 64

        self.mtime = 0
Vicent Marti committed
65
        self.enabled = True
Russell Belfer committed
66
        self.modified = False
Vicent Marti committed
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107

    def clean_name(self):
        return self.name.replace("_", "::")

    def _skip_comments(self, text):
        SKIP_COMMENTS_REGEX = re.compile(
            r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
            re.DOTALL | re.MULTILINE)

        def _replacer(match):
            s = match.group(0)
            return "" if s.startswith('/') else s

        return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)

    def parse(self, contents):
        TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\(\s*void\s*\))\s*\{"

        contents = self._skip_comments(contents)
        regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)

        self.callbacks = []
        self.initialize = None
        self.cleanup = None

        for (declaration, symbol, short_name) in regex.findall(contents):
            data = {
                "short_name" : short_name,
                "declaration" : declaration,
                "symbol" : symbol
            }

            if short_name == 'initialize':
                self.initialize = data
            elif short_name == 'cleanup':
                self.cleanup = data
            else:
                self.callbacks.append(data)

        return self.callbacks != []

Russell Belfer committed
108 109 110
    def refresh(self, path):
        self.modified = False

Vicent Marti committed
111
        try:
Russell Belfer committed
112 113 114 115 116 117 118 119 120
            st = os.stat(path)

            # Not modified
            if st.st_mtime == self.mtime:
                return True

            self.modified = True
            self.mtime = st.st_mtime

Vicent Marti committed
121
            with open(path) as fp:
Russell Belfer committed
122 123
                raw_content = fp.read()

Vicent Marti committed
124 125 126
        except IOError:
            return False

Russell Belfer committed
127 128
        return self.parse(raw_content)

Vicent Marti committed
129
class TestSuite(object):
Russell Belfer committed
130

Vicent Marti committed
131 132 133
    def __init__(self, path):
        self.path = path

Russell Belfer committed
134 135 136 137 138 139 140 141 142
    def should_generate(self, path):
        if not os.path.isfile(path):
            return True

        if any(module.modified for module in self.modules.values()):
            return True

        return False

Vicent Marti committed
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
    def find_modules(self):
        modules = []
        for root, _, 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)
                module_name = "_".join(module_root + [test_file[:-2]])

                modules.append((full_path, module_name))

        return modules

Russell Belfer committed
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
    def load_cache(self):
        path = os.path.join(self.path, '.clarcache')
        cache = {}

        try:
            fp = open(path, 'rb')
            cache = pickle.load(fp)
            fp.close()
        except (IOError, ValueError):
            pass

        return cache

    def save_cache(self):
        path = os.path.join(self.path, '.clarcache')
        with open(path, 'wb') as cache:
            pickle.dump(self.modules, cache)

Vicent Marti committed
177 178
    def load(self, force = False):
        module_data = self.find_modules()
Russell Belfer committed
179
        self.modules = {} if force else self.load_cache()
Vicent Marti committed
180 181 182 183 184

        for path, name in module_data:
            if name not in self.modules:
                self.modules[name] = Module(name)

Russell Belfer committed
185
            if not self.modules[name].refresh(path):
Vicent Marti committed
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
                del self.modules[name]

    def disable(self, excluded):
        for exclude in excluded:
            for module in self.modules.values():
                name = module.clean_name()
                if name.startswith(exclude):
                    module.enabled = False
                    module.modified = True

    def suite_count(self):
        return len(self.modules)

    def callback_count(self):
        return sum(len(module.callbacks) for module in self.modules.values())

    def write(self):
        output = os.path.join(self.path, 'clar.suite')

Russell Belfer committed
205 206 207
        if not self.should_generate(output):
            return False

Vicent Marti committed
208 209 210 211 212 213 214 215 216 217
        with open(output, 'w') as data:
            for module in self.modules.values():
                t = Module.DeclarationTemplate(module)
                data.write(t.render())

            for module in self.modules.values():
                t = Module.CallbacksTemplate(module)
                data.write(t.render())

            suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
Russell Belfer committed
218 219
                Module.InfoTemplate(module).render() for module in sorted(self.modules.values(), key=lambda module: module.name)
            ) + "\n};\n"
Vicent Marti committed
220 221 222

            data.write(suites)

Russell Belfer committed
223 224
            data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
            data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
Vicent Marti committed
225

Russell Belfer committed
226 227 228
        suite.save_cache()
        return True

Vicent Marti committed
229 230 231 232
if __name__ == '__main__':
    from optparse import OptionParser

    parser = OptionParser()
Russell Belfer committed
233
    parser.add_option('-f', '--force', dest='force', default=False)
Vicent Marti committed
234 235 236 237 238 239
    parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])

    options, args = parser.parse_args()

    for path in args or ['.']:
        suite = TestSuite(path)
Russell Belfer committed
240
        suite.load(options.force)
Vicent Marti committed
241
        suite.disable(options.excluded)
Russell Belfer committed
242 243
        if suite.write():
            print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
Vicent Marti committed
244