Mercurial > public > fructose_gen
view fructose_gen.py @ 4:9d536614c4c0 tip
Added tag fructose1.1.0 for changeset d8aeeb7f6785
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Sat, 21 May 2011 21:11:44 -0500 |
parents | d8aeeb7f6785 |
children |
line wrap: on
line source
#!/usr/bin/env python """ Copyright (C) 2011 by Brian Neal <bgneal@gmail.com> fructose_gen.py - A program to auto-generate the main() routine for the C++ testing framework Fructose. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. """ from __future__ import with_statement import re import optparse import sys USAGE = "usage: %prog [options] test1.h test2.h ... > main.cpp" DESCRIPTION = "Generates the main() routine for the Fructose C++ testing framework" VERSION = "%prog 0.1" INDENT_CNT = 4 INDENT = " " * INDENT_CNT def strip_comments(s): """ A simple function to strip out C++ style // comments from a string. This function is really dumb; it doesn't know about string literals, etc., but it should suit our purposes for finding commented out test classes and cases. """ i = s.find('//') return s if i == -1 else s[:i] class TestClass(object): """ This class represents a Fructose test class. Each test class has a name attribute and a list of test case names (strings). """ def __init__(self, name): self.name = name self.test_cases = [] class TestFile(object): """ A class to represent a Fructose unit test file. Each test file has a filename attribute and a list of test classes. """ def __init__(self, filename): self.filename = filename self.test_classes = [] class TestFileParser(object): """ Base class for parsing Fructose unit test files. """ def __init__(self, filename): self.test_file = TestFile(filename) def parse(self): """ Parse the file by reading it line by line. Returns a TestFile object that contains the test classes found within. """ with open(self.test_file.filename, 'r') as f: for line in f: s = strip_comments(line) if s: self._parse_line(s) return self.test_file def _parse_line(self, line): """ Parses each line of the test file, calling into derived classes to find test classes and test cases. """ test_class = self._parse_test_class(line) if test_class: self.test_file.test_classes.append(test_class) else: test_case = self._parse_test_case(line) if len(self.test_file.test_classes) and test_case: self.test_file.test_classes[-1].test_cases.append(test_case) def _parse_test_class(self, line): """Derived classes override this""" raise NotImplementedError def _parse_test_case(self, line): """Derived classes override this""" raise NotImplementedError class GeneratorFileParser(TestFileParser): """ This class parses Fructose test files using the generator style of code generation. """ CLASS_RE = re.compile(r'\bFRUCTOSE_(?:CLASS|STRUCT)\s*\(\s*([a-zA-Z_]\w*)\s*\)') CASE_RE = re.compile(r'\bFRUCTOSE_TEST\s*\(\s*([a-zA-Z_]\w*)\s*\)') def _parse_test_class(self, line): m = self.CLASS_RE.search(line) return TestClass(m.group(1)) if m else None def _parse_test_case(self, line): m = self.CASE_RE.search(line) return m.group(1) if m else None class XunitFileParser(TestFileParser): """ This class parses Fructose test files using the xUnit style of code generation. """ CLASS_RE = re.compile(r'\b(?:struct|class)\s+([a-zA-Z_]\w*)\s+:\s+public' r'\s+(?:fructose\s*::\s*)?test_base\s*<\s*\1\s*>') CASE_RE = re.compile(r'\bvoid\s+(test\w+)\s*\(const\s+(?:std::)?string\s*&' r'(?:\s+[a-zA-Z_]\w+)?\s*\)') def _parse_test_class(self, line): m = self.CLASS_RE.search(line) return TestClass(m.group(1)) if m else None def _parse_test_case(self, line): m = self.CASE_RE.search(line) return m.group(1) if m else None def generate_test_instance(test_class): """ Generates the code to instantiate a test instance, register and run the tests. """ type_name = test_class.name instance = type_name + '_instance' print "%s{" % INDENT block_indent = INDENT * 2 print "%s%s %s;" % (block_indent, type_name, instance) for test_case in test_class.test_cases: print '%s%s.add_test("%s", &%s::%s);' % ( block_indent, instance, test_case, type_name, test_case, ) print "%sconst int r = %s.run(argc, argv);" % (block_indent, instance) print "%sretval = retval == EXIT_SUCCESS ? r : EXIT_FAILURE;" % block_indent print "%s}" % INDENT def generate_main(test_files): """ Generates the main() file given a list of TestFile objects. """ for test_file in test_files: print '#include "%s"' % test_file.filename print '\n#include <stdlib.h>\n' print 'int main(int argc, char* argv[])\n{' print '%sint retval = EXIT_SUCCESS;\n' % INDENT for test_file in test_files: for test_class in test_file.test_classes: generate_test_instance(test_class) print '\n%sreturn retval;\n}' % INDENT def main(argv=None): parser = optparse.OptionParser(usage=USAGE, description=DESCRIPTION, version=VERSION) parser.set_defaults( generator=False, ) parser.add_option("-g", "--generator", action="store_true", help="use generator style code generation [default: %default]") opts, args = parser.parse_args(args=argv) xunit = not opts.generator parser_class = XunitFileParser if xunit else GeneratorFileParser if len(args) == 0: sys.exit("No input files") test_files = [] for test_file in args: test_parser = parser_class(test_file) try: test_files.append(test_parser.parse()) except IOError, ex: sys.stderr.write("Error parsing %s: %s, skipping" % (test_file, ex)) generate_main(test_files) if __name__ == '__main__': try: main() except IOError, ex: sys.exit("IO Error: %s" % ex) except KeyboardInterrupt: sys.exit("Control-C interrupt")