mirror of
https://github.com/google/pebble.git
synced 2025-05-19 01:44:53 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
361
tools/clar/_clar.py
Executable file
361
tools/clar/_clar.py
Executable file
|
@ -0,0 +1,361 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from __future__ import with_statement
|
||||
from string import Template
|
||||
import re, fnmatch, os
|
||||
|
||||
VERSION = "0.10.0"
|
||||
|
||||
TEST_FUNC_REGEX = r"^(void\s+(%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)
|
||||
|
||||
CATEGORY_REGEX = re.compile(r"CL_IN_CATEGORY\(\s*\"([^\"]+)\"\s*\)")
|
||||
|
||||
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')
|
||||
parser.add_option('-f', '--file', dest='file')
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
folder = args[0] or '.'
|
||||
print 'folder: %s' % folder
|
||||
builder = ClarTestBuilder(folder,
|
||||
clar_path = options.clar_path,
|
||||
print_mode = options.print_mode)
|
||||
|
||||
if options.file is not None:
|
||||
builder.load_file(options.file)
|
||||
else:
|
||||
builder.load_dir(folder)
|
||||
|
||||
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.category_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",
|
||||
"clar_mock.c",
|
||||
"clar_categorize.c",
|
||||
]
|
||||
|
||||
self.modules.append("clar_print_%s.c" % print_mode)
|
||||
|
||||
def load_dir(self, folder):
|
||||
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())
|
||||
|
||||
def load_file(self, filename):
|
||||
with open(filename) as f:
|
||||
test_name = os.path.basename(filename)[:-2]
|
||||
self._process_test_file(test_name, f.read())
|
||||
|
||||
def render(self):
|
||||
if not self.suite_data:
|
||||
raise RuntimeError('No tests found under "%s"' % self.path)
|
||||
|
||||
if not os.path.isdir(self.path):
|
||||
os.makedirs(self.path)
|
||||
|
||||
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},
|
||||
${categories},
|
||||
${cb_ptr}, ${cb_count}
|
||||
}
|
||||
""")
|
||||
|
||||
callbacks = {}
|
||||
for cb in ['initialize', 'cleanup']:
|
||||
callbacks[cb] = (self._render_cb(suite[cb])
|
||||
if suite[cb] else "{NULL, NULL}")
|
||||
|
||||
if len(self.category_data[suite['name']]) > 0:
|
||||
cats = "_clar_cat_%s" % suite['name']
|
||||
else:
|
||||
cats = "NULL"
|
||||
|
||||
return template.substitute(
|
||||
suite_index = index,
|
||||
clean_name = suite['name'].replace("_", "::"),
|
||||
initialize = callbacks['initialize'],
|
||||
cleanup = callbacks['cleanup'],
|
||||
categories = cats,
|
||||
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_categories(self, suite_name, categories):
|
||||
template = Template(
|
||||
r"""
|
||||
static const char *_clar_cat_${suite_name}[] = { "${categories}", NULL };
|
||||
""")
|
||||
if len(categories) > 0:
|
||||
return template.substitute(
|
||||
suite_name = suite_name,
|
||||
categories = '","'.join(categories)
|
||||
).strip()
|
||||
else:
|
||||
return ""
|
||||
|
||||
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()
|
||||
)
|
||||
|
||||
categories = [
|
||||
self._render_categories(s, self.category_data[s])
|
||||
for s in suite_names
|
||||
]
|
||||
|
||||
return template.substitute(
|
||||
clar_modules = self._get_modules(),
|
||||
clar_callbacks = "\n".join(callbacks),
|
||||
clar_categories = "".join(categories),
|
||||
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)
|
||||
self._process_categories(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))
|
||||
|
||||
def _process_categories(self, suite_name, contents):
|
||||
self.category_data[suite_name] = [
|
||||
cat for cat in CATEGORY_REGEX.findall(contents) ]
|
Loading…
Add table
Add a link
Reference in a new issue