1# encoding: utf-8
2#
3# Gramps - a GTK+/GNOME based genealogy program
4#
5# Copyright (C) 2000-2006  Martin Hawlisch, Donald N. Allingham
6# Copyright (C) 2008       Brian G. Matherly
7# Copyright (C) 2010       Jakim Friant
8# Copyright (C) 2011       Tim G L Lyons
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23#
24
25"""Tools/Debug/Generate Testcases for Persons and Families"""
26# pylint: disable=too-many-statements,too-many-locals,too-many-branches
27# pylint: disable=wrong-import-position,too-many-public-methods,no-self-use
28# pylint: disable=too-many-arguments
29# ------------------------------------------------------------------------
30#
31# standard python modules
32#
33# ------------------------------------------------------------------------
34import sys
35import os
36import random
37from gramps.gen.const import GRAMPS_LOCALE as glocale
38_ = glocale.translation.gettext
39
40# ------------------------------------------------------------------------
41#
42# GNOME libraries
43#
44# ------------------------------------------------------------------------
45from gi.repository import Gtk
46
47# ------------------------------------------------------------------------
48#
49# Gramps modules
50#
51# ------------------------------------------------------------------------
52from gramps.gen.lib import (
53    Address, Attribute, AttributeType, ChildRef,
54    ChildRefType, Citation, Date, Event, EventRef, EventRoleType,
55    EventType, Family, FamilyRelType, GrampsType, LdsOrd, Location,
56    Media, MediaRef, Name, NameOriginType, NameType, Note,
57    NoteType, Person, PersonRef, Place, PlaceType, PlaceRef, PlaceName,
58    RepoRef, Repository, RepositoryType, Source, SourceMediaType,
59    SrcAttribute, SrcAttributeType, Surname, Tag, Url, UrlType)
60from gramps.gen.lib.addressbase import AddressBase
61from gramps.gen.lib.attrbase import AttributeBase
62from gramps.gen.lib.primaryobj import BasicPrimaryObject
63from gramps.gen.lib.citationbase import CitationBase
64from gramps.gen.lib.date import Today
65from gramps.gen.lib.datebase import DateBase
66from gramps.gen.lib.ldsordbase import LdsOrdBase
67from gramps.gen.lib.locationbase import LocationBase
68from gramps.gen.lib.mediabase import MediaBase
69from gramps.gen.lib.notebase import NoteBase
70from gramps.gen.lib.placebase import PlaceBase
71from gramps.gen.lib.privacybase import PrivacyBase
72from gramps.gen.lib.tagbase import TagBase
73from gramps.gen.lib.urlbase import UrlBase
74from gramps.gen.lib import StyledText, StyledTextTag, StyledTextTagType
75from gramps.gen.db import DbTxn
76from gramps.gen.mime import get_type
77from gramps.gui.plug import tool
78from gramps.gen.utils.string import conf_strings
79from gramps.gen.utils.lds import TEMPLES
80from gramps.gen.db.dbconst import *
81from gramps.gen.const import ICON, LOGO, SPLASH
82from gramps.gui.display import display_help
83from gramps.gen.const import URL_MANUAL_PAGE
84
85# ------------------------------------------------------------------------
86#
87# Constants
88#
89# ------------------------------------------------------------------------
90WIKI_HELP_PAGE = '%s_-_Tools' % URL_MANUAL_PAGE
91WIKI_HELP_SEC = _('Generate_Testcases_for_Persons_and_Families')
92
93# the following allows test code to access a private copy of the random
94# number generator.  The access is typically used for seeding the generator
95# to make it repeatable across runs.  The private copy is unaffected by other
96# uses of the global random() functions.
97try:
98    from gramps.gen.const import myrand
99except (NameError, ImportError):
100    myrand = random.Random()
101except:
102    print("Unexpected error:", sys.exc_info()[0])
103_random = myrand.random
104_choice = myrand.choice
105_randint = myrand.randint
106
107
108LDS_ORD_BAPT_STATUS = (
109    LdsOrd.STATUS_NONE,
110    LdsOrd.STATUS_CHILD, LdsOrd.STATUS_CLEARED,
111    LdsOrd.STATUS_COMPLETED, LdsOrd.STATUS_INFANT,
112    LdsOrd.STATUS_PRE_1970, LdsOrd.STATUS_QUALIFIED,
113    LdsOrd.STATUS_STILLBORN, LdsOrd.STATUS_SUBMITTED,
114    LdsOrd.STATUS_UNCLEARED)
115
116LDS_ORD_CHILD_SEALING_STATUS = (
117    LdsOrd.STATUS_NONE,
118    LdsOrd.STATUS_BIC, LdsOrd.STATUS_CLEARED,
119    LdsOrd.STATUS_COMPLETED, LdsOrd.STATUS_DNS,
120    LdsOrd.STATUS_PRE_1970, LdsOrd.STATUS_QUALIFIED,
121    LdsOrd.STATUS_STILLBORN, LdsOrd.STATUS_SUBMITTED,
122    LdsOrd.STATUS_UNCLEARED)
123
124LDS_ENDOWMENT_DATE_STATUS = (
125    LdsOrd.STATUS_NONE,
126    LdsOrd.STATUS_CHILD, LdsOrd.STATUS_CLEARED,
127    LdsOrd.STATUS_COMPLETED, LdsOrd.STATUS_INFANT,
128    LdsOrd.STATUS_PRE_1970, LdsOrd.STATUS_QUALIFIED,
129    LdsOrd.STATUS_STILLBORN, LdsOrd.STATUS_SUBMITTED,
130    LdsOrd.STATUS_UNCLEARED)
131
132LDS_SPOUSE_SEALING_DATE_STATUS = (
133    LdsOrd.STATUS_NONE,
134    LdsOrd.STATUS_CANCELED, LdsOrd.STATUS_CLEARED,
135    LdsOrd.STATUS_COMPLETED, LdsOrd.STATUS_DNS,
136    LdsOrd.STATUS_DNS_CAN, LdsOrd.STATUS_PRE_1970,
137    LdsOrd.STATUS_QUALIFIED, LdsOrd.STATUS_SUBMITTED,
138    LdsOrd.STATUS_UNCLEARED)
139
140LDS_INDIVIDUAL_ORD = [(LdsOrd.BAPTISM, LDS_ORD_BAPT_STATUS),
141                      (LdsOrd.CONFIRMATION, LDS_ORD_BAPT_STATUS),
142                      (LdsOrd.ENDOWMENT, LDS_ENDOWMENT_DATE_STATUS),
143                      (LdsOrd.SEAL_TO_PARENTS, LDS_ORD_CHILD_SEALING_STATUS)]
144
145LDS_SPOUSE_SEALING = [(LdsOrd.SEAL_TO_SPOUSE,
146                       LDS_SPOUSE_SEALING_DATE_STATUS)]
147
148
149# ------------------------------------------------------------------------
150
151class TestcaseGenerator(tool.BatchTool):
152    '''
153    This tool generates various test cases for problems that have occured.
154    The issues it generates can be corrected via the 'Check and Repair' tool.
155    '''
156    NUMERIC = 0
157    FIRSTNAME = 1
158    FIRSTNAME_FEMALE = 2
159    FIRSTNAME_MALE = 3
160    LASTNAME = 4
161    NOTE = 5
162    SHORT = 6
163    LONG = 7
164    TAG = 8
165    STYLED_TEXT = 9
166
167#    GEDCON definition:
168#
169#    FAMILY_EVENT_STRUCTURE:=
170#    [
171#    n [ ANUL | CENS | DIV | DIVF ] [Y|<NULL>] {1:1}
172#    +1 <<EVENT_DETAIL>> {0:1} p.29
173#    |
174#    n [ ENGA | MARR | MARB | MARC ] [Y|<NULL>] {1:1}
175#    +1 <<EVENT_DETAIL>> {0:1} p.29
176#    |
177#    n [ MARL | MARS ] [Y|<NULL>] {1:1}
178#    +1 <<EVENT_DETAIL>> {0:1} p.29
179#    |
180#    n EVEN {1:1}
181#    +1 <<EVENT_DETAIL>> {0:1} p.29
182#    ]
183
184    FAMILY_EVENTS = set([
185        EventType.ANNULMENT,
186        EventType.CENSUS,
187        EventType.DIVORCE,
188        EventType.DIV_FILING,
189        EventType.ENGAGEMENT,
190        EventType.MARRIAGE,
191        EventType.MARR_BANNS,
192        EventType.MARR_CONTR,
193        EventType.MARR_LIC,
194        EventType.MARR_SETTL,
195        EventType.CUSTOM])
196
197    def __init__(self, dbstate, user, options_class, name, callback=None):
198        uistate = user.uistate
199        if uistate:
200            parent_window = uistate.window
201        else:
202            parent_window = None
203        self.progress = user.progress
204
205# ******** This ensures that a chunk of code below never executes!!!!
206        self.person = None
207
208        if dbstate.db.readonly:
209            return
210
211        tool.BatchTool.__init__(self, dbstate, user, options_class, name,
212                                parent=parent_window)
213
214        if self.fail:
215            return
216
217        self.options_dict = self.options.handler.options_dict
218        self.person_count = 0
219        self.max_person_count = self.options_dict['person_count']
220        self.persons_todo = []
221        self.parents_todo = []
222        self.person_dates = {}
223        self.generated_repos = []
224        self.generated_sources = []
225        self.generated_citations = []
226        self.generated_media = []
227        self.generated_places = []
228        self.generated_events = []
229        self.generated_families = []
230        self.generated_notes = []
231        self.generated_tags = []
232        self.text_serial_number = 1
233        self.trans = None
234
235        self.parent_places = {}
236        for type_num in range(1, 8):
237            self.parent_places[type_num] = []
238
239        # If an active persons exists the generated tree is connected to that
240        # person
241        if self.person:
242            # try to get birth and death year
243            try:
244                birth_h = self.person.get_birth_handle()
245                birth_e = self.db.get_event_from_handle(birth_h)
246                dat_o = birth_e.get_date_object()
247                birth = dat_o.get_year()
248            except AttributeError:
249                birth = None
250            try:
251                death_h = self.person.get_death_handle()
252                death_e = self.db.get_event_from_handle(death_h)
253                dat_o = death_e.get_date_object()
254                death = dat_o.get_year()
255            except AttributeError:
256                death = None
257            if not birth and not death:
258                birth = _randint(1700, 1900)
259            if birth and not death:
260                death = birth + _randint(20, 90)
261            if death and not birth:
262                birth = death - _randint(20, 90)
263            self.person_dates[self.person.get_handle()] = (birth, death)
264
265            self.persons_todo.append(self.person.get_handle())
266            self.parents_todo.append(self.person.get_handle())
267
268        if uistate:
269            self.init_gui(uistate)
270        else:
271            self.run_tool(cli=True)
272
273    def init_gui(self, uistate):
274        title = "%s - Gramps" % _("Generate testcases")
275        self.top = Gtk.Dialog(title, parent=uistate.window)
276        self.window = uistate.window
277        self.top.set_default_size(400, 150)
278        self.top.vbox.set_spacing(5)
279        label = Gtk.Label(label='<span size="larger" weight="bold">%s</span>'
280                          % _("Generate testcases"))
281        label.set_use_markup(True)
282        self.top.vbox.pack_start(label, 0, 0, 5)
283
284        self.check_lowlevel = Gtk.CheckButton(label=_(
285            "Generate low level database "
286            "errors\nCorrection needs database reload"))
287        self.check_lowlevel.set_active(self.options_dict['lowlevel'])
288        self.top.vbox.pack_start(self.check_lowlevel, 0, 0, 5)
289
290        self.check_bugs = Gtk.CheckButton(label=_("Generate database errors"))
291        self.check_bugs.set_active(self.options_dict['bugs'])
292        self.top.vbox.pack_start(self.check_bugs, 0, 0, 5)
293
294        self.check_persons = Gtk.CheckButton(label=_("Generate dummy data"))
295        self.check_persons.set_active(self.options_dict['persons'])
296        self.check_persons.connect('clicked', self.on_dummy_data_clicked)
297        self.top.vbox.pack_start(self.check_persons, 0, 0, 5)
298
299        self.check_longnames = Gtk.CheckButton(label=_("Generate long names"))
300        self.check_longnames.set_active(self.options_dict['long_names'])
301        self.top.vbox.pack_start(self.check_longnames, 0, 0, 5)
302
303        self.check_specialchars = Gtk.CheckButton(label=_(
304            "Add special characters"))
305        self.check_specialchars.set_active(self.options_dict['specialchars'])
306        self.top.vbox.pack_start(self.check_specialchars, 0, 0, 5)
307
308        self.check_serial = Gtk.CheckButton(label=_("Add serial number"))
309        self.check_serial.set_active(self.options_dict['add_serial'])
310        self.top.vbox.pack_start(self.check_serial, 0, 0, 5)
311
312        self.check_linebreak = Gtk.CheckButton(label=_("Add line break"))
313        self.check_linebreak.set_active(self.options_dict['add_linebreak'])
314        self.top.vbox.pack_start(self.check_linebreak, 0, 0, 5)
315
316        self.label = Gtk.Label(label=_(
317            "Number of people to generate\n"
318            "(Number is approximate because families are generated)"))
319        self.label.set_halign(Gtk.Align.START)
320        self.top.vbox.pack_start(self.label, 0, 0, 5)
321
322        self.entry_count = Gtk.Entry()
323        self.entry_count.set_text(str(self.max_person_count))
324        self.on_dummy_data_clicked(self.check_persons)
325        self.top.vbox.pack_start(self.entry_count, 0, 0, 5)
326
327        self.top.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
328        self.top.add_button(_('_OK'), Gtk.ResponseType.OK)
329        self.top.add_button(_('_Help'), Gtk.ResponseType.HELP)
330        self.top.show_all()
331
332        while True:
333            response = self.top.run()
334            if response == Gtk.ResponseType.HELP:
335                display_help(webpage=WIKI_HELP_PAGE,
336                             section=WIKI_HELP_SEC)
337            else:
338                break
339        self.options_dict['lowlevel'] = int(
340            self.check_lowlevel.get_active())
341        self.options_dict['bugs'] = int(
342            self.check_bugs.get_active())
343        self.options_dict['persons'] = int(
344            self.check_persons.get_active())
345        self.options_dict['long_names'] = int(
346            self.check_longnames.get_active())
347        self.options_dict['specialchars'] = int(
348            self.check_specialchars.get_active())
349        self.options_dict['add_serial'] = int(
350            self.check_serial.get_active())
351        self.options_dict['add_linebreak'] = int(
352            self.check_linebreak.get_active())
353        self.options_dict['person_count'] = int(
354            self.entry_count.get_text())
355        self.top.destroy()
356
357        if response == Gtk.ResponseType.OK:
358            self.run_tool(cli=False)
359            # Save options
360            self.options.handler.save_options()
361
362    def on_dummy_data_clicked(self, obj):
363        self.label.set_sensitive(obj.get_active())
364        self.entry_count.set_sensitive(obj.get_active())
365
366    def run_tool(self, cli=False):
367        self.cli = cli
368        if not cli:
369            while Gtk.events_pending():
370                Gtk.main_iteration()
371        else:
372            self.window = None
373
374        self.transaction_count = 0
375
376        if self.options_dict['lowlevel']:
377            with self.progress(_('Generating testcases'),
378                               _('Generating low level database errors'),
379                               1) as step:
380                self.test_low_level()
381                step()
382
383        if self.options_dict['bugs'] or self.options_dict['persons']:
384            self.generate_tags()
385
386        if self.options_dict['bugs']:
387            with self.progress(_('Generating testcases'),
388                               _('Generating database errors'),
389                               20) as step:
390                self.generate_data_errors(step)
391
392        if self.options_dict['persons']:
393            with self.progress(_('Generating testcases'),
394                               _('Generating families'),
395                               self.max_person_count) \
396                               as self.progress_step:
397                self.person_count = 0
398
399                while True:
400                    if not self.persons_todo:
401                        pers_h = self.generate_person(0)
402                        self.persons_todo.append(pers_h)
403                        self.parents_todo.append(pers_h)
404                    person_h = self.persons_todo.pop(0)
405                    self.generate_family(person_h)
406                    if _randint(0, 3) == 0:
407                        self.generate_family(person_h)
408                    if _randint(0, 7) == 0:
409                        self.generate_family(person_h)
410                    if self.person_count > self.max_person_count:
411                        break
412                    for child_h in self.parents_todo:
413                        self.generate_parents(child_h)
414                        if self.person_count > self.max_person_count:
415                            break
416
417        if not cli:
418            self.top.destroy()
419
420    def generate_data_errors(self, step):
421        """This generates errors in the database to test src/plugins/tool/Check
422        The module names correspond to the checking methods in
423        src/plugins/tool/Check.CheckIntegrity """
424        # The progress meter is normally stepped every time a person is
425        # generated by generate_person. However in this case, generate_person
426        # is called by some of the constituent functions, but we only want the
427        # meter to be stepped every time a test function has been completed.
428        self.progress_step = lambda: None
429
430        self.test_fix_encoding()
431        step()
432        self.test_fix_ctrlchars_in_notes()
433        step()
434        self.test_fix_alt_place_names()
435        step()
436        self.test_fix_duplicated_grampsid()
437        step()
438        self.test_clean_deleted_name_format()
439        step()
440        self.test_cleanup_empty_objects()
441        step()
442        self.test_chk_for_broke_family_link()
443        step()
444        self.test_check_parent_relationships()
445        step()
446        self.test_cleanup_empty_families()
447        step()
448        self.test_cleanup_duplicate_spouses()
449        step()
450        self.test_check_events()
451        step()
452        self.test_check_person_references()
453        step()
454        self.test_check_family_references()
455        step()
456        self.test_check_place_references()
457        step()
458        self.test_check_source_references()
459        step()
460        self.test_check_citation_references()
461        step()
462        self.test_check_media_references()
463        step()
464        self.test_check_repo_references()
465        step()
466        self.test_check_note_references()
467        step()
468
469    def test_low_level(self):
470        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
471                   self.db) as self.trans:
472            self.transaction_count += 1
473
474            obj = Note()
475            obj.set("dup 1" + self.rand_text(self.NOTE))
476            obj.set_format(_choice((Note.FLOWED, Note.FORMATTED)))
477            obj.set_type(self.rand_type(NoteType()))
478            self.db.add_note(obj, self.trans)
479            print("object %s, handle %s, Gramps_Id %s" % (obj, obj.handle,
480                                                          obj.gramps_id))
481
482            handle = obj.get_handle()
483
484            src = Source()
485            src.set_title("dup 2" + self.rand_text(self.SHORT))
486            if _randint(0, 1) == 1:
487                src.set_author(self.rand_text(self.SHORT))
488            if _randint(0, 1) == 1:
489                src.set_publication_info(self.rand_text(self.LONG))
490            if _randint(0, 1) == 1:
491                src.set_abbreviation(self.rand_text(self.SHORT))
492            while _randint(0, 1) == 1:
493                sattr = SrcAttribute()
494                sattr.set_type(self.rand_text(self.SHORT))
495                sattr.set_value(self.rand_text(self.SHORT))
496                src.add_attribute(sattr)
497            src.set_handle(handle)
498            self.db.add_source(src, self.trans)
499            print("object %s, handle %s, Gramps_Id %s" % (src, src.handle,
500                                                          src.gramps_id))
501
502    def test_fix_encoding(self):
503        """ Creates a media object with character encoding errors. This tests
504        Check.fix_encoding() and also cleanup_missing_photos
505        """
506        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
507                   self.db) as self.trans:
508            self.transaction_count += 1
509
510            med = Media()
511            self.fill_object(med)
512            med.set_description("leave this media object invalid description"
513                                "\x9f")
514            med.set_path("/tmp/click_on_keep_reference.png\x9f")
515            med.set_mime_type("image/png\x9f")
516            self.db.add_media(med, self.trans)
517
518            med = Media()
519            self.fill_object(med)
520            med.set_description("reselect this media object invalid "
521                                "description\x9f")
522            med.set_path("/tmp/click_on_select_file.png\x9f")
523            med.set_mime_type("image/png\x9f")
524            self.db.add_media(med, self.trans)
525
526            # setup media attached to Source and Citation to be removed
527
528            med = Media()
529            self.fill_object(med)
530            med.set_description('remove this media object')
531            med.set_path("/tmp/click_on_remove_object.png")
532            med.set_mime_type("image/png")
533            self.db.add_media(med, self.trans)
534
535            src = Source()
536            src.set_title('media should be removed from this source')
537            ref = MediaRef()
538            ref.set_reference_handle(med.handle)
539            src.add_media_reference(ref)
540            self.db.add_source(src, self.trans)
541
542            cit = Citation()
543            self.fill_object(cit)
544            cit.set_reference_handle(src.handle)
545            cit.set_page('media should be removed from this citation')
546            ref = MediaRef()
547            ref.set_reference_handle(med.handle)
548            cit.add_media_reference(ref)
549            self.db.add_citation(cit, self.trans)
550
551    def test_fix_ctrlchars_in_notes(self):
552        """ Creates a note with control characters. This tests
553        Check.fix_ctrlchars_in_notes()
554        """
555        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
556                   self.db) as self.trans:
557            self.transaction_count += 1
558
559            obj = Note()
560            obj.set("This is a text note with a \x03 control character")
561            obj.set_format(_choice((Note.FLOWED, Note.FORMATTED)))
562            obj.set_type(self.rand_type(NoteType()))
563            self.db.add_note(obj, self.trans)
564
565    def test_fix_alt_place_names(self):
566        """
567        Creates a place with a duplicate of primary in alt_names,
568        a blank alt_name, and a duplicate of one of the alt_names. Also
569        include two alt names that are almost duplicates, but not quite.
570        This tests Check.fix_alt_place_names()
571        """
572        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
573                   self.db) as self.trans:
574            self.transaction_count += 1
575
576            plac = Place()
577            pri_name = PlaceName()
578            pri_name.set_value("Primary name")
579            alt_name1 = PlaceName()
580            alt_name1.set_value("Alt name 1")
581            alt_name2 = PlaceName()
582            alt_name2.set_value("Alt name 1")
583            alt_name2.set_language("testish")
584            alt_name3 = PlaceName()
585            alt_name3.set_value("Alt name 1")
586            alt_name3.set_date_object(Today())
587            alt_names = [pri_name, alt_name1, alt_name1, PlaceName(),
588                         alt_name2, alt_name3]
589            plac.set_name(pri_name)
590            plac.set_alternative_names(alt_names)
591            self.db.add_place(plac, self.trans)
592
593    def test_fix_duplicated_grampsid(self):
594        """
595        Create some duplicate Gramps IDs in various object types
596        This tests Check.fix_duplicated_grampsid()
597        """
598        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
599                   self.db) as self.trans:
600            self.transaction_count += 1
601            for dummy in range(0, 2):
602                cit = Citation()
603                self.fill_object(cit)
604                cit.set_gramps_id("C1001")
605                self.db.add_citation(cit, self.trans)
606
607                evt = Event()
608                self.fill_object(evt)
609                evt.set_gramps_id("E1001")
610                self.db.add_event(evt, self.trans)
611
612                person1_h = self.generate_person(
613                    Person.MALE, "Smith",
614                    "Dup Gramps ID test F1001")
615                person2_h = self.generate_person(Person.FEMALE, "Jones", None)
616                fam = Family()
617                fam.set_father_handle(person1_h)
618                fam.set_mother_handle(person2_h)
619                fam.set_relationship((FamilyRelType.MARRIED, ''))
620                fam.set_gramps_id("F1001")
621                fam_h = self.db.add_family(fam, self.trans)
622                person1 = self.db.get_person_from_handle(person1_h)
623                person1.add_family_handle(fam_h)
624                self.db.commit_person(person1, self.trans)
625                person2 = self.db.get_person_from_handle(person2_h)
626                person2.add_family_handle(fam_h)
627                self.db.commit_person(person2, self.trans)
628
629                med = Media()
630                self.fill_object(med)
631                med.set_gramps_id("O1001")
632                self.db.add_media(med, self.trans)
633
634                note = Note()
635                self.fill_object(note)
636                note.set_gramps_id("N1001")
637                self.db.add_note(note, self.trans)
638
639                person1_h = self.generate_person(Person.MALE, "Smith",
640                                                 "Dup GID test GID I1001")
641                person1 = self.db.get_person_from_handle(person1_h)
642                person1.set_gramps_id("I1001")
643                self.db.commit_person(person1, self.trans)
644
645                place = Place()
646                self.fill_object(place)
647                place.set_gramps_id("P1001")
648                self.db.add_place(place, self.trans)
649
650                rep = Repository()
651                self.fill_object(rep)
652                rep.set_gramps_id("R1001")
653                self.db.add_repository(rep, self.trans)
654
655                src = Source()
656                self.fill_object(src)
657                src.set_gramps_id("S1001")
658                self.db.add_source(src, self.trans)
659
660    def test_cleanup_missing_photos(self):
661        pass
662
663    def test_clean_deleted_name_format(self):
664        pass
665
666    def test_cleanup_empty_objects(self):
667        """ Generate empty objects to test their deletion """
668        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
669                   self.db) as self.trans:
670            self.transaction_count += 1
671
672            pers = Person()
673            self.db.add_person(pers, self.trans)
674
675            fam = Family()
676            self.db.add_family(fam, self.trans)
677
678            evt = Event()
679            self.db.add_event(evt, self.trans)
680
681            place = Place()
682            self.db.add_place(place, self.trans)
683
684            src = Source()
685            self.db.add_source(src, self.trans)
686
687            cit = Citation()
688            self.db.add_citation(cit, self.trans)
689
690            med = Media()
691            self.db.add_media(med, self.trans)
692
693            ref = Repository()
694            self.db.add_repository(ref, self.trans)
695
696            note = Note()
697            self.db.add_note(note, self.trans)
698
699    def test_chk_for_broke_family_link(self):
700        """ Create various family related errors """
701        # Create a family, that links to father and mother, but father does not
702        # link back
703        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
704                   self.db) as self.trans:
705            self.transaction_count += 1
706            person1_h = self.generate_person(
707                Person.MALE, "Broken1",
708                "Family links to this person, but person does not link back")
709            person2_h = self.generate_person(Person.FEMALE, "Broken1", None)
710            fam = Family()
711            fam.set_father_handle(person1_h)
712            fam.set_mother_handle(person2_h)
713            fam.set_relationship((FamilyRelType.MARRIED, ''))
714            fam_h = self.db.add_family(fam, self.trans)
715            # person1 = self.db.get_person_from_handle(person1_h)
716            # person1.add_family_handle(fam_h)
717            # self.db.commit_person(person1, self.trans)
718            person2 = self.db.get_person_from_handle(person2_h)
719            person2.add_family_handle(fam_h)
720            self.db.commit_person(person2, self.trans)
721
722        # Create a family, that misses the link to the father
723        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
724                   self.db) as self.trans:
725            self.transaction_count += 1
726            person1_h = self.generate_person(Person.MALE, "Broken2", None)
727            person2_h = self.generate_person(Person.FEMALE, "Broken2", None)
728            fam = Family()
729            # fam.set_father_handle(person1_h)
730            fam.set_mother_handle(person2_h)
731            fam.set_relationship((FamilyRelType.MARRIED, ''))
732            fam_h = self.db.add_family(fam, self.trans)
733            person1 = self.db.get_person_from_handle(person1_h)
734            person1.add_family_handle(fam_h)
735            self.db.commit_person(person1, self.trans)
736            person2 = self.db.get_person_from_handle(person2_h)
737            person2.add_family_handle(fam_h)
738            self.db.commit_person(person2, self.trans)
739
740        # Create a family, that misses the link to the mother
741        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
742                   self.db) as self.trans:
743            self.transaction_count += 1
744            person1_h = self.generate_person(Person.MALE, "Broken3", None)
745            person2_h = self.generate_person(Person.FEMALE, "Broken3", None)
746            fam = Family()
747            fam.set_father_handle(person1_h)
748            # fam.set_mother_handle(person2_h)
749            fam.set_relationship((FamilyRelType.MARRIED, ''))
750            fam_h = self.db.add_family(fam, self.trans)
751            person1 = self.db.get_person_from_handle(person1_h)
752            person1.add_family_handle(fam_h)
753            self.db.commit_person(person1, self.trans)
754            person2 = self.db.get_person_from_handle(person2_h)
755            person2.add_family_handle(fam_h)
756            self.db.commit_person(person2, self.trans)
757
758        # Create a family, that links to father and mother, but mother does not
759        # link back
760        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
761                   self.db) as self.trans:
762            self.transaction_count += 1
763            person1_h = self.generate_person(Person.MALE, "Broken4", None)
764            person2_h = self.generate_person(
765                Person.FEMALE, "Broken4",
766                "Family links to this person, but person does not link back")
767            fam = Family()
768            fam.set_father_handle(person1_h)
769            fam.set_mother_handle(person2_h)
770            fam.set_relationship((FamilyRelType.MARRIED, ''))
771            fam_h = self.db.add_family(fam, self.trans)
772            person1 = self.db.get_person_from_handle(person1_h)
773            person1.add_family_handle(fam_h)
774            self.db.commit_person(person1, self.trans)
775            # person2 = self.db.get_person_from_handle(person2_h)
776            # person2.add_family_handle(fam_h)
777            # self.db.commit_person(person2, self.trans)
778
779        # Create two married people of same sex.
780        # This is NOT detected as an error by plugins/tool/Check.py
781        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
782                   self.db) as self.trans:
783            self.transaction_count += 1
784            person1_h = self.generate_person(Person.MALE, "Broken5", None)
785            person2_h = self.generate_person(Person.MALE, "Broken5", None)
786            fam = Family()
787            fam.set_father_handle(person1_h)
788            fam.set_mother_handle(person2_h)
789            fam.set_relationship((FamilyRelType.MARRIED, ''))
790            fam_h = self.db.add_family(fam, self.trans)
791            person1 = self.db.get_person_from_handle(person1_h)
792            person1.add_family_handle(fam_h)
793            self.db.commit_person(person1, self.trans)
794            person2 = self.db.get_person_from_handle(person2_h)
795            person2.add_family_handle(fam_h)
796            self.db.commit_person(person2, self.trans)
797
798        # Create a family, that contains an invalid handle to for the father
799        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
800                   self.db) as self.trans:
801            self.transaction_count += 1
802            # person1_h = self.generate_person(Person.MALE, "Broken6", None)
803            person2_h = self.generate_person(Person.FEMALE, "Broken6", None)
804            fam = Family()
805            fam.set_father_handle("InvalidHandle1")
806            fam.set_mother_handle(person2_h)
807            fam.set_relationship((FamilyRelType.MARRIED, ''))
808            fam_h = self.db.add_family(fam, self.trans)
809            # person1 = self.db.get_person_from_handle(person1_h)
810            # person1.add_family_handle(fam_h)
811            # self.db.commit_person(person1, self.trans)
812            person2 = self.db.get_person_from_handle(person2_h)
813            person2.add_family_handle(fam_h)
814            self.db.commit_person(person2, self.trans)
815
816        # Create a family, that contains an invalid handle to for the mother
817        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
818                   self.db) as self.trans:
819            self.transaction_count += 1
820            person1_h = self.generate_person(Person.MALE, "Broken7", None)
821            # person2_h = self.generate_person(Person.FEMALE, "Broken7", None)
822            fam = Family()
823            fam.set_father_handle(person1_h)
824            fam.set_mother_handle("InvalidHandle2")
825            fam.set_relationship((FamilyRelType.MARRIED, ''))
826            fam_h = self.db.add_family(fam, self.trans)
827            person1 = self.db.get_person_from_handle(person1_h)
828            person1.add_family_handle(fam_h)
829            self.db.commit_person(person1, self.trans)
830            # person2 = self.db.get_person_from_handle(person2_h)
831            # person2.add_family_handle(fam_h)
832            # self.db.commit_person(person2, self.trans)
833
834        # Creates a family where the child does not link back to the family
835        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
836                   self.db) as self.trans:
837            self.transaction_count += 1
838            person1_h = self.generate_person(Person.MALE, "Broken8", None)
839            person2_h = self.generate_person(Person.FEMALE, "Broken8", None)
840            child_h = self.generate_person(None, "Broken8", None)
841            fam = Family()
842            fam.set_father_handle(person1_h)
843            fam.set_mother_handle(person2_h)
844            fam.set_relationship((FamilyRelType.MARRIED, ''))
845            child_ref = ChildRef()
846            child_ref.set_reference_handle(child_h)
847            self.fill_object(child_ref)
848            fam.add_child_ref(child_ref)
849            fam_h = self.db.add_family(fam, self.trans)
850            person1 = self.db.get_person_from_handle(person1_h)
851            person1.add_family_handle(fam_h)
852            self.db.commit_person(person1, self.trans)
853            person2 = self.db.get_person_from_handle(person2_h)
854            person2.add_family_handle(fam_h)
855            self.db.commit_person(person2, self.trans)
856            # child = self.db.get_person_from_handle(child_h)
857            # person2.add_parent_family_handle(fam_h)
858            # self.db.commit_person(child, self.trans)
859
860        # Creates a family where the child is not linked, but the child links
861        # to the family
862        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
863                   self.db) as self.trans:
864            self.transaction_count += 1
865            person1_h = self.generate_person(Person.MALE, "Broken9", None)
866            person2_h = self.generate_person(Person.FEMALE, "Broken9", None)
867            child_h = self.generate_person(None, "Broken9", None)
868            fam = Family()
869            fam.set_father_handle(person1_h)
870            fam.set_mother_handle(person2_h)
871            fam.set_relationship((FamilyRelType.MARRIED, ''))
872            # child_ref = ChildRef()
873            # child_ref.set_reference_handle(child_h)
874            # self.fill_object(child_ref)
875            # fam.add_child_ref(child_ref)
876            fam_h = self.db.add_family(fam, self.trans)
877            person1 = self.db.get_person_from_handle(person1_h)
878            person1.add_family_handle(fam_h)
879            self.db.commit_person(person1, self.trans)
880            person2 = self.db.get_person_from_handle(person2_h)
881            person2.add_family_handle(fam_h)
882            self.db.commit_person(person2, self.trans)
883            child = self.db.get_person_from_handle(child_h)
884            child.add_parent_family_handle(fam_h)
885            self.db.commit_person(child, self.trans)
886
887        # Creates a family where the child is one of the parents
888        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
889                   self.db) as self.trans:
890            self.transaction_count += 1
891            person1_h = self.generate_person(Person.MALE, "Broken19", None)
892            person2_h = self.generate_person(Person.FEMALE, "Broken19", None)
893            child_h = person2_h
894            fam = Family()
895            fam.set_father_handle(person1_h)
896            fam.set_mother_handle(person2_h)
897            fam.set_relationship((FamilyRelType.MARRIED, ''))
898            child_ref = ChildRef()
899            child_ref.set_reference_handle(child_h)
900            self.fill_object(child_ref)
901            fam.add_child_ref(child_ref)
902            fam_h = self.db.add_family(fam, self.trans)
903            person1 = self.db.get_person_from_handle(person1_h)
904            person1.add_family_handle(fam_h)
905            self.db.commit_person(person1, self.trans)
906            person2 = self.db.get_person_from_handle(person2_h)
907            person2.add_family_handle(fam_h)
908            self.db.commit_person(person2, self.trans)
909            child = self.db.get_person_from_handle(child_h)
910            child.add_parent_family_handle(fam_h)
911            self.db.commit_person(child, self.trans)
912
913        # Creates a couple that refer to a family that does not exist in the
914        # database.
915        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
916                   self.db) as self.trans:
917            self.transaction_count += 1
918            person1_h = self.generate_person(Person.MALE, "Broken20", None)
919            person2_h = self.generate_person(Person.FEMALE, "Broken20", None)
920#            fam = Family()
921#            fam.set_father_handle(person1_h)
922#            fam.set_mother_handle(person2_h)
923#            fam.set_relationship((FamilyRelType.MARRIED, ''))
924#            child_ref = ChildRef()
925#            # child_ref.set_reference_handle(child_h)
926#            # self.fill_object(child_ref)
927#            # fam.add_child_ref(child_ref)
928#            fam_h = self.db.add_family(fam, self.trans)
929            person1 = self.db.get_person_from_handle(person1_h)
930            person1.add_family_handle("InvalidHandle3")
931            self.db.commit_person(person1, self.trans)
932            person2 = self.db.get_person_from_handle(person2_h)
933            person2.add_family_handle("InvalidHandle3")
934            self.db.commit_person(person2, self.trans)
935#            child = self.db.get_person_from_handle(child_h)
936#            child.add_parent_family_handle(fam_h)
937#            self.db.commit_person(child, self.trans)
938
939    def test_check_parent_relationships(self):
940        pass
941
942    def test_cleanup_empty_families(self):
943        pass
944
945    def test_cleanup_duplicate_spouses(self):
946        pass
947
948    def test_check_events(self):
949        """ Various event related tests """
950        # Creates a person having a non existing birth event handle set
951        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
952                   self.db) as self.trans:
953            self.transaction_count += 1
954            person_h = self.generate_person(None, "Broken11", None)
955            person = self.db.get_person_from_handle(person_h)
956            event_ref = EventRef()
957            event_ref.set_reference_handle("InvalidHandle4")
958            person.set_birth_ref(event_ref)
959            self.db.commit_person(person, self.trans)
960
961        # Creates a person having a non existing death event handle set
962        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
963                   self.db) as self.trans:
964            self.transaction_count += 1
965            person_h = self.generate_person(None, "Broken12", None)
966            person = self.db.get_person_from_handle(person_h)
967            event_ref = EventRef()
968            event_ref.set_reference_handle("InvalidHandle5")
969            person.set_death_ref(event_ref)
970            self.db.commit_person(person, self.trans)
971
972        # Creates a person having a non existing event handle set
973        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
974                   self.db) as self.trans:
975            self.transaction_count += 1
976            person_h = self.generate_person(None, "Broken13", None)
977            person = self.db.get_person_from_handle(person_h)
978            event_ref = EventRef()
979            event_ref.set_reference_handle("InvalidHandle6")
980            person.add_event_ref(event_ref)
981            self.db.commit_person(person, self.trans)
982
983        # Creates a person with a birth event having an empty type
984        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
985                   self.db) as self.trans:
986            self.transaction_count += 1
987            person_h = self.generate_person(None, "Broken14", None)
988            event = Event()
989            # The default type _DEFAULT = BIRTH is set in eventtype
990            event.set_type('')
991            event.set_description("Test for Broken14")
992            event_h = self.db.add_event(event, self.trans)
993            event_ref = EventRef()
994            event_ref.set_reference_handle(event_h)
995            person = self.db.get_person_from_handle(person_h)
996            person.set_birth_ref(event_ref)
997            self.db.commit_person(person, self.trans)
998
999        # Creates a person with a death event having an empty type
1000        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
1001                   self.db) as self.trans:
1002            self.transaction_count += 1
1003            person_h = self.generate_person(None, "Broken15", None)
1004            event = Event()
1005            # The default type _DEFAULT = BIRTH is set in eventtype
1006            event.set_type('')
1007            event.set_description("Test for Broken15")
1008            event_h = self.db.add_event(event, self.trans)
1009            event_ref = EventRef()
1010            event_ref.set_reference_handle(event_h)
1011            person = self.db.get_person_from_handle(person_h)
1012            person.set_death_ref(event_ref)
1013            self.db.commit_person(person, self.trans)
1014
1015        # Creates a person with an event having an empty type
1016        # This is NOT detected as an error by plugins/tool/Check.py
1017        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
1018                   self.db) as self.trans:
1019            self.transaction_count += 1
1020            person_h = self.generate_person(None, "Broken16", None)
1021            event = Event()
1022            # The default type _DEFAULT = BIRTH is set in eventtype
1023            event.set_type('')
1024            event.set_description("Test for Broken16")
1025            event_h = self.db.add_event(event, self.trans)
1026            event_ref = EventRef()
1027            event_ref.set_reference_handle(event_h)
1028            person = self.db.get_person_from_handle(person_h)
1029            person.add_event_ref(event_ref)
1030            self.db.commit_person(person, self.trans)
1031
1032    def test_check_person_references(self):
1033        pass
1034
1035    def test_check_family_references(self):
1036        pass
1037
1038    def test_check_place_references(self):
1039        """ Tests various place reference errors """
1040        # Creates a person with a birth event pointing to nonexisting place
1041        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
1042                   self.db) as self.trans:
1043            self.transaction_count += 1
1044            person_h = self.generate_person(None, "Broken17", None)
1045            event = Event()
1046            event.set_type(EventType.BIRTH)
1047            event.set_place_handle("InvalidHandle7")
1048            event.set_description("Test for Broken17")
1049            event_h = self.db.add_event(event, self.trans)
1050            event_ref = EventRef()
1051            event_ref.set_reference_handle(event_h)
1052            person = self.db.get_person_from_handle(person_h)
1053            person.set_birth_ref(event_ref)
1054            self.db.commit_person(person, self.trans)
1055
1056        # Creates a person with an event pointing to nonexisting place
1057        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
1058                   self.db) as self.trans:
1059            self.transaction_count += 1
1060            person_h = self.generate_person(None, "Broken18", None)
1061            event = Event()
1062            event.set_type(EventType.BIRTH)
1063            event.set_place_handle("InvalidHandle8")
1064            event.set_description("Test for Broken18")
1065            event_h = self.db.add_event(event, self.trans)
1066            event_ref = EventRef()
1067            event_ref.set_reference_handle(event_h)
1068            person = self.db.get_person_from_handle(person_h)
1069            person.add_event_ref(event_ref)
1070            self.db.commit_person(person, self.trans)
1071
1072    def test_check_source_references(self):
1073        """ Tests various source reference errors """
1074        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
1075                   self.db) as self.trans:
1076            self.transaction_count += 1
1077
1078            cit = Citation()
1079            self.fill_object(cit)
1080            cit.set_reference_handle("unknownsourcehandle")
1081            cit.set_page('unreferenced citation with invalid source ref')
1082            self.db.add_citation(cit, self.trans)
1083
1084            cit = Citation()
1085            self.fill_object(cit)
1086            cit.set_reference_handle(None)
1087            cit.set_page('unreferenced citation with invalid source ref')
1088            self.db.add_citation(cit, self.trans)
1089
1090            cit = Citation()
1091            self.fill_object(cit)
1092            cit.set_reference_handle("unknownsourcehandle")
1093            cit.set_page('citation and references to it should be removed')
1094            c_h1 = self.db.add_citation(cit, self.trans)
1095
1096            cit = Citation()
1097            self.fill_object(cit)
1098            cit.set_reference_handle(None)
1099            cit.set_page('citation and references to it should be removed')
1100            c_h2 = self.db.add_citation(cit, self.trans)
1101
1102            self.create_all_possible_citations([c_h1, c_h2], "Broken21",
1103                                               'non-existent source')
1104
1105    def test_check_citation_references(self):
1106        """ Generate objects that refer to non-existant citations """
1107        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
1108                   self.db) as self.trans:
1109            self.transaction_count += 1
1110
1111            c_h = "unknowncitationhandle"
1112            self.create_all_possible_citations([c_h, ''], "Broken22",
1113                                               'non-existent citation')
1114
1115    def create_all_possible_citations(self, c_h_list, name, message):
1116        """ Create citations attached to each of the following objects:
1117            Person
1118             Name
1119             Address
1120             Attribute
1121             PersonRef
1122             MediaRef
1123              Attribute
1124             LdsOrd
1125
1126            Family
1127             Attribute
1128             ChildRef
1129             MediaRef
1130              Attribute
1131             LdsOrd
1132
1133            Event
1134             Attribute
1135             MediaRef
1136              Attribute
1137
1138            Media
1139             Attribute
1140
1141            Place
1142             MediaRef
1143              Attribute
1144
1145            Repository (Repositories themselves do not have SourceRefs)
1146             Address
1147        """
1148        med = Media()
1149        med.set_description(message)
1150        med.set_path(os.path.abspath(str(ICON)))
1151        med.set_mime_type(get_type(med.get_path()))
1152        med.add_citation(_choice(c_h_list))
1153        # Media : Attribute
1154        att = Attribute()
1155        att.set_type(self.rand_type(AttributeType()))
1156        att.set_value(message)
1157        att.add_citation(_choice(c_h_list))
1158        med.add_attribute(att)
1159        self.db.add_media(med, self.trans)
1160
1161        person1_h = self.generate_person(Person.MALE, name, None)
1162        person2_h = self.generate_person(Person.FEMALE, name, None)
1163        child_h = self.generate_person(None, name, None)
1164        fam = Family()
1165        fam.set_father_handle(person1_h)
1166        fam.set_mother_handle(person2_h)
1167        fam.set_relationship((FamilyRelType.MARRIED, ''))
1168        # Family
1169        fam.add_citation(_choice(c_h_list))
1170        # Family : Attribute
1171        att = Attribute()
1172        att.set_type(self.rand_type(AttributeType()))
1173        att.set_value(message)
1174        att.add_citation(_choice(c_h_list))
1175        fam.add_attribute(att)
1176        # Family : ChildRef
1177        child_ref = ChildRef()
1178        child_ref.set_reference_handle(child_h)
1179        self.fill_object(child_ref)
1180        child_ref.add_citation(_choice(c_h_list))
1181        fam.add_child_ref(child_ref)
1182        # Family : MediaRef
1183        mref = MediaRef()
1184        mref.set_reference_handle(med.handle)
1185        mref.add_citation(_choice(c_h_list))
1186        # Family : MediaRef : Attribute
1187        att = Attribute()
1188        att.set_type(self.rand_type(AttributeType()))
1189        att.set_value(message)
1190        att.add_citation(_choice(c_h_list))
1191        mref.add_attribute(att)
1192        fam.add_media_reference(mref)
1193        # Family : LDSORD
1194        ldsord = LdsOrd()
1195        self.fill_object(ldsord)
1196        # TODO: adapt type and status to family/person
1197        # if isinstance(obj, Person):
1198        # if isinstance(obj, Family):
1199        # pylint: disable=protected-access
1200        ldsord.set_type(_choice([item[0] for item in LdsOrd._TYPE_MAP]))
1201        ldsord.set_status(_randint(0, len(LdsOrd._STATUS_MAP) - 1))
1202        ldsord.add_citation(_choice(c_h_list))
1203        fam.add_lds_ord(ldsord)
1204        # Family : EventRef
1205        evt = Event()
1206        evt.set_type(EventType.MARRIAGE)
1207        (dummy, date) = self.rand_date()
1208        evt.set_date_object(date)
1209        evt.set_description(message)
1210        event_h = self.db.add_event(evt, self.trans)
1211        eref = EventRef()
1212        eref.set_reference_handle(event_h)
1213        eref.set_role(self.rand_type(EventRoleType()))
1214        # Family : EventRef : Attribute
1215        att = Attribute()
1216        att.set_type(self.rand_type(AttributeType()))
1217        att.set_value(message)
1218        att.add_citation(_choice(c_h_list))
1219        eref.add_attribute(att)
1220        fam.add_event_ref(eref)
1221        fam_h = self.db.add_family(fam, self.trans)
1222        person1 = self.db.get_person_from_handle(person1_h)
1223        person1.add_family_handle(fam_h)
1224        # Person
1225        person1.add_citation(_choice(c_h_list))
1226        # Person : Name
1227        alt_name = Name(person1.get_primary_name())
1228        alt_name.set_first_name(message)
1229        alt_name.add_citation(_choice(c_h_list))
1230        person1.add_alternate_name(alt_name)
1231        # Person : Address
1232        add = Address()
1233        add.set_street(message)
1234        add.add_citation(_choice(c_h_list))
1235        person1.add_address(add)
1236        # Person : Attribute
1237        att = Attribute()
1238        att.set_type(self.rand_type(AttributeType()))
1239        att.set_value(message)
1240        att.add_citation(_choice(c_h_list))
1241        person1.add_attribute(att)
1242        # Person : PersonRef
1243        asso_h = self.generate_person()
1244        asso = PersonRef()
1245        asso.set_reference_handle(asso_h)
1246        asso.set_relation(self.rand_text(self.SHORT))
1247        self.fill_object(asso)
1248        asso.add_citation(_choice(c_h_list))
1249        person1.add_person_ref(asso)
1250        # Person : MediaRef
1251        mref = MediaRef()
1252        mref.set_reference_handle(med.handle)
1253        mref.add_citation(_choice(c_h_list))
1254        # Person : MediaRef : Attribute
1255        att = Attribute()
1256        att.set_type(self.rand_type(AttributeType()))
1257        att.set_value(self.rand_text(self.SHORT))
1258        att.add_citation(_choice(c_h_list))
1259        mref.add_attribute(att)
1260        person1.add_media_reference(mref)
1261        # Person : LDSORD
1262        ldsord = LdsOrd()
1263        self.fill_object(ldsord)
1264        # TODO: adapt type and status to family/person
1265        # if isinstance(obj, Person):
1266        # if isinstance(obj, Family):
1267        ldsord.set_type(_choice(
1268            [item[0] for item in LdsOrd._TYPE_MAP]))
1269        ldsord.set_status(_randint(0, len(LdsOrd._STATUS_MAP) - 1))
1270        ldsord.add_citation(_choice(c_h_list))
1271        person1.add_lds_ord(ldsord)
1272        # Person : EventRef
1273        evt = Event()
1274        evt.set_type(EventType.ELECTED)
1275        (dummy, dat) = self.rand_date()
1276        evt.set_date_object(dat)
1277        evt.set_description(message)
1278        event_h = self.db.add_event(evt, self.trans)
1279        eref = EventRef()
1280        eref.set_reference_handle(event_h)
1281        eref.set_role(self.rand_type(EventRoleType()))
1282        # Person : EventRef : Attribute
1283        att = Attribute()
1284        att.set_type(self.rand_type(AttributeType()))
1285        att.set_value(message)
1286        att.add_citation(_choice(c_h_list))
1287        eref.add_attribute(att)
1288        person1.add_event_ref(eref)
1289        self.db.commit_person(person1, self.trans)
1290        person2 = self.db.get_person_from_handle(person2_h)
1291        person2.add_family_handle(fam_h)
1292        self.db.commit_person(person2, self.trans)
1293
1294        evt = Event()
1295        evt.set_description(message)
1296        evt.set_type(EventType.MARRIAGE)
1297        # Event
1298        evt.add_citation(_choice(c_h_list))
1299        # Event : Attribute
1300        att = Attribute()
1301        att.set_type(self.rand_type(AttributeType()))
1302        att.set_value(message)
1303        att.add_citation(_choice(c_h_list))
1304        evt.add_attribute(att)
1305        # Event : MediaRef
1306        mref = MediaRef()
1307        mref.set_reference_handle(med.handle)
1308        mref.add_citation(_choice(c_h_list))
1309        # Event : MediaRef : Attribute
1310        att = Attribute()
1311        att.set_type(self.rand_type(AttributeType()))
1312        att.set_value(self.rand_text(self.SHORT))
1313        att.add_citation(_choice(c_h_list))
1314        mref.add_attribute(att)
1315        evt.add_media_reference(mref)
1316        self.db.add_event(evt, self.trans)
1317
1318        place = Place()
1319        place.set_title(message)
1320        place.add_citation(_choice(c_h_list))
1321        # Place : MediaRef
1322        mref = MediaRef()
1323        mref.set_reference_handle(med.handle)
1324        mref.add_citation(_choice(c_h_list))
1325        # Place : MediaRef : Attribute
1326        att = Attribute()
1327        att.set_type(self.rand_type(AttributeType()))
1328        att.set_value(self.rand_text(self.SHORT))
1329        att.add_citation(_choice(c_h_list))
1330        mref.add_attribute(att)
1331        place.add_media_reference(mref)
1332        self.db.add_place(place, self.trans)
1333
1334        ref = Repository()
1335        ref.set_name(message)
1336        ref.set_type(RepositoryType.LIBRARY)
1337        # Repository : Address
1338        add = Address()
1339        add.set_street(message)
1340        add.add_citation(_choice(c_h_list))
1341        ref.add_address(add)
1342        self.db.add_repository(ref, self.trans)
1343
1344    def test_check_media_references(self):
1345        pass
1346
1347    def test_check_repo_references(self):
1348        pass
1349
1350    def test_check_note_references(self):
1351        pass
1352
1353    def generate_person(self, gender=None, lastname=None, note=None,
1354                        alive_in_year=None):
1355        """ This generates a person with lots of attachments """
1356        if not self.cli:
1357            if self.person_count % 10 == 0:
1358                while Gtk.events_pending():
1359                    Gtk.main_iteration()
1360
1361        pers = Person()
1362        self.fill_object(pers)
1363
1364        # Gender
1365        if gender is None:
1366            gender = _randint(0, 1)
1367        if _randint(0, 10) == 1:  # Set some persons to unknown gender
1368            pers.set_gender(Person.UNKNOWN)
1369        else:
1370            pers.set_gender(gender)
1371
1372        # Name
1373        name = Name()
1374        (firstname, lastname) = self.rand_name(lastname, gender)
1375        name.set_first_name(firstname)
1376        surname = Surname()
1377        surname.set_surname(lastname)
1378        name.add_surname(surname)
1379        self.fill_object(name)
1380        pers.set_primary_name(name)
1381
1382        # generate some slightly different alternate name
1383        firstname2 = \
1384            firstname.replace("m", "n").replace("l", "i").replace("b", "d")
1385        if firstname2 != firstname:
1386            alt_name = Name(name)
1387            self.fill_object(alt_name)
1388            if _randint(0, 2) == 1:
1389                surname = Surname()
1390                surname.set_surname(self.rand_text(self.LASTNAME))
1391                alt_name.add_surname(surname)
1392            elif _randint(0, 2) == 1:
1393                surname = Surname()
1394                surname.set_surname(lastname)
1395                alt_name.add_surname(surname)
1396            if _randint(0, 1) == 1:
1397                alt_name.set_first_name(firstname2)
1398            if _randint(0, 1) == 1:
1399                alt_name.set_title(self.rand_text(self.SHORT))
1400            if _randint(0, 1) == 1:
1401                patronymic = Surname()
1402                patronymic.set_surname(self.rand_text(self.FIRSTNAME_MALE))
1403                patronymic.set_origintype(NameOriginType.PATRONYMIC)
1404                alt_name.add_surname(patronymic)
1405            if _randint(0, 1) == 1:
1406                alt_name.get_primary_surname().set_prefix(
1407                    self.rand_text(self.SHORT))
1408            if _randint(0, 1) == 1:
1409                alt_name.set_suffix(self.rand_text(self.SHORT))
1410            if _randint(0, 1) == 1:
1411                alt_name.set_call_name(self.rand_text(self.FIRSTNAME))
1412            pers.add_alternate_name(alt_name)
1413        firstname2 = \
1414            firstname.replace("a", "e").replace("o", "u").replace("r", "p")
1415        if firstname2 != firstname:
1416            alt_name = Name(name)
1417            self.fill_object(alt_name)
1418            if _randint(0, 2) == 1:
1419                surname = Surname()
1420                surname.set_surname(self.rand_text(self.LASTNAME))
1421                alt_name.add_surname(surname)
1422            elif _randint(0, 2) == 1:
1423                surname = Surname()
1424                surname.set_surname(lastname)
1425                alt_name.add_surname(surname)
1426            if _randint(0, 1) == 1:
1427                alt_name.set_first_name(firstname2)
1428            if _randint(0, 1) == 1:
1429                alt_name.set_title(self.rand_text(self.SHORT))
1430            if _randint(0, 1) == 1:
1431                patronymic = Surname()
1432                patronymic.set_surname(self.rand_text(self.FIRSTNAME_MALE))
1433                patronymic.set_origintype(NameOriginType.PATRONYMIC)
1434                alt_name.add_surname(patronymic)
1435            if _randint(0, 1) == 1:
1436                alt_name.get_primary_surname().set_prefix(
1437                    self.rand_text(self.SHORT))
1438            if _randint(0, 1) == 1:
1439                alt_name.set_suffix(self.rand_text(self.SHORT))
1440            if _randint(0, 1) == 1:
1441                alt_name.set_call_name(self.rand_text(self.FIRSTNAME))
1442            pers.add_alternate_name(alt_name)
1443
1444        if not alive_in_year:
1445            alive_in_year = _randint(1700, 2000)
1446
1447        b_y = alive_in_year - _randint(0, 60)
1448        d_y = alive_in_year + _randint(0, 60)
1449
1450        # birth
1451        if _randint(0, 1) == 1:
1452            (dummy, eref) = self.rand_personal_event(EventType.BIRTH, b_y, b_y)
1453            pers.set_birth_ref(eref)
1454
1455        # baptism
1456        if _randint(0, 1) == 1:
1457            (dummy, eref) = self.rand_personal_event(
1458                _choice((EventType.BAPTISM, EventType.CHRISTEN)), b_y, b_y + 2)
1459            pers.add_event_ref(eref)
1460
1461        # death
1462        if _randint(0, 1) == 1:
1463            (dummy, eref) = self.rand_personal_event(EventType.DEATH, d_y, d_y)
1464            pers.set_death_ref(eref)
1465
1466        # burial
1467        if _randint(0, 1) == 1:
1468            (dummy, eref) = self.rand_personal_event(
1469                _choice((EventType.BURIAL, EventType.CREMATION)), d_y, d_y + 2)
1470            pers.add_event_ref(eref)
1471
1472        # some other events
1473        while _randint(0, 5) == 1:
1474            (dummy, eref) = self.rand_personal_event(None, b_y, d_y)
1475            pers.add_event_ref(eref)
1476
1477        # some shared events
1478        if self.generated_events:
1479            while _randint(0, 5) == 1:
1480                e_h = _choice(self.generated_events)
1481                eref = EventRef()
1482                self.fill_object(eref)
1483                eref.set_reference_handle(e_h)
1484                pers.add_event_ref(eref)
1485
1486        # PersonRef
1487        if _randint(0, 3) == 1:
1488            for dummy in range(0, _randint(1, 2)):
1489                if self.person_count > self.max_person_count:
1490                    break
1491                if alive_in_year:
1492                    asso_h = self.generate_person(None, None,
1493                                                  alive_in_year=alive_in_year)
1494                else:
1495                    asso_h = self.generate_person()
1496                asso = PersonRef()
1497                asso.set_reference_handle(asso_h)
1498                asso.set_relation(self.rand_text(self.SHORT))
1499                self.fill_object(asso)
1500                pers.add_person_ref(asso)
1501                if _randint(0, 2) == 0:
1502                    self.persons_todo.append(asso_h)
1503
1504        # Note
1505        if note:
1506            pass  # Add later?
1507
1508        person_handle = self.db.add_person(pers, self.trans)
1509
1510        self.person_count = self.person_count + 1
1511        self.progress_step()
1512        if self.person_count % 10 == 1:
1513            print("person count", self.person_count)
1514        self.person_dates[person_handle] = (b_y, d_y)
1515
1516        return person_handle
1517
1518    def generate_family(self, person1_h):
1519        """ Make up a family """
1520        person1 = self.db.get_person_from_handle(person1_h)
1521        if not person1:
1522            return
1523        alive_in_year = None
1524        if person1_h in self.person_dates:
1525            (born, died) = self.person_dates[person1_h]
1526            alive_in_year = min(born + _randint(10, 50),
1527                                died + _randint(-10, 10))
1528
1529        if person1.get_gender() == 1:
1530            if _randint(0, 7) == 1:
1531                person2_h = None
1532            else:
1533                if alive_in_year:
1534                    person2_h = \
1535                        self.generate_person(0, alive_in_year=alive_in_year)
1536                else:
1537                    person2_h = self.generate_person(0)
1538        else:
1539            person2_h = person1_h
1540            if _randint(0, 7) == 1:
1541                person1_h = None
1542            else:
1543                if alive_in_year:
1544                    person1_h = \
1545                        self.generate_person(1, alive_in_year=alive_in_year)
1546                else:
1547                    person1_h = self.generate_person(1)
1548
1549        if person1_h and _randint(0, 2) > 0:
1550            self.parents_todo.append(person1_h)
1551        if person2_h and _randint(0, 2) > 0:
1552            self.parents_todo.append(person2_h)
1553
1554        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
1555                   self.db) as self.trans:
1556            self.transaction_count += 1
1557            fam = Family()
1558            self.add_defaults(fam)
1559            if person1_h:
1560                fam.set_father_handle(person1_h)
1561            if person2_h:
1562                fam.set_mother_handle(person2_h)
1563
1564            # Avoid adding the same event more than once to the same family
1565            event_set = set()
1566
1567            # Generate at least one family event with a probability of 75%
1568            if _randint(0, 3) > 0:
1569                (dummy, eref) = self.rand_family_event(None)
1570                fam.add_event_ref(eref)
1571                event_set.add(eref.get_reference_handle())
1572
1573            # generate some more events with a lower probability
1574            while _randint(0, 3) == 1:
1575                (dummy, eref) = self.rand_family_event(None)
1576                if eref.get_reference_handle() in event_set:
1577                    continue
1578                fam.add_event_ref(eref)
1579                event_set.add(eref.get_reference_handle())
1580
1581            # some shared events
1582            if self.generated_events:
1583                while _randint(0, 5) == 1:
1584                    typeval = EventType.UNKNOWN
1585                    while int(typeval) not in self.FAMILY_EVENTS:
1586                        e_h = _choice(self.generated_events)
1587                        typeval = self.db.get_event_from_handle(e_h).get_type()
1588                    if e_h in event_set:
1589                        break
1590                    eref = EventRef()
1591                    self.fill_object(eref)
1592                    eref.set_reference_handle(e_h)
1593                    fam.add_event_ref(eref)
1594                    event_set.add(e_h)
1595
1596            fam_h = self.db.add_family(fam, self.trans)
1597            self.generated_families.append(fam_h)
1598            fam = self.db.commit_family(fam, self.trans)
1599            if person1_h:
1600                person1 = self.db.get_person_from_handle(person1_h)
1601                person1.add_family_handle(fam_h)
1602                self.db.commit_person(person1, self.trans)
1603            if person2_h:
1604                person2 = self.db.get_person_from_handle(person2_h)
1605                person2.add_family_handle(fam_h)
1606                self.db.commit_person(person2, self.trans)
1607
1608            lastname = person1.get_primary_name().get_surname()
1609
1610            for i in range(0, _randint(1, 10)):
1611                if self.person_count > self.max_person_count:
1612                    break
1613                if alive_in_year:
1614                    child_h = self.generate_person(
1615                        None, lastname,
1616                        alive_in_year=alive_in_year +
1617                        _randint(16 + 2 * i, 30 + 2 * i))
1618                else:
1619                    child_h = self.generate_person(None, lastname)
1620                    (born, died) = self.person_dates[child_h]
1621                    alive_in_year = born
1622                fam = self.db.get_family_from_handle(fam_h)
1623                child_ref = ChildRef()
1624                child_ref.set_reference_handle(child_h)
1625                self.fill_object(child_ref)
1626                fam.add_child_ref(child_ref)
1627                self.db.commit_family(fam, self.trans)
1628                child = self.db.get_person_from_handle(child_h)
1629                child.add_parent_family_handle(fam_h)
1630                self.db.commit_person(child, self.trans)
1631                if _randint(0, 3) > 0:
1632                    self.persons_todo.append(child_h)
1633
1634    def generate_parents(self, child_h):
1635        """ Add parents to a person, if not present already"""
1636        if not child_h:
1637            return
1638        child = self.db.get_person_from_handle(child_h)
1639        if not child:
1640            print("ERROR: Person handle %s does not exist in database" %
1641                  child_h)
1642            return
1643        if child.get_parent_family_handle_list():
1644            return
1645
1646        lastname = child.get_primary_name().get_surname()
1647        if child_h in self.person_dates:
1648            (born, dummy) = self.person_dates[child_h]
1649            person1_h = self.generate_person(1, lastname, alive_in_year=born)
1650            person2_h = self.generate_person(0, alive_in_year=born)
1651        else:
1652            person1_h = self.generate_person(1, lastname)
1653            person2_h = self.generate_person(0)
1654
1655        if _randint(0, 2) > 1:
1656            self.parents_todo.append(person1_h)
1657        if _randint(0, 2) > 1:
1658            self.parents_todo.append(person2_h)
1659
1660        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
1661                   self.db) as self.trans:
1662            self.transaction_count += 1
1663            fam = Family()
1664            self.add_defaults(fam)
1665            fam.set_father_handle(person1_h)
1666            fam.set_mother_handle(person2_h)
1667            child_ref = ChildRef()
1668            child_ref.set_reference_handle(child_h)
1669            self.fill_object(child_ref)
1670            fam.add_child_ref(child_ref)
1671            fam_h = self.db.add_family(fam, self.trans)
1672            self.generated_families.append(fam_h)
1673            fam = self.db.commit_family(fam, self.trans)
1674            person1 = self.db.get_person_from_handle(person1_h)
1675            person1.add_family_handle(fam_h)
1676            self.db.commit_person(person1, self.trans)
1677            person2 = self.db.get_person_from_handle(person2_h)
1678            person2.add_family_handle(fam_h)
1679            self.db.commit_person(person2, self.trans)
1680            child.add_parent_family_handle(fam_h)
1681            self.db.commit_person(child, self.trans)
1682
1683    def generate_tags(self):
1684        """ Make up some odd tags """
1685        with DbTxn(_("Testcase generator step %d") % self.transaction_count,
1686                   self.db) as self.trans:
1687            self.transaction_count += 1
1688            for dummy in range(10):
1689                tag = Tag()
1690                tag.set_name(self.rand_text(self.TAG))
1691                tag.set_color(self.rand_color())
1692                tag.set_priority(self.db.get_number_of_tags())
1693                tag_handle = self.db.add_tag(tag, self.trans)
1694                self.generated_tags.append(tag_handle)
1695
1696    def add_defaults(self, obj):
1697        self.fill_object(obj)
1698
1699    def rand_name(self, lastname=None, gender=None):
1700        """ Create a name pair (first, last)"""
1701        if gender == Person.MALE:
1702            firstname = self.rand_text(self.FIRSTNAME_MALE)
1703        elif gender == Person.FEMALE:
1704            firstname = self.rand_text(self.FIRSTNAME_FEMALE)
1705        else:
1706            firstname = self.rand_text(self.FIRSTNAME)
1707        if not lastname:
1708            lastname = self.rand_text(self.LASTNAME)
1709        return (firstname, lastname)
1710
1711    def rand_date(self, start=None, end=None):
1712        """
1713        Generates a random date object between the given years start and end
1714        """
1715        if not start and not end:
1716            start = _randint(1700, 2000)
1717        if start and not end:
1718            end = start + _randint(0, 100)
1719        if end and not start:
1720            start = end - _randint(0, 100)
1721        year = _randint(start, end)
1722
1723        ndate = Date()
1724        if _randint(0, 10) == 1:
1725            # Some get a textual date
1726            ndate.set_as_text(_choice((self.rand_text(self.SHORT), "Unknown",
1727                                       "??", "Don't know", "TODO!")))
1728        else:
1729            if _randint(0, 10) == 1:
1730                # some get an empty date
1731                pass
1732            else:
1733                # regular dates
1734                calendar = Date.CAL_GREGORIAN
1735                quality = _choice((Date.QUAL_NONE,
1736                                   Date.QUAL_ESTIMATED,
1737                                   Date.QUAL_CALCULATED))
1738                modifier = _choice((Date.MOD_NONE,
1739                                    Date.MOD_BEFORE,
1740                                    Date.MOD_AFTER,
1741                                    Date.MOD_ABOUT,
1742                                    Date.MOD_RANGE,
1743                                    Date.MOD_SPAN))
1744                day = _randint(0, 28)
1745                if day > 0:  # avoid days without month
1746                    month = _randint(1, 12)
1747                else:
1748                    month = _randint(0, 12)
1749
1750                if modifier in (Date.MOD_RANGE, Date.MOD_SPAN):
1751                    day2 = _randint(0, 28)
1752                    if day2 > 0:
1753                        month2 = _randint(1, 12)
1754                    else:
1755                        month2 = _randint(0, 12)
1756                    year2 = year + _randint(1, 5)
1757                    ndate.set(quality, modifier, calendar,
1758                              (day, month, year, False, day2, month2, year2,
1759                               False), "")
1760                else:
1761                    ndate.set(quality, modifier, calendar,
1762                              (day, month, year, False), "")
1763
1764        return (year, ndate)
1765
1766    def fill_object(self, obj):
1767        ''' Generic object fill routine '''
1768
1769        if issubclass(obj.__class__, AddressBase):
1770            while _randint(0, 1) == 1:
1771                addr = Address()
1772                self.fill_object(addr)
1773                obj.add_address(addr)
1774
1775        if isinstance(obj, Attribute):
1776            obj.set_type(self.rand_type(AttributeType()))
1777            obj.set_value(self.rand_text(self.SHORT))
1778
1779        if issubclass(obj.__class__, AttributeBase):
1780            while _randint(0, 1) == 1:
1781                att = Attribute()
1782                self.fill_object(att)
1783                obj.add_attribute(att)
1784
1785        if isinstance(obj, ChildRef):
1786            if _randint(0, 3) == 1:
1787                obj.set_mother_relation(self.rand_type(ChildRefType()))
1788            if _randint(0, 3) == 1:
1789                obj.set_father_relation(self.rand_type(ChildRefType()))
1790
1791        if issubclass(obj.__class__, DateBase):
1792            if _randint(0, 1) == 1:
1793                (dummy, dat) = self.rand_date()
1794                obj.set_date_object(dat)
1795
1796        if isinstance(obj, Event):
1797            if _randint(0, 1) == 1:
1798                obj.set_description(self.rand_text(self.LONG))
1799
1800        if issubclass(obj.__class__, EventRef):
1801            obj.set_role(self.rand_type(EventRoleType()))
1802
1803        if isinstance(obj, Family):
1804            if _randint(0, 2) == 1:
1805                obj.set_relationship(self.rand_type(FamilyRelType()))
1806            else:
1807                obj.set_relationship(FamilyRelType(FamilyRelType.MARRIED))
1808
1809        if isinstance(obj, LdsOrd):
1810            if _randint(0, 1) == 1:
1811                obj.set_temple(_choice(TEMPLES.name_code_data())[1])
1812
1813        if issubclass(obj.__class__, LdsOrdBase):
1814            while _randint(0, 1) == 1:
1815                ldsord = LdsOrd()
1816                self.fill_object(ldsord)
1817                if isinstance(obj, Person):
1818                    lds_type = _choice([item for item in LDS_INDIVIDUAL_ORD])
1819                if isinstance(obj, Family):
1820                    lds_type = LDS_SPOUSE_SEALING[0]
1821                    if self.generated_families:
1822                        ldsord.set_family_handle(
1823                            _choice(self.generated_families))
1824                ldsord.set_type(lds_type[0])
1825                status = _choice(lds_type[1])
1826                if status != LdsOrd.STATUS_NONE:
1827                    ldsord.set_status(status)
1828                obj.add_lds_ord(ldsord)
1829
1830        if isinstance(obj, Location):
1831            if _randint(0, 1) == 1:
1832                obj.set_parish(self.rand_text(self.SHORT))
1833
1834        if issubclass(obj.__class__, LocationBase):
1835            if _randint(0, 1) == 1:
1836                obj.set_street(self.rand_text(self.SHORT))
1837            if _randint(0, 1) == 1:
1838                obj.set_city(self.rand_text(self.SHORT))
1839            if _randint(0, 1) == 1:
1840                obj.set_postal_code(self.rand_text(self.SHORT))
1841            if _randint(0, 1) == 1:
1842                obj.set_phone(self.rand_text(self.SHORT))
1843            if _randint(0, 1) == 1:
1844                obj.set_state(self.rand_text(self.SHORT))
1845            if _randint(0, 1) == 1:
1846                obj.set_country(self.rand_text(self.SHORT))
1847            if _randint(0, 1) == 1:
1848                obj.set_county(self.rand_text(self.SHORT))
1849
1850        if issubclass(obj.__class__, MediaBase):
1851            # FIXME: frequency changed to prevent recursion
1852            while _randint(0, 10) == 1:
1853                obj.add_media_reference(self.fill_object(MediaRef()))
1854
1855        if isinstance(obj, Media):
1856            if _randint(0, 3) == 1:
1857                obj.set_description(str(self.rand_text(self.LONG)))
1858                path = os.path.abspath(_choice((ICON, LOGO, SPLASH)))
1859                obj.set_path(str(path))
1860                mime = get_type(path)
1861                obj.set_mime_type(mime)
1862            else:
1863                obj.set_description(str(self.rand_text(self.SHORT)))
1864                obj.set_path(os.path.abspath(str(ICON)))
1865                obj.set_mime_type("image/png")
1866
1867        if isinstance(obj, MediaRef):
1868            if not self.generated_media or _randint(0, 10) == 1:
1869                med = Media()
1870                self.fill_object(med)
1871                self.db.add_media(med, self.trans)
1872                self.generated_media.append(med.get_handle())
1873            obj.set_reference_handle(_choice(self.generated_media))
1874            if _randint(0, 1) == 1:
1875                obj.set_rectangle((_randint(0, 200), _randint(0, 200),
1876                                   _randint(0, 200), _randint(0, 200)))
1877
1878        if isinstance(obj, Name):
1879            obj.set_type(self.rand_type(NameType()))
1880            if _randint(0, 1) == 1:
1881                obj.set_title(self.rand_text(self.SHORT))
1882            if _randint(0, 1) == 1:
1883                patronymic = Surname()
1884                patronymic.set_surname(self.rand_text(self.FIRSTNAME_MALE))
1885                patronymic.set_origintype(NameOriginType.PATRONYMIC)
1886                obj.add_surname(patronymic)
1887            if _randint(0, 1) == 1:
1888                obj.get_primary_surname().set_prefix(
1889                    self.rand_text(self.SHORT))
1890            if _randint(0, 1) == 1:
1891                obj.set_suffix(self.rand_text(self.SHORT))
1892            if _randint(0, 1) == 1:
1893                obj.set_call_name(self.rand_text(self.FIRSTNAME))
1894            if _randint(0, 1) == 1:
1895                obj.set_group_as(obj.get_surname()[:1])
1896            # obj.set_display_as()
1897            # obj.set_sort_as()
1898
1899        if isinstance(obj, Note):
1900            n_type = self.rand_type(NoteType())
1901            if n_type == NoteType.HTML_CODE:
1902                obj.set(self.rand_text(self.NOTE))
1903            else:
1904                obj.set_styledtext(self.rand_text(self.STYLED_TEXT))
1905            obj.set_format(_choice((Note.FLOWED, Note.FORMATTED)))
1906            obj.set_type(n_type)
1907
1908        if issubclass(obj.__class__, NoteBase):
1909            while _randint(0, 1) == 1:
1910                if not self.generated_notes or _randint(0, 10) == 1:
1911                    note = Note()
1912                    self.fill_object(note)
1913                    self.db.add_note(note, self.trans)
1914                    self.generated_notes.append(note.get_handle())
1915                n_h = _choice(self.generated_notes)
1916                obj.add_note(n_h)
1917
1918        if isinstance(obj, Place):
1919            obj.set_title(self.rand_text(self.LONG))
1920            obj.set_name(PlaceName(value=self.rand_text(self.SHORT)))
1921            obj.set_code(self.rand_text(self.SHORT))
1922            if _randint(0, 1) == 1:
1923                if _randint(0, 4) == 1:
1924                    obj.set_longitude(self.rand_text(self.SHORT))
1925                else:
1926                    obj.set_longitude(str(_random() * 360.0 - 180.0))
1927            if _randint(0, 1) == 1:
1928                if _randint(0, 4) == 1:
1929                    obj.set_latitude(self.rand_text(self.SHORT))
1930                else:
1931                    obj.set_latitude(str(_random() * 180.0 - 90.0))
1932            while _randint(0, 1) == 1:
1933                obj.add_alternate_locations(self.fill_object(Location()))
1934
1935        if issubclass(obj.__class__, PlaceBase):
1936            if _randint(0, 1) == 1:
1937                obj.set_place_handle(self.rand_place())
1938
1939        if issubclass(obj.__class__, BasicPrimaryObject):
1940            if _randint(0, 1) == 1:
1941                obj.set_gramps_id(self.rand_text(self.SHORT))
1942
1943        if issubclass(obj.__class__, PrivacyBase):
1944            obj.set_privacy(_randint(0, 5) == 1)
1945
1946        if isinstance(obj, RepoRef):
1947            if not self.generated_repos or _randint(0, 10) == 1:
1948                rep = Repository()
1949                self.fill_object(rep)
1950                self.db.add_repository(rep, self.trans)
1951                self.generated_repos.append(rep.get_handle())
1952            obj.set_reference_handle(_choice(self.generated_repos))
1953            if _randint(0, 1) == 1:
1954                obj.set_call_number(self.rand_text(self.SHORT))
1955            obj.set_media_type(self.rand_type(SourceMediaType()))
1956
1957        if isinstance(obj, Repository):
1958            obj.set_type(self.rand_type(RepositoryType()))
1959            obj.set_name(self.rand_text(self.SHORT))
1960
1961        if isinstance(obj, Source):
1962            obj.set_title(self.rand_text(self.SHORT))
1963            if _randint(0, 1) == 1:
1964                obj.set_author(self.rand_text(self.SHORT))
1965            if _randint(0, 1) == 1:
1966                obj.set_publication_info(self.rand_text(self.LONG))
1967            if _randint(0, 1) == 1:
1968                obj.set_abbreviation(self.rand_text(self.SHORT))
1969            while _randint(0, 1) == 1:
1970                sattr = SrcAttribute()
1971                sattr.set_type(self.rand_text(self.SHORT))
1972                sattr.set_value(self.rand_text(self.SHORT))
1973                obj.add_attribute(sattr)
1974            while _randint(0, 1) == 1:
1975                rep_ref = RepoRef()
1976                self.fill_object(rep_ref)
1977                obj.add_repo_reference(rep_ref)
1978
1979        if issubclass(obj.__class__, CitationBase):
1980            while _randint(0, 1) == 1:
1981                if not self.generated_citations or _randint(1, 10) == 1:
1982                    cit = Citation()
1983                    self.fill_object(cit)
1984                    self.db.add_citation(cit, self.trans)
1985                    self.generated_citations.append(cit.get_handle())
1986                s_h = _choice(self.generated_citations)
1987                obj.add_citation(s_h)
1988
1989        if isinstance(obj, Citation):
1990            if not self.generated_sources or _randint(0, 10) == 1:
1991                src = Source()
1992                self.fill_object(src)
1993                self.db.add_source(src, self.trans)
1994                self.generated_sources.append(src.get_handle())
1995            obj.set_reference_handle(_choice(self.generated_sources))
1996            if _randint(0, 1) == 1:
1997                obj.set_page(self.rand_text(self.NUMERIC))
1998            # if _randint(0, 1) == 1:
1999            #    obj.set_text( self.rand_text(self.SHORT))
2000            # if _randint(0, 1) == 1:
2001            #    (year, dat) = self.rand_date( )
2002            #    obj.set_date_object( dat)
2003            # sort to provide deterministic output in unit tests
2004            obj.set_confidence_level(_choice(sorted(conf_strings.keys())))
2005
2006        if issubclass(obj.__class__, TagBase):
2007            if _randint(0, 1) == 1:
2008                obj.set_tag_list(self.rand_tags())
2009
2010        if issubclass(obj.__class__, UrlBase):
2011            while _randint(0, 1) == 1:
2012                url = Url()
2013                self.fill_object(url)
2014                obj.add_url(url)
2015
2016        if isinstance(obj, Url):
2017            obj.set_path("http://www.gramps-project.org/?test=%s" %
2018                         self.rand_text(self.SHORT))
2019            obj.set_description(self.rand_text(self.SHORT))
2020            obj.set_type(self.rand_type(UrlType()))
2021
2022        return obj
2023
2024    def rand_personal_event(self, e_type=None, start=None, end=None):
2025        """ Random personal event """
2026        if e_type:
2027            typeval = EventType(e_type)
2028        else:
2029            typeval = self.rand_type(EventType())
2030        return self._rand_event(typeval, start, end)
2031
2032    def rand_family_event(self, e_type=None, start=None, end=None):
2033        """ Random family event """
2034        if e_type:
2035            EventType(e_type)
2036        else:
2037            typeval = EventType.UNKNOWN
2038            while int(typeval) not in self.FAMILY_EVENTS:
2039                typeval = self.rand_type(EventType())
2040        return self._rand_event(typeval, start, end)
2041
2042    def _rand_event(self, e_type, start, end):
2043        """ Random general event """
2044        evt = Event()
2045        self.fill_object(evt)
2046        evt.set_type(e_type)
2047        (year, dat) = self.rand_date(start, end)
2048        evt.set_date_object(dat)
2049        event_h = self.db.add_event(evt, self.trans)
2050        self.generated_events.append(event_h)
2051        event_ref = EventRef()
2052        self.fill_object(event_ref)
2053        event_ref.set_reference_handle(event_h)
2054        return (year, event_ref)
2055
2056    def rand_type(self, gtype):
2057        if issubclass(gtype.__class__, GrampsType):
2058            gmap = gtype.get_map()
2059            # sort to provide deterministic output in unit tests
2060            key = _choice(sorted(gmap.keys()))
2061            if key == gtype.get_custom():
2062                value = self.rand_text(self.SHORT)
2063            else:
2064                value = ''
2065            gtype.set((key, value))
2066            return gtype
2067
2068    def rand_place(self):
2069        if not self.generated_places or _randint(0, 10) == 1:
2070            self.generate_place()
2071        return _choice(self.generated_places)
2072
2073    def generate_place(self):
2074        parent_handle = None
2075        for type_num in range(1, 8):
2076            if type_num > 1 and _randint(1, 3) == 1:
2077                # skip some levels in the place hierarchy
2078                continue
2079            place = Place()
2080            place.set_type(PlaceType(type_num))
2081            if parent_handle is not None:
2082                self.add_parent_place(place, parent_handle)
2083            if type_num > 1 and _randint(1, 3) == 1:
2084                # add additional parent place
2085                parent_handle = self.find_parent_place(type_num - 1)
2086                if parent_handle is not None:
2087                    self.add_parent_place(place, parent_handle)
2088            self.fill_object(place)
2089            self.db.add_place(place, self.trans)
2090            parent_handle = place.get_handle()
2091            self.generated_places.append(place.get_handle())
2092            self.parent_places[type_num].append(place.get_handle())
2093
2094    def find_parent_place(self, type_num):
2095        if len(self.parent_places[type_num]) > 0:
2096            return _choice(self.parent_places[type_num])
2097        else:
2098            return None
2099
2100    def add_parent_place(self, place, handle):
2101        place_ref = PlaceRef()
2102        place_ref.ref = handle
2103        dummy, random_date = self.rand_date()
2104        place_ref.set_date_object(random_date)
2105        place.add_placeref(place_ref)
2106
2107    def rand_text(self, t_type=None):
2108        """ make random text strings according to desired type """
2109        # for lastnamesnames
2110        syllables1 = ["sa", "li", "na", "ma", "no", "re", "mi",
2111                      "cha", "ki", "du", "ba", "ku", "el"]
2112        # for firstnames
2113        syllables2 = ["as", "il", "an", "am", "on", "er", "im",
2114                      "ach", "ik", "ud", "ab", "ul", "le"]
2115        # others
2116        syllables3 = ["ka", "po", "lo", "chi", "she", "di", "fa",
2117                      "go", "ja", "ne", "pe"]
2118
2119        syllables = syllables1 + syllables2 + syllables3
2120        minwords = 5
2121        maxwords = 8
2122        minsyllables = 2
2123        maxsyllables = 5
2124
2125        # result = "" if t_type != self.STYLED_TEXT else StyledText("")
2126        if t_type == self.STYLED_TEXT:
2127            result = StyledText("")
2128        else:
2129            result = ""
2130
2131        if self.options_dict['specialchars']:
2132            result = result + "ä<ö&ü%ß'\""
2133
2134        if self.options_dict['add_serial'] and t_type != self.TAG:
2135            result = result + "#+#%06d#-#" % self.text_serial_number
2136            self.text_serial_number = self.text_serial_number + 1
2137
2138        if not t_type:
2139            t_type = self.SHORT
2140
2141        if t_type == self.SHORT or t_type == self.TAG:
2142            minwords = 1
2143            maxwords = 3
2144            minsyllables = 2
2145            maxsyllables = 4
2146
2147        if t_type == self.LONG:
2148            minwords = 5
2149            maxwords = 8
2150            minsyllables = 2
2151            maxsyllables = 5
2152
2153        if t_type == self.FIRSTNAME:
2154            t_type = _choice((self.FIRSTNAME_MALE, self.FIRSTNAME_FEMALE))
2155
2156        if t_type == self.FIRSTNAME_MALE or t_type == self.FIRSTNAME_FEMALE:
2157            syllables = syllables2
2158            minwords = 1
2159            maxwords = 5
2160            minsyllables = 2
2161            maxsyllables = 5
2162            if not self.options_dict['long_names']:
2163                maxwords = 2
2164                maxsyllables = 3
2165
2166        if t_type == self.LASTNAME:
2167            syllables = syllables1
2168            minwords = 1
2169            maxwords = 1
2170            minsyllables = 2
2171            maxsyllables = 5
2172            if not self.options_dict['long_names']:
2173                maxsyllables = 3
2174
2175        if t_type == self.NOTE or t_type == self.STYLED_TEXT:
2176            result = result + "Generated by TestcaseGenerator."
2177            minwords = 20
2178            maxwords = 100
2179
2180        if t_type == self.NUMERIC:
2181            if _randint(0, 1) == 1:
2182                return "%d %s" % (_randint(1, 100), result)
2183            if _randint(0, 1) == 1:
2184                return "%d, %d %s" % (_randint(1, 100), _randint(100, 1000),
2185                                      result)
2186            med = _randint(100, 1000)
2187            return "%d - %d %s" % (med, med + _randint(1, 5), result)
2188
2189        for dummy in range(0, _randint(minwords, maxwords)):
2190            if result:
2191                result = result + " "
2192            word = ""
2193            for j in range(0, _randint(minsyllables, maxsyllables)):
2194                word = word + _choice(syllables)
2195            if t_type == self.FIRSTNAME_MALE:
2196                word = word + _choice(("a", "e", "i", "o", "u"))
2197            if _randint(0, 3) == 1:
2198                word = word.title()
2199            if t_type == self.NOTE:
2200                if _randint(0, 10) == 1:
2201                    word = "<b>%s</b>" % word
2202                elif _randint(0, 10) == 1:
2203                    word = "<i>%s</i>" % word
2204                elif _randint(0, 10) == 1:
2205                    word = "<i>%s</i>" % word
2206                if _randint(0, 20) == 1:
2207                    word = word + "."
2208                elif _randint(0, 30) == 1:
2209                    word = word + ".\n"
2210            if t_type == self.STYLED_TEXT:
2211                tags = []
2212                if _randint(0, 10) == 1:
2213                    tags += [StyledTextTag(StyledTextTagType.BOLD, True,
2214                                           [(0, len(word))])]
2215                elif _randint(0, 10) == 1:
2216                    tags += [StyledTextTag(StyledTextTagType.ITALIC, True,
2217                                           [(0, len(word))])]
2218                elif _randint(0, 10) == 1:
2219                    tags += [StyledTextTag(StyledTextTagType.UNDERLINE, True,
2220                                           [(0, len(word))])]
2221                sword = StyledText(word, tags)
2222                if _randint(0, 20) == 1:
2223                    sword = sword + "."
2224                elif _randint(0, 30) == 1:
2225                    sword = sword + ".\n"
2226                result = StyledText("").join((result, sword))
2227            else:
2228                result += word
2229
2230        if t_type == self.LASTNAME:
2231            case = _randint(0, 2)
2232            if case == 0:
2233                result = result.title()
2234            elif case == 1:
2235                result = result.upper()
2236
2237        if self.options_dict['add_linebreak'] and \
2238                t_type != self.TAG:
2239            result = result + "\nNEWLINE"
2240
2241        return result
2242
2243    def rand_color(self):
2244        return '#%012X' % _randint(0, 281474976710655)
2245
2246    def rand_tags(self):
2247        maxtags = 5
2248        taglist = []
2249        for dummy in range(0, _randint(1, maxtags)):
2250            tag = _choice(self.generated_tags)
2251            if tag not in taglist:
2252                taglist.append(tag)
2253        return taglist
2254
2255
2256# -----------------------------------------------------------------------
2257#
2258# The options class for the tool
2259#
2260# -----------------------------------------------------------------------
2261class TestcaseGeneratorOptions(tool.ToolOptions):
2262    """
2263    Defines options and provides handling interface.
2264    """
2265
2266    def __init__(self, name, person_id=None):
2267        tool.ToolOptions.__init__(self, name, person_id)
2268
2269        # Options specific for this report
2270        self.options_dict = {
2271            'lowlevel':       0,
2272            'bugs':           0,
2273            'persons':        1,
2274            'person_count':   2000,
2275            'long_names':     0,
2276            'specialchars':   0,
2277            'add_serial':     0,
2278            'add_linebreak':  0,
2279        }
2280        self.options_help = {
2281            'lowlevel':      ("=0/1",
2282                              "Whether to create low level database errors.",
2283                              ["Skip test",
2284                               "Create low level database errors"],
2285                              True),
2286            'bugs':          ("=0/1",
2287                              "Whether to create invalid database references.",
2288                              ["Skip test",
2289                               "Create invalid Database references"],
2290                              True),
2291            'persons':       ("=0/1",
2292                              "Whether to create a bunch of dummy persons",
2293                              ["Dont create persons", "Create dummy persons"],
2294                              True),
2295            'person_count':  ("=int",
2296                              "Number of dummy persons to generate",
2297                              "Number of persons"),
2298            'long_names':    ("=0/1",
2299                              "Whether to create short or long names",
2300                              ["Short names", "Long names"],
2301                              True),
2302            'specialchars':  ("=0/1",
2303                              "Whether to ass some special characters to every"
2304                              " text field",
2305                              ["No special characters",
2306                               "Add special characters"],
2307                              True),
2308            'add_serial':    ("=0/1",
2309                              "Whether to add a serial number to every text "
2310                              "field",
2311                              ["No serial", "Add serial number"],
2312                              True),
2313            'add_linebreak': ("=0/1",
2314                              "Whether to add a line break to every text "
2315                              "field",
2316                              ["No linebreak", "Add line break"],
2317                              True),
2318        }
2319