Преглед изворни кода

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

Andrew Godwin пре 5 година
родитељ
комит
052388aba4
4 измењених фајлова са 54 додато и 4 уклоњено
  1. 38 2
      django/test/runner.py
  2. 6 0
      docs/ref/django-admin.txt
  3. 3 0
      docs/releases/3.0.txt
  4. 7 2
      tests/runtests.py

+ 38 - 2
django/test/runner.py

@@ -19,6 +19,11 @@ from django.test.utils import (
 from django.utils.datastructures import OrderedSet
 from django.utils.version import PY37
 
+try:
+    import ipdb as pdb
+except ImportError:
+    import pdb
+
 try:
     import tblib.pickling_support
 except ImportError:
@@ -72,6 +77,26 @@ class DebugSQLTextTestResult(unittest.TextTestResult):
             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:
     """
     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,
                  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, **kwargs):
+                 tags=None, exclude_tags=None, test_name_patterns=None,
+                 pdb=False, **kwargs):
 
         self.pattern = pattern
         self.top_level = top_level
@@ -422,6 +448,9 @@ class DiscoverRunner:
         self.parallel = parallel
         self.tags = set(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
         if test_name_patterns:
             # unittest does not export the _convert_select_pattern function
@@ -470,6 +499,10 @@ class DiscoverRunner:
             '--exclude-tag', action='append', dest='exclude_tags',
             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:
             parser.add_argument(
                 '-k', action='append', dest='test_name_patterns',
@@ -574,7 +607,10 @@ class DiscoverRunner:
         )
 
     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):
         return {

+ 6 - 0
docs/ref/django-admin.txt

@@ -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.
 
+.. 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``
 --------------

+ 3 - 0
docs/releases/3.0.txt

@@ -348,6 +348,9 @@ Tests
 * Django test runner now supports ``--start-at`` and ``--start-after`` options
   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
 ~~~~
 

+ 7 - 2
tests/runtests.py

@@ -284,7 +284,7 @@ class ActionSelenium(argparse.Action):
 
 def django_tests(verbosity, interactive, failfast, keepdb, reverse,
                  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)
     extra_tests = []
 
@@ -304,6 +304,7 @@ def django_tests(verbosity, interactive, failfast, keepdb, reverse,
         tags=tags,
         exclude_tags=exclude_tags,
         test_name_patterns=test_name_patterns,
+        pdb=pdb,
     )
     failures = test_runner.run_tests(
         test_labels or get_installed(),
@@ -495,6 +496,10 @@ if __name__ == "__main__":
         '--start-at', dest='start_at',
         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:
         parser.add_argument(
             '-k', dest='test_name_patterns', action='append',
@@ -561,7 +566,7 @@ if __name__ == "__main__":
             options.debug_sql, options.parallel, options.tags,
             options.exclude_tags,
             getattr(options, 'test_name_patterns', None),
-            options.start_at, options.start_after,
+            options.start_at, options.start_after, options.pdb,
         )
         if failures:
             sys.exit(1)