diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index 0a0b1ec26..b545a0a91 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -3,11 +3,12 @@ from __future__ import with_statement import os +import sys +from contextlib import contextmanager +from io import StringIO import pytest -from contextlib import contextmanager - from . import live_server_helper from .django_compat import is_django_unittest @@ -97,6 +98,8 @@ def django_db_setup( **setup_databases_args ) + run_checks(request) + def teardown_database(): with django_db_blocker.unblock(): teardown_databases( @@ -108,6 +111,35 @@ def teardown_database(): request.addfinalizer(teardown_database) +def run_checks(request): + from django.core.management import call_command + from django.core.management.base import SystemCheckError + + config = request.config + + # Only run once per process + if getattr(config, '_pytest_django_checks_ran', False): + return + config._pytest_django_checks_ran = True + + out = StringIO() + try: + call_command('check', stdout=out, stderr=out) + except SystemCheckError as exc: + config._pytest_django_checks_exc = exc + + if hasattr(request.config, 'slaveinput'): + # Kill the xdist test process horribly + # N.B. 'shouldstop' may be obeyed properly in the future as hinted at in + # https://github.com/pytest-dev/pytest-xdist/commit/e8fa73719662d1be5074a0750329fe0c35583484 + print(exc.args[0]) + sys.exit(1) + else: + # Ensure we get the EXIT_TESTSFAILED exit code + request.session.testsfailed += 1 + request.session.shouldstop = True + + def _django_db_fixture_helper(transactional, request, django_db_blocker): if is_django_unittest(request): return diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 1585c6983..6a99a40ca 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -31,6 +31,7 @@ from .fixtures import django_username_field # noqa from .fixtures import live_server # noqa from .fixtures import rf # noqa +from .fixtures import run_checks # noqa from .fixtures import settings # noqa from .fixtures import transactional_db # noqa from .pytest_compat import getfixturevalue @@ -329,6 +330,14 @@ def pytest_runtest_setup(item): _disable_class_methods(cls) +def pytest_terminal_summary(terminalreporter, exitstatus): + config = terminalreporter.config + check_exc = getattr(config, '_pytest_django_checks_exc', None) + if check_exc: + terminalreporter.write('\n') + terminalreporter.write(str(check_exc)) + + @pytest.fixture(autouse=True, scope='session') def django_test_environment(request): """ diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py index 10ceac35c..26a2e687f 100644 --- a/tests/test_fixtures.py +++ b/tests/test_fixtures.py @@ -222,6 +222,65 @@ def test_set_non_existent(settings): ]) +@pytest.mark.django_project(extra_settings=""" + INSTALLED_APPS = ['tpkg.app'] +""") +class TestChecks: + + def test_checks_are_run(self, django_testdir): + django_testdir.create_app_file(""" + from django.core.checks import Error, register + + @register() + def succeed(app_configs, **kwargs): + succeed.did_run = True + return [] + """, '__init__.py') + django_testdir.makepyfile(""" + from tpkg.app import succeed + + def test_simple(db): + assert getattr(succeed, 'did_run', None) is True + """) + + result = django_testdir.runpytest_subprocess('-s') + assert result.ret == 0 + + def test_failing_checks_fail_tests(self, django_testdir): + django_testdir.create_app_file(""" + from django.core.checks import Error, register + + @register() + def fail(app_configs, **kwargs): + return [Error('My failure message', id='test.001')] + """, '__init__.py') + django_testdir.makepyfile(""" + def test_simple(db): + assert True + """) + + result = django_testdir.runpytest_subprocess('-s') + assert result.ret != 0 + result.stdout.fnmatch_lines(['*My failure message*']) + + def test_failing_checks_fail_tests_on_xdist(self, django_testdir): + django_testdir.create_app_file(""" + from django.core.checks import Error, register + + @register() + def fail(app_configs, **kwargs): + return [Error('My failure message', id='test.001')] + """, '__init__.py') + django_testdir.makepyfile(""" + def test_simple(db): + assert True + """) + + result = django_testdir.runpytest_subprocess('-s', '-n2') + assert result.ret != 0 + result.stdout.fnmatch_lines(['*My failure message*']) + + class TestLiveServer: def test_url(self, live_server): assert live_server.url == force_text(live_server)