]> git.proxmox.com Git - libgit2.git/blame - tests/generate.py
Merge pull request #2746 from libgit2/cmn/neg-ignore-dir
[libgit2.git] / tests / generate.py
CommitLineData
2e6f06a8 1#!/usr/bin/env python
e8a92fe1
RB
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#
2e6f06a8
VM
8
9from __future__ import with_statement
10from string import Template
11import re, fnmatch, os, codecs, pickle
12
13class 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:
e8a92fe1
RB
20 return ' { NULL, NULL }'
21 return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
2e6f06a8
VM
22
23 class DeclarationTemplate(Template):
24 def render(self):
e8a92fe1 25 out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
2e6f06a8
VM
26
27 if self.module.initialize:
28 out += "extern %s;\n" % self.module.initialize['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 return Template(
e8a92fe1
RB
45 r"""
46 {
47 "${clean_name}",
48 ${initialize},
49 ${cleanup},
50 ${cb_ptr}, ${cb_count}, ${enabled}
51 }"""
2e6f06a8
VM
52 ).substitute(
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)
59 )
60
61 def __init__(self, name):
62 self.name = name
7202ec29
RB
63
64 self.mtime = 0
2e6f06a8 65 self.enabled = True
7202ec29 66 self.modified = False
2e6f06a8
VM
67
68 def clean_name(self):
69 return self.name.replace("_", "::")
70
71 def _skip_comments(self, text):
72 SKIP_COMMENTS_REGEX = re.compile(
73 r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
74 re.DOTALL | re.MULTILINE)
75
76 def _replacer(match):
77 s = match.group(0)
78 return "" if s.startswith('/') else s
79
80 return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
81
82 def parse(self, contents):
83 TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\(\s*void\s*\))\s*\{"
84
85 contents = self._skip_comments(contents)
86 regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
87
88 self.callbacks = []
89 self.initialize = None
90 self.cleanup = None
91
92 for (declaration, symbol, short_name) in regex.findall(contents):
93 data = {
94 "short_name" : short_name,
95 "declaration" : declaration,
96 "symbol" : symbol
97 }
98
99 if short_name == 'initialize':
100 self.initialize = data
101 elif short_name == 'cleanup':
102 self.cleanup = data
103 else:
104 self.callbacks.append(data)
105
106 return self.callbacks != []
107
7202ec29
RB
108 def refresh(self, path):
109 self.modified = False
110
2e6f06a8 111 try:
7202ec29
RB
112 st = os.stat(path)
113
114 # Not modified
115 if st.st_mtime == self.mtime:
116 return True
117
118 self.modified = True
119 self.mtime = st.st_mtime
120
2e6f06a8 121 with open(path) as fp:
7202ec29
RB
122 raw_content = fp.read()
123
2e6f06a8
VM
124 except IOError:
125 return False
126
7202ec29
RB
127 return self.parse(raw_content)
128
2e6f06a8 129class TestSuite(object):
7202ec29 130
2e6f06a8
VM
131 def __init__(self, path):
132 self.path = path
133
7202ec29
RB
134 def should_generate(self, path):
135 if not os.path.isfile(path):
136 return True
137
138 if any(module.modified for module in self.modules.values()):
139 return True
140
141 return False
142
2e6f06a8
VM
143 def find_modules(self):
144 modules = []
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]
148
149 tests_in_module = fnmatch.filter(files, "*.c")
150
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]])
154
155 modules.append((full_path, module_name))
156
157 return modules
158
7202ec29
RB
159 def load_cache(self):
160 path = os.path.join(self.path, '.clarcache')
161 cache = {}
162
163 try:
164 fp = open(path, 'rb')
165 cache = pickle.load(fp)
166 fp.close()
167 except (IOError, ValueError):
168 pass
169
170 return cache
171
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)
176
2e6f06a8
VM
177 def load(self, force = False):
178 module_data = self.find_modules()
7202ec29 179 self.modules = {} if force else self.load_cache()
2e6f06a8
VM
180
181 for path, name in module_data:
182 if name not in self.modules:
183 self.modules[name] = Module(name)
184
7202ec29 185 if not self.modules[name].refresh(path):
2e6f06a8
VM
186 del self.modules[name]
187
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
195
196 def suite_count(self):
197 return len(self.modules)
198
199 def callback_count(self):
200 return sum(len(module.callbacks) for module in self.modules.values())
201
202 def write(self):
203 output = os.path.join(self.path, 'clar.suite')
204
7202ec29
RB
205 if not self.should_generate(output):
206 return False
207
2e6f06a8
VM
208 with open(output, 'w') as data:
209 for module in self.modules.values():
210 t = Module.DeclarationTemplate(module)
211 data.write(t.render())
212
213 for module in self.modules.values():
214 t = Module.CallbacksTemplate(module)
215 data.write(t.render())
216
217 suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
e8a92fe1
RB
218 Module.InfoTemplate(module).render() for module in sorted(self.modules.values(), key=lambda module: module.name)
219 ) + "\n};\n"
2e6f06a8
VM
220
221 data.write(suites)
222
e8a92fe1
RB
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())
2e6f06a8 225
7202ec29
RB
226 suite.save_cache()
227 return True
228
2e6f06a8
VM
229if __name__ == '__main__':
230 from optparse import OptionParser
231
232 parser = OptionParser()
7202ec29 233 parser.add_option('-f', '--force', dest='force', default=False)
2e6f06a8
VM
234 parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
235
236 options, args = parser.parse_args()
237
238 for path in args or ['.']:
239 suite = TestSuite(path)
7202ec29 240 suite.load(options.force)
2e6f06a8 241 suite.disable(options.excluded)
7202ec29
RB
242 if suite.write():
243 print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
2e6f06a8 244