diff --git a/data/tests/paris.gramps b/data/tests/Paris.gramps similarity index 100% rename from data/tests/paris.gramps rename to data/tests/Paris.gramps diff --git a/gramps/plugins/test/test_imports.py b/gramps/plugins/test/test_imports.py index 79d6bb2f6..84045603a 100644 --- a/gramps/plugins/test/test_imports.py +++ b/gramps/plugins/test/test_imports.py @@ -1,4 +1,5 @@ #! /usr/bin/env python3 +""" Test program for import modules """ # # Gramps - a GTK+/GNOME based genealogy program # @@ -23,13 +24,12 @@ import unittest import os import sys import re -import time import logging from gramps.gen.merge.diff import diff_dbs, import_as_dict from gramps.gen.simple import SimpleAccess from gramps.gen.utils.id import set_det_id -from gramps.cli import user +from gramps.cli.user import User from gramps.gen.const import TEMP_DIR, DATA_DIR logger = logging.getLogger(__name__) @@ -42,21 +42,21 @@ TEST_DIR = os.path.abspath(os.path.join(DATA_DIR, "tests")) # ------------------------------------------------------------------ -def todate(t): - return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t)) - - class CompleteCheck(unittest.TestCase): """The test class cases will be dynamically created at import time from files to be tested. The following defs are used by the test cases""" def prepare_result(self, diffs, added, missing): + """ Looks through the diffs, added, and missing items and begins + reporting process. Returns True if there were significant errors. """ + # pylint: disable=E1101 + # pylint does not like dynamically created methods deltas = False if diffs: for diff in diffs: obj_type, item1, item2 = diff - msg = self.report_diff(obj_type, item1.to_struct(), - item2.to_struct()) + msg = self._report_diff(obj_type, item1.to_struct(), + item2.to_struct()) if msg != "": if hasattr(item1, "gramps_id"): self.msg += "%s: %s handle=%s\n" % \ @@ -68,63 +68,20 @@ class CompleteCheck(unittest.TestCase): deltas = True if missing: deltas = True - sa = SimpleAccess(self.database1) + sac = SimpleAccess(self.database1) for pair in missing: obj_type, item = pair - self.msg += "Missing %s: %s\n" % (obj_type, sa.describe(item)) + self.msg += "Missing %s: %s\n" % (obj_type, sac.describe(item)) if added: deltas = True - sa = SimpleAccess(self.database2) + sac = SimpleAccess(self.database2) for pair in added: obj_type, item = pair - self.msg += "Added %s: %s\n" % (obj_type, sa.describe(item)) + self.msg += "Added %s: %s\n" % (obj_type, sac.describe(item)) return deltas - def format_struct_path(self, path): - retval = "" - parts = path.split(".") - for part in parts: - if retval: - retval += ", " - if "[" in part and "]" in part: - part, index = re.match("(.*)\[(\d*)\]", part).groups() - retval += "%s #%s" % (part.replace("_", " "), int(index) + 1) - else: - retval += part - return retval - def report_details(self, path, diff1, diff2): - if isinstance(diff1, bool): - desc1 = repr(diff1) - else: - desc1 = str(diff1) if diff1 else "" - if isinstance(diff2, bool): - desc2 = repr(diff2) - else: - desc2 = str(diff2) if diff2 else "" - d1t = type(diff1) - d2t = type(diff2) - # the xml exporter edits the data base by stripping spaces, so - # we have to ignore these differeces - if d1t == str: diff1 = diff1.strip() - if d2t == str: diff2 = diff2.strip() - d1l = len(diff1) if d1t == str else "" - d2l = len(diff2) if d2t == str else "" - # 'change' date is not significant for comparison - if path.endswith(".change"): - return "" - # the xml exporter edits the data base by converting media path - # to unix '/' conventions, so we have to ignore these differences - if path == "Media.path": - diff1 = diff1.replace('\\', '/') - if diff1 != diff2: - msg = " Diff on: %s\n %s%s: %s\n %s%s: %s\n" % \ - (self.format_struct_path(path), d1t, d1l, desc1, - d2t, d2l, desc2) - return msg - return "" - - def report_diff(self, path, struct1, struct2): + def _report_diff(self, path, struct1, struct2): """ Compare two struct objects and report differences. """ @@ -138,7 +95,7 @@ class CompleteCheck(unittest.TestCase): for pos in range(max(len1, len2)): value1 = struct1[pos] if pos < len1 else None value2 = struct2[pos] if pos < len2 else None - msg += self.report_diff(path + ("[%d]" % pos), value1, value2) + msg += self._report_diff(path + ("[%d]" % pos), value1, value2) elif isinstance(struct1, dict) or isinstance(struct2, dict): keys = struct1.keys() if isinstance(struct1, dict)\ else struct2.keys() @@ -146,40 +103,92 @@ class CompleteCheck(unittest.TestCase): value1 = struct1[key] if struct1 is not None else None value2 = struct2[key] if struct2 is not None else None if key == "dict": # a raw dict, not a struct - msg += self.report_details(path, value1, value2) + msg += _report_details(path, value1, value2) else: - msg += self.report_diff(path + "." + key, value1, value2) + msg += self._report_diff(path + "." + key, value1, value2) else: - msg += self.report_details(path, struct1, struct2) + msg += _report_details(path, struct1, struct2) return msg -# The following make_test_function creates a test function (a method, -# to be precise) that compares the import file with the expected -# result '.gramps' file. -def make_tst_function(tstfile, fname): +def _report_details(path, diff1, diff2): + """ Checks if a detail is significant, needs adjusting for xml filter + effects, and returns a string describing the specific difference.""" + if isinstance(diff1, bool): + desc1 = repr(diff1) + else: + desc1 = str(diff1) if diff1 else "" + if isinstance(diff2, bool): + desc2 = repr(diff2) + else: + desc2 = str(diff2) if diff2 else "" + d1t = type(diff1) + d2t = type(diff2) + # the xml exporter edits the data base by stripping spaces, so + # we have to ignore these differences + if d1t == str: + diff1 = diff1.strip() + if d2t == str: + diff2 = diff2.strip() + d1l = len(diff1) if d1t == str else "" + d2l = len(diff2) if d2t == str else "" + # 'change' date is not significant for comparison + if path.endswith(".change"): + return "" + # the xml exporter edits the data base by converting media path + # to unix '/' conventions, so we have to ignore these differences + if path == "Media.path": + diff1 = diff1.replace('\\', '/') + if diff1 != diff2: + msg = " Diff on: %s\n %s%s: %s\n %s%s: %s\n" % \ + (_format_struct_path(path), d1t, d1l, desc1, + d2t, d2l, desc2) + return msg + return "" + + +def _format_struct_path(path): + """ prepares a 'path' string for the report out of the structure. """ + retval = "" + parts = path.split(".") + for part in parts: + if retval: + retval += ", " + if "[" in part and "]" in part: + part, index = re.match(r"(.*)\[(\d*)\]", part).groups() + retval += "%s #%s" % (part.replace("_", " "), int(index) + 1) + else: + retval += part + return retval + + +def make_tst_function(tstfile, file_name): + """ This is here to support the dynamic function creation. This creates + the test function (a method, to be precise). """ def tst(self): - self._user = user.User(quiet=True) - f1 = os.path.join(TEST_DIR, tstfile) - f2 = os.path.join(TEST_DIR, (fname + ".gramps")) - fres = os.path.join(TEMP_DIR, (fname + ".difs")) + """ This compares the import file with the expected result '.gramps' + file. """ + self.user = User(quiet=True) + fn1 = os.path.join(TEST_DIR, tstfile) + fn2 = os.path.join(TEST_DIR, (file_name + ".gramps")) + fres = os.path.join(TEMP_DIR, (file_name + ".difs")) try: os.remove(fres) except OSError: pass - logging.info("\n**** %s ****" % tstfile) + logging.info("\n**** %s ****", tstfile) set_det_id(True) - self.database1 = import_as_dict(f1, self._user) + self.database1 = import_as_dict(fn1, self.user) set_det_id(True) - self.database2 = import_as_dict(f2, self._user) + self.database2 = import_as_dict(fn2, self.user) self.assertIsNotNone(self.database1, - "Unable to import file: %s" % f1) + "Unable to import file: %s" % fn1) self.assertIsNotNone(self.database2, - "Unable to import expected result file: %s" % f2) + "Unable to import expected result file: %s" % fn2) if self.database2 is None or self.database1 is None: return diffs, added, missing = diff_dbs(self.database1, - self.database2, self._user) + self.database2, self.user) self.msg = "Mismatch on file: %s\n" % tstfile deltas = self.prepare_result(diffs, added, missing) # We save a copy of any issues in the users Gramps TEMP_DIR in a file @@ -190,12 +199,12 @@ def make_tst_function(tstfile, fname): hres.write(self.msg) hres.close() # let's see if we have any allowed exception file - fdif = os.path.join(TEST_DIR, (fname + ".difs")) + fdif = os.path.join(TEST_DIR, (file_name + ".difs")) try: hdif = open(fdif) msg = hdif.read() hdif.close() - except: + except (FileNotFoundError, IOError): msg = "" # if exception file matches exactly, we are done. if self.msg != msg: @@ -205,30 +214,31 @@ def make_tst_function(tstfile, fname): # let's see if we have a single file to run, example; # "python test_import.py -i sample.ged" # This only works for files in normal test directory, so don't add a path -tstfile = "" +#pylint: disable=invalid-name +_tstfile = "" if __name__ == "__main__": for i, option in enumerate(sys.argv): if option == '-i': - tstfile = sys.argv[i+1] + _tstfile = sys.argv[i+1] del sys.argv[i] del sys.argv[i] # The following code dynamically creates the methods for each test file. # The methods are inserted at load time into the 'CompleteCheck' class # via the modules' globals, taking advantage that they are a dict. -if tstfile: # single file mode - (fname, ext) = os.path.splitext(os.path.basename(tstfile)) - test_func = make_tst_function(tstfile, fname) - clname = 'Import_{0}'.format(tstfile) +if _tstfile: # single file mode + (fname, ext) = os.path.splitext(os.path.basename(_tstfile)) + test_func = make_tst_function(_tstfile, fname) + clname = 'Import_{0}'.format(_tstfile) globals()[clname] = type(clname, (CompleteCheck,), {"testit": test_func}) else: - for tstfile in os.listdir(TEST_DIR): - (fname, ext) = os.path.splitext(os.path.basename(tstfile)) + for _tstfile in os.listdir(TEST_DIR): + (fname, ext) = os.path.splitext(os.path.basename(_tstfile)) if ext == ".gramps" or ext == ".difs" or ext == ".bak": continue - test_func = make_tst_function(tstfile, fname) - clname = 'Import_{0}'.format(tstfile) + test_func = make_tst_function(_tstfile, fname) + clname = 'Import_{0}'.format(_tstfile) globals()[clname] = type(clname, (CompleteCheck,), {"testit": test_func})