Ticket #8138: 8138withdocs.diff

File 8138withdocs.diff, 25.0 KB (added by Karen Tracey, 15 years ago)
  • django/test/simple.py

     
    33from django.db.models import get_app, get_apps
    44from django.test import _doctest as doctest
    55from django.test.utils import setup_test_environment, teardown_test_environment
    6 from django.test.testcases import OutputChecker, DocTestRunner
     6from django.test.testcases import OutputChecker, DocTestRunner, TestCase
    77
    88# The module name for tests outside models.py
    99TEST_MODULE = 'tests'
     
    9999    else: # label is app.TestClass.test_method
    100100        return TestClass(parts[2])
    101101
     102def partition_suite(suite, classes, bins):
     103    """
     104    Partitions a test suite by test type.
     105   
     106    classes is a sequence of types
     107    bins is a sequence of TestSuites, one more than classes
     108   
     109    Tests of type classes[i] are added to bins[i],
     110    tests with no match found in classes are place in bins[-1]
     111    """
     112    for test in suite:
     113        if isinstance(test, unittest.TestSuite):
     114            partition_suite(test, classes, bins)
     115        else:
     116            for i in range(len(classes)):
     117                if isinstance(test, classes[i]):
     118                    bins[i].addTest(test)
     119                    break
     120            else:
     121                bins[-1].addTest(test)
     122           
     123def reorder_suite(suite, classes):
     124    """
     125    Reorders a test suite by test type.
     126   
     127    classes is a sequence of types
     128   
     129    All tests of type clases[0] are placed first, then tests of type classes[1], etc.
     130    Tests with no match in classes are placed last.
     131    """
     132    class_count = len(classes)
     133    bins = [unittest.TestSuite() for i in range(class_count+1)]
     134    partition_suite(suite, classes, bins)
     135    for i in range(class_count):
     136        bins[0].addTests(bins[i+1])
     137    return bins[0]
     138
    102139def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]):
    103140    """
    104141    Run the unit tests for all the test labels in the provided list.
     
    137174    for test in extra_tests:
    138175        suite.addTest(test)
    139176
     177    suite = reorder_suite(suite, (TestCase,))
     178
    140179    old_name = settings.DATABASE_NAME
    141180    from django.db import connection
    142181    connection.creation.create_test_db(verbosity, autoclobber=not interactive)
  • django/test/client.py

     
    1919from django.utils.encoding import smart_str
    2020from django.utils.http import urlencode
    2121from django.utils.itercompat import is_iterable
     22from django.db import transaction, close_connection
    2223
    2324BOUNDARY = 'BoUnDaRyStRiNg'
    2425MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
     
    6970                response = middleware_method(request, response)
    7071            response = self.apply_response_fixes(request, response)
    7172        finally:
     73            signals.request_finished.disconnect(close_connection)           
    7274            signals.request_finished.send(sender=self.__class__)
     75            signals.request_finished.connect(close_connection)
    7376
    7477        return response
    7578
  • django/test/testcases.py

     
    77from django.core import mail
    88from django.core.management import call_command
    99from django.core.urlresolvers import clear_url_caches
    10 from django.db import transaction
     10from django.db import transaction, connection
    1111from django.http import QueryDict
    1212from django.test import _doctest as doctest
    1313from django.test.client import Client
     
    2727        value = [value]
    2828    return value
    2929
     30real_commit = transaction.commit
     31real_rollback = transaction.rollback
     32real_enter_transaction_management = transaction.enter_transaction_management
     33real_leave_transaction_management = transaction.leave_transaction_management
     34real_savepoint_commit = transaction.savepoint_commit
     35real_savepoint_rollback = transaction.savepoint_rollback
    3036
     37def nop(x=None):
     38    return
     39
     40def disable_transaction_methods():
     41    transaction.commit = nop
     42    transaction.rollback = nop
     43    transaction.savepoint_commit = nop
     44    transaction.savepoint_rollback = nop
     45    transaction.enter_transaction_management = nop
     46    transaction.leave_transaction_management = nop       
     47
     48def restore_transaction_methods():
     49    transaction.commit = real_commit
     50    transaction.rollback = real_rollback
     51    transaction.savepoint_commit = real_savepoint_commit
     52    transaction.savepoint_rollback = real_savepoint_rollback
     53    transaction.enter_transaction_management = real_enter_transaction_management
     54    transaction.leave_transaction_management = real_leave_transaction_management
     55
    3156class OutputChecker(doctest.OutputChecker):
    3257    def check_output(self, want, got, optionflags):
    3358        "The entry method for doctest output checking. Defers to a sequence of child checkers"
     
    173198        # Rollback, in case of database errors. Otherwise they'd have
    174199        # side effects on other tests.
    175200        transaction.rollback_unless_managed()
    176 
    177 class TestCase(unittest.TestCase):
     201       
     202class TransactionTestCase(unittest.TestCase):
    178203    def _pre_setup(self):
    179204        """Performs any pre-test setup. This includes:
    180205
     
    185210              ROOT_URLCONF with it.
    186211            * Clearing the mail test outbox.
    187212        """
     213        self._fixture_setup()
     214        self._urlconf_setup()
     215        mail.outbox = []
     216
     217    def _fixture_setup(self):
    188218        call_command('flush', verbosity=0, interactive=False)
    189219        if hasattr(self, 'fixtures'):
    190220            # We have to use this slightly awkward syntax due to the fact
    191221            # that we're using *args and **kwargs together.
    192222            call_command('loaddata', *self.fixtures, **{'verbosity': 0})
     223
     224    def _urlconf_setup(self):
    193225        if hasattr(self, 'urls'):
    194226            self._old_root_urlconf = settings.ROOT_URLCONF
    195227            settings.ROOT_URLCONF = self.urls
    196228            clear_url_caches()
    197         mail.outbox = []
    198229
    199230    def __call__(self, result=None):
    200231        """
     
    211242            import sys
    212243            result.addError(self, sys.exc_info())
    213244            return
    214         super(TestCase, self).__call__(result)
     245        super(TransactionTestCase, self).__call__(result)       
    215246        try:
    216247            self._post_teardown()
    217248        except (KeyboardInterrupt, SystemExit):
     
    226257
    227258            * Putting back the original ROOT_URLCONF if it was changed.
    228259        """
     260        self._fixture_teardown()
     261        self._urlconf_teardown()
     262
     263    def _fixture_teardown(self):
     264        pass
     265
     266    def _urlconf_teardown(self):       
    229267        if hasattr(self, '_old_root_urlconf'):
    230268            settings.ROOT_URLCONF = self._old_root_urlconf
    231269            clear_url_caches()
     
    359397        self.failIf(template_name in template_names,
    360398            (u"Template '%s' was used unexpectedly in rendering the"
    361399             u" response") % template_name)
     400
     401class TestCase(TransactionTestCase):
     402    """
     403    Does basically the same as TransactionTestCase, but surrounds every test
     404    with a transaction, monkey-patches the real transaction management routines to
     405    do nothing, and rollsback the test transaction at the end of the test. You have
     406    to use TransactionTestCase, if you need transaction management inside a test.
     407    """
     408
     409    def _fixture_setup(self):
     410        if not settings.DATABASE_SUPPORTS_TRANSACTIONS:
     411            return super(TestCase, self)._fixture_setup()
     412       
     413        transaction.enter_transaction_management()
     414        transaction.managed(True)
     415        disable_transaction_methods()
     416
     417        from django.contrib.sites.models import Site
     418        Site.objects.clear_cache()
     419
     420        if hasattr(self, 'fixtures'):
     421            call_command('loaddata', *self.fixtures, **{
     422                                                        'verbosity': 0,
     423                                                        'commit': False
     424                                                        })
     425
     426    def _fixture_teardown(self):
     427        if not settings.DATABASE_SUPPORTS_TRANSACTIONS:
     428            return super(TestCase, self)._fixture_teardown()
     429               
     430        restore_transaction_methods()
     431        transaction.rollback()
     432        transaction.leave_transaction_management()
     433        connection.close()
     434 No newline at end of file
  • django/test/__init__.py

     
    33"""
    44
    55from django.test.client import Client
    6 from django.test.testcases import TestCase
     6from django.test.testcases import TestCase, TransactionTestCase
  • django/db/backends/creation.py

     
    311311
    312312        self.connection.close()
    313313        settings.DATABASE_NAME = test_database_name
    314 
     314        settings.DATABASE_SUPPORTS_TRANSACTIONS = self._rollback_works()
     315       
    315316        call_command('syncdb', verbosity=verbosity, interactive=False)
    316317
    317318        if settings.CACHE_BACKEND.startswith('db://'):
     
    362363                sys.exit(1)
    363364
    364365        return test_database_name
    365 
     366   
     367    def _rollback_works(self):
     368        cursor = self.connection.cursor()
     369        cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)')
     370        self.connection._commit()
     371        cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)')
     372        self.connection._rollback()
     373        cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST')
     374        count, = cursor.fetchone()
     375        cursor.execute('DROP TABLE ROLLBACK_TEST')
     376        self.connection._commit()
     377        return count == 0
     378       
    366379    def destroy_test_db(self, old_database_name, verbosity=1):
    367380        """
    368381        Destroy a test database, prompting the user for confirmation if the
  • tests/regressiontests/file_uploads/tests.py

     
    238238        self.obj = FileModel()
    239239        if not os.path.isdir(temp_storage.location):
    240240            os.makedirs(temp_storage.location)
     241        if os.path.isdir(UPLOAD_TO):
     242            os.chmod(UPLOAD_TO, 0700)
     243            shutil.rmtree(UPLOAD_TO)
    241244
    242245    def tearDown(self):
    243246        os.chmod(temp_storage.location, 0700)
  • tests/regressiontests/generic_inline_admin/tests.py

     
    2121        # relies on content type IDs, which will vary depending on what
    2222        # other tests have been run), thus we do it here.
    2323        e = Episode.objects.create(name='This Week in Django')
     24        self.episode_pk = e.pk
    2425        m = Media(content_object=e, url='http://example.com/podcast.mp3')
    2526        m.save()
     27        self.media_pk = m.pk
    2628   
    2729    def tearDown(self):
    2830        self.client.logout()
     
    3941        """
    4042        A smoke test to ensure GET on the change_view works.
    4143        """
    42         response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episode/1/')
     44        response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episode/%d/' % self.episode_pk)
    4345        self.failUnlessEqual(response.status_code, 200)
    4446   
    4547    def testBasicAddPost(self):
     
    6466            # inline data
    6567            "generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": u"2",
    6668            "generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": u"1",
    67             "generic_inline_admin-media-content_type-object_id-0-id": u"1",
     69            "generic_inline_admin-media-content_type-object_id-0-id": u"%d" % self.media_pk,
    6870            "generic_inline_admin-media-content_type-object_id-0-url": u"http://example.com/podcast.mp3",
    6971            "generic_inline_admin-media-content_type-object_id-1-id": u"",
    7072            "generic_inline_admin-media-content_type-object_id-1-url": u"",
    7173        }
    72         response = self.client.post('/generic_inline_admin/admin/generic_inline_admin/episode/1/', post_data)
     74        url = '/generic_inline_admin/admin/generic_inline_admin/episode/%d/' % self.episode_pk
     75        response = self.client.post(url, post_data)
    7376        self.failUnlessEqual(response.status_code, 302) # redirect somewhere
  • tests/regressiontests/comment_tests/tests/moderation_view_tests.py

     
    88
    99    def testFlagGet(self):
    1010        """GET the flag view: render a confirmation page."""
    11         self.createSomeComments()
     11        comments = self.createSomeComments()
     12        pk = comments[0].pk
    1213        self.client.login(username="normaluser", password="normaluser")
    13         response = self.client.get("/flag/1/")
     14        response = self.client.get("/flag/%d/" % pk)
    1415        self.assertTemplateUsed(response, "comments/flag.html")
    1516
    1617    def testFlagPost(self):
    1718        """POST the flag view: actually flag the view (nice for XHR)"""
    18         self.createSomeComments()
     19        comments = self.createSomeComments()
     20        pk = comments[0].pk
    1921        self.client.login(username="normaluser", password="normaluser")
    20         response = self.client.post("/flag/1/")
    21         self.assertEqual(response["Location"], "http://testserver/flagged/?c=1")
    22         c = Comment.objects.get(pk=1)
     22        response = self.client.post("/flag/%d/" % pk)
     23        self.assertEqual(response["Location"], "http://testserver/flagged/?c=%d" % pk)
     24        c = Comment.objects.get(pk=pk)
    2325        self.assertEqual(c.flags.filter(flag=CommentFlag.SUGGEST_REMOVAL).count(), 1)
    2426        return c
    2527
    2628    def testFlagPostTwice(self):
    2729        """Users don't get to flag comments more than once."""
    2830        c = self.testFlagPost()
    29         self.client.post("/flag/1/")
    30         self.client.post("/flag/1/")
     31        self.client.post("/flag/%d/" % c.pk)
     32        self.client.post("/flag/%d/" % c.pk)
    3133        self.assertEqual(c.flags.filter(flag=CommentFlag.SUGGEST_REMOVAL).count(), 1)
    3234
    3335    def testFlagAnon(self):
    3436        """GET/POST the flag view while not logged in: redirect to log in."""
    35         self.createSomeComments()
    36         response = self.client.get("/flag/1/")
    37         self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/flag/1/")
    38         response = self.client.post("/flag/1/")
    39         self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/flag/1/")
     37        comments = self.createSomeComments()
     38        pk = comments[0].pk       
     39        response = self.client.get("/flag/%d/" % pk)
     40        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/flag/%d/" % pk)
     41        response = self.client.post("/flag/%d/" % pk)
     42        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/flag/%d/" % pk)
    4043
    4144    def testFlaggedView(self):
    42         self.createSomeComments()
    43         response = self.client.get("/flagged/", data={"c":1})
     45        comments = self.createSomeComments()
     46        pk = comments[0].pk       
     47        response = self.client.get("/flagged/", data={"c":pk})
    4448        self.assertTemplateUsed(response, "comments/flagged.html")
    4549
    4650    def testFlagSignals(self):
     
    7074
    7175    def testDeletePermissions(self):
    7276        """The delete view should only be accessible to 'moderators'"""
    73         self.createSomeComments()
     77        comments = self.createSomeComments()
     78        pk = comments[0].pk       
    7479        self.client.login(username="normaluser", password="normaluser")
    75         response = self.client.get("/delete/1/")
    76         self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/delete/1/")
     80        response = self.client.get("/delete/%d/" % pk)
     81        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/delete/%d/" % pk)
    7782
    7883        makeModerator("normaluser")
    79         response = self.client.get("/delete/1/")
     84        response = self.client.get("/delete/%d/" % pk)
    8085        self.assertEqual(response.status_code, 200)
    8186
    8287    def testDeletePost(self):
    8388        """POSTing the delete view should mark the comment as removed"""
    84         self.createSomeComments()
     89        comments = self.createSomeComments()
     90        pk = comments[0].pk
    8591        makeModerator("normaluser")
    8692        self.client.login(username="normaluser", password="normaluser")
    87         response = self.client.post("/delete/1/")
    88         self.assertEqual(response["Location"], "http://testserver/deleted/?c=1")
    89         c = Comment.objects.get(pk=1)
     93        response = self.client.post("/delete/%d/" % pk)
     94        self.assertEqual(response["Location"], "http://testserver/deleted/?c=%d" % pk)
     95        c = Comment.objects.get(pk=pk)
    9096        self.failUnless(c.is_removed)
    9197        self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_DELETION, user__username="normaluser").count(), 1)
    9298
     
    103109        self.assertEqual(received_signals, [signals.comment_was_flagged])
    104110
    105111    def testDeletedView(self):
    106         self.createSomeComments()
    107         response = self.client.get("/deleted/", data={"c":1})
     112        comments = self.createSomeComments()
     113        pk = comments[0].pk       
     114        response = self.client.get("/deleted/", data={"c":pk})
    108115        self.assertTemplateUsed(response, "comments/deleted.html")
    109116
    110117class ApproveViewTests(CommentTestCase):
    111118
    112119    def testApprovePermissions(self):
    113120        """The delete view should only be accessible to 'moderators'"""
    114         self.createSomeComments()
     121        comments = self.createSomeComments()
     122        pk = comments[0].pk       
    115123        self.client.login(username="normaluser", password="normaluser")
    116         response = self.client.get("/approve/1/")
    117         self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/approve/1/")
     124        response = self.client.get("/approve/%d/" % pk)
     125        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/approve/%d/" % pk)
    118126
    119127        makeModerator("normaluser")
    120         response = self.client.get("/approve/1/")
     128        response = self.client.get("/approve/%d/" % pk)
    121129        self.assertEqual(response.status_code, 200)
    122130
    123131    def testApprovePost(self):
     
    127135
    128136        makeModerator("normaluser")
    129137        self.client.login(username="normaluser", password="normaluser")
    130         response = self.client.post("/approve/1/")
    131         self.assertEqual(response["Location"], "http://testserver/approved/?c=1")
    132         c = Comment.objects.get(pk=1)
     138        response = self.client.post("/approve/%d/" % c1.pk)
     139        self.assertEqual(response["Location"], "http://testserver/approved/?c=%d" % c1.pk)
     140        c = Comment.objects.get(pk=c1.pk)
    133141        self.failUnless(c.is_public)
    134142        self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_APPROVAL, user__username="normaluser").count(), 1)
    135143
     
    146154        self.assertEqual(received_signals, [signals.comment_was_flagged])
    147155
    148156    def testApprovedView(self):
    149         self.createSomeComments()
    150         response = self.client.get("/approved/", data={"c":1})
     157        comments = self.createSomeComments()
     158        pk = comments[0].pk       
     159        response = self.client.get("/approved/", data={"c":pk})
    151160        self.assertTemplateUsed(response, "comments/approved.html")
    152161
    153162
  • tests/regressiontests/comment_tests/tests/comment_view_tests.py

     
     1import re
    12from django.conf import settings
    23from django.contrib.auth.models import User
    34from django.contrib.comments import signals
     
    56from regressiontests.comment_tests.models import Article
    67from regressiontests.comment_tests.tests import CommentTestCase
    78
     9post_redirect_re = re.compile(r'^http://testserver/posted/\?c=(?P<pk>\d+$)')
     10
    811class CommentViewTests(CommentTestCase):
    912
    1013    def testPostCommentHTTPMethods(self):
     
    181184        a = Article.objects.get(pk=1)
    182185        data = self.getValidData(a)
    183186        response = self.client.post("/post/", data)
    184         self.assertEqual(response["Location"], "http://testserver/posted/?c=1")
    185 
     187        location = response["Location"]
     188        match = post_redirect_re.match(location)
     189        self.failUnless(match != None, "Unexpected redirect location: %s" % location)
     190       
    186191        data["next"] = "/somewhere/else/"
    187192        data["comment"] = "This is another comment"
    188193        response = self.client.post("/post/", data)
    189         self.assertEqual(response["Location"], "http://testserver/somewhere/else/?c=2")
     194        location = response["Location"]       
     195        match = re.search(r"^http://testserver/somewhere/else/\?c=\d+$", location)
     196        self.failUnless(match != None, "Unexpected redirect location: %s" % location)
    190197
    191198    def testCommentDoneView(self):
    192199        a = Article.objects.get(pk=1)
    193200        data = self.getValidData(a)
    194201        response = self.client.post("/post/", data)
    195         response = self.client.get("/posted/", {'c':1})
     202        location = response["Location"]       
     203        match = post_redirect_re.match(location)
     204        self.failUnless(match != None, "Unexpected redirect location: %s" % location)
     205        pk = int(match.group('pk'))
     206        response = self.client.get(location)
    196207        self.assertTemplateUsed(response, "comments/posted.html")
    197         self.assertEqual(response.context[0]["comment"], Comment.objects.get(pk=1))
     208        self.assertEqual(response.context[0]["comment"], Comment.objects.get(pk=pk))
    198209
  • docs/topics/testing.txt

     
    785785will continue to be available, but it will be augmented with some useful
    786786additions.
    787787
     788.. versionadded:: 1.1
     789
     790.. class:: TransactionTestCase()
     791
     792Django ``TestCase`` classes make use of database transaction facilities, if
     793available, to speed up the process of resetting the database to a known state
     794at the beginning of each test. A consequence of this, however, is that the
     795effects of transaction commit and rollback cannot be tested by a Django
     796``TestCase`` class. If your test requires testing of such transactional
     797behavior, you should use a Django ``TransactionTestCase``.
     798
     799``TransactionTestCase`` and ``TestCase`` are identical except for the manner
     800in which the database is reset to a known state and the ability for test code
     801to test the effects of commit and rollback. A ``TranscationTestCase`` resets
     802the database before the test runs by truncating all tables and reloading
     803initial data. A ``TransactionTestCase`` may call commit and rollback and
     804observe the effects of these calls on the database. 
     805
     806A ``TestCase``, on the other hand, does not truncate tables and reload initial
     807data at the beginning of a test. Instead, it encloses the test code in a
     808database transaction that is rolled back at the end of the test.  It also
     809prevents the code under test from issuing any commit or rollback operations
     810on the database, to ensure that the rollback at the end of the test restores
     811the database to its initial state. In order to guarantee that all ``TestCase``
     812code starts with a clean database, the Django test runner runs all ``TestCase``
     813tests first, before any other tests (e.g. doctests) that may alter the
     814database without restoring it to its original state.
     815
     816When running on a database that does not support rollback (e.g. MySQL with the
     817MyISAM storage engine), ``TestCase`` falls back to initializing the database
     818by truncating tables and reloading initial data.
     819
     820
     821.. note::
     822    The ``TestCase`` use of rollback to un-do the effects of the test code
     823    may reveal previously-undetected errors in test code.  For example,
     824    test code that assumes primary keys values will be assigned starting at
     825    one may find that assumption no longer holds true when rollbacks instead
     826    of table truncation are being used to reset the database.  Similarly,
     827    the reordering of tests so that all ``TestCase`` classes run first may
     828    reveal unexpected dependencies on test case ordering.  In such cases a
     829    quick fix is to switch the ``TestCase`` to a ``TransactionTestCase``.
     830    A better long-term fix, that allows the test to take advantage of the
     831    speed benefit of ``TestCase``, is to fix the underlying test problem.
     832             
     833
    788834Default test client
    789835~~~~~~~~~~~~~~~~~~~
    790836
Back to Top