#!/usr/bin/env python3 # convert.py: This script converts a Boost.Test unit test into an # equivalent Python unit test. # # Copyright (c) 2003-2023, John Wiegley. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # - Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # - Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # - Neither the name of New Artisans LLC nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import re import sys import os source = os.path.realpath(sys.argv[1]) base = os.path.splitext(source)[0] target = os.path.realpath(sys.argv[2]) dirname = os.path.dirname(target) if not os.path.isdir(dirname): try: os.makedirs(dirname) except: pass fd = open(source, "r") fo = open(target, "w") fo.write('''# -*- coding: utf-8 -*- import unittest import exceptions import operator from ledger import * from StringIO import * from datetime import * internalAmount = Amount.exact class %sTestCase(unittest.TestCase): testSession = None''' % os.path.basename(base)) not_for_python = 0 class_name = None for line in fd.readlines(): if re.match(r'^#ifndef NOT_FOR_PYTHON', line): not_for_python += 1 continue elif not_for_python > 0: if re.match(r'^#endif // NOT_FOR_PYTHON', line): not_for_python -= 1 continue if re.match(r'^(using|CPP|[#{}/])', line): continue if re.match(r'^\s+[{}]\s+$', line): continue if not re.search(r'assert', line): match = re.match(r'^};', line) if match: continue match = re.match(r'BOOST_.*_TEST_SUITE', line) if match: continue match = re.match(r'^struct (.*?) {', line) if match: class_name = match.group(1) continue if class_name: match = re.search(r'(~)?%s\(\) {' % class_name, line) if match: if match.group(1): fo.write(' def tearDown(self):\n') else: fo.write(' def setUp(self):\n') continue match = re.match(r'BOOST_AUTO_TEST_CASE\((.+?)\)', line) if match: fo.write(' def %s(self):\n' % match.group(1)) continue match = re.match(r'void [^:]+::(test[^(]+|setUp|tearDown)\(\)', line) if match: fo.write(' def %s(self):\n' % match.group(1)) continue match = re.search(r' ([a-z:_<>]+?)&?\s+([a-z0-9_]+)(\((.+?)\))?;', line) if match: if match.group(1) != "std::string": line = ' %s = %s(%s)\n' % (match.group(2), match.group(1), match.group(4) or "") else: line = '' match = re.search(r' ([a-z:_<>]+?)&?\s+([a-z0-9]+)\s*=\s*([^(]+);', line) if match: line = ' %s = %s(%s)\n' % (match.group(2), match.group(1), match.group(3)) match = re.search(r' ([a-z:_<>]+?)\s+([a-z0-9]+)\s*=\s*(.+?)$', line) if match: line = ' %s = %s\n' % (match.group(2), match.group(3)) line = re.sub(r'BOOST_CHECK_NE', 'self.assertNotEqual', line) line = re.sub(r'BOOST_CHECK_EQUAL', 'self.assertEqual', line) line = re.sub('BOOST_CHECK_THROW\(([^,]+), ([^,)]+?)\)', 'self.assertRaises(\\2, lambda: \\1)', line) line = re.sub(r'BOOST_CHECK', 'self.assertTrue', line) # jww (2010-06-20): Determine this list automatically by scanning # the class_ lines in src/py_*.cc line = re.sub(r'amount_t::precision_t\(([^)]+?)\)', '\\1', line) line = re.sub(r'amount_t::', 'Amount.', line) line = re.sub(r'Amount\.PARSE_', 'AmountParse.', line) line = re.sub(r'commodity_t\(([^)]+?)\)', '\\1', line) line = re.sub(r'commodity_t::', 'Commodity.', line) line = re.sub(r'balance_t::', 'Balance.', line) line = re.sub(r'balance_pair_t::', 'BalancePair.', line) line = re.sub(r'value_t::', 'Value.', line) line = re.sub(r'amount_t', 'Amount', line) line = re.sub(r'commodity_t', 'Commodity', line) line = re.sub(r'balance_t', 'Balance', line) line = re.sub(r'balance_pair_t', 'BalancePair', line) line = re.sub(r'value_t', 'Value', line) line = re.sub(r'PARSE_DEFAULT', "ParseFlags.Default", line) line = re.sub(r'PARSE_PARTIAL', "ParseFlags.Partial", line) line = re.sub(r'PARSE_SINGLE', "ParseFlags.Single", line) line = re.sub(r'PARSE_NO_MIGRATE', "ParseFlags.NoMigrate", line) line = re.sub(r'PARSE_NO_REDUCE', "ParseFlags.NoReduce", line) line = re.sub(r'PARSE_NO_ASSIGN', "ParseFlags.NoAssign", line) line = re.sub(r'PARSE_NO_DATES', "ParseFlags.NoDates", line) line = re.sub(r'PARSE_OP_CONTEXT', "ParseFlags.OpContext", line) line = re.sub(r'PARSE_SOFT_FAIL', "ParseFlags.SoftFail", line) line = re.sub(r'ledger::', '', line) line = re.sub(r'std::istringstream', 'StringIO', line) line = re.sub(r'std::ostringstream', 'StringIO', line) line = re.sub('set_session_context\(&session\)', 'self.testSession = session()\n set_session_context(self.testSession)', line) line = re.sub('set_session_context\(\)', 'set_session_context()\n self.testSession = None', line) line = re.sub(r'([a-z_]+?)_t\b', '\\1', line) line = re.sub(r'('[^"]+")', 'u\\1', line) line = re.sub(r'std::string\(([^)]+?)\)', '\\1', line) line = re.sub(r'string\(([^)]+?)\)', '\\1', line) line = re.sub(r'\.print\(([^)]+?)\)', '.print_(\\1)', line) line = re.sub(r'true', 'True', line) line = re.sub(r'false', 'False', line) line = re.sub(r'CURRENT_TIME\(\)', 'datetime.now()', line) line = re.sub(r'CURRENT_DATE\(\)', 'date.today()', line) line = re.sub(r'commodity\(\)', 'commodity', line) line = re.sub(r'precision\(\)', 'precision', line) line = re.sub(r'([0-9]+)[FL]', '\\1', line) line = re.sub(r'([0-9]+)UL', '\\1L', line) line = re.sub(r';', '', line) line = re.sub(r'//', '#', line) line = re.sub(r'->', '.', line) line = re.sub('(\s+|\()(\S+?) \? (.+?) : (.+?)\)', '\\1\\3 if \\2 else \\4)', line) line = re.sub(r'if \((.+?)\)( {)?$', 'if \\1:', line) line = re.sub(r'(} )?else( {)?$', 'else:', line) line = re.sub(r'amount_error', 'exceptions.ArithmeticError', line) match = re.match(r'^ ', line) if match: fo.write(' ' + line) else: fo.write(line) fo.write(''' def suite(): return unittest.TestLoader().loadTestsFromTestCase(%sTestCase) if __name__ == '__main__': unittest.main() ''' % os.path.basename(base)) fo.close() fd.close()