[Python-checkins] python/dist/src/Lib/test test_weakref.py,1.17.4.1,1.17.4.2

bwarsaw@users.sourceforge.net bwarsaw@users.sourceforge.net
Wed, 28 May 2003 16:03:32 -0700


Update of /cvsroot/python/python/dist/src/Lib/test
In directory sc8-pr-cvs1:/tmp/cvs-serv30946/Lib/test

Modified Files:
      Tag: release22-maint
	test_weakref.py 
Log Message:
The backport gets Fred's seal of approval:

    SF 742860: WeakKeyDictionary __delitem__ uses iterkeys

    Someone review this, please!  Final releases are getting close, Fred
    (the weakref guy) won't be around until Tuesday, and the pre-patch
    code can indeed raise spurious RuntimeErrors in the presence of
    threads or mutating comparison functions.

    See the bug report for my confusions:  I can't see any reason for why
    __delitem__ iterated over the keys.  The new one-liner implementation
    is much faster, can't raise RuntimeError, and should be better-behaved
    in all respects wrt threads.

    New tests test_weak_keyed_bad_delitem and
    test_weak_keyed_cascading_deletes fail before this patch.

Backported the tests and the patch.


Index: test_weakref.py
===================================================================
RCS file: /cvsroot/python/python/dist/src/Lib/test/test_weakref.py,v
retrieving revision 1.17.4.1
retrieving revision 1.17.4.2
diff -C2 -d -r1.17.4.1 -r1.17.4.2
*** test_weakref.py	23 Aug 2002 16:29:27 -0000	1.17.4.1
--- test_weakref.py	28 May 2003 23:03:30 -0000	1.17.4.2
***************
*** 503,506 ****
--- 503,564 ----
          self.assert_(d.items() == [('something else', o2)])
  
+     def test_weak_keyed_bad_delitem(self):
+         d = weakref.WeakKeyDictionary()
+         o = Object('1')
+         # An attempt to delete an object that isn't there should raise
+         # KeyError.  It didn't before 2.3.
+         self.assertRaises(KeyError, d.__delitem__, o)
+         self.assertRaises(KeyError, d.__getitem__, o)
+ 
+         # If a key isn't of a weakly referencable type, __getitem__ and
+         # __setitem__ raise TypeError.  __delitem__ should too.
+         self.assertRaises(TypeError, d.__delitem__,  13)
+         self.assertRaises(TypeError, d.__getitem__,  13)
+         self.assertRaises(TypeError, d.__setitem__,  13, 13)
+ 
+     def test_weak_keyed_cascading_deletes(self):
+         # SF bug 742860.  For some reason, before 2.3 __delitem__ iterated
+         # over the keys via self.data.iterkeys().  If things vanished from
+         # the dict during this (or got added), that caused a RuntimeError.
+ 
+         d = weakref.WeakKeyDictionary()
+         mutate = False
+ 
+         class C(object):
+             def __init__(self, i):
+                 self.value = i
+             def __hash__(self):
+                 return hash(self.value)
+             def __eq__(self, other):
+                 if mutate:
+                     # Side effect that mutates the dict, by removing the
+                     # last strong reference to a key.
+                     del objs[-1]
+                 return self.value == other.value
+ 
+         objs = [C(i) for i in range(4)]
+         for o in objs:
+             d[o] = o.value
+         del o   # now the only strong references to keys are in objs
+         # Find the order in which iterkeys sees the keys.
+         objs = d.keys()
+         # Reverse it, so that the iteration implementation of __delitem__
+         # has to keep looping to find the first object we delete.
+         objs.reverse()
+ 
+         # Turn on mutation in C.__eq__.  The first time thru the loop,
+         # under the iterkeys() business the first comparison will delete
+         # the last item iterkeys() would see, and that causes a
+         #     RuntimeError: dictionary changed size during iteration
+         # when the iterkeys() loop goes around to try comparing the next
+         # key.  After this was fixed, it just deletes the last object *our*
+         # "for o in obj" loop would have gotten to.
+         mutate = True
+         count = 0
+         for o in objs:
+             count += 1
+             del d[o]
+         self.assertEqual(len(d), 0)
+         self.assertEqual(count, 2)
  
  def test_main():