#15026: Test failures in django.contrib.sessions on default project when 
memcached
used as CACHE_BACKEND
----------------------------------------------+-----------------------------
          Reporter:  jsdalton                 |         Owner:  nobody
            Status:  new                      |     Milestone:  1.3   
         Component:  django.contrib.sessions  |       Version:  SVN   
        Resolution:                           |      Keywords:        
             Stage:  Unreviewed               |     Has_patch:  0     
        Needs_docs:  0                        |   Needs_tests:  0     
Needs_better_patch:  0                        |  
----------------------------------------------+-----------------------------
Changes (by jsdalton):

  * needs_better_patch:  => 0
  * needs_tests:  => 0
  * needs_docs:  => 0

Comment:

 I found some time to investigate this more closely, and it would appear
 that this is a bug in the test code.

 In a brief nutshell, this is the body of the test method being run:

 {{{
     def test_invalid_key(self):
         # Submitting an invalid session key (either by guessing, or if the
 db has
         # removed the key) results in a new key being generated.
         session = self.backend('1')
         session.save()
         self.assertNotEqual(session.session_key, '1')
         self.assertEqual(session.get('cat'), None)
         session.delete()
 }}}

 When the `save()` method is called, a new session object is created with
 that original session_key and saved to the backend. For example, this is
 what happens in the db store backend:

   1. The session with `session_key` of '1' is saved via `save()`. A new
 `Session` object is then created. The `session_data` attribute of that
 objects is populated by an encoded version of the underlying session data,
 which is retrieved via `_get_session()`. `_get_session()` looks for a
 value in `_session_cache`, and seeing nothing, calls `load()`. `load()`
 checks the DB to see if an object with that original `session_key` of '1'
 exists. Seeing that this object does not exit, `load()` calls `create()`.
 At this point, a new `session_key` is generated, replacing the original
 value of '1' with an autogenerated string.

   1. `save()` is now called again, this time with `must_create` set to
 `True`. Another `Session` object is now created, this time with the new
 `session_key` value. The `session_data` attribute is once again set as the
 encoded version of the underlying session data, again retrieved via
 `_get_session()`, however this time `_get_session()` is called with
 `no_load` set to `True`. As a result, `load()` is not called and the
 `session_cache` is set to `{}`, which is what `_get_session()` ultimately
 returns. Now we have actually saved a new Session record to the database
 meaning the original call to `create()` in step 1 has finally returned.
 That original call was in the `load()` method, which, after having
 finished the create process, now returns `{}` back to the original
 `_get_session()` call from way above. `_get_session()` sets the cache once
 again to `{}` and returns that value to the original original Session
 object being created when we first called `save()`. This, importantly, is
 our original object with `session_key` of '1'. That object is now in the
 database as well.

 The end result is that we now have *two* new Session objects in the
 database: one being the Session with key of '1', the other being the
 Session with the newly generated key. Importantly, only the newly created
 key exists in the Session object in memory (i.e. this is the value of
 `_session_key`). The old key of '1' is gone.

 So....where does that leave us? Well, at the end of the test method above,
 we call `session.delete()`. This deletes the Session with our newly
 generated key, but it leaves the Session object with session_key of '1'
 sitting in the database.

 Now, thanks to the django test suite, that object is erased from the DB
 when the db is flushed at the end of each test method, so it never comes
 back to bite us. For the file-backed session backend, there is a
 `tearDown()` method which flushes the file cache after each test, which is
 effectively the same result.

 For the cache however, we're not so lucky. The cache is not flushed after
 each test, so the problem is that session "record" with session_key of '1'
 is still sitting in the cache. In the `save()` sequence described above
 (which is pretty similar to what happens for the db), there is a call to
 `load()`which we would expect to find nothing in the cache. Here, it does
 find the old record hanging around. It returns this record instead of
 calling `create()`. `create()` is what generates the new session_key, and
 since it never gets called, a new key is never created.

 Thus, the tests fails.

 Being unfamiliar with the sessions application internals for the most
 part, I'm not certain what to conclude here. One conclusion and easy fix
 is just to add a `tearDown()` method to the cache-backed session store
 tests that flushes the cache after each test. This does "solve" the
 problem and all the tests pass. Essentially, this is the equivalent as
 what is happening in the db store and the file store test cases.

 The other conclusion is that perhaps something deeper is amiss? Maybe this
 is such an edge case that it doesn't matter, but it would appear that two
 records are being created when a session is saved and that one of them is
 being orphaned? Honestly my brain hurts too much from putting all of the
 above together to be sure of this at the moment. But I am fairly certain
 right now that the result of this test method is that records are being
 orphaned, and the only reason why this isn't apparent is that the test
 suite flushes the db and file set.

 If it's just a matter of fixing the tests by flushing the cache, that's a
 one-line patch I could upload. Anything more, someone more familiar with
 the sessions app might want to take a closer look.

-- 
Ticket URL: <http://code.djangoproject.com/ticket/15026#comment:1>
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 django-upda...@googlegroups.com.
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to