Repository: translate/pootle

Issue:
Create Pootle pytest plugin
This will allow pootle fixtures to be reused in 3rd party tests

It will also give us a chance to think through and potentially fix #3898 



Discussion:

Resolving patch:
diff --git a/pootle_pytest/fixtures/models/__init__.py b/pootle_pytest/fixtures/models/__init__.py
new file mode 100644
diff --git a/pootle_pytest/plugin.py b/pootle_pytest/plugin.py
new file mode 100644
--- /dev/null
+++ b/pootle_pytest/plugin.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) Pootle contributors.
+#
+# This file is a part of the Pootle project. It is distributed under the GPL3
+# or later license. See the LICENSE file for a copy of the license and the
+# AUTHORS file for copyright and authorship information.
+
+from fixtures.models.user import (
+    default, system, nobody, trans_nobody, admin, member, trans_member,
+    trans_system, member_with_email, member2, evil_member)
+from fixtures.models.directory import projects, root
+from fixtures.models.language import (
+    english, templates, afrikaans, arabic, french, spanish, italian, russian,
+    fish, klingon, klingon_vpw)
+from fixtures.models.permission import (
+    pootle_content_type, view, hide, suggest, translate, review, administrate)
+from fixtures.models.permission_set import nobody_ps, default_ps
+from fixtures.models.project import (
+    tutorial, tutorial_disabled, project_foo, project_bar, vfolder_test)
+from fixtures.models.store import (
+    UPDATE_STORE_TESTS,
+    po_directory, af_tutorial_po, en_tutorial_po, en_tutorial_po_no_file,
+    en_tutorial_po_member_updated, it_tutorial_po, af_tutorial_subdir_po,
+    issue_2401_po, test_get_units_po, fr_tutorial_subdir_to_remove_po,
+    fr_tutorial_remove_sync_po, es_tutorial_subdir_remove_po, ru_tutorial_po,
+    ru_update_save_changed_units_po, ru_update_set_last_sync_revision_po,
+    af_vfolder_test_browser_defines_po, templates_tutorial_pot,
+    param_update_store_test, store_diff_tests)
+from fixtures.models.translation_project import (
+    afrikaans_tutorial, arabic_tutorial_obsolete, english_tutorial,
+    french_tutorial, spanish_tutorial, italian_tutorial, russian_tutorial,
+    afrikaans_vfolder_test, templates_tutorial)
+
+from fixtures.cache import delete_pattern
+from fixtures.import_export_fixtures import (
+    FILE_IMPORT_FAIL_TESTS,
+    file_import_failure, ts_directory, en_tutorial_ts)
+from fixtures.revision import revision
+from fixtures.views import admin_client
+
+
+PARAMETERS = (
+    ("update_store_test_names", UPDATE_STORE_TESTS),
+    ("file_import_failure_names", FILE_IMPORT_FAIL_TESTS))
+
+
+def pytest_generate_tests(metafunc):
+    for name, params in PARAMETERS:
+        if name in metafunc.fixturenames:
+            metafunc.parametrize(name, params)
+
+__all__ = (
+    'admin', 'default', 'evil_member', 'member', 'member2',
+    'member_with_email', 'nobody', 'system', 'trans_member', 'trans_nobody',
+    'trans_system', 'projects', 'root', 'afrikaans', 'arabic', 'english',
+    'fish', 'french', 'italian', 'klingon', 'klingon_vpw', 'russian',
+    'spanish', 'templates', 'administrate', 'hide', 'pootle_content_type',
+    'review', 'suggest', 'translate', 'view', 'default_ps', 'nobody_ps',
+    'project_bar', 'project_foo', 'tutorial', 'tutorial_disabled',
+    'vfolder_test', 'af_tutorial_po', 'af_tutorial_subdir_po',
+    'af_vfolder_test_browser_defines_po', 'en_tutorial_po',
+    'en_tutorial_po_member_updated', 'en_tutorial_po_no_file',
+    'es_tutorial_subdir_remove_po', 'fr_tutorial_remove_sync_po',
+    'fr_tutorial_subdir_to_remove_po', 'issue_2401_po', 'it_tutorial_po',
+    'param_update_store_test', 'po_directory', 'ru_tutorial_po',
+    'ru_update_save_changed_units_po', 'ru_update_set_last_sync_revision_po',
+    'store_diff_tests', 'templates_tutorial_pot', 'test_get_units_po',
+    'afrikaans_tutorial', 'afrikaans_vfolder_test', 'arabic_tutorial_obsolete',
+    'english_tutorial', 'french_tutorial', 'italian_tutorial',
+    'russian_tutorial', 'spanish_tutorial', 'templates_tutorial',
+    'delete_pattern', 'en_tutorial_ts', 'file_import_failure', 'ts_directory',
+    'revision', 'admin_client')
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -254,6 +254,7 @@ def get_check_description(name, filterfunc):
         'console_scripts': [
             'pootle = pootle.runner:main',
         ],
