|
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 |
|