3 # Copyright (c) Vicent Marti. All rights reserved.
5 # This file is part of clar, distributed under the ISC license.
6 # For full terms see the included COPYING file.
9 from __future__
import with_statement
10 from string
import Template
11 import re
, fnmatch
, os
, codecs
, pickle
14 class Template(object):
15 def __init__(self
, module
):
18 def _render_callback(self
, cb
):
20 return ' { NULL, NULL }'
21 return ' { "%s", &%s }' % (cb
['short_name'], cb
['symbol'])
23 class DeclarationTemplate(Template
):
25 out
= "\n".join("extern %s;" % cb
['declaration'] for cb
in self
.module
.callbacks
) + "\n"
27 if self
.module
.initialize
:
28 out
+= "extern %s;\n" % self
.module
.initialize
['declaration']
30 if self
.module
.cleanup
:
31 out
+= "extern %s;\n" % self
.module
.cleanup
['declaration']
35 class CallbacksTemplate(Template
):
37 out
= "static const struct clar_func _clar_cb_%s[] = {\n" % self
.module
.name
38 out
+= ",\n".join(self
._render
_callback
(cb
) for cb
in self
.module
.callbacks
)
42 class InfoTemplate(Template
):
50 ${cb_ptr}, ${cb_count}, ${enabled}
53 clean_name
= self
.module
.clean_name(),
54 initialize
= self
._render
_callback
(self
.module
.initialize
),
55 cleanup
= self
._render
_callback
(self
.module
.cleanup
),
56 cb_ptr
= "_clar_cb_%s" % self
.module
.name
,
57 cb_count
= len(self
.module
.callbacks
),
58 enabled
= int(self
.module
.enabled
)
61 def __init__(self
, name
):
69 return self
.name
.replace("_", "::")
71 def _skip_comments(self
, text
):
72 SKIP_COMMENTS_REGEX
= re
.compile(
73 r
'//.*?$|/\*.*?\*/|\'(?
:\\.|
[^
\\\'])*\'|
"(?:\\.|[^\\"])*"',
74 re.DOTALL | re.MULTILINE)
78 return "" if s.startswith('/') else s
80 return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
82 def parse(self, contents):
83 TEST_FUNC_REGEX = r"^
(void\s
+(test_
%s__(\w
+))\s
*\
(\s
*void\s
*\
))\s
*\
{"
85 contents = self._skip_comments(contents)
86 regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
89 self.initialize = None
92 for (declaration, symbol, short_name) in regex.findall(contents):
94 "short_name
" : short_name,
95 "declaration
" : declaration,
99 if short_name == 'initialize':
100 self.initialize = data
101 elif short_name == 'cleanup':
104 self.callbacks.append(data)
106 return self.callbacks != []
108 def refresh(self, path):
109 self.modified = False
115 if st.st_mtime == self.mtime:
119 self.mtime = st.st_mtime
121 with codecs.open(path, encoding='utf-8') as fp:
122 raw_content = fp.read()
127 return self.parse(raw_content)
129 class TestSuite(object):
131 def __init__(self, path):
134 def should_generate(self, path):
135 if not os.path.isfile(path):
138 if any(module.modified for module in self.modules.values()):
143 def find_modules(self):
145 for root, _, files in os.walk(self.path):
146 module_root = root[len(self.path):]
147 module_root = [c for c in module_root.split(os.sep) if c]
149 tests_in_module = fnmatch.filter(files, "*.c
")
151 for test_file in tests_in_module:
152 full_path = os.path.join(root, test_file)
153 module_name = "_
".join(module_root + [test_file[:-2]]).replace("-", "_
")
155 modules.append((full_path, module_name))
159 def load_cache(self):
160 path = os.path.join(self.path, '.clarcache')
164 fp = open(path, 'rb')
165 cache = pickle.load(fp)
167 except (IOError, ValueError):
172 def save_cache(self):
173 path = os.path.join(self.path, '.clarcache')
174 with open(path, 'wb') as cache:
175 pickle.dump(self.modules, cache)
177 def load(self, force = False):
178 module_data = self.find_modules()
179 self.modules = {} if force else self.load_cache()
181 for path, name in module_data:
182 if name not in self.modules:
183 self.modules[name] = Module(name)
185 if not self.modules[name].refresh(path):
186 del self.modules[name]
188 def disable(self, excluded):
189 for exclude in excluded:
190 for module in self.modules.values():
191 name = module.clean_name()
192 if name.startswith(exclude):
193 module.enabled = False
194 module.modified = True
196 def suite_count(self):
197 return len(self.modules)
199 def callback_count(self):
200 return sum(len(module.callbacks) for module in self.modules.values())
203 output = os.path.join(self.path, 'clar.suite')
205 if not self.should_generate(output):
208 with open(output, 'w') as data:
209 for module in self.modules.values():
210 t = Module.DeclarationTemplate(module)
211 data.write(t.render())
213 for module in self.modules.values():
214 t = Module.CallbacksTemplate(module)
215 data.write(t.render())
217 suites = "static struct clar_suite _clar_suites
[] = {" + ','.join(
218 Module.InfoTemplate(module).render() for module in sorted(self.modules.values(), key=lambda module: module.name)
223 data.write("static const size_t _clar_suite_count
= %d;\n" % self.suite_count())
224 data.write("static const size_t _clar_callback_count
= %d;\n" % self.callback_count())
229 if __name__ == '__main__':
230 from optparse import OptionParser
232 parser = OptionParser()
233 parser.add_option('-f', '--force', action="store_true
", dest='force', default=False)
234 parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
236 options, args = parser.parse_args()
238 for path in args or ['.']:
239 suite = TestSuite(path)
240 suite.load(options.force)
241 suite.disable(options.excluded)
243 print("Written `clar
.suite`
(%d tests
in %d suites
)" % (suite.callback_count(), suite.suite_count()))