Fixed #30676 -- Added --pdb option to test runner.

This commit is contained in:
Andrew Godwin 2019-08-05 13:41:14 +10:00 committed by Carlton Gibson
parent c5075360c5
commit 052388aba4
4 changed files with 54 additions and 4 deletions

View File

@ -19,6 +19,11 @@ from django.test.utils import (
from django.utils.datastructures import OrderedSet from django.utils.datastructures import OrderedSet
from django.utils.version import PY37 from django.utils.version import PY37
try:
import ipdb as pdb
except ImportError:
import pdb
try: try:
import tblib.pickling_support import tblib.pickling_support
except ImportError: except ImportError:
@ -72,6 +77,26 @@ class DebugSQLTextTestResult(unittest.TextTestResult):
self.stream.writeln(sql_debug) self.stream.writeln(sql_debug)
class PDBDebugResult(unittest.TextTestResult):
"""
Custom result class that triggers a PDB session when an error or failure
occurs.
"""
def addError(self, test, err):
super().addError(test, err)
self.debug(err)
def addFailure(self, test, err):
super().addFailure(test, err)
self.debug(err)
def debug(self, error):
exc_type, exc_value, traceback = error
print("\nOpening PDB: %r" % exc_value)
pdb.post_mortem(traceback)
class RemoteTestResult: class RemoteTestResult:
""" """
Record information about which tests have succeeded and which have failed. Record information about which tests have succeeded and which have failed.
@ -408,7 +433,8 @@ class DiscoverRunner:
def __init__(self, pattern=None, top_level=None, verbosity=1, def __init__(self, pattern=None, top_level=None, verbosity=1,
interactive=True, failfast=False, keepdb=False, interactive=True, failfast=False, keepdb=False,
reverse=False, debug_mode=False, debug_sql=False, parallel=0, reverse=False, debug_mode=False, debug_sql=False, parallel=0,
tags=None, exclude_tags=None, test_name_patterns=None, **kwargs): tags=None, exclude_tags=None, test_name_patterns=None,
pdb=False, **kwargs):
self.pattern = pattern self.pattern = pattern
self.top_level = top_level self.top_level = top_level
@ -422,6 +448,9 @@ class DiscoverRunner:
self.parallel = parallel self.parallel = parallel
self.tags = set(tags or []) self.tags = set(tags or [])
self.exclude_tags = set(exclude_tags or []) self.exclude_tags = set(exclude_tags or [])
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.')
self.test_name_patterns = None self.test_name_patterns = None
if test_name_patterns: if test_name_patterns:
# unittest does not export the _convert_select_pattern function # unittest does not export the _convert_select_pattern function
@ -470,6 +499,10 @@ class DiscoverRunner:
'--exclude-tag', action='append', dest='exclude_tags', '--exclude-tag', action='append', dest='exclude_tags',
help='Do not run tests with the specified tag. Can be used multiple times.', help='Do not run tests with the specified tag. Can be used multiple times.',
) )
parser.add_argument(
'--pdb', action='store_true',
help='Runs a debugger (pdb, or ipdb if installed) on error or failure.'
)
if PY37: if PY37:
parser.add_argument( parser.add_argument(
'-k', action='append', dest='test_name_patterns', '-k', action='append', dest='test_name_patterns',
@ -574,7 +607,10 @@ class DiscoverRunner:
) )
def get_resultclass(self): def get_resultclass(self):
return DebugSQLTextTestResult if self.debug_sql else None if self.debug_sql:
return DebugSQLTextTestResult
elif self.pdb:
return PDBDebugResult
def get_test_runner_kwargs(self): def get_test_runner_kwargs(self):
return { return {

View File

@ -1456,6 +1456,12 @@ Runs test methods and classes matching test name patterns, in the same way as
This feature is only available for Python 3.7 and later. This feature is only available for Python 3.7 and later.
.. django-admin-option:: --pdb
.. versionadded:: 3.0
Spawns a ``pdb`` debugger at each test error or failure. If you have it
installed, ``ipdb`` is used instead.
``testserver`` ``testserver``
-------------- --------------

View File

@ -348,6 +348,9 @@ Tests
* Django test runner now supports ``--start-at`` and ``--start-after`` options * Django test runner now supports ``--start-at`` and ``--start-after`` options
to run tests starting from a specific top-level module. to run tests starting from a specific top-level module.
* Django test runner now supports a ``--pdb`` option to spawn a debugger at
each error or failure.
URLs URLs
~~~~ ~~~~

View File

@ -284,7 +284,7 @@ class ActionSelenium(argparse.Action):
def django_tests(verbosity, interactive, failfast, keepdb, reverse, def django_tests(verbosity, interactive, failfast, keepdb, reverse,
test_labels, debug_sql, parallel, tags, exclude_tags, test_labels, debug_sql, parallel, tags, exclude_tags,
test_name_patterns, start_at, start_after): test_name_patterns, start_at, start_after, pdb):
state = setup(verbosity, test_labels, parallel, start_at, start_after) state = setup(verbosity, test_labels, parallel, start_at, start_after)
extra_tests = [] extra_tests = []
@ -304,6 +304,7 @@ def django_tests(verbosity, interactive, failfast, keepdb, reverse,
tags=tags, tags=tags,
exclude_tags=exclude_tags, exclude_tags=exclude_tags,
test_name_patterns=test_name_patterns, test_name_patterns=test_name_patterns,
pdb=pdb,
) )
failures = test_runner.run_tests( failures = test_runner.run_tests(
test_labels or get_installed(), test_labels or get_installed(),
@ -495,6 +496,10 @@ if __name__ == "__main__":
'--start-at', dest='start_at', '--start-at', dest='start_at',
help='Run tests starting at the specified top-level module.', help='Run tests starting at the specified top-level module.',
) )
parser.add_argument(
'--pdb', action='store_true',
help='Runs the PDB debugger on error or failure.'
)
if PY37: if PY37:
parser.add_argument( parser.add_argument(
'-k', dest='test_name_patterns', action='append', '-k', dest='test_name_patterns', action='append',
@ -561,7 +566,7 @@ if __name__ == "__main__":
options.debug_sql, options.parallel, options.tags, options.debug_sql, options.parallel, options.tags,
options.exclude_tags, options.exclude_tags,
getattr(options, 'test_name_patterns', None), getattr(options, 'test_name_patterns', None),
options.start_at, options.start_after, options.start_at, options.start_after, options.pdb,
) )
if failures: if failures:
sys.exit(1) sys.exit(1)