diff --git a/.travis.yml b/.travis.yml index d4f70d0eb..60ecef3d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,71 +1,61 @@ language: python -dist: xenial +dist: focal cache: false jobs: fast_finish: true include: - stage: baseline - python: 3.6 + python: 3.8 env: - - TOXENV=py36-dj20-postgres-xdist-coverage + - TOXENV=py38-dj31-postgres-xdist-coverage # Test in verbose mode. - PYTEST_ADDOPTS=-vv services: - postgresql - - python: 3.6 - env: TOXENV=py36-dj111-mysql_innodb-coverage + - python: 3.7 + env: TOXENV=py37-dj30-mysql_innodb-coverage services: - mysql - - python: 2.7 - env: TOXENV=py27-dj111-sqlite-xdist-coverage - python: 3.6 + env: TOXENV=py36-dj22-sqlite-xdist-coverage + - python: 3.8 env: TOXENV=checkqa,docs - stage: test python: 3.7 - env: TOXENV=py37-dj21-sqlite-coverage - - python: 3.7 env: TOXENV=py37-dj22-sqlite-xdist-coverage - python: 3.8 env: TOXENV=py38-dj30-sqlite-xdist-coverage - python: 3.8 env: TOXENV=py38-dj31-sqlite-xdist-coverage - # Explicitly test (older) pytest 4.1. - - python: 3.7 - env: TOXENV=py37-dj21-sqlite-pytest41-coverage - - - python: 3.6 - env: TOXENV=py36-djmaster-sqlite-coverage + - python: 3.8 + env: TOXENV=py38-djmaster-sqlite-coverage - # Explicitly test (older) pytest 5.3. + # Explicitly test (older) pytest 5.4. - python: 3.5 - env: TOXENV=py35-dj110-postgres-pytest53-coverage + env: TOXENV=py35-dj22-postgres-pytest54-coverage services: - postgresql - - python: 3.4 - env: TOXENV=py34-dj19-sqlite_file-coverage + - python: 3.5 + env: TOXENV=py35-dj22-sqlite_file-coverage - - python: 2.7 - env: TOXENV=py27-dj111-mysql_myisam-coverage + - python: 3.6 + env: TOXENV=py36-dj31-mysql_myisam-coverage services: - mysql - - python: 2.7 - env: TOXENV=py27-dj18-postgres-coverage - services: - - postgresql - # pypy/pypy3: not included with coverage reports (much slower then). - - python: pypy - env: TOXENV=pypy-dj111-sqlite_file + # pypy3: not included with coverage reports (much slower then). - python: pypy3 - env: TOXENV=pypy3-dj110-sqlite + env: TOXENV=pypy3-dj22-postgres + services: + - postgresql - stage: test_release - python: 3.6 - env: TOXENV=py36-dj20-postgres + python: 3.8 + env: TOXENV=py38-dj31-postgres services: - postgresql @@ -85,7 +75,7 @@ jobs: # NOTE: does not show up in "allowed failures" section, but is allowed to # fail (for the "test" stage). allow_failures: - - env: TOXENV=py36-djmaster-sqlite-coverage + - env: TOXENV=py38-djmaster-sqlite-coverage stages: - name: baseline @@ -98,7 +88,7 @@ stages: if: tag IS present install: - - pip install tox==3.9.0 + - pip install tox==3.20.0 script: - tox diff --git a/README.rst b/README.rst index 47255c882..367367c63 100644 --- a/README.rst +++ b/README.rst @@ -28,10 +28,12 @@ pytest-django allows you to test your Django project/applications with the `_ * Version compatibility: - * Django: 1.8-1.11, 2.0-2.2, - and latest master branch (compatible at the time of each release) - * Python: CPython 2.7, 3.4-3.7 or PyPy 2, 3 - * pytest: >=3.6 + * Django: 2.2, 3.0, 3.1 and latest master branch (compatible at the time of + each release) + * Python: CPython>=3.5 or PyPy 3 + * pytest: >=5.4 + + For compatibility with older versions, use the pytest-django 3.*.* series. * Licence: BSD * Project maintainers: Andreas Pelme, Floris Bruynooghe and Daniel Hahler diff --git a/docs/conf.py b/docs/conf.py index c64d2d2a2..b049c68ee 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import os import sys import datetime diff --git a/docs/faq.rst b/docs/faq.rst index 1c6be1be2..432e47f48 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -76,7 +76,7 @@ test runner like this: .. code-block:: python - class PytestTestRunner(object): + class PytestTestRunner: """Runs pytest to discover and run tests.""" def __init__(self, verbosity=1, failfast=False, keepdb=False, **kwargs): diff --git a/pytest_django/asserts.py b/pytest_django/asserts.py index 35fe979ba..12a3fc565 100644 --- a/pytest_django/asserts.py +++ b/pytest_django/asserts.py @@ -23,10 +23,10 @@ def assertion_func(*args, **kwargs): __all__ = [] assertions_names = set() assertions_names.update( - set(attr for attr in vars(TestCase) if attr.startswith('assert')), - set(attr for attr in vars(SimpleTestCase) if attr.startswith('assert')), - set(attr for attr in vars(LiveServerTestCase) if attr.startswith('assert')), - set(attr for attr in vars(TransactionTestCase) if attr.startswith('assert')), + {attr for attr in vars(TestCase) if attr.startswith('assert')}, + {attr for attr in vars(SimpleTestCase) if attr.startswith('assert')}, + {attr for attr in vars(LiveServerTestCase) if attr.startswith('assert')}, + {attr for attr in vars(TransactionTestCase) if attr.startswith('assert')}, ) for assert_func in assertions_names: diff --git a/pytest_django/compat.py b/pytest_django/compat.py deleted file mode 100644 index 97a847c24..000000000 --- a/pytest_django/compat.py +++ /dev/null @@ -1,12 +0,0 @@ -# This file cannot be imported from until Django sets up -try: - # Django 1.11+ - from django.test.utils import setup_databases, teardown_databases # noqa: F401, F811 -except ImportError: - # In Django prior to 1.11, teardown_databases is only available as a method on DiscoverRunner - from django.test.runner import setup_databases, DiscoverRunner # noqa: F401, F811 - - def teardown_databases(db_cfg, verbosity): - DiscoverRunner(verbosity=verbosity, interactive=False).teardown_databases( - db_cfg - ) diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index ce8111f1e..0f2dd6115 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -1,9 +1,7 @@ """All pytest-django fixtures""" -from __future__ import with_statement import os -import warnings from contextlib import contextmanager from functools import partial @@ -91,7 +89,7 @@ def django_db_setup( django_db_modify_db_settings, ): """Top level fixture to ensure test databases are available""" - from .compat import setup_databases, teardown_databases + from django.test.utils import setup_databases, teardown_databases setup_databases_args = {} @@ -164,7 +162,7 @@ def _disable_native_migrations(): class MigrateSilentCommand(migrate.Command): def handle(self, *args, **kwargs): kwargs["verbosity"] = 0 - return super(MigrateSilentCommand, self).handle(*args, **kwargs) + return super().handle(*args, **kwargs) migrate.Command = MigrateSilentCommand @@ -320,7 +318,7 @@ def rf(): return RequestFactory() -class SettingsWrapper(object): +class SettingsWrapper: _to_restore = [] def __delattr__(self, attr): @@ -370,8 +368,8 @@ def live_server(request): The address the server is started from is taken from the --liveserver command line option or if this is not provided from the DJANGO_LIVE_TEST_SERVER_ADDRESS environment variable. If - neither is provided ``localhost:8081,8100-8200`` is used. See the - Django documentation for its full syntax. + neither is provided ``localhost`` is used. See the Django + documentation for its full syntax. NOTE: If the live server needs database access to handle a request your test will have to request database access. Furthermore @@ -385,27 +383,9 @@ def live_server(request): """ skip_if_no_django() - import django - addr = request.config.getvalue("liveserver") or os.getenv( "DJANGO_LIVE_TEST_SERVER_ADDRESS" - ) - - if addr and ":" in addr: - if django.VERSION >= (1, 11): - ports = addr.split(":")[1] - if "-" in ports or "," in ports: - warnings.warn( - "Specifying multiple live server ports is not supported " - "in Django 1.11. This will be an error in a future " - "pytest-django release." - ) - - if not addr: - if django.VERSION < (1, 11): - addr = "localhost:8081,8100-8200" - else: - addr = "localhost" + ) or "localhost" server = live_server_helper.LiveServer(addr) request.addfinalizer(server.stop) @@ -458,14 +438,14 @@ def _assert_num_queries(config, num, exact=True, connection=None, info=None): num, "" if exact else "or less ", "but {} done".format( - num_performed == 1 and "1 was" or "%d were" % (num_performed,) + num_performed == 1 and "1 was" or "{} were".format(num_performed) ), ) if info: msg += "\n{}".format(info) if verbose: sqls = (q["sql"] for q in context.captured_queries) - msg += "\n\nQueries:\n========\n\n%s" % "\n\n".join(sqls) + msg += "\n\nQueries:\n========\n\n" + "\n\n".join(sqls) else: msg += " (add -v option to show queries)" pytest.fail(msg) diff --git a/pytest_django/live_server_helper.py b/pytest_django/live_server_helper.py index 94ded3315..f61034900 100644 --- a/pytest_django/live_server_helper.py +++ b/pytest_django/live_server_helper.py @@ -1,8 +1,4 @@ -import six - - -@six.python_2_unicode_compatible -class LiveServer(object): +class LiveServer: """The liveserver fixture This is the object that the ``live_server`` fixture returns. @@ -10,7 +6,6 @@ class LiveServer(object): """ def __init__(self, addr): - import django from django.db import connections from django.test.testcases import LiveServerThread from django.test.utils import modify_settings @@ -39,17 +34,13 @@ def __init__(self, addr): liveserver_kwargs["static_handler"] = _StaticFilesHandler - if django.VERSION < (1, 11): - host, possible_ports = parse_addr(addr) - self.thread = LiveServerThread(host, possible_ports, **liveserver_kwargs) + try: + host, port = addr.split(":") + except ValueError: + host = addr else: - try: - host, port = addr.split(":") - except ValueError: - host = addr - else: - liveserver_kwargs["port"] = int(port) - self.thread = LiveServerThread(host, **liveserver_kwargs) + liveserver_kwargs["port"] = int(port) + self.thread = LiveServerThread(host, **liveserver_kwargs) self._live_server_modified_settings = modify_settings( ALLOWED_HOSTS={"append": host} @@ -69,41 +60,13 @@ def stop(self): @property def url(self): - return "http://%s:%s" % (self.thread.host, self.thread.port) + return "http://{}:{}".format(self.thread.host, self.thread.port) def __str__(self): return self.url def __add__(self, other): - return "%s%s" % (self, other) + return "{}{}".format(self, other) def __repr__(self): return "" % self.url - - -def parse_addr(specified_address): - """Parse the --liveserver argument into a host/IP address and port range""" - # This code is based on - # django.test.testcases.LiveServerTestCase.setUpClass - - # The specified ports may be of the form '8000-8010,8080,9200-9300' - # i.e. a comma-separated list of ports or ranges of ports, so we break - # it down into a detailed list of all possible ports. - possible_ports = [] - try: - host, port_ranges = specified_address.split(":") - for port_range in port_ranges.split(","): - # A port range can be of either form: '8000' or '8000-8010'. - extremes = list(map(int, port_range.split("-"))) - assert len(extremes) in (1, 2) - if len(extremes) == 1: - # Port range of the form '8000' - possible_ports.append(extremes[0]) - else: - # Port range of the form '8000-8010' - for port in range(extremes[0], extremes[1] + 1): - possible_ports.append(port) - except Exception: - raise Exception('Invalid address ("%s") for live server.' % specified_address) - - return host, possible_ports diff --git a/pytest_django/migrations.py b/pytest_django/migrations.py index 3c39cb9f6..efcabf929 100644 --- a/pytest_django/migrations.py +++ b/pytest_django/migrations.py @@ -2,7 +2,7 @@ from pytest_django.lazy_django import get_django_version -class DisableMigrations(object): +class DisableMigrations: def __init__(self): self._django_version = get_django_version() @@ -10,7 +10,4 @@ def __contains__(self, item): return True def __getitem__(self, item): - if self._django_version >= (1, 9): - return None - else: - return "notmigrations" + return None diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 4d692de27..a13b2b81a 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -8,6 +8,7 @@ import inspect from functools import reduce import os +import pathlib import sys import types @@ -39,22 +40,11 @@ from .lazy_django import django_settings_is_configured, skip_if_no_django -try: - import pathlib -except ImportError: - import pathlib2 as pathlib - SETTINGS_MODULE_ENV = "DJANGO_SETTINGS_MODULE" CONFIGURATION_ENV = "DJANGO_CONFIGURATION" INVALID_TEMPLATE_VARS_ENV = "FAIL_INVALID_TEMPLATE_VARS" -PY2 = sys.version_info[0] == 2 - -# pytest 4.2 handles unittest setup/teardown itself via wrapping fixtures. -_pytest_version_info = tuple(int(x) for x in pytest.__version__.split(".", 2)[:2]) -_handle_unittest_methods = _pytest_version_info < (4, 2) - _report_header = [] @@ -303,11 +293,11 @@ def _get_option_with_source(option, envname): dc, dc_source = _get_option_with_source(options.dc, CONFIGURATION_ENV) if ds: - _report_header.append("settings: %s (from %s)" % (ds, ds_source)) + _report_header.append("settings: {} (from {})".format(ds, ds_source)) os.environ[SETTINGS_MODULE_ENV] = ds if dc: - _report_header.append("configuration: %s (from %s)" % (dc, dc_source)) + _report_header.append("configuration: {} (from {})".format(dc, dc_source)) os.environ[CONFIGURATION_ENV] = dc # Install the django-configurations importer @@ -330,7 +320,7 @@ def pytest_report_header(): return ["django: " + ", ".join(_report_header)] -@pytest.mark.trylast +@pytest.hookimpl(trylast=True) def pytest_configure(): # Allow Django settings to be configured in a user pytest_configure call, # but make sure we call django.setup() @@ -354,13 +344,7 @@ def _classmethod_is_defined_at_leaf(cls, method_name): try: f = method.__func__ except AttributeError: - pytest.fail("%s.%s should be a classmethod" % (cls, method_name)) - if PY2 and not ( - inspect.ismethod(method) - and inspect.isclass(method.__self__) - and issubclass(cls, method.__self__) - ): - pytest.fail("%s.%s should be a classmethod" % (cls, method_name)) + pytest.fail("{}.{} should be a classmethod".format(cls, method_name)) return f is not super_method.__func__ @@ -409,12 +393,6 @@ def _restore_class_methods(cls): cls.tearDownClass = tearDownClass -def pytest_runtest_setup(item): - if _handle_unittest_methods: - if django_settings_is_configured() and is_django_unittest(item): - _disable_class_methods(item.cls) - - @pytest.hookimpl(tryfirst=True) def pytest_collection_modifyitems(items): # If Django is not configured we don't need to bother @@ -523,33 +501,21 @@ def _django_setup_unittest(request, django_db_blocker): # Fix/patch pytest. # Before pytest 5.4: https://github.com/pytest-dev/pytest/issues/5991 # After pytest 5.4: https://github.com/pytest-dev/pytest-django/issues/824 - from _pytest.monkeypatch import MonkeyPatch + from _pytest.unittest import TestCaseFunction + original_runtest = TestCaseFunction.runtest def non_debugging_runtest(self): self._testcase(result=self) - mp_debug = MonkeyPatch() - mp_debug.setattr("_pytest.unittest.TestCaseFunction.runtest", non_debugging_runtest) - - request.getfixturevalue("django_db_setup") - - cls = request.node.cls - - with django_db_blocker.unblock(): - if _handle_unittest_methods: - _restore_class_methods(cls) - cls.setUpClass() - _disable_class_methods(cls) + try: + TestCaseFunction.runtest = non_debugging_runtest - yield + request.getfixturevalue("django_db_setup") - _restore_class_methods(cls) - cls.tearDownClass() - else: + with django_db_blocker.unblock(): yield - - if mp_debug: - mp_debug.undo() + finally: + TestCaseFunction.runtest = original_runtest @pytest.fixture(scope="function", autouse=True) @@ -591,12 +557,7 @@ def _django_set_urlconf(request): if marker: skip_if_no_django() import django.conf - - try: - from django.urls import clear_url_caches, set_urlconf - except ImportError: - # Removed in Django 2.0 - from django.core.urlresolvers import clear_url_caches, set_urlconf + from django.urls import clear_url_caches, set_urlconf urls = validate_urls(marker) original_urlconf = django.conf.settings.ROOT_URLCONF @@ -629,7 +590,7 @@ def _fail_for_invalid_template_variable(): ``pytest.mark.ignore_template_errors`` """ - class InvalidVarException(object): + class InvalidVarException: """Custom handler for invalid strings in templates.""" def __init__(self): @@ -677,7 +638,7 @@ def __mod__(self, var): """Handle TEMPLATE_STRING_IF_INVALID % var.""" origin = self._get_origin() if origin: - msg = "Undefined template variable '%s' in '%s'" % (var, origin) + msg = "Undefined template variable '{}' in '{}'".format(var, origin) else: msg = "Undefined template variable '%s'" % var if self.fail: @@ -732,7 +693,7 @@ def _django_clear_site_cache(): # ############### Helper Functions ################ -class _DatabaseBlockerContextManager(object): +class _DatabaseBlockerContextManager: def __init__(self, db_blocker): self._db_blocker = db_blocker @@ -743,7 +704,7 @@ def __exit__(self, exc_type, exc_value, traceback): self._db_blocker.restore() -class _DatabaseBlocker(object): +class _DatabaseBlocker: """Manager for django.db.backends.base.base.BaseDatabaseWrapper. This is the object returned by django_db_blocker. diff --git a/pytest_django_test/app/migrations/0001_initial.py b/pytest_django_test/app/migrations/0001_initial.py index 7791cafcf..3a853e557 100644 --- a/pytest_django_test/app/migrations/0001_initial.py +++ b/pytest_django_test/app/migrations/0001_initial.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.9a1 on 2016-06-22 04:33 -from __future__ import unicode_literals from django.db import migrations, models diff --git a/pytest_django_test/compat.py b/pytest_django_test/compat.py deleted file mode 100644 index 0c9ce91f1..000000000 --- a/pytest_django_test/compat.py +++ /dev/null @@ -1,4 +0,0 @@ -try: - from urllib2 import urlopen, HTTPError -except ImportError: - from urllib.request import urlopen, HTTPError # noqa: F401 diff --git a/pytest_django_test/db_helpers.py b/pytest_django_test/db_helpers.py index 29f095711..5e927df47 100644 --- a/pytest_django_test/db_helpers.py +++ b/pytest_django_test/db_helpers.py @@ -31,7 +31,7 @@ def get_db_engine(): return _settings["ENGINE"].split(".")[-1] -class CmdResult(object): +class CmdResult: def __init__(self, status_code, std_out, std_err): self.status_code = status_code self.std_out = std_out @@ -64,7 +64,7 @@ def skip_if_sqlite_in_memory(): def _get_db_name(db_suffix=None): name = TEST_DB_NAME if db_suffix: - name = "%s_%s" % (name, db_suffix) + name = "{}_{}".format(name, db_suffix) return name @@ -72,7 +72,7 @@ def drop_database(db_suffix=None): name = _get_db_name(db_suffix) db_engine = get_db_engine() - if db_engine == "postgresql_psycopg2": + if db_engine == "postgresql": r = run_cmd("psql", "postgres", "-c", "DROP DATABASE %s" % name) assert "DROP DATABASE" in force_str( r.std_out @@ -94,7 +94,7 @@ def db_exists(db_suffix=None): name = _get_db_name(db_suffix) db_engine = get_db_engine() - if db_engine == "postgresql_psycopg2": + if db_engine == "postgresql": r = run_cmd("psql", name, "-c", "SELECT 1") return r.status_code == 0 @@ -111,7 +111,7 @@ def db_exists(db_suffix=None): def mark_database(): db_engine = get_db_engine() - if db_engine == "postgresql_psycopg2": + if db_engine == "postgresql": r = run_cmd("psql", TEST_DB_NAME, "-c", "CREATE TABLE mark_table();") assert r.status_code == 0 return @@ -136,7 +136,7 @@ def mark_database(): def mark_exists(): db_engine = get_db_engine() - if db_engine == "postgresql_psycopg2": + if db_engine == "postgresql": r = run_cmd("psql", TEST_DB_NAME, "-c", "SELECT 1 FROM mark_table") # When something pops out on std_out, we are good diff --git a/pytest_django_test/settings_base.py b/pytest_django_test/settings_base.py index 050386299..4c9b456f9 100644 --- a/pytest_django_test/settings_base.py +++ b/pytest_django_test/settings_base.py @@ -1,5 +1,3 @@ -import django - ROOT_URLCONF = "pytest_django_test.urls" INSTALLED_APPS = [ "django.contrib.auth", @@ -20,9 +18,6 @@ "django.contrib.messages.middleware.MessageMiddleware", ] -if django.VERSION < (1, 10): - MIDDLEWARE_CLASSES = MIDDLEWARE - TEMPLATES = [ { diff --git a/pytest_django_test/settings_postgres.py b/pytest_django_test/settings_postgres.py index 2598beec9..a926438b6 100644 --- a/pytest_django_test/settings_postgres.py +++ b/pytest_django_test/settings_postgres.py @@ -2,7 +2,7 @@ # PyPy compatibility try: - from psycopg2ct import compat + from psycopg2cffi import compat compat.register() except ImportError: @@ -11,9 +11,7 @@ DATABASES = { "default": { - "ENGINE": "django.db.backends.postgresql_psycopg2", + "ENGINE": "django.db.backends.postgresql", "NAME": "pytest_django_should_never_get_accessed", - "HOST": "localhost", - "USER": "", } } diff --git a/pytest_django_test/urls.py b/pytest_django_test/urls.py index e96a371df..363b979c5 100644 --- a/pytest_django_test/urls.py +++ b/pytest_django_test/urls.py @@ -1,8 +1,8 @@ -from django.conf.urls import url +from django.urls import path from .app import views urlpatterns = [ - url(r"^item_count/$", views.item_count), - url(r"^admin-required/$", views.admin_required_view), + path("item_count/", views.item_count), + path("admin-required/", views.admin_required_view), ] diff --git a/pytest_django_test/urls_overridden.py b/pytest_django_test/urls_overridden.py index eca54e663..255a2ca9a 100644 --- a/pytest_django_test/urls_overridden.py +++ b/pytest_django_test/urls_overridden.py @@ -1,6 +1,6 @@ -from django.conf.urls import url +from django.urls import path from django.http import HttpResponse urlpatterns = [ - url(r"^overridden_url/$", lambda r: HttpResponse("Overridden urlconf works!")) + path("overridden_url/", lambda r: HttpResponse("Overridden urlconf works!")) ] diff --git a/setup.py b/setup.py index 8513c299a..361649fc4 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import codecs import os @@ -28,11 +27,10 @@ def read(fname): license='BSD-3-Clause', packages=['pytest_django'], long_description=read('README.rst'), - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=3.5', setup_requires=['setuptools_scm>=1.11.1'], install_requires=[ - 'pytest>=3.6', - 'pathlib2;python_version<"3.4"', + 'pytest>=5.4.0', ], extras_require={ 'docs': [ @@ -42,17 +40,10 @@ def read(fname): 'testing': [ 'Django', 'django-configurations>=2.0', - 'six', ], }, classifiers=['Development Status :: 5 - Production/Stable', 'Framework :: Django', - 'Framework :: Django :: 1.8', - 'Framework :: Django :: 1.9', - 'Framework :: Django :: 1.10', - 'Framework :: Django :: 1.11', - 'Framework :: Django :: 2.0', - 'Framework :: Django :: 2.1', 'Framework :: Django :: 2.2', 'Framework :: Django :: 3.0', 'Framework :: Django :: 3.1', @@ -60,8 +51,6 @@ def read(fname): 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/tests/conftest.py b/tests/conftest.py index 8b76aba29..8481c8dfc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,6 @@ from textwrap import dedent import pytest -import six from django.conf import settings try: @@ -58,7 +57,7 @@ def django_testdir(request, testdir, monkeypatch): # Pypy compatibility try: - from psycopg2ct import compat + from psycopg2cffi import compat except ImportError: pass else: @@ -81,9 +80,6 @@ def django_testdir(request, testdir, monkeypatch): 'django.contrib.messages.middleware.MessageMiddleware', ] - if django.VERSION < (1, 10): - MIDDLEWARE_CLASSES = MIDDLEWARE - TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', @@ -118,7 +114,7 @@ def django_testdir(request, testdir, monkeypatch): test_app_path = tpkg_path.join("app") # Copy the test app to make it available in the new test run - shutil.copytree(six.text_type(app_source), six.text_type(test_app_path)) + shutil.copytree(str(app_source), str(test_app_path)) tpkg_path.join("the_settings.py").write(test_settings) monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "tpkg.the_settings") diff --git a/tests/test_database.py b/tests/test_database.py index 7bcb06289..4b5a0a5d5 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -1,5 +1,3 @@ -from __future__ import with_statement - import pytest from django.db import connection from django.test.testcases import connections_support_transactions diff --git a/tests/test_db_setup.py b/tests/test_db_setup.py index 375cb8449..6499b1019 100644 --- a/tests/test_db_setup.py +++ b/tests/test_db_setup.py @@ -1,6 +1,5 @@ import pytest -from pytest_django.lazy_django import get_django_version from pytest_django_test.db_helpers import ( db_exists, drop_database, @@ -453,33 +452,7 @@ def test_a(): result.stdout.fnmatch_lines(["*PASSED*test_a*"]) -@pytest.mark.skipif( - get_django_version() >= (1, 9), - reason=( - "Django 1.9 requires migration and has no concept of initial data fixtures" - ), -) -def test_initial_data(django_testdir_initial): - """Test that initial data gets loaded.""" - django_testdir_initial.create_test_module( - """ - import pytest - - from .app.models import Item - - @pytest.mark.django_db - def test_inner(): - assert [x.name for x in Item.objects.all()] \ - == ["mark_initial_data"] - """ - ) - - result = django_testdir_initial.runpytest_subprocess("--tb=short", "-v") - assert result.ret == 0 - result.stdout.fnmatch_lines(["*test_inner*PASSED*"]) - - -class TestNativeMigrations(object): +class TestNativeMigrations: """ Tests for Django Migrations """ def test_no_migrations(self, django_testdir): diff --git a/tests/test_django_settings_module.py b/tests/test_django_settings_module.py index f7c0a5d83..685937cce 100644 --- a/tests/test_django_settings_module.py +++ b/tests/test_django_settings_module.py @@ -3,7 +3,6 @@ If these tests fail you probably forgot to run "python setup.py develop". """ -import django import pytest @@ -308,10 +307,6 @@ def test_debug_is_false(): assert r.ret == 0 -@pytest.mark.skipif( - not hasattr(django, "setup"), - reason="This Django version does not support app loading", -) @pytest.mark.django_project( extra_settings=""" INSTALLED_APPS = [ @@ -329,10 +324,7 @@ class TestApp(AppConfig): name = 'tpkg.app' def ready(self): - try: - populating = apps.loading - except AttributeError: # Django < 2.0 - populating = apps._lock.locked() + populating = apps.loading print('READY(): populating=%r' % populating) """, "apps.py", @@ -342,10 +334,7 @@ def ready(self): """ from django.apps import apps - try: - populating = apps.loading - except AttributeError: # Django < 2.0 - populating = apps._lock.locked() + populating = apps.loading print('IMPORT: populating=%r,ready=%r' % (populating, apps.ready)) SOME_THING = 1234 @@ -360,10 +349,7 @@ def ready(self): from tpkg.app.models import SOME_THING def test_anything(): - try: - populating = apps.loading - except AttributeError: # Django < 2.0 - populating = apps._lock.locked() + populating = apps.loading print('TEST: populating=%r,ready=%r' % (populating, apps.ready)) """ @@ -372,10 +358,7 @@ def test_anything(): result = django_testdir.runpytest_subprocess("-s", "--tb=line") result.stdout.fnmatch_lines(["*IMPORT: populating=True,ready=False*"]) result.stdout.fnmatch_lines(["*READY(): populating=True*"]) - if django.VERSION < (2, 0): - result.stdout.fnmatch_lines(["*TEST: populating=False,ready=True*"]) - else: - result.stdout.fnmatch_lines(["*TEST: populating=True,ready=True*"]) + result.stdout.fnmatch_lines(["*TEST: populating=True,ready=True*"]) assert result.ret == 0 diff --git a/tests/test_environment.py b/tests/test_environment.py index 95f903a1b..87e45f5ff 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -1,5 +1,3 @@ -from __future__ import with_statement - import os import pytest @@ -8,7 +6,6 @@ from django.core import mail from django.db import connection from django.test import TestCase -from pytest_django.lazy_django import get_django_version from pytest_django_test.app.models import Item @@ -57,11 +54,11 @@ def test_two(self): def test_invalid_template_variable(django_testdir): django_testdir.create_app_file( """ - from django.conf.urls import url + from django.urls import path from tpkg.app import views - urlpatterns = [url(r'invalid_template/', views.invalid_template)] + urlpatterns = [path('invalid_template/', views.invalid_template)] """, "urls.py", ) @@ -95,10 +92,7 @@ def test_ignore(client): ) result = django_testdir.runpytest_subprocess("-s", "--fail-on-template-vars") - if get_django_version() >= (1, 9): - origin = "'*/tpkg/app/templates/invalid_template_base.html'" - else: - origin = "'invalid_template.html'" + origin = "'*/tpkg/app/templates/invalid_template_base.html'" result.stdout.fnmatch_lines_random( [ "tpkg/test_the_test.py F.*", @@ -163,11 +157,11 @@ def test_for_invalid_template(): def test_invalid_template_variable_opt_in(django_testdir): django_testdir.create_app_file( """ - from django.conf.urls import url + from django.urls import path from tpkg.app import views - urlpatterns = [url(r'invalid_template/', views.invalid_template)] + urlpatterns = [path('invalid_template', views.invalid_template)] """, "urls.py", ) @@ -255,14 +249,9 @@ def test_verbose_with_v(self, testdir): """Verbose output with '-v'.""" result = testdir.runpytest_subprocess("-s", "-v") result.stdout.fnmatch_lines_random(["tpkg/test_the_test.py:*", "*PASSED*"]) - if get_django_version() >= (2, 2): - result.stderr.fnmatch_lines( - ["*Destroying test database for alias 'default'*"] - ) - else: - result.stdout.fnmatch_lines( - ["*Destroying test database for alias 'default'...*"] - ) + result.stderr.fnmatch_lines( + ["*Destroying test database for alias 'default'*"] + ) def test_more_verbose_with_vv(self, testdir): """More verbose output with '-v -v'.""" @@ -275,37 +264,22 @@ def test_more_verbose_with_vv(self, testdir): "*PASSED*", ] ) - if get_django_version() >= (2, 2): - result.stderr.fnmatch_lines( - [ - "*Creating test database for alias*", - "*Destroying test database for alias 'default'*", - ] - ) - else: - result.stdout.fnmatch_lines( - [ - "*Creating test database for alias*", - "*Destroying test database for alias 'default'*", - ] - ) + result.stderr.fnmatch_lines( + [ + "*Creating test database for alias*", + "*Destroying test database for alias 'default'*", + ] + ) def test_more_verbose_with_vv_and_reusedb(self, testdir): """More verbose output with '-v -v', and --create-db.""" result = testdir.runpytest_subprocess("-s", "-v", "-v", "--create-db") result.stdout.fnmatch_lines(["tpkg/test_the_test.py:*", "*PASSED*"]) - if get_django_version() >= (2, 2): - result.stderr.fnmatch_lines(["*Creating test database for alias*"]) - assert ( - "*Destroying test database for alias 'default' ('*')...*" - not in result.stderr.str() - ) - else: - result.stdout.fnmatch_lines(["*Creating test database for alias*"]) - assert ( - "*Destroying test database for alias 'default' ('*')...*" - not in result.stdout.str() - ) + result.stderr.fnmatch_lines(["*Creating test database for alias*"]) + assert ( + "*Destroying test database for alias 'default' ('*')...*" + not in result.stderr.str() + ) @pytest.mark.django_db diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py index fae054350..0a25f771f 100644 --- a/tests/test_fixtures.py +++ b/tests/test_fixtures.py @@ -4,10 +4,10 @@ fixtures are tested in test_database. """ -from __future__ import with_statement import socket from contextlib import contextmanager +from urllib.request import urlopen, HTTPError import pytest from django.conf import settings as real_settings @@ -17,9 +17,7 @@ from django.test.testcases import connections_support_transactions from django.utils.encoding import force_str -from pytest_django.lazy_django import get_django_version from pytest_django_test.app.models import Item -from pytest_django_test.compat import HTTPError, urlopen @contextmanager @@ -322,7 +320,7 @@ def test_settings_before(self): from django.conf import settings assert ( - "%s.%s" % (settings.__class__.__module__, settings.__class__.__name__) + "{}.{}".format(settings.__class__.__module__, settings.__class__.__name__) == "django.conf.Settings" ) TestLiveServer._test_settings_before_run = True @@ -335,18 +333,14 @@ def test_change_settings(self, live_server, settings): def test_settings_restored(self): """Ensure that settings are restored after test_settings_before.""" - import django from django.conf import settings assert TestLiveServer._test_settings_before_run is True assert ( - "%s.%s" % (settings.__class__.__module__, settings.__class__.__name__) + "{}.{}".format(settings.__class__.__module__, settings.__class__.__name__) == "django.conf.Settings" ) - if django.VERSION >= (1, 11): - assert settings.ALLOWED_HOSTS == ["testserver"] - else: - assert settings.ALLOWED_HOSTS == ["*"] + assert settings.ALLOWED_HOSTS == ["testserver"] def test_transactions(self, live_server): if not connections_support_transactions(): @@ -417,12 +411,9 @@ def test_serve_static_with_staticfiles_app(self, django_testdir, settings): """ django_testdir.create_test_module( """ - from django.utils.encoding import force_str + from urllib.request import urlopen - try: - from urllib2 import urlopen - except ImportError: - from urllib.request import urlopen + from django.utils.encoding import force_str class TestLiveServer: def test_a(self, live_server, settings): @@ -445,28 +436,6 @@ def test_serve_static_dj17_without_staticfiles_app(self, live_server, settings): with pytest.raises(HTTPError): urlopen(live_server + "/static/a_file.txt").read() - @pytest.mark.skipif( - get_django_version() < (1, 11), reason="Django >= 1.11 required" - ) - def test_specified_port_range_error_message_django_111(self, django_testdir): - django_testdir.create_test_module( - """ - def test_with_live_server(live_server): - pass - """ - ) - - result = django_testdir.runpytest_subprocess("--liveserver=localhost:1234-2345") - result.stdout.fnmatch_lines( - [ - "*Specifying multiple live server ports is not supported in Django 1.11. This " - "will be an error in a future pytest-django release.*" - ] - ) - - @pytest.mark.skipif( - get_django_version() < (1, 11, 2), reason="Django >= 1.11.2 required" - ) def test_specified_port_django_111(self, django_testdir): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: @@ -516,16 +485,11 @@ class MyCustomUser(AbstractUser): ) django_testdir.create_app_file( """ - from tpkg.app import views + from django.urls import path - try: - from django.urls import path - except ImportError: - from django.conf.urls import url + from tpkg.app import views - urlpatterns = [url(r'admin-required/', views.admin_required_view)] - else: - urlpatterns = [path('admin-required/', views.admin_required_view)] + urlpatterns = [path('admin-required/', views.admin_required_view)] """, "urls.py", ) @@ -556,9 +520,6 @@ def test_custom_user_model(admin_client): django_testdir.create_app_file("", "migrations/__init__.py") django_testdir.create_app_file( """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import django.utils.timezone import django.core.validators diff --git a/tests/test_unittest.py b/tests/test_unittest.py index 1f6dcd55c..95ea3eb3c 100644 --- a/tests/test_unittest.py +++ b/tests/test_unittest.py @@ -1,7 +1,6 @@ import pytest from django.test import TestCase -from pytest_django.plugin import _pytest_version_info from pytest_django_test.app.models import Item @@ -161,16 +160,8 @@ def test_pass(self): result = django_testdir.runpytest_subprocess("-v", "-s") expected_lines = [ "* ERROR at setup of TestFoo.test_pass *", + "E * TypeError: *", ] - if _pytest_version_info < (4, 2): - expected_lines += [ - "E *Failed: .setUpClass should be a classmethod", # noqa:E501 - ] - else: - expected_lines += [ - "E * TypeError: *", - ] - result.stdout.fnmatch_lines(expected_lines) assert result.ret == 1 @@ -217,7 +208,7 @@ def test_setUpClass_mixin(self, django_testdir): """ from django.test import TestCase - class TheMixin(object): + class TheMixin: @classmethod def setUpClass(cls): super(TheMixin, cls).setUpClass() @@ -289,7 +280,7 @@ def test_multi_inheritance_setUpClass(self, django_testdir): # Using a mixin is a regression test, see #280 for more details: # https://github.com/pytest-dev/pytest-django/issues/280 - class SomeMixin(object): + class SomeMixin: pass class TestA(SomeMixin, TestCase): diff --git a/tests/test_urls.py b/tests/test_urls.py index 9001fddce..945540593 100644 --- a/tests/test_urls.py +++ b/tests/test_urls.py @@ -1,14 +1,11 @@ import pytest from django.conf import settings +from django.urls import is_valid_path from django.utils.encoding import force_str @pytest.mark.urls("pytest_django_test.urls_overridden") def test_urls(): - try: - from django.urls import is_valid_path - except ImportError: - from django.core.urlresolvers import is_valid_path assert settings.ROOT_URLCONF == "pytest_django_test.urls_overridden" assert is_valid_path("/overridden_url/") @@ -22,21 +19,18 @@ def test_urls_client(client): def test_urls_cache_is_cleared(testdir): testdir.makepyfile( myurls=""" - from django.conf.urls import url + from django.urls import path def fake_view(request): pass - urlpatterns = [url(r'first/$', fake_view, name='first')] + urlpatterns = [path('first', fake_view, name='first')] """ ) testdir.makepyfile( """ - try: - from django.urls import reverse, NoReverseMatch - except ImportError: # Django < 2.0 - from django.core.urlresolvers import reverse, NoReverseMatch + from django.urls import reverse, NoReverseMatch import pytest @pytest.mark.urls('myurls') @@ -58,32 +52,29 @@ def test_something_else(): def test_urls_cache_is_cleared_and_new_urls_can_be_assigned(testdir): testdir.makepyfile( myurls=""" - from django.conf.urls import url + from django.urls import path def fake_view(request): pass - urlpatterns = [url(r'first/$', fake_view, name='first')] + urlpatterns = [path('first', fake_view, name='first')] """ ) testdir.makepyfile( myurls2=""" - from django.conf.urls import url + from django.urls import path def fake_view(request): pass - urlpatterns = [url(r'second/$', fake_view, name='second')] + urlpatterns = [path('second', fake_view, name='second')] """ ) testdir.makepyfile( """ - try: - from django.urls import reverse, NoReverseMatch - except ImportError: # Django < 2.0 - from django.core.urlresolvers import reverse, NoReverseMatch + from django.urls import reverse, NoReverseMatch import pytest @pytest.mark.urls('myurls') diff --git a/tox.ini b/tox.ini index d6fb991e4..c7ffafaf5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,36 +1,27 @@ [tox] envlist = - py37-dj{31,30,22,21,20,111}-postgres - py36-dj{31,30,22,21,20,111,110,19,18}-postgres - py35-dj{22,21,20,111,110,19,18}-postgres - py34-dj{20,111,110}-postgres - py27-dj{111,110}-{mysql_innodb,mysql_myisam,postgres} - py27-dj{111,110,19,18}-postgres + py38-dj{31,30,22}-postgres + py37-dj{31,30,22}-postgres + py36-dj{31,30,22}-postgres + py35-dj{22}-postgres checkqa [testenv] extras = testing deps = djmaster: https://github.com/django/django/archive/master.tar.gz - dj31: Django>=3.1rc1,<3.2 + dj31: Django>=3.1,<3.2 dj30: Django>=3.0,<3.1 dj22: Django>=2.2,<2.3 - dj21: Django>=2.1,<2.2 - dj20: Django>=2.0,<2.1 - dj111: Django>=1.11,<1.12 - dj110: Django>=1.10,<1.11 - dj19: Django>=1.9,<1.10 - dj18: Django>=1.8,<1.9 mysql_myisam: mysqlclient==1.4.2.post1 mysql_innodb: mysqlclient==1.4.2.post1 postgres: psycopg2-binary + postgres: psycopg2cffi coverage: coverage-enable-subprocess - pytest41: pytest>=4.1,<4.2 - pytest41: attrs==17.4.0 - pytest53: pytest>=5.3,<5.4 + pytest54: pytest>=5.4,<5.5 xdist: pytest-xdist>=1.15 setenv = @@ -66,7 +57,7 @@ commands = [testenv:doc8] extras = -basepython = python3.6 +basepython = python3.8 skip_install = true deps = sphinx @@ -81,7 +72,7 @@ commands = sphinx-build -n -W -b html -d docs/_build/doctrees docs docs/_build/h [testenv:readme] extras = -basepython = python3.5 +basepython = python3.8 deps = readme_renderer commands =