comparison fructose_gen.py @ 0:d098192f01d9

Initial commit to the repository.
author Brian Neal <bgneal@gmail.com>
date Sat, 19 Mar 2011 19:53:12 -0500
parents
children d8aeeb7f6785
comparison
equal deleted inserted replaced
-1:000000000000 0:d098192f01d9
1 #!/usr/bin/env python
2 """
3 Copyright (C) 2011 by Brian Neal <bgneal@gmail.com>
4
5 fructose_gen.py - A program to auto-generate the main() routine for the C++
6 testing framework Fructose.
7
8 """
9 from __future__ import with_statement
10 import re
11 import optparse
12 import sys
13
14
15 USAGE = "usage: %prog [options] test1.h test2.h ... > main.cpp"
16 DESCRIPTION = "Generates the main() routine for the Fructose C++ testing framework"
17 VERSION = "%prog 0.1"
18 INDENT_CNT = 4
19 INDENT = " " * INDENT_CNT
20
21
22 def strip_comments(s):
23 """
24 A simple function to strip out C++ style // comments from a string.
25 This function is really dumb; it doesn't know about string literals, etc.,
26 but it should suit our purposes for finding commented out test classes and
27 cases.
28
29 """
30 i = s.find('//')
31 return s if i == -1 else s[:i]
32
33
34 class TestClass(object):
35 """
36 This class represents a Fructose test class.
37 Each test class has a name attribute and a list of test case names
38 (strings).
39
40 """
41 def __init__(self, name):
42 self.name = name
43 self.test_cases = []
44
45
46 class TestFile(object):
47 """
48 A class to represent a Fructose unit test file.
49 Each test file has a filename attribute and a list of test classes.
50
51 """
52 def __init__(self, filename):
53 self.filename = filename
54 self.test_classes = []
55
56
57 class TestFileParser(object):
58 """
59 Base class for parsing Fructose unit test files.
60 """
61 def __init__(self, filename):
62 self.test_file = TestFile(filename)
63
64 def parse(self):
65 """
66 Parse the file by reading it line by line.
67 Returns a TestFile object that contains the test classes found within.
68
69 """
70 with open(self.test_file.filename, 'r') as f:
71 for line in f:
72 s = strip_comments(line)
73 if s:
74 self._parse_line(s)
75
76 return self.test_file
77
78 def _parse_line(self, line):
79 """
80 Parses each line of the test file, calling into derived classes to
81 find test classes and test cases.
82
83 """
84 test_class = self._parse_test_class(line)
85 if test_class:
86 self.test_file.test_classes.append(test_class)
87 else:
88 test_case = self._parse_test_case(line)
89 if len(self.test_file.test_classes) and test_case:
90 self.test_file.test_classes[-1].test_cases.append(test_case)
91
92 def _parse_test_class(self, line):
93 """Derived classes override this"""
94 raise NotImplementedError
95
96 def _parse_test_case(self, line):
97 """Derived classes override this"""
98 raise NotImplementedError
99
100
101 class GeneratorFileParser(TestFileParser):
102 """
103 This class parses Fructose test files using the generator style of code
104 generation.
105
106 """
107 CLASS_RE = re.compile(r'\bFRUCTOSE_(?:CLASS|STRUCT)\s*\(\s*([a-zA-Z_]\w*)\s*\)')
108 CASE_RE = re.compile(r'\bFRUCTOSE_TEST\s*\(\s*([a-zA-Z_]\w*)\s*\)')
109
110 def _parse_test_class(self, line):
111 m = self.CLASS_RE.search(line)
112 return TestClass(m.group(1)) if m else None
113
114 def _parse_test_case(self, line):
115 m = self.CASE_RE.search(line)
116 return m.group(1) if m else None
117
118
119 class XunitFileParser(TestFileParser):
120 """
121 This class parses Fructose test files using the xUnit style of code
122 generation.
123
124 """
125 CLASS_RE = re.compile(r'\b(?:struct|class)\s+([a-zA-Z_]\w*)\s+:\s+public'
126 r'\s+(?:fructose\s*::\s*)?test_base\s*<\s*\1\s*>')
127
128 CASE_RE = re.compile(r'\bvoid\s+(test\w+)\s*\(const\s+(?:std::)?string\s*&'
129 r'(?:\s+[a-zA-Z_]\w+)?\s*\)')
130
131 def _parse_test_class(self, line):
132 m = self.CLASS_RE.search(line)
133 return TestClass(m.group(1)) if m else None
134
135 def _parse_test_case(self, line):
136 m = self.CASE_RE.search(line)
137 return m.group(1) if m else None
138
139
140 def generate_test_instance(test_class):
141 """
142 Generates the code to instantiate a test instance, register and run the
143 tests.
144
145 """
146 type_name = test_class.name
147 instance = type_name + '_instance'
148
149 print "%s{" % INDENT
150 block_indent = INDENT * 2
151 print "%s%s %s;" % (block_indent, type_name, instance)
152 for test_case in test_class.test_cases:
153 print '%s%s.add_test("%s", &%s::%s);' % (
154 block_indent,
155 instance,
156 test_case,
157 type_name,
158 test_case,
159 )
160 print "%sconst int r = %s.run(argc, argv);" % (block_indent, instance)
161 print "%sretval = retval == EXIT_SUCCESS ? r : EXIT_FAILURE;" % block_indent
162 print "%s}" % INDENT
163
164
165
166 def generate_main(test_files):
167 """
168 Generates the main() file given a list of TestFile objects.
169
170 """
171 for test_file in test_files:
172 print '#include "%s"' % test_file.filename
173
174 print '\n#include <stdlib.h>\n'
175 print 'int main(int argc, char* argv[])\n{'
176 print '%sint retval = EXIT_SUCCESS;\n' % INDENT
177
178 for test_file in test_files:
179 for test_class in test_file.test_classes:
180 generate_test_instance(test_class)
181
182 print '\n%sreturn retval;\n}' % INDENT
183
184
185 def main(argv=None):
186
187 parser = optparse.OptionParser(usage=USAGE, description=DESCRIPTION,
188 version=VERSION)
189 parser.set_defaults(
190 generator=False,
191 )
192 parser.add_option("-g", "--generator", action="store_true",
193 help="use generator style code generation [default: %default]")
194
195 opts, args = parser.parse_args(args=argv)
196
197 xunit = not opts.generator
198
199 parser_class = XunitFileParser if xunit else GeneratorFileParser
200
201 if len(args) == 0:
202 sys.exit("No input files")
203
204 test_files = []
205 for test_file in args:
206
207 test_parser = parser_class(test_file)
208 try:
209 test_files.append(test_parser.parse())
210 except IOError, ex:
211 sys.stderr.write("Error parsing %s: %s, skipping" % (test_file, ex))
212
213 generate_main(test_files)
214
215
216 if __name__ == '__main__':
217 try:
218 main()
219 except IOError, ex:
220 sys.exit("IO Error: %s" % ex)
221 except KeyboardInterrupt:
222 sys.exit("Control-C interrupt")