+        'pytest11': ['pootle = pootle_pytest.plugin'],
     },
     cmdclass={
         'build_checks_templates': BuildChecksTemplatesCommand,


Test patch:
diff --git a/tests/fixtures/__init__.py b/pootle_pytest/__init__.py
similarity index 100%
rename from tests/fixtures/__init__.py
rename to pootle_pytest/__init__.py
diff --git a/tests/factories.py b/pootle_pytest/factories.py
similarity index 100%
rename from tests/factories.py
rename to pootle_pytest/factories.py
diff --git a/tests/fixtures/models/__init__.py b/pootle_pytest/fixtures/__init__.py
similarity index 100%
rename from tests/fixtures/models/__init__.py
rename to pootle_pytest/fixtures/__init__.py
diff --git a/tests/fixtures/cache.py b/pootle_pytest/fixtures/cache.py
similarity index 100%
rename from tests/fixtures/cache.py
rename to pootle_pytest/fixtures/cache.py
diff --git a/tests/fixtures/import_export_fixtures.py b/pootle_pytest/fixtures/import_export_fixtures.py
similarity index 66%
rename from tests/fixtures/import_export_fixtures.py
rename to pootle_pytest/fixtures/import_export_fixtures.py
--- a/tests/fixtures/import_export_fixtures.py
+++ b/pootle_pytest/fixtures/import_export_fixtures.py
@@ -4,6 +4,7 @@
 # or later license. See the LICENSE file for a copy of the license and the
 # AUTHORS file for copyright and authorship information.
 
+from collections import OrderedDict
 import os
 import shutil
 import pytest
@@ -11,26 +12,21 @@
 from .models import store
 
 
+FILE_IMPORT_FAIL_TESTS = OrderedDict()
+FILE_IMPORT_FAIL_TESTS["revision_header_missing.po"] = "MissingPootleRevError"
+FILE_IMPORT_FAIL_TESTS["revision_header_invalid.po"] = "MissingPootleRevError"
+FILE_IMPORT_FAIL_TESTS["path_header_missing.po"] = "MissingPootlePathError"
+FILE_IMPORT_FAIL_TESTS["path_header_invalid.po"] = "FileImportError"
+
+
 @pytest.fixture
-def file_import_failure(request):
-    return request.params
-
-
-def pytest_generate_tests(metafunc):
-    if "file_import_failure" not in metafunc.fixturenames:
-        return
-    from import_export.exceptions import (MissingPootlePathError,
-                                          MissingPootleRevError,
-                                          FileImportError)
-    metafunc.parametrize("file_import_failure",
-                         [(u"revision_header_missing.po",
-                           MissingPootleRevError),
-                          (u"revision_header_invalid.po",
-                           MissingPootleRevError),
-                          ("path_header_missing.po",
-                           MissingPootlePathError),
-                          ("path_header_invalid.po",
-                           FileImportError)])
+def file_import_failure(file_import_failure_names):
+    from import_export import exceptions
+
+    return (
+        file_import_failure_names,
+        getattr(
+            exceptions, FILE_IMPORT_FAIL_TESTS[file_import_failure_names]))
 
 
 @pytest.fixture
diff --git a/tests/fixtures/mock.py b/pootle_pytest/fixtures/mock.py
similarity index 100%
rename from tests/fixtures/mock.py
rename to pootle_pytest/fixtures/mock.py
diff --git a/tests/fixtures/models/directory.py b/pootle_pytest/fixtures/models/directory.py
similarity index 95%
rename from tests/fixtures/models/directory.py
rename to pootle_pytest/fixtures/models/directory.py
--- a/tests/fixtures/models/directory.py
+++ b/pootle_pytest/fixtures/models/directory.py
@@ -11,7 +11,7 @@
 
 
 @pytest.fixture
-def root(transactional_db):
+def root(transactional_db, system):
     """Require the root directory."""
     from pootle_app.models import Directory
     root, created = Directory.objects.get_or_create(name='')
diff --git a/tests/fixtures/models/language.py b/pootle_pytest/fixtures/models/language.py
similarity index 98%
rename from tests/fixtures/models/language.py
rename to pootle_pytest/fixtures/models/language.py
--- a/tests/fixtures/models/language.py
+++ b/pootle_pytest/fixtures/models/language.py
@@ -33,7 +33,7 @@ def _require_language(code, fullname, plurals=2, plural_equation='(n != 1)'):
 
 
 @pytest.fixture
-def english(root, system):
+def english(root):
     """Require the English language."""
     return _require_language('en', 'English')
 
diff --git a/tests/fixtures/models/permission.py b/pootle_pytest/fixtures/models/permission.py
similarity index 100%
rename from tests/fixtures/models/permission.py
rename to pootle_pytest/fixtures/models/permission.py
diff --git a/tests/fixtures/models/permission_set.py b/pootle_pytest/fixtures/models/permission_set.py
similarity index 100%
rename from tests/fixtures/models/permission_set.py
rename to pootle_pytest/fixtures/models/permission_set.py
diff --git a/tests/fixtures/models/project.py b/pootle_pytest/fixtures/models/project.py
similarity index 100%
rename from tests/fixtures/models/project.py
rename to pootle_pytest/fixtures/models/project.py
diff --git a/tests/fixtures/models/store.py b/pootle_pytest/fixtures/models/store.py
similarity index 96%
rename from tests/fixtures/models/store.py
rename to pootle_pytest/fixtures/models/store.py
--- a/tests/fixtures/models/store.py
+++ b/pootle_pytest/fixtures/models/store.py
@@ -16,7 +16,7 @@
 
 import pytest
 
-from ...utils import update_store, create_store
+from pootle_pytest.utils import update_store, create_store
 
 
 DEFAULT_STORE_UNITS_1 = [("Unit 1", "Unit 1"),
@@ -111,11 +111,6 @@
 }
 
 
-def pytest_generate_tests(metafunc):
-    if "_update_store_tests" in metafunc.fixturenames:
-        metafunc.parametrize("_update_store_tests", UPDATE_STORE_TESTS)
-
-
 def _setup_store_test(store, member, member2, test):
     from pootle_store.models import FILE_WINS, POOTLE_WINS
 
@@ -160,19 +155,20 @@ def _setup_store_test(store, member, member2, test):
 
 
 @pytest.fixture
-def store_diff_tests(en_tutorial_po, member, member2, _update_store_tests):
+def store_diff_tests(en_tutorial_po, member, member2, update_store_test_names):
     from pootle_store.models import StoreDiff
 
     test = _setup_store_test(en_tutorial_po, member, member2,
-                             UPDATE_STORE_TESTS[_update_store_tests])
+                             UPDATE_STORE_TESTS[update_store_test_names])
     test_store = create_store(units=test[1])
     return [StoreDiff(test[0], test_store, test[2])] + list(test[:3])
 
 
 @pytest.fixture
