Beispiel #1
0
    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()
Beispiel #2
0
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
Beispiel #3
0
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)
Beispiel #4
0
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