def __call__(self): """ Manage a database Session object for the life of the context. Yields a database Session object, then either commits the tranaction if there were no Exceptions or rolls back the transaction. In either case, it also will close and remove the Session. """ session = Session() try: yield session session.commit() except Exception as e: # It is possible for session.rolback() to raise Exceptions, so we will wrap it in an # Exception handler as well so we can log the rollback failure and still raise the # original Exception. try: session.rollback() except Exception: log.exception( 'An Exception was raised while rolling back a transaction.' ) raise e finally: session.close() Session.remove()
class BaseTestCase(unittest.TestCase): """ The base test class for Bodhi. This class configures the global scoped session with a test database. The test database makes use of nested transactions to provide a clean slate for each test. Tests may call both ``commit`` and ``rollback`` on the database session they acquire from ``bodhi.server.Session``. """ _populate_db = True app_settings = { 'authtkt.secret': 'sssshhhhhh', 'authtkt.secure': False, 'mako.directories': 'bodhi:server/templates', 'session.type': 'memory', 'session.key': 'testing', 'session.secret': 'foo', 'dogpile.cache.backend': 'dogpile.cache.memory', 'dogpile.cache.expiration_time': 0, 'cache.type': 'memory', 'cache.regions': 'default_term, second, short_term, long_term', 'cache.second.expire': '1', 'cache.short_term.expire': '60', 'cache.default_term.expire': '300', 'cache.long_term.expire': '3600', 'acl_system': 'dummy', 'buildsystem': 'dummy', 'important_groups': 'proventesters provenpackager releng', 'admin_groups': 'bodhiadmin releng', 'admin_packager_groups': 'provenpackager', 'mandatory_packager_groups': 'packager', 'critpath_pkgs': 'kernel', 'critpath.num_admin_approvals': 0, 'bugtracker': 'dummy', 'stats_blacklist': 'bodhi autoqa', 'system_users': 'bodhi autoqa', 'max_update_length_for_ui': '70', 'openid.provider': 'https://id.stg.fedoraproject.org/openid/', 'openid.url': 'https://id.stg.fedoraproject.org', 'test_case_base_url': 'https://fedoraproject.org/wiki/', 'openid_template': '{username}.id.fedoraproject.org', 'site_requirements': u'rpmlint', 'resultsdb_api_url': 'whatever', 'base_address': 'http://0.0.0.0:6543', 'cors_connect_src': 'http://0.0.0.0:6543', 'cors_origins_ro': 'http://0.0.0.0:6543', 'cors_origins_rw': 'http://0.0.0.0:6543', 'sqlalchemy.url': DEFAULT_DB } def setUp(self): """Set up Bodhi for testing.""" # Ensure "cached" objects are cleared before each test. models.Release._all_releases = None models.Release._tag_cache = None if engine is None: self.engine = _configure_test_db() else: self.engine = engine self.connection = self.engine.connect() models.Base.metadata.create_all(bind=self.connection) self.transaction = self.connection.begin() Session.remove() Session.configure(bind=self.engine, autoflush=False, expire_on_commit=False) self.Session = Session self.db = Session() self.db.begin_nested() if self._populate_db: populate(self.db) bugs.set_bugtracker() buildsys.setup_buildsystem({'buildsystem': 'dev'}) self._request_sesh = mock.patch( 'bodhi.server.webapp._complete_database_session', webapp._rollback_or_commit) self._request_sesh.start() # Create the test WSGI app one time. We should avoid creating too many # of these since Pyramid holds global references to the objects it creates # and this results in a substantial memory leak. Long term we should figure # out how to make Pyramid forget about these. global _app if _app is None: # We don't want to call Session.remove() during the unit tests, because that will # trigger the restart_savepoint() callback defined above which will remove the data # added by populate(). with mock.patch('bodhi.server.Session.remove'): _app = BodhiTestApp( main({}, testing=u'guest', **self.app_settings)) self.app = _app def get_csrf_token(self, app=None): """ Return a CSRF token that can be used by tests as they test the REST API. Args: app (BodhiTestApp): The app to use to get the token. Defaults to None, which will use self.app. Returns: basestring: A CSRF token. """ if not app: app = self.app return app.get('/csrf', headers={ 'Accept': 'application/json' }).json_body['csrf_token'] def get_update(self, builds='bodhi-2.0-1.fc17', stable_karma=3, unstable_karma=-3): """ Return a dict describing an update. This is useful for tests that want to POST to the API to create an update. Args: builds (basestring): A comma-separated list of NVRs to include in the update. stable_karma (int): The stable karma threshold to use on the update. unstable_karma (int): The unstable karma threshold to use on the update. """ if isinstance(builds, list): builds = ','.join(builds) if not isinstance(builds, str): builds = builds.encode('utf-8') return { 'builds': builds, 'bugs': u'', 'notes': u'this is a test update', 'type': u'bugfix', 'autokarma': True, 'stable_karma': stable_karma, 'unstable_karma': unstable_karma, 'requirements': u'rpmlint', 'require_bugs': False, 'require_testcases': True, 'csrf_token': self.get_csrf_token(), } def tearDown(self): """Roll back all the changes from the test and clean up the session.""" self._request_sesh.stop() self.db.close() self.transaction.rollback() self.connection.close() Session.remove() def create_update(self, build_nvrs, release_name=u'F17'): """ Create and return an Update with the given iterable of build_nvrs. Each build_nvr should be a tuple of strings describing the name, version, and release for the build. For example, build_nvrs might look like this: ((u'bodhi', u'2.3.3', u'1.fc24'), (u'python-fedora-atomic-composer', u'2016.3', u'1.fc24')) You can optionally pass a release_name to select a different release than the default F17, but the release must already exist in the database. This is a convenience wrapper around bodhi.tests.server.create_update so that tests can just call self.create_update() and not have to pass self.db. Args: build_nvrs (iterable): An iterable of 3-tuples. Each 3-tuple is strings that express the name, version, and release of the desired build. release_name (basestring): The name of the release to associate with the new updates. Returns: bodhi.server.models.Update: The new update. """ return create_update(self.db, build_nvrs, release_name) def create_release(self, version): """ Create and return a :class:`Release` with the given version. Args: version (basestring): A string of the version of the release, such as 27. Returns: bodhi.server.models.Release: A new release. """ release = models.Release( name=u'F{}'.format(version), long_name=u'Fedora {}'.format(version), id_prefix=u'FEDORA', version=u'{}'.format(version.replace('M', '')), dist_tag=u'f{}'.format(version), stable_tag=u'f{}-updates'.format(version), testing_tag=u'f{}-updates-testing'.format(version), candidate_tag=u'f{}-updates-candidate'.format(version), pending_signing_tag=u'f{}-updates-testing-signing'.format(version), pending_testing_tag=u'f{}-updates-testing-pending'.format(version), pending_stable_tag=u'f{}-updates-pending'.format(version), override_tag=u'f{}-override'.format(version), branch=u'f{}'.format(version), state=models.ReleaseState.current) self.db.add(release) models.Release._all_releases = None models.Release._tag_cache = None self.db.flush() return release
class BaseTestCase(unittest.TestCase): """ The base test class for Bodhi. This class configures the global scoped session with a test database. The test database makes use of nested transactions to provide a clean slate for each test. Tests may call both ``commit`` and ``rollback`` on the database session they acquire from ``bodhi.server.Session``. """ _populate_db = True app_settings = { 'authtkt.secret': 'sssshhhhhh', 'authtkt.secure': False, 'mako.directories': 'bodhi:server/templates', 'session.type': 'memory', 'session.key': 'testing', 'session.secret': 'foo', 'dogpile.cache.backend': 'dogpile.cache.memory', 'dogpile.cache.expiration_time': 0, 'cache.type': 'memory', 'cache.regions': 'default_term, second, short_term, long_term', 'cache.second.expire': '1', 'cache.short_term.expire': '60', 'cache.default_term.expire': '300', 'cache.long_term.expire': '3600', 'acl_system': 'dummy', 'buildsystem': 'dummy', 'important_groups': 'proventesters provenpackager releng', 'admin_groups': 'bodhiadmin releng', 'admin_packager_groups': 'provenpackager', 'mandatory_packager_groups': 'packager', 'critpath_pkgs': 'kernel', 'critpath.num_admin_approvals': 0, 'bugtracker': 'dummy', 'stats_blacklist': 'bodhi autoqa', 'system_users': 'bodhi autoqa', 'max_update_length_for_ui': '70', 'openid.provider': 'https://id.stg.fedoraproject.org/openid/', 'openid.url': 'https://id.stg.fedoraproject.org', 'test_case_base_url': 'https://fedoraproject.org/wiki/', 'openid_template': '{username}.id.fedoraproject.org', 'site_requirements': u'rpmlint', 'resultsdb_api_url': 'whatever', 'base_address': 'http://0.0.0.0:6543', 'cors_connect_src': 'http://0.0.0.0:6543', 'cors_origins_ro': 'http://0.0.0.0:6543', 'cors_origins_rw': 'http://0.0.0.0:6543', } def setUp(self): # Ensure "cached" objects are cleared before each test. models.Release._all_releases = None models.Release._tag_cache = None if engine is None: self.engine = _configure_test_db() else: self.engine = engine self.connection = self.engine.connect() models.Base.metadata.create_all(bind=self.connection) self.transaction = self.connection.begin() Session.remove() Session.configure(bind=self.engine, autoflush=False, expire_on_commit=False) self.Session = Session self.db = Session() self.db.begin_nested() if self._populate_db: populate(self.db) bugs.set_bugtracker() buildsys.setup_buildsystem({'buildsystem': 'dev'}) def request_db(request=None): """ Replace the db session function with one that doesn't close the session. This allows tests to make assertions about the database. Without it, all the changes would be rolled back to when the nested transaction is started. """ def cleanup(request): if request.exception is not None: Session().rollback() else: Session().commit() request.add_finished_callback(cleanup) return Session() self._request_sesh = mock.patch( 'bodhi.server.get_db_session_for_request', request_db) self._request_sesh.start() # Create the test WSGI app one time. We should avoid creating too many # of these since Pyramid holds global references to the objects it creates # and this results in a substantial memory leak. Long term we should figure # out how to make Pyramid forget about these. global _app if _app is None: _app = TestApp(main({}, testing=u'guest', **self.app_settings)) self.app = _app def get_csrf_token(self): return self.app.get('/csrf').json_body['csrf_token'] def get_update(self, builds='bodhi-2.0-1.fc17', stable_karma=3, unstable_karma=-3): if isinstance(builds, list): builds = ','.join(builds) if not isinstance(builds, str): builds = builds.encode('utf-8') return { 'builds': builds, 'bugs': u'', 'notes': u'this is a test update', 'type': u'bugfix', 'autokarma': True, 'stable_karma': stable_karma, 'unstable_karma': unstable_karma, 'requirements': u'rpmlint', 'require_bugs': False, 'require_testcases': True, 'csrf_token': self.get_csrf_token(), } def tearDown(self): """Roll back all the changes from the test and clean up the session.""" self._request_sesh.stop() self.db.close() self.transaction.rollback() self.connection.close() Session.remove() def create_update(self, build_nvrs, release_name=u'F17'): """ Create and return an Update with the given iterable of build_nvrs. Each build_nvr should be a tuple of strings describing the name, version, and release for the build. For example, build_nvrs might look like this: ((u'bodhi', u'2.3.3', u'1.fc24'), (u'python-fedora-atomic-composer', u'2016.3', u'1.fc24')) You can optionally pass a release_name to select a different release than the default F17, but the release must already exist in the database. This is a convenience wrapper around bodhi.tests.server.create_update so that tests can just call self.create_update() and not have to pass self.db. """ return create_update(self.db, build_nvrs, release_name)
class BaseTestCaseMixin: """ The base test class for Bodhi. This class configures the global scoped session with a test database before calling test methods. The test database makes use of nested transactions to provide a clean slate for each test. Tests may call both ``commit`` and ``rollback`` on the database session they acquire from ``bodhi.server.Session``. """ _populate_db = True def _setup_method(self): """Set up Bodhi for testing.""" self.config = testing.setUp() self.app_settings = get_appsettings(os.environ["BODHI_CONFIG"]) config.config.clear() config.config.load_config(self.app_settings) # Ensure "cached" objects are cleared before each test. models.Release.clear_all_releases_cache() models.Release._tag_cache = None if engine is None: self.engine = _configure_test_db(config.config["sqlalchemy.url"]) else: self.engine = engine self.connection = self.engine.connect() models.Base.metadata.create_all(bind=self.connection) self.transaction = self.connection.begin() Session.remove() Session.configure(bind=self.engine, autoflush=False, expire_on_commit=False) self.Session = Session self.db = Session() self.db.begin_nested() if self._populate_db: populate(self.db) bugs.set_bugtracker() buildsys.setup_buildsystem({'buildsystem': 'dev'}) self._request_sesh = mock.patch( 'bodhi.server.webapp._complete_database_session', webapp._rollback_or_commit) self._request_sesh.start() # Create the test WSGI app one time. We should avoid creating too many # of these since Pyramid holds global references to the objects it creates # and this results in a substantial memory leak. Long term we should figure # out how to make Pyramid forget about these. global _app if _app is None: # We don't want to call Session.remove() during the unit tests, because that will # trigger the restart_savepoint() callback defined above which will remove the data # added by populate(). with mock.patch('bodhi.server.Session.remove'): _app = TestApp( main({}, testing='guest', session=self.db, **self.app_settings)) self.app = _app self.registry = self.app.app.registry # ensure a clean state of the dev build system buildsys.DevBuildsys.clear() def get_csrf_token(self, app=None): """ Return a CSRF token that can be used by tests as they test the REST API. Args: app (TestApp): The app to use to get the token. Defaults to None, which will use self.app. Returns: str: A CSRF token. """ if not app: app = self.app return app.get('/csrf', headers={ 'Accept': 'application/json' }).json_body['csrf_token'] def get_update(self, builds='bodhi-2.0-1.fc17', from_tag=None, stable_karma=3, unstable_karma=-3): """ Return a dict describing an update. This is useful for tests that want to POST to the API to create an update. Args: builds (str): A comma-separated list of NVRs to include in the update. from_tag (str): A tag from which to fill the list of builds. stable_karma (int): The stable karma threshold to use on the update. unstable_karma (int): The unstable karma threshold to use on the update. """ update = { 'bugs': '', 'notes': 'this is a test update', 'type': 'bugfix', 'autokarma': True, 'stable_karma': stable_karma, 'unstable_karma': unstable_karma, 'requirements': 'rpmlint', 'require_bugs': False, 'require_testcases': True, 'csrf_token': self.get_csrf_token(), } if builds: if isinstance(builds, list): builds = ','.join(builds) if not isinstance(builds, str): builds = builds.encode('utf-8') update['builds'] = builds if from_tag: update['from_tag'] = from_tag return update def _teardown_method(self): """Roll back all the changes from the test and clean up the session.""" self._request_sesh.stop() self.db.close() self.transaction.rollback() self.connection.close() Session.remove() testing.tearDown() def create_update(self, build_nvrs, release_name='F17'): """ Create and return an Update with the given iterable of build_nvrs. Each build_nvr should be a string describing the name, version, and release for the build separated by dashes. For example, build_nvrs might look like this: ('bodhi-2.3.3-1.fc24', 'python-fedora-atomic-composer-2016.3-1.fc24') You can optionally pass a release_name to select a different release than the default F17, but the release must already exist in the database. This is a convenience wrapper around create_update so that tests can just call self.create_update() and not have to pass self.db. Args: build_nvrs (iterable): An iterable of 3-tuples. Each 3-tuple is strings that express the name, version, and release of the desired build. release_name (str): The name of the release to associate with the new updates. Returns: bodhi.server.models.Update: The new update. """ return create_update(self.db, build_nvrs, release_name) def create_release(self, version, create_automatic_updates=False): """ Create and return a :class:`Release` with the given version. Args: version (str): A string of the version of the release, such as 27. Returns: bodhi.server.models.Release: A new release. """ release = models.Release( name='F{}'.format(version), long_name='Fedora {}'.format(version), id_prefix='FEDORA', version='{}'.format(version.replace('M', '')), dist_tag='f{}'.format(version), stable_tag='f{}-updates'.format(version), testing_tag='f{}-updates-testing'.format(version), candidate_tag='f{}-updates-candidate'.format(version), pending_signing_tag='f{}-updates-testing-signing'.format(version), pending_testing_tag='f{}-updates-testing-pending'.format(version), pending_stable_tag='f{}-updates-pending'.format(version), override_tag='f{}-override'.format(version), branch='f{}'.format(version), state=models.ReleaseState.current, create_automatic_updates=create_automatic_updates, package_manager=models.PackageManager.unspecified, testing_repository=None) self.db.add(release) models.Release.clear_all_releases_cache() models.Release._tag_cache = None self.db.flush() return release