Fix issue2669: bsddb simple/legacy interface iteration silently fails
when database changes size during iteration.

It now behaves like a dictionary, the next attempt to get a value from
the iterator after the database has changed size will raise a RuntimeError.
diff --git a/Lib/test/test_bsddb.py b/Lib/test/test_bsddb.py
index acd4972..f6e6ad0 100755
--- a/Lib/test/test_bsddb.py
+++ b/Lib/test/test_bsddb.py
@@ -66,9 +66,6 @@
         self.assertSetEquals(d.iteritems(), f.iteritems())
 
     def test_iter_while_modifying_values(self):
-        if not hasattr(self.f, '__iter__'):
-            return
-
         di = iter(self.d)
         while 1:
             try:
@@ -80,20 +77,62 @@
         # it should behave the same as a dict.  modifying values
         # of existing keys should not break iteration.  (adding
         # or removing keys should)
+        loops_left = len(self.f)
         fi = iter(self.f)
         while 1:
             try:
                 key = fi.next()
                 self.f[key] = 'modified '+key
+                loops_left -= 1
             except StopIteration:
                 break
+        self.assertEqual(loops_left, 0)
 
         self.test_mapping_iteration_methods()
 
-    def test_iteritems_while_modifying_values(self):
-        if not hasattr(self.f, 'iteritems'):
-            return
+    def test_iter_abort_on_changed_size(self):
+        def DictIterAbort():
+            di = iter(self.d)
+            while 1:
+                try:
+                    di.next()
+                    self.d['newkey'] = 'SPAM'
+                except StopIteration:
+                    break
+        self.assertRaises(RuntimeError, DictIterAbort)
 
+        def DbIterAbort():
+            fi = iter(self.f)
+            while 1:
+                try:
+                    fi.next()
+                    self.f['newkey'] = 'SPAM'
+                except StopIteration:
+                    break
+        self.assertRaises(RuntimeError, DbIterAbort)
+
+    def test_iteritems_abort_on_changed_size(self):
+        def DictIteritemsAbort():
+            di = self.d.iteritems()
+            while 1:
+                try:
+                    di.next()
+                    self.d['newkey'] = 'SPAM'
+                except StopIteration:
+                    break
+        self.assertRaises(RuntimeError, DictIteritemsAbort)
+
+        def DbIteritemsAbort():
+            fi = self.f.iteritems()
+            while 1:
+                try:
+                    key, value = fi.next()
+                    del self.f[key]
+                except StopIteration:
+                    break
+        self.assertRaises(RuntimeError, DbIteritemsAbort)
+
+    def test_iteritems_while_modifying_values(self):
         di = self.d.iteritems()
         while 1:
             try:
@@ -105,13 +144,16 @@
         # it should behave the same as a dict.  modifying values
         # of existing keys should not break iteration.  (adding
         # or removing keys should)
+        loops_left = len(self.f)
         fi = self.f.iteritems()
         while 1:
             try:
                 k, v = fi.next()
                 self.f[k] = 'modified '+v
+                loops_left -= 1
             except StopIteration:
                 break
+        self.assertEqual(loops_left, 0)
 
         self.test_mapping_iteration_methods()
 
@@ -177,8 +219,8 @@
         # the database write and locking+threading support is enabled
         # the cursor's read lock will deadlock the write lock request..
 
-        # test the iterator interface (if present)
-        if hasattr(self.f, 'iteritems'):
+        # test the iterator interface
+        if True:
             if debug: print "D"
             i = self.f.iteritems()
             k,v = i.next()
@@ -213,10 +255,7 @@
         self.assert_(self.f[k], "be gone with ye deadlocks")
 
     def test_for_cursor_memleak(self):
-        if not hasattr(self.f, 'iteritems'):
-            return
-
-        # do the bsddb._DBWithCursor _iter_mixin internals leak cursors?
+        # do the bsddb._DBWithCursor iterator internals leak cursors?
         nc1 = len(self.f._cursor_refs)
         # create iterator
         i = self.f.iteritems()