Ticket #10868: 10868.diff

File 10868.diff, 6.4 KB (added by Anssi Kääriäinen, 12 years ago)

Solve the issue by checking threads created during testing

  • django/test/simple.py

    diff --git a/django/test/simple.py b/django/test/simple.py
    index c9adfd2..7e722cd 100644
    a b  
     1import threading
    12import unittest as real_unittest
    23
    34from django.conf import settings
    class DjangoTestSuiteRunner(object):  
    252253        return reorder_suite(suite, (TestCase,))
    253254
    254255    def setup_databases(self, **kwargs):
     256        # Record existing threads, so that we can check for threads created
     257        # during testing before tearing down databases. See #10868.
     258        self._record_threads()
    255259        from django.db import connections, DEFAULT_DB_ALIAS
    256260
    257261        # First pass -- work out which databases actually need to be created,
    class DjangoTestSuiteRunner(object):  
    311315        return unittest.TextTestRunner(verbosity=self.verbosity, failfast=self.failfast).run(suite)
    312316
    313317    def teardown_databases(self, old_config, **kwargs):
     318        # Check that there are no alive threads left behind from the tests.
     319        # If these would sprung to life, they would access the production DB.
     320        # see #10868.
     321        self._check_alive_threads()
    314322        from django.db import connections
    315323        old_names, mirrors = old_config
    316324        # Point all the mirrors back to the originals
    class DjangoTestSuiteRunner(object):  
    356364        self.teardown_databases(old_config)
    357365        self.teardown_test_environment()
    358366        return self.suite_result(suite, result)
     367
     368    def _get_thread_ident(self, thread):
     369        """
     370        Get an identifier for the given thread.
     371
     372        Python 2.5 doesn't have thread.ident, so use thread.name if ident
     373        doesn't exists.
     374        """
     375        try:
     376            return thread.ident
     377        except AttributeError:
     378            return thread.name
     379
     380    def _check_alive_threads(self):
     381        for thread in threading.enumerate():
     382            if self._get_thread_ident(thread) not in self._pre_test_threads:
     383                thread.join(timeout=1.0)
     384                if thread.is_alive():
     385                    raise RuntimeError(
     386                        "Thread (name=%s) still alive after tests.",
     387                        thread.name)
     388
     389    def _record_threads(self):
     390        self._pre_test_threads = set()
     391        for thread in threading.enumerate():
     392            self._pre_test_threads.add(self._get_thread_ident(thread))
  • docs/releases/1.4.txt

    diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
    index f614dee..c4d8442 100644
    a b useful, it was removed in Django 1.4. If you relied on it, you must edit your  
    995995settings file to list all your applications explicitly.
    996996
    997997.. _this can't be done reliably: http://docs.python.org/tutorial/modules.html#importing-from-a-package
     998
     999Threads created during testing must be also closed during testing
     1000~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     1001
     1002To make sure test threads will not access the production database (or
     1003otherwise create havoc after tests) Django now enforces that threads created
     1004during testing must not be alive after the tests have ran.
  • tests/regressiontests/test_runner/tests.py

    diff --git a/tests/regressiontests/test_runner/tests.py b/tests/regressiontests/test_runner/tests.py
    index 4e2555e..4dd04f4 100644
    a b from __future__ import absolute_import  
    55
    66import StringIO
    77from optparse import make_option
     8import time
     9from threading import Thread
    810import warnings
    911
    1012from django.core.exceptions import ImproperlyConfigured
    1113from django.core.management import call_command
    1214from django.test import simple
    1315from django.test.simple import get_tests
    14 from django.test.utils import get_warnings_state, restore_warnings_state
     16from django.test.utils import (get_warnings_state, restore_warnings_state,
     17                               override_settings)
    1518from django.utils import unittest
    1619from django.utils.importlib import import_module
    1720
    class ModulesTestsPackages(unittest.TestCase):  
    225228        "Test for #12658 - Tests with ImportError's shouldn't fail silently"
    226229        module = import_module(TEST_APP_ERROR)
    227230        self.assertRaises(ImportError, get_tests, module)
     231
     232class Condition(object):
     233    def __init__(self):
     234        self._break = False
     235
     236def waiter_thread(condition):
     237    while True:
     238        if condition._break:
     239            break
     240        time.sleep(0.1)
     241
     242
     243class ThreadsTest(unittest.TestCase):
     244    """
     245    Test that all threads created while running the tests are gone before
     246    tearing down the testing databases. Refs: #10868.
     247
     248    The tests target _record_threads and _check_alive_threds, because actually
     249    running teardown_databases seems a little problematic.
     250    """
     251    def test_threads_left_alive(self):
     252        """
     253        Check that there is an error if there are threads left alive after tests.
     254        """
     255        dj = simple.DjangoTestSuiteRunner()
     256        dj._record_threads()
     257        condition = Condition()
     258        t = Thread(target=waiter_thread, args=(condition,))
     259        t.start()
     260        with self.assertRaises(RuntimeError):
     261            dj._check_alive_threads()
     262        condition._break = True
     263        t.join()
     264
     265    def test_threads_created_before(self):
     266        """
     267        Check that threads started before testing are allowed to exist also
     268        after tests.
     269        """
     270        condition = Condition()
     271        t = Thread(target=waiter_thread, args=(condition,))
     272        t.start()
     273        dj = simple.DjangoTestSuiteRunner()
     274        dj._record_threads()
     275        try:
     276            dj._check_alive_threads()
     277        except RuntimeError:
     278            self.assertFalse(True) # Should not raise this error!
     279        condition._break = True
     280        t.join()
     281
     282    def test_threads_mixed(self):
     283        """
     284        Check that if there are threads created before tests, and threads left
     285        alive during testing, there is an error.
     286        """
     287        condition1 = Condition()
     288        condition2 = Condition()
     289        t1 = Thread(target=waiter_thread, args=(condition1,))
     290        t1.start()
     291        dj = simple.DjangoTestSuiteRunner()
     292        dj._record_threads()
     293        t2 = Thread(target=waiter_thread, args=(condition2,))
     294        t2.start()
     295        with self.assertRaises(RuntimeError):
     296            dj._check_alive_threads()
     297        condition1._break = True
     298        condition2._break = True
     299        t1.join()
     300        t2.join()
Back to Top