From b7a438c7e21cdcb26d47f1c1fc5afdc1b3d1a9ef Mon Sep 17 00:00:00 2001 From: ovkulkarni <2019okulkarn@tjhsst.edu> Date: Wed, 6 May 2020 13:52:12 -0400 Subject: [PATCH] Fixed #31509 -- Made DiscoverRunner enable faulthandler by default. --- django/test/runner.py | 14 ++++++++++- docs/ref/django-admin.txt | 8 +++++++ docs/releases/3.2.txt | 4 ++++ docs/topics/testing/advanced.txt | 9 ++++++- tests/test_runner/test_discover_runner.py | 29 ++++++++++++++++++++++- 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/django/test/runner.py b/django/test/runner.py index 5b634b2ac9..ddf56b336f 100644 --- a/django/test/runner.py +++ b/django/test/runner.py @@ -1,9 +1,12 @@ import ctypes +import faulthandler +import io import itertools import logging import multiprocessing import os import pickle +import sys import textwrap import unittest from importlib import import_module @@ -434,7 +437,7 @@ class DiscoverRunner: interactive=True, failfast=False, keepdb=False, reverse=False, debug_mode=False, debug_sql=False, parallel=0, tags=None, exclude_tags=None, test_name_patterns=None, - pdb=False, buffer=False, **kwargs): + pdb=False, buffer=False, enable_faulthandler=True, **kwargs): self.pattern = pattern self.top_level = top_level @@ -448,6 +451,11 @@ class DiscoverRunner: self.parallel = parallel self.tags = set(tags or []) self.exclude_tags = set(exclude_tags or []) + if not faulthandler.is_enabled() and enable_faulthandler: + try: + faulthandler.enable(file=sys.stderr.fileno()) + except (AttributeError, io.UnsupportedOperation): + faulthandler.enable(file=sys.__stderr__.fileno()) self.pdb = pdb if self.pdb and self.parallel > 1: raise ValueError('You cannot use --pdb with parallel tests; pass --parallel=1 to use it.') @@ -513,6 +521,10 @@ class DiscoverRunner: '-b', '--buffer', action='store_true', help='Discard output from passing tests.', ) + parser.add_argument( + '--no-faulthandler', action='store_false', dest='enable_faulthandler', + help='Disables the Python faulthandler module during tests.', + ) if PY37: parser.add_argument( '-k', action='append', dest='test_name_patterns', diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index b0913becf0..99cdafd2c8 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1515,6 +1515,14 @@ installed, ``ipdb`` is used instead. Discards output (``stdout`` and ``stderr``) for passing tests, in the same way as :option:`unittest's --buffer option`. +.. django-admin-option:: --no-faulthandler + +.. versionadded:: 3.2 + +Django automatically calls :func:`faulthandler.enable()` when starting the +tests, which allows it to print a traceback if the interpreter crashes. Pass +``--no-faulthandler`` to disable this behavior. + ``testserver`` -------------- diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt index 0c877bc1f5..64b3b0257c 100644 --- a/docs/releases/3.2.txt +++ b/docs/releases/3.2.txt @@ -266,6 +266,10 @@ Tests creating deep copies with :py:func:`copy.deepcopy`. Assigning objects which don't support ``deepcopy()`` is deprecated and will be removed in Django 4.1. +* :class:`~django.test.runner.DiscoverRunner` now enables + :py:mod:`faulthandler` by default. This can be disabled by using the + :option:`test --no-faulthandler` option. + * :class:`~django.test.Client` now preserves the request query string when following 307 and 308 redirects. diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt index c4fbee060a..06bd351ccf 100644 --- a/docs/topics/testing/advanced.txt +++ b/docs/topics/testing/advanced.txt @@ -510,7 +510,7 @@ behavior. This class defines the ``run_tests()`` entry point, plus a selection of other methods that are used to by ``run_tests()`` to set up, execute and tear down the test suite. -.. class:: DiscoverRunner(pattern='test*.py', top_level=None, verbosity=1, interactive=True, failfast=False, keepdb=False, reverse=False, debug_mode=False, debug_sql=False, test_name_patterns=None, pdb=False, buffer=False, **kwargs) +.. class:: DiscoverRunner(pattern='test*.py', top_level=None, verbosity=1, interactive=True, failfast=False, keepdb=False, reverse=False, debug_mode=False, debug_sql=False, test_name_patterns=None, pdb=False, buffer=False, enable_faulthandler=True, **kwargs) ``DiscoverRunner`` will search for tests in any file matching ``pattern``. @@ -557,6 +557,9 @@ execute and tear down the test suite. If ``buffer`` is ``True``, outputs from passing tests will be discarded. + If ``enable_faulthandler`` is ``True``, :py:mod:`faulthandler` will be + enabled. + Django may, from time to time, extend the capabilities of the test runner by adding new arguments. The ``**kwargs`` declaration allows for this expansion. If you subclass ``DiscoverRunner`` or write your own test @@ -571,6 +574,10 @@ execute and tear down the test suite. The ``buffer`` argument was added. + .. versionadded:: 3.2 + + The ``enable_faulthandler`` argument was added. + Attributes ~~~~~~~~~~ diff --git a/tests/test_runner/test_discover_runner.py b/tests/test_runner/test_discover_runner.py index 61af22d818..8caacd2620 100644 --- a/tests/test_runner/test_discover_runner.py +++ b/tests/test_runner/test_discover_runner.py @@ -1,7 +1,9 @@ import os from argparse import ArgumentParser from contextlib import contextmanager -from unittest import TestSuite, TextTestRunner, defaultTestLoader, skipUnless +from unittest import ( + TestSuite, TextTestRunner, defaultTestLoader, mock, skipUnless, +) from django.db import connections from django.test import SimpleTestCase @@ -297,6 +299,31 @@ class DiscoverRunnerTests(SimpleTestCase): self.assertIn('Write to stderr.', stderr.getvalue()) self.assertIn('Write to stdout.', stdout.getvalue()) + @mock.patch('faulthandler.enable') + def test_faulthandler_enabled(self, mocked_enable): + with mock.patch('faulthandler.is_enabled', return_value=False): + DiscoverRunner(enable_faulthandler=True) + mocked_enable.assert_called() + + @mock.patch('faulthandler.enable') + def test_faulthandler_already_enabled(self, mocked_enable): + with mock.patch('faulthandler.is_enabled', return_value=True): + DiscoverRunner(enable_faulthandler=True) + mocked_enable.assert_not_called() + + @mock.patch('faulthandler.enable') + def test_faulthandler_enabled_fileno(self, mocked_enable): + # sys.stderr that is not an actual file. + with mock.patch('faulthandler.is_enabled', return_value=False), captured_stderr(): + DiscoverRunner(enable_faulthandler=True) + mocked_enable.assert_called() + + @mock.patch('faulthandler.enable') + def test_faulthandler_disabled(self, mocked_enable): + with mock.patch('faulthandler.is_enabled', return_value=False): + DiscoverRunner(enable_faulthandler=False) + mocked_enable.assert_not_called() + class DiscoverRunnerGetDatabasesTests(SimpleTestCase): runner = DiscoverRunner(verbosity=2)