Skip to content

Commit

Permalink
Merge 43ed6aa into 9fc014c
Browse files Browse the repository at this point in the history
  • Loading branch information
lnielsen committed Sep 13, 2020
2 parents 9fc014c + 43ed6aa commit 778e2f5
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 3 deletions.
1 change: 1 addition & 0 deletions pytest.ini
Expand Up @@ -10,3 +10,4 @@
pep8ignore = docs/conf.py ALL
addopts = --pep8 --doctest-glob="*.rst" --doctest-modules --cov=pytest_invenio --cov-report=term-missing --cov-append
testpaths = docs tests pytest_invenio
filterwarnings = ignore::pytest.PytestDeprecationWarning
27 changes: 27 additions & 0 deletions pytest_invenio/__init__.py
Expand Up @@ -240,6 +240,33 @@ def create_app():
instance you may want to customize the celery configuration only for a
specific Python test file.
Injecting entry points
~~~~~~~~~~~~~~~~~~~~~~
Invenio relies heavily upon entry points for constructing a Flask application,
and it can be rather cumbersome to try to manually register database models,
mappings and other features afterwards.
You can therefore inject extra entry points if needed during testing via the
:py:data:`~fixtures.extra_entry_points` fixture and using it in your custom
``create_app()`` fixture:
.. code-block:: python
@pytest.fixture(scope="module")
def extra_entry_points():
return {
'invenio_db.models': [
'mock_module = mock_module.models',
]
}
@pytest.fixture(scope="module")
def create_app(entry_points):
return _create_api
Note that ``create_app()`` depends on the :py:data:`~fixtures.entry_points`
fixture not the ``extra_entry_points()``.
.. _views-testing:
Views testing
Expand Down
81 changes: 81 additions & 0 deletions pytest_invenio/fixtures.py
Expand Up @@ -17,6 +17,7 @@
import sys
import tempfile
from datetime import datetime
from functools import partial

import pkg_resources
import pytest
Expand Down Expand Up @@ -633,3 +634,83 @@ def create_bucket_from_dir(source_dir, location_obj=None):
db.session.commit()
return bucket_obj
return create_bucket_from_dir


class MockDistribution(pkg_resources.Distribution):
"""A mocked distribution that we can inject entry points with."""

def __init__(self, extra_entry_points):
"""Initialise the extra entry point."""
self._ep_map = {}
# Create the entry point group map (which eventually will be used to
# iterate over entry points). See source code for Distribution,
# EntryPoint and WorkingSet in pkg_resources module.
for group, entries in extra_entry_points.items():
group_map = {}
for ep_str in entries:
ep = pkg_resources.EntryPoint.parse(ep_str)
ep.require = self._require_noop
group_map[ep.name] = ep
self._ep_map[group] = group_map
# Note location must have a non-empty string value, as it is used as a
# key into a dictionary.
super().__init__(location='unknown')

def _require_noop(self, *args, **kwargs):
"""Do nothing on entry point require."""
pass


@pytest.fixture(scope="module")
def entry_points(extra_entry_points):
"""Entry points fixture.
Scope: module
Invenio relies heavily on Python entry points for constructing an
application and it can be rather cumbersome to try to register database
models, search mappings etc yourself afterwards.
This fixture allows you to inject extra entry points into the application
loading, so that you can load e.g. a testing module or test mapping.
To use the fixture simply define the ``extra_entry_points()`` fixture,
and then depend on the ``entry_points()`` fixture in your ``create_app``
fixture:
.. code-block:: python
@pytest.fixture(scope="module")
def extra_entry_points():
return {
'invenio_db.models': [
'mock_module = mock_module.models',
]
}
@pytest.fixture(scope="module")
def create_app(instance_path, entry_points):
return _create_api
"""
# First make a copy of the working_set state, so that we can restore the
# state.
workingset_state = pkg_resources.working_set.__getstate__()

# Next, make a fake distribution that wil yield the extra entry points and
# add it to the global working_set.
dist = MockDistribution(extra_entry_points)
pkg_resources.working_set.add(dist)

yield dist

# Last, we restore the original workingset state.
pkg_resources.working_set.__setstate__(workingset_state)


@pytest.fixture(scope="module")
def extra_entry_points():
"""Extra entry points.
Overwrite this fixture to define extra entry points.
"""
return {}
5 changes: 3 additions & 2 deletions pytest_invenio/plugin.py
Expand Up @@ -26,8 +26,9 @@

from .fixtures import _monkeypatch_response_class, app, app_config, appctx, \
base_app, base_client, broker_uri, browser, bucket_from_dir, \
celery_config_ext, cli_runner, database, db, db_uri, default_handler, es, \
es_clear, instance_path, location, mailbox, script_info
celery_config_ext, cli_runner, database, db, db_uri, default_handler, \
entry_points, es, es_clear, extra_entry_points, instance_path, location, \
mailbox, script_info


def pytest_generate_tests(metafunc):
Expand Down
2 changes: 1 addition & 1 deletion run-tests.sh
Expand Up @@ -12,7 +12,7 @@
rm -f .coverage
rm -f .coverage.eager.*
pydocstyle pytest_invenio tests docs && \
isort -rc -c -df && \
isort pytest_invenio tests --check-only --diff && \
check-manifest --ignore ".travis-*" && \
sphinx-build -qnNW docs docs/_build/html && \
# Following is needed in order to get proper code coverage for pytest plugins.
Expand Down
54 changes: 54 additions & 0 deletions tests/test_fixtures.py
Expand Up @@ -431,3 +431,57 @@ def test_creating_location_and_use_bucket_from_dir(bucket_from_dir):
assert files_from_bucket.one().key == "output_file"
""")
conftest_testdir.runpytest().assert_outcomes(passed=1)


def test_entrypoint(testdir):
"""Test database creation and initialization."""
testdir.makeconftest("""
import pytest
from flask import Flask
from functools import partial
from invenio_db import InvenioDB
def _factory(name, **config):
app_ = Flask(name)
app_.config.update(**config)
InvenioDB(app_)
return app_
@pytest.fixture(scope='module')
def create_app(entry_points):
return partial(_factory, 'app')
""")
testdir.makepyfile(mock_module="""
from invenio_db import db
from pkg_resources import iter_entry_points
class Place(db.Model):
id = db.Column(db.Integer, primary_key=True)
def db_entry_points():
return list(iter_entry_points('invenio_db.models'))
""")
testdir.makepyfile(test_ep="""
import pytest
# By importing we get a reference to iter_entry_points before it
# has been mocked (to test that this case also works).
from mock_module import db_entry_points
@pytest.fixture(scope='module')
def extra_entry_points():
return {
'invenio_db.models': [
'mock_module = mock_module',
],
}
def test_app(base_app, db):
from mock_module import Place
assert Place.query.count() == 0
def test_extras(base_app, db):
assert len(db_entry_points()) == 3
""")
testdir.runpytest("-s").assert_outcomes(passed=2)

0 comments on commit 778e2f5

Please sign in to comment.