From 9e916281e2b787046e7ac4b30aa4fefd1baeea22 Mon Sep 17 00:00:00 2001 From: Kamil Páral Date: Sep 03 2021 08:29:00 +0000 Subject: new commands: create-test-data and remove-test-data These new commands allow to create example data (milestones, bugs, updates) under a release with number 101. This is mostly useful for development, but I thought it might be a good idea to make it generally available instead of just to developers. With this data, it's easy to explore most functionality of BlockerBugs and see how different pages and widgets look like. Merges: https://pagure.io/fedora-qa/blockerbugs/pull-request/203 --- diff --git a/blockerbugs/cli.py b/blockerbugs/cli.py index aec005a..c3d1edd 100644 --- a/blockerbugs/cli.py +++ b/blockerbugs/cli.py @@ -14,6 +14,7 @@ from blockerbugs.util.bz_interface import BlockerBugs import blockerbugs.util.discussion_sync as discussion_sync import blockerbugs.util.pagure_bot as pagure_bot from blockerbugs.models.bug import Bug +from blockerbugs.util import testdata from alembic.config import Config as al_Config from alembic import command as al_command from alembic import script @@ -238,6 +239,16 @@ def close_inactive_discussions(args): discussion_sync.close_discussions_inactive_releases(args.dryrun) +def create_test_data(args): + """Create fake test data in the database""" + testdata.create_test_data() + + +def remove_test_data(args): + """Remove fake test data in the database""" + testdata.remove_test_data() + + def main(): parser = ArgumentParser() @@ -331,6 +342,16 @@ def main(): help="Don't make any actual changes") close_inactive_discussions_parser.set_defaults(func=close_inactive_discussions) + create_test_data_parser = subparsers.add_parser( + 'create-test-data', help='Create fake test data under release 101. Can be called ' + 'repeatedly, always deletes everything under that release and creates test data anew. ' + 'WARNING: Never run this if you have actual real data under release 101!') + create_test_data_parser.set_defaults(func=create_test_data) + + remove_test_data_parser = subparsers.add_parser( + 'remove-test-data', help='Remove fake test data. Removes release 101 and everything under ' + 'it. WARNING: Never run this if you have actual real data under release 101!') + remove_test_data_parser.set_defaults(func=remove_test_data) args = parser.parse_args() diff --git a/blockerbugs/util/testdata.py b/blockerbugs/util/testdata.py new file mode 100644 index 0000000..4e386c6 --- /dev/null +++ b/blockerbugs/util/testdata.py @@ -0,0 +1,249 @@ +"""Manipulate test data in the DB in order to develop/experiment with the BlockerBugs app""" + +import datetime + +from blockerbugs import db +from blockerbugs.models.update import Update +from blockerbugs.models.bug import Bug +from blockerbugs.models.release import Release +from blockerbugs.models.milestone import Milestone + +# we need some random strings for test data, let's use something better than lorem ipsum: +zen = str.splitlines('''\ +Beautiful is better than ugly. +Explicit is better than implicit. +Simple is better than complex. +Complex is better than complicated. +Flat is better than nested. +Sparse is better than dense. +Readability counts. +Special cases aren't special enough to break the rules. +Although practicality beats purity. +Errors should never pass silently. +Unless explicitly silenced. +In the face of ambiguity, refuse the temptation to guess. +There should be one-- and preferably only one --obvious way to do it. +Although that way may not be obvious at first unless you're Dutch. +Now is better than never. +Although never is often better than *right* now. +If the implementation is hard to explain, it's a bad idea. +If the implementation is easy to explain, it may be a good idea. +Namespaces are one honking great idea -- let's do more of those!''') +virtues = [ + 'perfection', + 'generosity', + 'proper conduct', + 'renunciation', + 'wisdom', + 'energy', + 'patience', + 'honesty', + 'determination', + 'goodwill', + 'equanimity', + 'non-attachment', + 'benevolence', + 'understanding', + 'compassion', + 'empathetic joy', + 'heedfulness', + 'mindfulness', + 'clear comprehension', + 'discrimination', + 'trust', + 'confidence', + 'self-respect', + 'decorum', + 'giving', + 'non-violence', +] + +zen_counter = 0 +'''The next zen line to use''' +virtues_counter = 0 +'''The next virtues line to use''' + +current_date = datetime.datetime.utcnow() +month_old_date = current_date - datetime.timedelta(days=30) + + +def get_zen(): + """Be told a next Zen wisdom. You can use this for bug titles, but you can also use it to + improve your life. + """ + global zen_counter + try: + wisdom = zen[zen_counter] + except IndexError: + wisdom = f'Zen quotes are a finite resource, use them well (advice #{zen_counter})' + zen_counter += 1 + return wisdom + + +def get_virtue(): + """Be told a next virtue. You can use it for bug components, but also for self-reflection. + """ + global virtues_counter + try: + virtue = virtues[virtues_counter] + except IndexError: + virtue = f'life virtue #{virtues_counter}' + virtues_counter += 1 + return virtue + + +def add_bug(bugid, milestone, summary=None, status='NEW', active=True, needinfo=False, + needinfo_requestee=None, depends_on=[], last_whiteboard_change=month_old_date, + last_bug_sync=month_old_date, **kwargs): + """Create a new Bug and return it. Use `**kwargs` for specifying additional attributes not + exposed in the Bug constructor. + """ + bug = Bug(bugid=bugid, + url=f'http://localhost/testbug_{bugid}', + summary=summary or get_zen(), + status=status, + component=get_virtue(), + milestone=milestone, + active=active, + needinfo=needinfo, + needinfo_requestee=needinfo_requestee, + last_whiteboard_change=last_whiteboard_change, + last_bug_sync=last_bug_sync, + depends_on=depends_on) + for key, val in kwargs.items(): + setattr(bug, key, val) + return bug + + +def add_update(updateid_num, release, bugs, status='testing', karma=0, + date_submitted=month_old_date, request=None, title=None, stable_karma=3): + """Create a new Update and return it. + """ + updateid = f'FEDORA-101-{updateid_num}' + update = Update(updateid=updateid, + release=release, + status=status, + karma=karma, + url=f'http://localhost/testupdate_{updateid}', + date_submitted=date_submitted, + request=request, + title=title, + stable_karma=stable_karma, + bugs=bugs) + return update + + +def remove_test_data(release_num=101): + """Remove fake test data from the database. Everything under Release `release_num` will be + DELETED (if present). + """ + release = Release.query.filter_by(number=release_num).one_or_none() + if not release: + return + + # delete all objects in a suitable order + for update in release.updates.all(): + db.session.delete(update) + for milestone in release.milestones.all(): + for bug in milestone.bugs.all(): + db.session.delete(bug) + db.session.delete(milestone) + db.session.delete(release) + db.session.commit() + + +def create_test_data(release_num=101): + """Create fake test data in the database. Everything under Release `release_num` will be DELETED + (if present) and the Release will be created again and populated with Milestones, Bugs, Updates, + etc, so that various BlockerBugs functionality can be easily inspected/tested. + """ + # remove everything that exists under the release + remove_test_data(release_num) + + # create the release + release = Release(number=release_num, active=True) + db.session.add(release) + + # create milestones + beta_milestone = Milestone(release=release, + version='beta', + name=f'{release_num}-beta', + blocker_tracker=10101, + fe_tracker=10102, + active=True, + current=False) + db.session.add(beta_milestone) + final_milestone = Milestone(release=release, + version='final', + name=f'{release_num}-final', + blocker_tracker=10103, + fe_tracker=10104, + active=True, + current=False) + db.session.add(final_milestone) + + # create bugs + # -- beta bugs + bug101 = add_bug(101, beta_milestone, proposed_blocker=True) + db.session.add(bug101) + bug102 = add_bug(102, beta_milestone, status='ASSIGNED', accepted_blocker=True, + discussion_link='http://localhost/discuss102', + last_whiteboard_change=current_date) + db.session.add(bug102) + bug103 = add_bug(103, beta_milestone, accepted_0day=True, depends_on=[12, 378]) + db.session.add(bug103) + bug104 = add_bug(104, beta_milestone, accepted_prevrel=True) + db.session.add(bug104) + bug105 = add_bug(105, beta_milestone, status='POST', proposed_fe=True, depends_on=[463], + last_bug_sync=current_date, last_whiteboard_change=current_date, + needinfo=True, needinfo_requestee='Buddha', + discussion_link='http://localhost/discuss105') + db.session.add(bug105) + bug106 = add_bug(106, beta_milestone, status='VERIFIED', accepted_fe=True) + db.session.add(bug106) + bug107 = add_bug(107, beta_milestone, prioritized=True) + db.session.add(bug107) + bug108 = add_bug(108, beta_milestone, status='MODIFIED', last_bug_sync=current_date, + proposed_blocker=True, accepted_fe=True, + discussion_link='http://localhost/discuss108') + bug108.votes = ''' + {"betablocker": {"-1": ["person1"], "0": [], "+1": ["person2", "person3"]}} + ''' + db.session.add(bug108) + # -- final bugs + bug200 = add_bug(200, final_milestone, proposed_blocker=True) + db.session.add(bug200) + bug201 = add_bug(201, final_milestone, status='ASSIGNED', proposed_blocker=True) + db.session.add(bug201) + # -- special bugs + bug900 = add_bug(900, beta_milestone, active=False, status='CLOSED', + summary="This shouldn't show up, because the bug is inactive") + db.session.add(bug900) + + # create updates + # -- updates for beta bugs + update1 = add_update(1, release, [bug101]) + db.session.add(update1) + update2 = add_update(2, release, [bug108], request='stable', karma=5) + db.session.add(update2) + update3 = add_update(3, release, [bug104], status='stable', stable_karma=0) + db.session.add(update3) + update4 = add_update(4, release, [bug102], status='pending', request='testing', + title='Sunshine tweak update') + db.session.add(update4) + update5 = add_update(5, release, [bug101], status='pending', date_submitted=month_old_date, + stable_karma=0, karma=-2) + db.session.add(update5) + update6 = add_update(6, release, [bug106], status='pending') + db.session.add(update6) + update7 = add_update(7, release, [bug107]) + db.session.add(update7) + # -- updates for final bugs + update20 = add_update(20, release, [bug201]) + db.session.add(update20) + # -- special updates + update90 = add_update(90, release, [bug101, bug102, bug201], status='pending', request='stable', + title='Update for several bugs and milestones') + db.session.add(update90) + # save + db.session.commit() diff --git a/testing/test_testdata.py b/testing/test_testdata.py new file mode 100644 index 0000000..5d77163 --- /dev/null +++ b/testing/test_testdata.py @@ -0,0 +1,105 @@ +'''Test blockerbugs.util.testdata''' + +from datetime import datetime + +from blockerbugs import db +from blockerbugs.models.update import Update +from blockerbugs.models.release import Release +from blockerbugs.models.milestone import Milestone +from blockerbugs.models.bug import Bug +from blockerbugs.util import testdata + + +class TestTestData(object): + def setup_method(self, method): + db.session.rollback() + db.drop_all() + db.create_all() + + def teardown_method(self, method): + db.session.rollback() + db.session.close() + db.drop_all() + + def test_remove_empty(self): + '''Removing a non-existent release shouldn't crash''' + testdata.remove_test_data() + + def test_create(self): + testdata.create_test_data() + + releases: list[Release] = Release.query.all() + assert len(releases) == 1 + release = releases[0] + assert release.number == 101 + + milestones: list[Milestone] = Milestone.query.all() + assert len(milestones) > 0 + for milestone in milestones: + assert milestone.release == release + + updates: list[Update] = Update.query.all() + assert len(updates) > 0 + for update in updates: + assert update.release == release + + bugs: list[Bug] = Bug.query.all() + assert len(bugs) > 0 + for bug in bugs: + assert bug.milestone in milestones + + def test_create_and_remove(self): + testdata.create_test_data() + testdata.remove_test_data() + + assert Release.query.count() == 0 + assert Milestone.query.count() == 0 + assert Update.query.count() == 0 + assert Bug.query.count() == 0 + + def test_double_create(self): + '''Creating test data for the second time should remove them and create them again, so there + should be no change (except object IDs) in the DB''' + testdata.create_test_data() + + num_releases = Release.query.count() + num_milestones = Milestone.query.count() + num_updates = Update.query.count() + num_bugs = Bug.query.count() + + testdata.create_test_data() + + assert Release.query.count() == num_releases + assert Milestone.query.count() == num_milestones + assert Update.query.count() == num_updates + assert Bug.query.count() == num_bugs + + def test_not_touch_other_data(self): + '''Creating or removing test data shouldn't affect any other real data''' + release = Release(1, active=True) + db.session.add(release) + milestone = Milestone(release, 'beta', blocker_tracker=1, fe_tracker=2, name='1-beta', + active=True, current=True) + db.session.add(milestone) + bug = Bug(bugid=1, url=None, summary='bug', status='NEW', component='distro', + milestone=milestone, active=True, needinfo=False, needinfo_requestee=None) + db.session.add(bug) + update = Update(updateid='U1', release=release, status='testing', karma=0, url='url', + date_submitted=datetime.utcnow()) + db.session.add(update) + + testdata.create_test_data() + + # make sure the objects haven't changed + assert Release.query.filter_by(id=release.id).one() == release + assert Milestone.query.filter_by(id=milestone.id).one() == milestone + assert Bug.query.filter_by(id=bug.id).one() == bug + assert Update.query.filter_by(id=update.id).one() == update + + testdata.remove_test_data() + + # make sure the objects haven't changed + assert Release.query.filter_by(id=release.id).one() == release + assert Milestone.query.filter_by(id=milestone.id).one() == milestone + assert Bug.query.filter_by(id=bug.id).one() == bug + assert Update.query.filter_by(id=update.id).one() == update