#8138: Switch django tests to use transactions
----------------------------------------+-----------------------------------
          Reporter:  mremolt            |         Owner:  nobody  
            Status:  new                |     Milestone:  post-1.0
         Component:  Testing framework  |       Version:  SVN     
        Resolution:                     |      Keywords:          
             Stage:  Accepted           |     Has_patch:  1       
        Needs_docs:  0                  |   Needs_tests:  0       
Needs_better_patch:  0                  |  
----------------------------------------+-----------------------------------
Comment (by kmtracey):

 I've looked at this a little more and figured out a few things.

 First, it never seemed well-understood from the mailing list discussion
 why tests that used the test Client had to continue to use the
 flush/reload and even then there were failures.  The problem is that
 `django.test.client.ClientHandler`'s `__call__` sends the
 `request_finished` signal, which is connected (in `django.db.__init__`) to
 `close_connection`, which severs the connection to the database.  So if a
 test POSTed via the test Client in order to login, for example, the
 session changes made during the POST were rolled back when the POST's
 `request_finished` signal severed the DB connection apparently mid-
 transaction, and subsequent stuff done by the testcase that relied on
 being logged in would fail.

 Disconnecting `close_connection` from from `request_finished` solves this
 problem, so that tests that use the test Client do not automatically need
 to use the old way, and may take advantage of the rollback approach.  In
 the patch I'll attach the signal is disconnected/reconnected around the
 sending of the signal...this is probably overkill.  I think it would be OK
 to just disconnect `close_connection` from that signal once and be done
 with it but I haven't had time to go back and experiment with that since I
 tried the first way.

 Second, having the fixture teardown code attempt to detect whether a
 commit had been performed by the test code (despite the test case being a
 type that supposedly didn't use transactions) and fallback to flush/reload
 didn't seem to be working too well.  I tried tracking down a few of the
 failures caused by these and eventually decided to try a different
 approach, which is: if it's a test case that says it doesn't need
 transactions to work, monkey patch the transaction commit/rollback and
 enter/leave transaction management routines so that even if they are
 called by the code being tested, they do nothing, for the duration of the
 test.

 I don't know how the monkey patch idea will be received, but this approach
 seems to work better.  Doing it this way, none of the existing Django
 tescases need to use the old flush/reload approach.  That is, the tests
 that do actually call commit or rollback (like admin_views, which call
 admin views that are wrapped in commit_on_success) don't actually need
 those calls to do anything in order for the test to properly test what it
 is trying to test.  Things work fine when those routines don't do
 anything, and the test fixture teardown is able to reliably un-do what the
 test has done, leaving things clean for the next test.

 Which is not to say there were no remaining failures, but what was left
 was somewhat easier to deal with.  First and easiest there were a few
 tests that assume objects they created would be assigned pk's starting
 with 1.  This is an invalid assumption when rollback is being used to un-
 do the test effects since autoincrement sequence numbers are not
 necessarily reset (sqlite seems to reset them, the others don't).  These
 were pretty easy to modify to retrieve and use the pk instead of assuming
 1, 2, etc.

 Second was a failure in the views test resulting from the Site's
 SITE_CACHE not being cleared during fixture loading.  The flush command
 sends out the post-syncdb signal, which Sites connects to to initialize a
 default site and clear the cache, so this SITE_CACHE was always clear on
 test start using the old approach.  With the rollback approach, the views
 test would pass if it was run by itself, but would fail if run with
 comment_tests, which apparently does something to cause SITE_CACHE to get
 an entry for site 1.  The value (default since comment_tests doesn't load
 anything into the Site model) didn't match what was expected by views
 (which loads a Site via a fixture, with values different from the
 default).  In the patch I'll attach this is fixed by calling the Site's
 clear_cache function during fixture loading.  Not particularly elegant and
 it points out a subtle difference between the old and new approaches --
 but this is the only problem I have found resulting from that difference.

 Third and one I haven't entirely figured out is this failure, only with
 PostgreSQL, in the fixtures test:

 {{{
 ======================================================================
 FAIL: Doctest: modeltests.fixtures.models.__test__.API_TESTS
 ----------------------------------------------------------------------
 Traceback (most recent call last):
   File "/home/kmt/tmp/django/trunk/django/test/_doctest.py", line 2180, in
 runTest
     raise self.failureException(self.format_failure(new.getvalue()))
 AssertionError: Failed doctest test for
 modeltests.fixtures.models.__test__.API_TESTS
   File "/home/kmt/tmp/django/trunk/tests/modeltests/fixtures/models.py",
 line unknown line number, in API_TESTS

 ----------------------------------------------------------------------
 File "/home/kmt/tmp/django/trunk/tests/modeltests/fixtures/models.py",
 line ?, in modeltests.fixtures.models.__test__.API_TESTS
 Failed example:
     management.call_command('dumpdata', 'fixtures', format='json')
 Expected:
     [{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time
 to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2,
 "model": "fixtures.article", "fields": {"headline": "Poker has no place on
 ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model":
 "fixtures.article", "fields": {"headline": "Python program becomes self
 aware", "pub_date": "2006-06-16 11:00:00"}}]
 Got:
     [{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time
 to reform copyright", "pub_date": "2006-06-16 14:00:00"}}, {"pk": 2,
 "model": "fixtures.article", "fields": {"headline": "Poker has no place on
 ESPN", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 1, "model":
 "fixtures.article", "fields": {"headline": "Python program becomes self
 aware", "pub_date": "2006-06-16 12:00:00"}}]


 ----------------------------------------------------------------------
 Ran 2 tests in 25.922s

 FAILED (failures=1)
 Destroying test database...
 }}}

 The difference is in the times, they are an hour off.  Luke also mentioned
 seeing this on the mailing list, only his times were off by 6 hours.  I'd
 guess Luke's real time zone was 6 hours off from 'America/Chicago' while I
 am 1 hour off from 'American/Chicago'.  I can make this failure go away by
 setting the TIME_ZONE setting to match my real time zone, but I haven't
 figured out why this is necessary only when using the rollback approach --
 how did the times workout properly without a TIME_ZONE setting in the
 flush/reload approach?

 I think that was it for problems encountered.  With this approach the test
 suite runs about 6x faster on sqlite, 8-12x faster on MySQL/InnoDB,
 PostgreSQL, and Oracle, on my (oldish to antique) machines, with no
 failures other than what I see using the current trunk code (InnoDB's
 failures are well-known, I think, I also see a PostgreSQL failure related
 to #9758/#9006 with both old and new test approaches, and my Oracle
 install on a Windows box has a quartet of failures that may be related to
 Windows file and charset issues that I haven't bothered to track down
 since I barely know how to spell Oracle).

 That leaves MySQL/MyISAM, which of course fails miserably with the
 rollback approach since its rollback doesn't actually do anything.  I
 added a routine that attempts to detect whether rollback works in the test
 DB, and if it does not the fixture setup/teardown falls back to the old
 way.  It occurs to me now that I didn't do anything to deal with the
 !DocTest runner's rollback not working (nor does the !DocTest runner
 monkey patch the transaction routines)...there's probably more somebody
 needs to understand about the !DocTest runner with the new approach, but
 I've run out of time to look at this for a few days probably so if anybody
 else has time to look at it go ahead.  I also suspect the "does rollback
 work" routine could be done more elegantly; what I've put in is pretty
 much a quick hack to try to get tests basically working with the patch on
 MySQL/MyISAM.

-- 
Ticket URL: <http://code.djangoproject.com/ticket/8138#comment:18>
Django <http://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to