]> git.proxmox.com Git - libgit2.git/blob - tests/generate.py
New upstream version 1.1.0+dfsg.1
[libgit2.git] / tests / generate.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) Vicent Marti. All rights reserved.
4 #
5 # This file is part of clar, distributed under the ISC license.
6 # For full terms see the included COPYING file.
7 #
8
9 from __future__ import with_statement
10 from string import Template
11 import re, fnmatch, os, sys, codecs, pickle
12
13 class Module(object):
14 class Template(object):
15 def __init__(self, module):
16 self.module = module
17
18 def _render_callback(self, cb):
19 if not cb:
20 return ' { NULL, NULL }'
21 return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
22
23 class DeclarationTemplate(Template):
24 def render(self):
25 out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
26
27 for initializer in self.module.initializers:
28 out += "extern %s;\n" % initializer['declaration']
29
30 if self.module.cleanup:
31 out += "extern %s;\n" % self.module.cleanup['declaration']
32
33 return out
34
35 class CallbacksTemplate(Template):
36 def render(self):
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)
39 out += "\n};\n"
40 return out
41
42 class InfoTemplate(Template):
43 def render(self):
44 templates = []
45
46 initializers = self.module.initializers
47 if len(initializers) == 0:
48 initializers = [ None ]
49
50 for initializer in initializers:
51 name = self.module.clean_name()
52 if initializer and initializer['short_name'].startswith('initialize_'):
53 variant = initializer['short_name'][len('initialize_'):]
54 name += " (%s)" % variant.replace('_', ' ')
55
56 template = Template(
57 r"""
58 {
59 "${clean_name}",
60 ${initialize},
61 ${cleanup},
62 ${cb_ptr}, ${cb_count}, ${enabled}
63 }"""
64 ).substitute(
65 clean_name = name,
66 initialize = self._render_callback(initializer),
67 cleanup = self._render_callback(self.module.cleanup),
68 cb_ptr = "_clar_cb_%s" % self.module.name,
69 cb_count = len(self.module.callbacks),
70 enabled = int(self.module.enabled)
71 )
72 templates.append(template)
73
74 return ','.join(templates)
75
76 def __init__(self, name):
77 self.name = name
78
79 self.mtime = 0
80 self.enabled = True
81 self.modified = False
82
83 def clean_name(self):
84 return self.name.replace("_", "::")
85
86 def _skip_comments(self, text):
87 SKIP_COMMENTS_REGEX = re.compile(
88 r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
89 re.DOTALL | re.MULTILINE)
90
91 def _replacer(match):
92 s = match.group(0)
93 return "" if s.startswith('/') else s
94
95 return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
96
97 def parse(self, contents):
98 TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
99
100 contents = self._skip_comments(contents)
101 regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
102
103 self.callbacks = []
104 self.initializers = []
105 self.cleanup = None
106
107 for (declaration, symbol, short_name) in regex.findall(contents):
108 data = {
109 "short_name" : short_name,
110 "declaration" : declaration,
111 "symbol" : symbol
112 }
113
114 if short_name.startswith('initialize'):
115 self.initializers.append(data)
116 elif short_name == 'cleanup':
117 self.cleanup = data
118 else:
119 self.callbacks.append(data)
120
121 return self.callbacks != []
122
123 def refresh(self, path):
124 self.modified = False
125
126 try:
127 st = os.stat(path)
128
129 # Not modified
130 if st.st_mtime == self.mtime:
131 return True
132
133 self.modified = True
134 self.mtime = st.st_mtime
135
136 with codecs.open(path, encoding='utf-8') as fp:
137 raw_content = fp.read()
138
139 except IOError:
140 return False
141
142 return self.parse(raw_content)
143
144 class TestSuite(object):
145
146 def __init__(self, path, output):
147 self.path = path
148 self.output = output
149
150 def should_generate(self, path):
151 if not os.path.isfile(path):
152 return True
153
154 if any(module.modified for module in self.modules.values()):
155 return True
156
157 return False
158
159 def find_modules(self):
160 modules = []
161 for root, _, files in os.walk(self.path):
162 module_root = root[len(self.path):]
163 module_root = [c for c in module_root.split(os.sep) if c]
164
165 tests_in_module = fnmatch.filter(files, "*.c")
166
167 for test_file in tests_in_module:
168 full_path = os.path.join(root, test_file)
169 module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
170
171 modules.append((full_path, module_name))
172
173 return modules
174
175 def load_cache(self):
176 path = os.path.join(self.output, '.clarcache')
177 cache = {}
178
179 try:
180 fp = open(path, 'rb')
181 cache = pickle.load(fp)
182 fp.close()
183 except (IOError, ValueError):
184 pass
185
186 return cache
187
188 def save_cache(self):
189 path = os.path.join(self.output, '.clarcache')
190 with open(path, 'wb') as cache:
191 pickle.dump(self.modules, cache)
192
193 def load(self, force = False):
194 module_data = self.find_modules()
195 self.modules = {} if force else self.load_cache()
196
197 for path, name in module_data:
198 if name not in self.modules:
199 self.modules[name] = Module(name)
200
201 if not self.modules[name].refresh(path):
202 del self.modules[name]
203
204 def disable(self, excluded):
205 for exclude in excluded:
206 for module in self.modules.values():
207 name = module.clean_name()
208 if name.startswith(exclude):
209 module.enabled = False
210 module.modified = True
211
212 def suite_count(self):
213 return sum(max(1, len(m.initializers)) for m in self.modules.values())
214
215 def callback_count(self):
216 return sum(len(module.callbacks) for module in self.modules.values())
217
218 def write(self):
219 output = os.path.join(self.output, 'clar.suite')
220
221 if not self.should_generate(output):
222 return False
223
224 with open(output, 'w') as data:
225 modules = sorted(self.modules.values(), key=lambda module: module.name)
226
227 for module in modules:
228 t = Module.DeclarationTemplate(module)
229 data.write(t.render())
230
231 for module in modules:
232 t = Module.CallbacksTemplate(module)
233 data.write(t.render())
234
235 suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
236 Module.InfoTemplate(module).render() for module in modules
237 ) + "\n};\n"
238
239 data.write(suites)
240
241 data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
242 data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
243
244 self.save_cache()
245 return True
246
247 if __name__ == '__main__':
248 from optparse import OptionParser
249
250 parser = OptionParser()
251 parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
252 parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
253 parser.add_option('-o', '--output', dest='output')
254
255 options, args = parser.parse_args()
256 if len(args) > 1:
257 print("More than one path given")
258 sys.exit(1)
259
260 path = args.pop() if args else '.'
261 output = options.output or path
262 suite = TestSuite(path, output)
263 suite.load(options.force)
264 suite.disable(options.excluded)
265 if suite.write():
266 print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
267