def assertEqualEntries(self, expected_entries, actual_entries): """Compare two lists of entries exactly and print missing entries verbosely if they occur. Args: expected_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. actual_entries: Same treatment as expected_entries, the other list of directives to compare to. Raises: AssertionError: If the exception fails. """ expected_entries = read_string_or_entries(expected_entries) actual_entries = read_string_or_entries(actual_entries) same, expected_missing, actual_missing = compare.compare_entries(expected_entries, actual_entries) if not same: assert expected_missing or actual_missing, "Missing is missing: {}, {}".format( expected_missing, actual_missing) oss = io.StringIO() if expected_missing: oss.write("Present in expected set and not in actual set:\n\n") for entry in expected_missing: oss.write(printer.format_entry(entry)) oss.write('\n') if actual_missing: oss.write("Present in actual set and not in expected set:\n\n") for entry in actual_missing: oss.write(printer.format_entry(entry)) oss.write('\n') self.fail(oss.getvalue())
def test_compare_entries(self): entries1, _, __ = loader.load_string(TEST_INPUT) entries2, _, __ = loader.load_string(TEST_INPUT) # Check two equal sets. same, missing1, missing2 = compare.compare_entries(entries1, entries2) self.assertTrue(same) self.assertFalse(missing1) self.assertFalse(missing2) # First > Second. same, missing1, missing2 = compare.compare_entries( entries1, entries2[:-1]) self.assertFalse(same) self.assertTrue(missing1) self.assertFalse(missing2) self.assertEqual(1, len(missing1)) self.assertTrue(isinstance(missing1.pop(), data.Close)) # First < Second. same, missing1, missing2 = compare.compare_entries( entries1[:-1], entries2) self.assertFalse(same) self.assertFalse(missing1) self.assertTrue(missing2) self.assertEqual(1, len(missing2)) self.assertTrue(isinstance(missing2.pop(), data.Close)) # Both have missing. same, missing1, missing2 = compare.compare_entries( entries1[1:], entries2[:-1]) self.assertFalse(same) self.assertTrue(missing1) self.assertTrue(missing2) self.assertEqual(1, len(missing1)) self.assertTrue(isinstance(missing1.pop(), data.Close)) self.assertEqual(1, len(missing2)) self.assertTrue(isinstance(missing2.pop(), data.Open))
def assertEqualEntries(expected_entries, actual_entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): """Compare two lists of entries exactly and print missing entries verbosely if they occur. Args: expected_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. actual_entries: Same treatment as expected_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. """ expected_entries = read_string_or_entries(expected_entries, allow_incomplete) actual_entries = read_string_or_entries(actual_entries, allow_incomplete) same, expected_missing, actual_missing = compare.compare_entries( expected_entries, actual_entries) if not same: assert expected_missing or actual_missing, "Missing is missing: {}, {}".format( expected_missing, actual_missing) oss = io.StringIO() if expected_missing: oss.write("Present in expected set and not in actual set:\n\n") for entry in expected_missing: oss.write(printer.format_entry(entry)) oss.write('\n') if actual_missing: oss.write("Present in actual set and not in expected set:\n\n") for entry in actual_missing: oss.write(printer.format_entry(entry)) oss.write('\n') failfunc(oss.getvalue())
def assertEqualEntries(self, expected_entries, actual_entries, allow_incomplete=False): """Check that two lists of entries are equal. Entries can be provided either as a list of directives or as a string. In the latter case, the string is parsed with beancount.parser.parse_string() and the resulting directives list is used. If allow_incomplete is True, light-weight booking is performed before comparing the directive lists, allowing to compare transactions with incomplete postings. Args: expected_entries: Expected entries. actual_entries: Actual entries. allow_incomplete: Perform booking before comparison. Raises: AssertionError: If the exception fails. """ expected_entries = read_string_or_entries(expected_entries, allow_incomplete) actual_entries = read_string_or_entries(actual_entries, allow_incomplete) same, expected_missing, actual_missing = \ compare.compare_entries(expected_entries, actual_entries) if not same: assert expected_missing or actual_missing, \ "Missing is missing: {}, {}".format(expected_missing, actual_missing) oss = io.StringIO() if expected_missing: oss.write("Present in expected set and not in actual set:\n\n") for entry in expected_missing: oss.write(printer.format_entry(entry)) oss.write('\n') if actual_missing: oss.write("Present in actual set and not in expected set:\n\n") for entry in actual_missing: oss.write(printer.format_entry(entry)) oss.write('\n') self.fail(oss.getvalue())
def do_roundtrip(filename, unused_args): """Round-trip test on arbitrary Ledger. Read a Ledger's transactions, print them out, re-read them again and compare them. Both sets of parsed entries should be equal. Both printed files are output to disk, so you can also run diff on them yourself afterwards. Args: filename: A string, the Beancount input filename. """ from beancount.parser import printer from beancount.core import compare from beancount import loader round1_filename = round2_filename = None try: logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') logging.info("Read the entries") entries, errors, options_map = loader.load_file(filename) printer.print_errors(errors, file=sys.stderr) logging.info("Print them out to a file") basename, extension = path.splitext(filename) round1_filename = ''.join([basename, '.roundtrip1', extension]) with open(round1_filename, 'w') as outfile: printer.print_entries(entries, file=outfile) logging.info("Read the entries from that file") # Note that we don't want to run any of the auto-generation here, but # parsing now returns incomplete objects and we assume idempotence on a # file that was output from the printer after having been processed, so # it shouldn't add anything new. That is, a processed file printed and # resolve when parsed again should contain the same entries, i.e. # nothing new should be generated. entries_roundtrip, errors, options_map = loader.load_file( round1_filename) # Print out the list of errors from parsing the results. if errors: print( ',----------------------------------------------------------------------' ) printer.print_errors(errors, file=sys.stdout) print( '`----------------------------------------------------------------------' ) logging.info("Print what you read to yet another file") round2_filename = ''.join([basename, '.roundtrip2', extension]) with open(round2_filename, 'w') as outfile: printer.print_entries(entries_roundtrip, file=outfile) logging.info("Compare the original entries with the re-read ones") same, missing1, missing2 = compare.compare_entries( entries, entries_roundtrip) if same: logging.info('Entries are the same. Congratulations.') else: logging.error('Entries differ!') print() print('\n\nMissing from original:') for entry in entries: print(entry) print(compare.hash_entry(entry)) print(printer.format_entry(entry)) print() print('\n\nMissing from round-trip:') for entry in missing2: print(entry) print(compare.hash_entry(entry)) print(printer.format_entry(entry)) print() finally: for rfilename in (round1_filename, round2_filename): if path.exists(rfilename): os.remove(rfilename)