summaryrefslogtreecommitdiffstats
path: root/tests/clar/generate.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:47:08 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:47:08 +0000
commit29b5ab554790bb57337a3b6ab9dcd963cf69d22e (patch)
treebe1456d2bc6c1fb078695fad7bc8f6b212062d3c /tests/clar/generate.py
parentInitial commit. (diff)
downloadlibgit2-c1e1eb71b50aea7924f9091d188fa736bc813c6f.tar.xz
libgit2-c1e1eb71b50aea7924f9091d188fa736bc813c6f.zip
Adding upstream version 1.7.2+ds.upstream/1.7.2+ds
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/clar/generate.py')
-rw-r--r--tests/clar/generate.py316
1 files changed, 316 insertions, 0 deletions
diff --git a/tests/clar/generate.py b/tests/clar/generate.py
new file mode 100644
index 0000000..d2cdb68
--- /dev/null
+++ b/tests/clar/generate.py
@@ -0,0 +1,316 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+from __future__ import with_statement
+from string import Template
+import re, fnmatch, os, sys, codecs, pickle, io
+
+class Module(object):
+ class Template(object):
+ def __init__(self, module):
+ self.module = module
+
+ def _render_callback(self, cb):
+ if not cb:
+ return ' { NULL, NULL }'
+ return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
+
+ class DeclarationTemplate(Template):
+ def render(self):
+ out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
+
+ for initializer in self.module.initializers:
+ out += "extern %s;\n" % initializer['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):
+ templates = []
+
+ initializers = self.module.initializers
+ if len(initializers) == 0:
+ initializers = [ None ]
+
+ for initializer in initializers:
+ name = self.module.clean_name()
+ if initializer and initializer['short_name'].startswith('initialize_'):
+ variant = initializer['short_name'][len('initialize_'):]
+ name += " (%s)" % variant.replace('_', ' ')
+
+ template = Template(
+ r"""
+ {
+ "${clean_name}",
+ ${initialize},
+ ${cleanup},
+ ${cb_ptr}, ${cb_count}, ${enabled}
+ }"""
+ ).substitute(
+ clean_name = name,
+ initialize = self._render_callback(initializer),
+ 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)
+ )
+ templates.append(template)
+
+ return ','.join(templates)
+
+ def __init__(self, name):
+ self.name = name
+
+ self.mtime = 0
+ self.enabled = True
+ self.modified = False
+
+ 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*\(\s*void\s*\))\s*\{"
+
+ contents = self._skip_comments(contents)
+ regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
+
+ self.callbacks = []
+ self.initializers = []
+ self.cleanup = None
+
+ for (declaration, symbol, short_name) in regex.findall(contents):
+ data = {
+ "short_name" : short_name,
+ "declaration" : declaration,
+ "symbol" : symbol
+ }
+
+ if short_name.startswith('initialize'):
+ self.initializers.append(data)
+ elif short_name == 'cleanup':
+ self.cleanup = data
+ else:
+ self.callbacks.append(data)
+
+ return self.callbacks != []
+
+ def refresh(self, path):
+ self.modified = False
+
+ try:
+ st = os.stat(path)
+
+ # Not modified
+ if st.st_mtime == self.mtime:
+ return True
+
+ self.modified = True
+ self.mtime = st.st_mtime
+
+ with codecs.open(path, encoding='utf-8') as fp:
+ raw_content = fp.read()
+
+ except IOError:
+ return False
+
+ return self.parse(raw_content)
+
+class TestSuite(object):
+
+ def __init__(self, path, output):
+ self.path = path
+ self.output = output
+
+ def maybe_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
+
+ 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]]).replace("-", "_")
+
+ modules.append((full_path, module_name))
+
+ return modules
+
+ def load_cache(self):
+ path = os.path.join(self.output, '.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.output, '.clarcache')
+ with open(path, 'wb') as cache:
+ pickle.dump(self.modules, cache)
+
+ def load(self, force = False):
+ module_data = self.find_modules()
+ self.modules = {} if force else self.load_cache()
+
+ for path, name in module_data:
+ if name not in self.modules:
+ self.modules[name] = Module(name)
+
+ if not self.modules[name].refresh(path):
+ 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 sum(max(1, len(m.initializers)) for m in self.modules.values())
+
+ def callback_count(self):
+ return sum(len(module.callbacks) for module in self.modules.values())
+
+ def write(self):
+ wrote_suite = self.write_suite()
+ wrote_header = self.write_header()
+
+ if wrote_suite or wrote_header:
+ self.save_cache()
+ return True
+
+ return False
+
+ def write_output(self, fn, data):
+ if not self.maybe_generate(fn):
+ return False
+
+ current = None
+
+ try:
+ with open(fn, 'r') as input:
+ current = input.read()
+ except OSError:
+ pass
+ except IOError:
+ pass
+
+ if current == data:
+ return False
+
+ with open(fn, 'w') as output:
+ output.write(data)
+
+ return True
+
+ def write_suite(self):
+ suite_fn = os.path.join(self.output, 'clar.suite')
+
+ with io.StringIO() as suite_file:
+ modules = sorted(self.modules.values(), key=lambda module: module.name)
+
+ for module in modules:
+ t = Module.DeclarationTemplate(module)
+ suite_file.write(t.render())
+
+ for module in modules:
+ t = Module.CallbacksTemplate(module)
+ suite_file.write(t.render())
+
+ suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
+ Module.InfoTemplate(module).render() for module in modules
+ ) + "\n};\n"
+
+ suite_file.write(suites)
+
+ suite_file.write(u"static const size_t _clar_suite_count = %d;\n" % self.suite_count())
+ suite_file.write(u"static const size_t _clar_callback_count = %d;\n" % self.callback_count())
+
+ return self.write_output(suite_fn, suite_file.getvalue())
+
+ return False
+
+ def write_header(self):
+ header_fn = os.path.join(self.output, 'clar_suite.h')
+
+ with io.StringIO() as header_file:
+ header_file.write(u"#ifndef _____clar_suite_h_____\n")
+ header_file.write(u"#define _____clar_suite_h_____\n")
+
+ modules = sorted(self.modules.values(), key=lambda module: module.name)
+
+ for module in modules:
+ t = Module.DeclarationTemplate(module)
+ header_file.write(t.render())
+
+ header_file.write(u"#endif\n")
+
+ return self.write_output(header_fn, header_file.getvalue())
+
+ return False
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser()
+ parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
+ parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
+ parser.add_option('-o', '--output', dest='output')
+
+ options, args = parser.parse_args()
+ if len(args) > 1:
+ print("More than one path given")
+ sys.exit(1)
+
+ path = args.pop() if args else '.'
+ output = options.output or path
+ suite = TestSuite(path, output)
+ suite.load(options.force)
+ suite.disable(options.excluded)
+ if suite.write():
+ print("Written `clar.suite`, `clar_suite.h` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
+