Commit b5c1a85b authored by Marc Tamlyn's avatar Marc Tamlyn
Browse files

Fixed #24118 -- Added --debug-sql option for tests.

Added a --debug-sql option for tests and runtests.py which outputs the
SQL logger for failing tests. When combined with --verbosity=2, it also
outputs the SQL for passing tests.

Thanks to Berker, Tim, Markus, Shai, Josh and Anssi for review and
discussion.
parent 68a439a1
Loading
Loading
Loading
Loading
+62 −5
Original line number Diff line number Diff line
from importlib import import_module
import logging
import os
import unittest
from unittest import TestSuite, defaultTestLoader
@@ -8,6 +9,47 @@ from django.core.exceptions import ImproperlyConfigured
from django.test import SimpleTestCase, TestCase
from django.test.utils import setup_test_environment, teardown_test_environment
from django.utils.datastructures import OrderedSet
from django.utils.six import StringIO


class DebugSQLTextTestResult(unittest.TextTestResult):
    def __init__(self, stream, descriptions, verbosity):
        self.logger = logging.getLogger('django.db.backends')
        self.logger.setLevel(logging.DEBUG)
        super(DebugSQLTextTestResult, self).__init__(stream, descriptions, verbosity)

    def startTest(self, test):
        self.debug_sql_stream = StringIO()
        self.handler = logging.StreamHandler(self.debug_sql_stream)
        self.logger.addHandler(self.handler)
        super(DebugSQLTextTestResult, self).startTest(test)

    def stopTest(self, test):
        super(DebugSQLTextTestResult, self).stopTest(test)
        self.logger.removeHandler(self.handler)
        if self.showAll:
            self.debug_sql_stream.seek(0)
            self.stream.write(self.debug_sql_stream.read())
            self.stream.writeln(self.separator2)

    def addError(self, test, err):
        super(DebugSQLTextTestResult, self).addError(test, err)
        self.debug_sql_stream.seek(0)
        self.errors[-1] = self.errors[-1] + (self.debug_sql_stream.read(),)

    def addFailure(self, test, err):
        super(DebugSQLTextTestResult, self).addFailure(test, err)
        self.debug_sql_stream.seek(0)
        self.failures[-1] = self.failures[-1] + (self.debug_sql_stream.read(),)

    def printErrorList(self, flavour, errors):
        for test, err, sql_debug in errors:
            self.stream.writeln(self.separator1)
            self.stream.writeln("%s: %s" % (flavour, self.getDescription(test)))
            self.stream.writeln(self.separator2)
            self.stream.writeln("%s" % err)
            self.stream.writeln(self.separator2)
            self.stream.writeln("%s" % sql_debug)


class DiscoverRunner(object):
@@ -20,9 +62,9 @@ class DiscoverRunner(object):
    test_loader = defaultTestLoader
    reorder_by = (TestCase, SimpleTestCase)

    def __init__(self, pattern=None, top_level=None,
                 verbosity=1, interactive=True, failfast=False, keepdb=False, reverse=False,
                 **kwargs):
    def __init__(self, pattern=None, top_level=None, verbosity=1,
                 interactive=True, failfast=False, keepdb=False,
                 reverse=False, debug_sql=False, **kwargs):

        self.pattern = pattern
        self.top_level = top_level
@@ -32,6 +74,7 @@ class DiscoverRunner(object):
        self.failfast = failfast
        self.keepdb = keepdb
        self.reverse = reverse
        self.debug_sql = debug_sql

    @classmethod
    def add_arguments(cls, parser):
@@ -47,6 +90,9 @@ class DiscoverRunner(object):
        parser.add_argument('-r', '--reverse', action='store_true', dest='reverse',
            default=False,
            help='Reverses test cases order.')
        parser.add_argument('-d', '--debug-sql', action='store_true', dest='debug_sql',
            default=False,
            help='Prints logged SQL queries on failure.')

    def setup_test_environment(self, **kwargs):
        setup_test_environment()
@@ -115,12 +161,20 @@ class DiscoverRunner(object):
        return reorder_suite(suite, self.reorder_by, self.reverse)

    def setup_databases(self, **kwargs):
        return setup_databases(self.verbosity, self.interactive, self.keepdb, **kwargs)
        return setup_databases(
            self.verbosity, self.interactive, self.keepdb, self.debug_sql,
            **kwargs
        )

    def get_resultclass(self):
        return DebugSQLTextTestResult if self.debug_sql else None

    def run_suite(self, suite, **kwargs):
        resultclass = self.get_resultclass()
        return self.test_runner(
            verbosity=self.verbosity,
            failfast=self.failfast,
            resultclass=resultclass,
        ).run(suite)

    def teardown_databases(self, old_config, **kwargs):
@@ -266,7 +320,7 @@ def partition_suite(suite, classes, bins, reverse=False):
                bins[-1].add(test)


def setup_databases(verbosity, interactive, keepdb=False, **kwargs):
def setup_databases(verbosity, interactive, keepdb=False, debug_sql=False, **kwargs):
    from django.db import connections, DEFAULT_DB_ALIAS

    # First pass -- work out which databases actually need to be created,
@@ -326,4 +380,7 @@ def setup_databases(verbosity, interactive, keepdb=False, **kwargs):
        connections[alias].settings_dict['NAME'] = (
            connections[mirror_alias].settings_dict['NAME'])

    if debug_sql:
        for alias in connections:
            connections[alias].force_debug_cursor = True
    return old_names, mirrors
+13 −5
Original line number Diff line number Diff line
@@ -323,6 +323,14 @@ cause any trouble:

    $ ./runtests.py basic --reverse

If you wish to examine the SQL being run in failing tests, you can turn on
:ref:`SQL logging <django-db-logger>` using the ``--debug-sql`` option. If you
combine this with ``--verbosity=2``, all SQL queries will be output.

.. code-block:: bash

    $ ./runtests.py basic --debug-sql

.. versionadded:: 1.8

    The ``--reverse`` option was added.
    The ``--reverse`` and ``--debug-sql`` options were added.
+8 −0
Original line number Diff line number Diff line
@@ -1449,6 +1449,14 @@ This may help in debugging tests that aren't properly isolated and have side
effects. :ref:`Grouping by test class <order-of-tests>` is preserved when using
this option.

.. django-admin-option:: --debug-sql

.. versionadded:: 1.8

The ``--debug-sql`` option can be used to enable :ref:`SQL logging
<django-db-logger>` for failing tests. If :djadminopt:`--verbosity` is ``2``,
then queries in passing tests are also output.

testserver <fixture fixture ...>
--------------------------------

+3 −2
Original line number Diff line number Diff line
@@ -625,8 +625,9 @@ Tests
  allows you to test that two JSON fragments are not equal.

* Added options to the :djadmin:`test` command to preserve the test database
  (:djadminopt:`--keepdb`) and to run the test cases in reverse order
  (:djadminopt:`--reverse`).
  (:djadminopt:`--keepdb`), to run the test cases in reverse order
  (:djadminopt:`--reverse`), and to enable SQL logging for failing tests
  (:djadminopt:`--debug-sql`).

* Added the :attr:`~django.test.Response.resolver_match` attribute to test
  client responses.
+2 −0
Original line number Diff line number Diff line
@@ -439,6 +439,8 @@ Messages to this logger have the following extra context:
* ``request``: The request object that generated the logging
  message.

.. _django-db-logger:

``django.db.backends``
~~~~~~~~~~~~~~~~~~~~~~

Loading