diff --git a/ChangeLog b/ChangeLog index 5041188c8..dbfca4c1d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2007-12-13 Jim Sack + src/test/gedread_util.py : added to support testing + src/test/test/gedread_util_test.py : added to test util above + src/GrampsDbUtils/test/GR_test.py : added 1st try at testing + first try at some gedcon read testing + 2007-12-13 Jim Sack * src/test/test_util.py * src/test/test/test_util_test.py diff --git a/src/GrampsDbUtils/test/GR_test.py b/src/GrampsDbUtils/test/GR_test.py new file mode 100644 index 000000000..20e1943bd --- /dev/null +++ b/src/GrampsDbUtils/test/GR_test.py @@ -0,0 +1,146 @@ +""" GR_test.py + +This is a first try at some gedcom read testing that does not +require running a gramps CLI + +The biggest difficulty is that every test fragment needs custom +test code. Maybe that's unavoidable, and the best that can be +done is to group similar tests together, so that setUp can be +shared. + +Maybe more can be shared: one commonly used test recipe is +to develop a data structure that can be looped over to test +similar fragments with the same piece of test code, putting +fragments and possible control or other validation information +in the data structure. + +A controlling principle for such structures is that they should be +designed for maximum ease (and intuitiveness) of data declaration +""" + +import sys, os, os.path as op + +import unittest as U +import re + +from test import test_util as tu +from test import gedread_util as gr + + +# NoteSource_frag +# tests structure: NOTE > SOUR +# using the 2 formats of the SOUR element +# bug #(?) does not properly ignore the SOUR +# test by looking for warning messages resulting +# from parse_record seeing the non-skipped SOUR +# +# SB: the NOTE data should contain the SOUR or xref +# but this is NYI (SOUR is ignored within NOTE) +# ----------------------------------------------- + + +# +# numcheck based testing +# verifies the number of db items via a get_number_of_X() call +# returns an error string or None +# +# ? candidate for inclusion in gedread_util.py +# +class nc(): + """nc object -- creates a numcheck function + + instantiate a nc object as follows + c = nc("people", 4) + and call, passing the database, as follows + err = c(db) + which will check for exactly 4 people in the db + and return a displayable message on error, else None + + NB: name _must_ match the X names in db get_number_of_X + """ + def dbncheck(s, dbcall): + err = None + got = dbcall() + if not got == s.num: + err = "%s: got %d, expected %d" % (s.name, got, s.num) + return err + def __init__(s, name, num): + s.name = name + s.num = num + s.getname = "get_number_of_" + name + def __call__(s, db): + dbcall = getattr(db,s.getname) + s.dbncheck(dbcall) + +class fnci(): + """fnci (frag-numcheckset item) is a data container for: + a fragment of gedcom + a sequence of nc items to check + """ + def __init__(s, frag, ncset): + s.frag = frag + s.ncset = ncset + +# test data table for Test1.test1a_numchecks +fnumchecks = ( + fnci("""0 @N1@ NOTE Note containing embedded source +1 SOUR embedded source""", + (nc("notes", 1),) + ), + fnci("""0 @N2@ NOTE Note containing referenced source +1 SOUR @SOUR1@ +0 @SOUR1@ SOUR +1 TITL Phoney source title""", + (nc("notes", 1), nc("sources",1),) + ), + )#end fnumchecks + + +# +# ? candidate for inclusion in test_util.py +# +def _checklog(tlogger, pat=None): + # look for absence of specific messages in log + matches = 0 + ltext = tlogger.logfile_getlines() + if ltext: + if pat is None: + matches = len(ltext) + else: + pat = re.compile(pat) + for l in ltext: + match = re.match(pat, l) + if match: + matches += 1 + # debugging + print "(%d) %r" % (matches, match) + return matches + + + +class Test1(U.TestCase): + def setUp(s): + # make a test subdir and compose some pathnames + s.tdir = tu.make_subdir("RG_test") + s.tdb = op.join(s.tdir,"test_db") + s.ifil = op.join(s.tdir,"test_in.ged") + s.lfil = op.join(s.tdir,"test.log") + + def test1a_numchecks(s): + tl = tu.TestLogger() + for i,f in enumerate(fnumchecks): + gr.make_gedcom_input(s.ifil, f.frag) + db = gr.create_empty_db(s.tdb) + tl.logfile_init(s.lfil) + gr.gread(db,s.ifil) + errs = _checklog(tl, r"Line \d+") + s.assertEquals(errs, 0, + "ncset(%d): got %d unexpected log messages" % + (i,errs)) + # ok, no log error message, check db counts + for call in f.ncset: + err = call(db) + s.assertFalse(err, err) + +if __name__ == "__main__": + U.main() diff --git a/src/test/gedread_util.py b/src/test/gedread_util.py new file mode 100644 index 000000000..f6ee1cd7f --- /dev/null +++ b/src/test/gedread_util.py @@ -0,0 +1,128 @@ +"""unittest support utilities for reading gedcom + +see gedread_test.py for sample usage + +""" + +import os.path +import shutil + +from test import test_util as tu +from GrampsDbUtils import _ReadGedcom as RG +from GrampsDbUtils import _GedcomStageOne as S1 +from GrampsDbUtils import _GedcomParse as GP +import DbState +import gen.db +import Config + +# extraneous leading newlines do not seem to cause problems +# (and actually make it convenient reading the test files!) +# future: may need to remove such lines here if problems develop + +# These ged-chunks provide/observe the following requirements +# - minimum required header elements +# - a trailer +# - and one record (spec minimum), using a SUBM +# Note: not all specified requirements seem strongly enforcced +# eg: at least one record, also nonexistent references seem +# ok by design, so the SUBM could have been missing +# Also note that the 'tail' containing the SUBM record referenced +# in the header causes a line of console output because we +# presently do not process SUBM records at all +# (seems like a bug to me -- to be dealt with later) +# --------------------------------------------------------------- + +# _head is presently simply a header with minimum content +_head =""" +0 HEAD +1 SOUR test_gedread_System_ID +1 SUBM @SUBM1@ +1 GEDC +2 VERS 5.5 +2 FORM LINEAGE-LINKED +1 CHAR ASCII +""" + +# _tail is presently a single (SUBM) record plus the trailer +# to satisfy the "one or more records" in the spec +# it also provides a target for the xref in the header +_tail = """ +0 @SUBM1@ SUBM +1 NAME test /gedread/ +0 TRLR +""" + +def make_gedcom_input(gfile, fragment): + """create gedcom file with 'fragment' between our head & tail + + fragment would normally be 1 or more complete records + fragment could be an empty string ("") + + """ + fh = open(gfile,"w") + for txt in (_head, fragment, _tail): + fh.write(txt) + fh.close() + + +# code patterned after contents of ReadGedcom.import2, +# but avoiding the occurrence of a popup DialogError. +# ------------------------------------------------------- +def gread(db, fname): + """read gedcom file into a test db + + NB: test modules may want to consider also, the simplified + test logging (from test_util) which is especially helpful + for testing gedcom support + + """ + cback = None + DEF_SRC = False + ifile = open(fname,"rU") + try: + try: + s1 = RG.StageOne(ifile) + s1.parse() + except Exception,e: + raise tu.TestError("stage1 error %r" % e) + + useTrans = False + ifile.seek(0) + try: + gp = RG.GedcomParser(db, ifile, fname, cback, s1, DEF_SRC) + except Exception, e: + raise tu.TestError("parser init error %r" % e) + + ##ro = db.readonly + ##db.readonly = False # why? + try: + gp.parse_gedcom_file(useTrans) + err = "" + except Exception, e: + raise tu.TestError("parse error %r" %e) + ##db.readonly = ro + finally: + ifile.close() + + +# test db creation +# +# This may deserve it's own module, but for now it is only used here +# +# state doesn't seem to be necessary for testing +# let's try just returning the db +#---------------------------------------------------- +def create_empty_db(dbpath): + """create an empty db for the test caller""" + state = DbState.DbState() + dbclass = gen.db.dbdir.GrampsDBDir + state.change_database(dbclass(Config.get(Config.TRANSACTIONS))) + # create empty db (files) via load() + cback = None + mode = "rw" + if os.path.isdir(dbpath): + shutil.rmtree(dbpath) + state.db.load(dbpath, cback, mode) + return state.db + +#===eof=== diff --git a/src/test/test/gedread_util_test.py b/src/test/test/gedread_util_test.py new file mode 100644 index 000000000..4ce08647d --- /dev/null +++ b/src/test/test/gedread_util_test.py @@ -0,0 +1,55 @@ +"""test for test/gedread_util.py + +also illustrates basic use +""" + +import os +import unittest as U +import logging + +from test import test_util as tu +from test import gedread_util as gr + + +class Test(U.TestCase): + def setUp(s): + # make a dir to hold an input gedcom file + s.tdir = tu.make_subdir("gr_test") + + def test1(s): + prec=""" +0 @I1@ INDI +1 NAME GedRead TEST /Person/ +""" + + # create a gedcom input file + # from canned head/tail -- see gedread_util + infil = os.path.join(s.tdir,"test_in.ged") + gr.make_gedcom_input(infil, prec) + s.assertTrue(os.path.isfile(infil), + "create input file %s" % infil) + + # create an empty database + dbpath = os.path.join(s.tdir,"test_db") + db = gr.create_empty_db(dbpath) + s.assertTrue(os.path.isdir(dbpath), + "create database (dir) %s" % dbpath) + + # create logfile to test for read log-messages + # (note: uses recently added test_util + log = os.path.join(s.tdir, "test_log") + tl = tu.TestLogger() + tl.logfile_init(log) + # now read the gedcom + gr.gread(db, infil) + logging.warn("nothing here") + loglines = tl.logfile_getlines() + s.assertEquals(len(loglines),1, + "log has no unexpected content") + # verify one person in database + np = db.get_number_of_people() + s.assertEquals(np,1, + tu.msg(np,1, "db has exactly one person")) + +if __name__ == "__main__": + U.main()