-def update_store_tests(en_tutorial_po, member, member2, _update_store_tests):
+def param_update_store_test(en_tutorial_po, member, member2,
+                            update_store_test_names):
     test = _setup_store_test(en_tutorial_po, member, member2,
-                             UPDATE_STORE_TESTS[_update_store_tests])
+                             UPDATE_STORE_TESTS[update_store_test_names])
     update_store(test[0],
                  units=test[1],
                  store_revision=test[2],
diff --git a/tests/fixtures/models/translation_project.py b/pootle_pytest/fixtures/models/translation_project.py
similarity index 100%
rename from tests/fixtures/models/translation_project.py
rename to pootle_pytest/fixtures/models/translation_project.py
diff --git a/tests/fixtures/models/user.py b/pootle_pytest/fixtures/models/user.py
similarity index 100%
rename from tests/fixtures/models/user.py
rename to pootle_pytest/fixtures/models/user.py
diff --git a/tests/fixtures/revision.py b/pootle_pytest/fixtures/revision.py
similarity index 100%
rename from tests/fixtures/revision.py
rename to pootle_pytest/fixtures/revision.py
diff --git a/tests/fixtures/views.py b/pootle_pytest/fixtures/views.py
similarity index 100%
rename from tests/fixtures/views.py
rename to pootle_pytest/fixtures/views.py
diff --git a/tests/utils.py b/pootle_pytest/utils.py
similarity index 100%
rename from tests/utils.py
rename to pootle_pytest/utils.py
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -8,14 +8,10 @@
 # AUTHORS file for copyright and authorship information.
 
 import os
-from pkgutil import iter_modules
 
 from django import setup
 from django.conf import settings
 
-from . import fixtures
-from .fixtures import models as fixture_models
-
 
 def pytest_configure():
     if not settings.configured:
@@ -26,16 +22,3 @@ def pytest_configure():
         os.environ['POOTLE_SETTINGS'] = os.path.join(WORKING_DIR,
                                                      'settings.py')
         setup()  # Required until pytest-dev/pytest-django#146 is fixed
-
-
-def _load_fixtures(*modules):
-    for mod in modules:
-        path = mod.__path__
-        prefix = '%s.' % mod.__name__
-
-        for loader, name, is_pkg in iter_modules(path, prefix):
-            if not is_pkg:
-                yield name
-
-
-pytest_plugins = tuple(_load_fixtures(fixtures, fixture_models), )
diff --git a/tests/core/views.py b/tests/core/views.py
--- a/tests/core/views.py
+++ b/tests/core/views.py
@@ -17,8 +17,8 @@
 from pootle.core.views import APIView
 from accounts.models import User
 
-from ..factories import UserFactory
-from ..utils import create_api_request
+from pootle_pytest.factories import UserFactory
+from pootle_pytest.utils import create_api_request
 
 
 class UserAPIView(APIView):
diff --git a/tests/models/legalpage.py b/tests/models/legalpage.py
--- a/tests/models/legalpage.py
+++ b/tests/models/legalpage.py
@@ -12,7 +12,8 @@
 from pootle.core.utils.timezone import aware_datetime
 from staticpages.models import LegalPage
 
-from ..factories import AgreementFactory, LegalPageFactory, UserFactory
+from pootle_pytest.factories import (
+    AgreementFactory, LegalPageFactory, UserFactory)
 
 
 @pytest.mark.django_db(transaction=True)
diff --git a/tests/models/project.py b/tests/models/project.py
--- a/tests/models/project.py
+++ b/tests/models/project.py
@@ -11,9 +11,11 @@
 
 from pootle_project.models import Project
 
-from ..factories import UserFactory
-from ..fixtures.models.permission_set import _require_permission_set
-from ..utils import items_equal
+from pootle_pytest.factories import UserFactory
+from pootle_pytest.fixtures.models.permission_set import (
+    _require_permission_set)
+
+from pootle_pytest.utils import items_equal
 
 
 @pytest.mark.django_db
diff --git a/tests/models/store.py b/tests/models/store.py
--- a/tests/models/store.py
+++ b/tests/models/store.py
@@ -406,11 +406,11 @@ def _test_store_update_units_now(*test_args):
 
 
 @pytest.mark.django_db
-def test_store_update(update_store_tests):
-    _test_store_update_indexes(*update_store_tests)
-    _test_store_update_units_before(*update_store_tests)
-    _test_store_update_units_now(*update_store_tests)
-    _test_store_update_ordering(*update_store_tests)
+def test_store_update(param_update_store_test):
+    _test_store_update_indexes(*param_update_store_test)
+    _test_store_update_units_before(*param_update_store_test)
+    _test_store_update_units_now(*param_update_store_test)
+    _test_store_update_ordering(*param_update_store_test)
 
 
 @pytest.mark.django_db
diff --git a/tests/models/submission.py b/tests/models/submission.py
--- a/tests/models/submission.py
+++ b/tests/models/submission.py
@@ -15,7 +15,7 @@
                                       SubmissionTypes, SubmissionFields)
 from pootle_store.util import TRANSLATED, UNTRANSLATED
 
-from ..factories import SubmissionFactory
+from pootle_pytest.factories import SubmissionFactory
 
 
 def _create_comment_submission(unit, user, creation_time, comment):
diff --git a/tests/models/user.py b/tests/models/user.py
--- a/tests/models/user.py
+++ b/tests/models/user.py
@@ -17,9 +17,10 @@
 import accounts
 from pootle_store.util import FUZZY, TRANSLATED
 
-from tests.fixtures.models.store import (TEST_EVIL_UPDATE_PO,
-                                         _create_submission_and_suggestion,
-                                         _create_comment_on_unit)
+from pootle_pytest.fixtures.models.store import (
+    TEST_EVIL_UPDATE_PO,
+    _create_submission_and_suggestion,
+    _create_comment_on_unit)
 
 
 def _make_evil_member_updates(store, evil_member):
diff --git a/tests/views/unit.py b/tests/views/unit.py
--- a/tests/views/unit.py
+++ b/tests/views/unit.py
@@ -13,7 +13,7 @@
 
 from pootle_store.views import get_units
 
-from ..utils import create_api_request
+from pootle_pytest.utils import create_api_request
 
 
 @pytest.mark.django_db