Browse Source

Refs #32552 -- Added DiscoverRunner.log() to allow customization.

Thanks Carlton Gibson, Chris Jerdonek, and David Smith for reviews.
Daniyal 4 years ago
parent
commit
a0410ffe8f

+ 30 - 11
django/test/runner.py

@@ -581,6 +581,19 @@ class DiscoverRunner:
             ),
         )
 
+    def log(self, msg, level=None):
+        """
+        Log the given message at the given logging level.
+
+        A verbosity of 1 logs INFO (the default level) or above, and verbosity
+        2 or higher logs all levels.
+        """
+        if self.verbosity <= 0 or (
+            self.verbosity == 1 and level is not None and level < logging.INFO
+        ):
+            return
+        print(msg)
+
     def setup_test_environment(self, **kwargs):
         setup_test_environment(debug=self.debug_mode)
         unittest.installHandler()
@@ -639,11 +652,16 @@ class DiscoverRunner:
         all_tests.extend(iter_test_cases(extra_tests))
 
         if self.tags or self.exclude_tags:
-            if self.verbosity >= 2:
-                if self.tags:
-                    print('Including test tag(s): %s.' % ', '.join(sorted(self.tags)))
-                if self.exclude_tags:
-                    print('Excluding test tag(s): %s.' % ', '.join(sorted(self.exclude_tags)))
+            if self.tags:
+                self.log(
+                    'Including test tag(s): %s.' % ', '.join(sorted(self.tags)),
+                    level=logging.DEBUG,
+                )
+            if self.exclude_tags:
+                self.log(
+                    'Excluding test tag(s): %s.' % ', '.join(sorted(self.exclude_tags)),
+                    level=logging.DEBUG,
+                )
             all_tests = filter_tests_by_tags(all_tests, self.tags, self.exclude_tags)
 
         # Put the failures detected at load time first for quicker feedback.
@@ -651,8 +669,7 @@ class DiscoverRunner:
         # found or that couldn't be loaded due to syntax errors.
         test_types = (unittest.loader._FailedTest, *self.reorder_by)
         all_tests = list(reorder_tests(all_tests, test_types, self.reverse))
-        if self.verbosity >= 1:
-            print('Found %d tests.' % len(all_tests))
+        self.log('Found %d tests.' % len(all_tests), level=logging.INFO)
         suite = self.test_suite(all_tests)
 
         if self.parallel > 1:
@@ -736,10 +753,12 @@ class DiscoverRunner:
 
     def get_databases(self, suite):
         databases = self._get_databases(suite)
-        if self.verbosity >= 2:
-            unused_databases = [alias for alias in connections if alias not in databases]
-            if unused_databases:
-                print('Skipping setup of unused database(s): %s.' % ', '.join(sorted(unused_databases)))
+        unused_databases = [alias for alias in connections if alias not in databases]
+        if unused_databases:
+            self.log(
+                'Skipping setup of unused database(s): %s.' % ', '.join(sorted(unused_databases)),
+                level=logging.DEBUG,
+            )
         return databases
 
     def run_tests(self, test_labels, extra_tests=None, **kwargs):

+ 3 - 0
docs/releases/4.0.txt

@@ -294,6 +294,9 @@ Tests
 * Django test runner now supports a :option:`--buffer <test --buffer>` option
   with parallel tests.
 
+* The new :meth:`.DiscoverRunner.log` method allows customizing the way
+  messages are logged.
+
 URLs
 ~~~~
 

+ 11 - 0
docs/topics/testing/advanced.txt

@@ -705,6 +705,17 @@ Methods
     Computes and returns a return code based on a test suite, and the result
     from that test suite.
 
+.. method:: DiscoverRunner.log(msg, level=None)
+
+    .. versionadded:: 4.0
+
+    Prints to the console a message with the given integer `logging level`_
+    (e.g. ``logging.DEBUG``, ``logging.INFO``, or ``logging.WARNING``),
+    respecting the current ``verbosity``. For example, an ``INFO`` message will
+    be logged if the ``verbosity`` is at least 1, and ``DEBUG`` will be logged
+    if it is at least 2.
+
+.. _`logging level`: https://docs.python.org/3/library/logging.html#levels
 
 Testing utilities
 -----------------

+ 38 - 0
tests/test_runner/test_discover_runner.py

@@ -1,3 +1,4 @@
+import logging
 import os
 import unittest.loader
 from argparse import ArgumentParser
@@ -378,6 +379,43 @@ class DiscoverRunnerTests(SimpleTestCase):
         self.assertTrue(isinstance(runner.time_keeper, TimeKeeper))
         self.assertIn('test', stderr.getvalue())
 
+    def test_log(self):
+        custom_low_level = 5
+        custom_high_level = 45
+        msg = 'logging message'
+        cases = [
+            (0, None, False),
+            (0, custom_low_level, False),
+            (0, logging.DEBUG, False),
+            (0, logging.INFO, False),
+            (0, logging.WARNING, False),
+            (0, custom_high_level, False),
+            (1, None, True),
+            (1, custom_low_level, False),
+            (1, logging.DEBUG, False),
+            (1, logging.INFO, True),
+            (1, logging.WARNING, True),
+            (1, custom_high_level, True),
+            (2, None, True),
+            (2, custom_low_level, True),
+            (2, logging.DEBUG, True),
+            (2, logging.INFO, True),
+            (2, logging.WARNING, True),
+            (2, custom_high_level, True),
+            (3, None, True),
+            (3, custom_low_level, True),
+            (3, logging.DEBUG, True),
+            (3, logging.INFO, True),
+            (3, logging.WARNING, True),
+            (3, custom_high_level, True),
+        ]
+        for verbosity, level, output in cases:
+            with self.subTest(verbosity=verbosity, level=level):
+                with captured_stdout() as stdout:
+                    runner = DiscoverRunner(verbosity=verbosity)
+                    runner.log(msg, level)
+                    self.assertEqual(stdout.getvalue(), f'{msg}\n' if output else '')
+
 
 class DiscoverRunnerGetDatabasesTests(SimpleTestCase):
     runner = DiscoverRunner(verbosity=2)