[Zope-Checkins] CVS: Zope3/lib/python/Persistence/BTrees/tests - __init__.py:1.2 testBTrees.py:1.2 testBTreesUnicode.py:1.2 testCompare.py:1.2 testConflict.py:1.2 testSetOps.py:1.2

Jim Fulton jim@zope.com
Mon, 10 Jun 2002 19:28:44 -0400


Update of /cvs-repository/Zope3/lib/python/Persistence/BTrees/tests
In directory cvs.zope.org:/tmp/cvs-serv17445/lib/python/Persistence/BTrees/tests

Added Files:
	__init__.py testBTrees.py testBTreesUnicode.py testCompare.py 
	testConflict.py testSetOps.py 
Log Message:
Merged Zope-3x-branch into newly forked Zope3 CVS Tree.


=== Zope3/lib/python/Persistence/BTrees/tests/__init__.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+# If tests is a package, debugging is a bit easier.
+


=== Zope3/lib/python/Persistence/BTrees/tests/testBTrees.py 1.1 => 1.2 === (721/821 lines abridged)
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+import os
+import random
+
+from Persistence.BTrees.OOBTree import OOBTree, OOBucket, OOSet, OOTreeSet
+from Persistence.BTrees.IOBTree import IOBTree, IOBucket, IOSet, IOTreeSet
+from Persistence.BTrees.IIBTree import IIBTree, IIBucket, IISet, IITreeSet
+from Persistence.BTrees.OIBTree import OIBTree, OIBucket, OISet, OITreeSet
+
+from unittest import TestCase, TestSuite, TextTestRunner, makeSuite
+
+from glob import glob
+
+class Base(TestCase):
+    """ Tests common to all types: sets, buckets, and BTrees """
+    def tearDown(self):
+        self.t = None
+        del self.t
+
+    def _getRoot(self):
+        from ZODB.FileStorage import FileStorage
+        from ZODB.DB import DB
+        n = 'fs_tmp__%s' % os.getpid()
+        s = FileStorage(n)
+        db = DB(s, cache_size=1)
+        root = db.open().root()
+        return root
+
+    def _closeDB(self, root):
+        if root is not None:
+            root._p_jar._db.close()
+
+    def _delDB(self):
+        for file in glob('fs_tmp__*'):
+            os.remove(file)
+
+    def testLoadAndStore(self):
+        for i in 0, 10, 1000:
+            t = self.t.__class__()

[-=- -=- -=- 721 lines omitted -=- -=- -=-]

+
+def make_test(klass, base):
+    class Test(base):
+        def setUp(self):
+            self.t = klass()
+    # Give the test an artificial name so that unittest output
+    # includes the name of the BTree object being tested.  Include a
+    # '!' in the name so that it is obvious that it doesn't occur in
+    # the source code anywhere.
+    Test.__name__ = "!%sTest" % klass.__name__
+    return makeSuite(Test)
+
+def test_suite():
+    # Handle the basic test cases for each type of object via make_test().
+    template = [(MappingBase, (IIBucket, IOBucket, OIBucket, OOBucket)),
+                (NormalSetTests, (IITreeSet, IOTreeSet, OITreeSet, OOTreeSet)),
+                (ExtendedSetTests, (IISet, IOSet, OISet, OOSet)),
+                (BTreeTests, (IIBTree, IOBTree,  OIBTree, OOBTree)),
+                ]
+
+    s = TestSuite()
+    for base, classes in template:
+        for klass in classes:
+            s.addTest(make_test(klass, base))
+
+    # Handle the odd assortment of other tests, which appear to be
+    # specific to whether keys and values are Is or Os.
+    for klass in TestIOBTrees, TestOIBTrees, TestIIBTrees, TestIOSets:
+        s.addTest(makeSuite(klass))
+
+    return s
+
+## utility functions
+
+def lsubtract(l1, l2):
+   l1 = list(l1)
+   l2 = list(l2)
+   l = filter(lambda x, l1=l1: x not in l1, l2)
+   l = l + filter(lambda x, l2=l2: x not in l2, l1)
+   return l
+
+def realseq(itemsob):
+    return [x for x in itemsob]
+
+def main():
+    TextTestRunner().run(test_suite())
+
+if __name__ == '__main__':
+    main()
+


=== Zope3/lib/python/Persistence/BTrees/tests/testBTreesUnicode.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+__version__ = '$Id$'
+
+import types
+import unittest
+from Persistence.BTrees.OOBTree import OOBTree
+
+# When an OOBtree contains unicode strings as keys,
+# it is neccessary accessing non-unicode strings are
+# either ascii strings or encoded as unicoded using the
+# corresponding encoding
+
+encoding = 'ISO-8859-1'
+
+class TestBTreesUnicode(unittest.TestCase):
+    """ test unicode"""
+
+    def setUp(self):
+        """setup an OOBTree with some unicode strings"""
+
+        self.s = unicode('dreit\xe4gigen', 'latin1')
+
+        self.data = [('alien', 1),
+                     ('k\xf6nnten', 2),
+                     ('fox', 3),
+                     ('future', 4),
+                     ('quick', 5),
+                     ('zerst\xf6rt', 6),
+                     (unicode('dreit\xe4gigen','latin1'), 7),
+                    ]
+
+        self.tree = OOBTree()
+        for k, v in self.data:
+            if isinstance(k, types.StringType):
+                k = unicode(k, 'latin1')
+            self.tree[k] = v
+
+    def testAllKeys(self):
+        # check every item of the tree
+        for k, v in self.data:
+            if isinstance(k, types.StringType):
+                k = unicode(k, encoding)
+            self.assert_(self.tree.has_key(k))
+            self.assertEqual(self.tree[k], v)
+
+    def testUnicodeKeys(self):
+        # try to access unicode keys in tree
+        k, v = self.data[-1]
+        self.assertEqual(k, self.s)
+        self.assertEqual(self.tree[k], v)
+        self.assertEqual(self.tree[self.s], v)
+
+    def testAsciiKeys(self):
+        # try to access some "plain ASCII" keys in the tree
+        for k, v in self.data[0], self.data[2]:
+            self.assert_(isinstance(k, types.StringType))
+            self.assertEqual(self.tree[k], v)
+
+def test_suite():
+    return unittest.makeSuite(TestBTreesUnicode)
+
+def main():
+    unittest.TextTestRunner().run(test_suite())
+
+if __name__ == '__main__':
+    main()


=== Zope3/lib/python/Persistence/BTrees/tests/testCompare.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""Test errors during comparison of BTree keys."""
+
+import unittest
+
+import ZODB
+from Persistence.BTrees.OOBTree import OOBucket as Bucket, OOSet as Set
+from ZODB.MappingStorage import MappingStorage
+
+class CompareTest(unittest.TestCase):
+
+    s = "A string with hi-bit-set characters: \700\701"
+    u = u"A unicode string"
+
+    def setUp(self):
+        # These defaults only make sense if the default encoding
+        # prevents s from being promoted to Unicode.
+        self.assertRaises(UnicodeError, unicode, self.s)
+
+        # An object needs to be added to the database to 
+        self.db = ZODB.DB(MappingStorage())
+        root = self.db.open().root()
+        self.bucket = root["bucket"] = Bucket()
+        self.set = root["set"] = Set()
+        get_transaction().commit()
+
+    def tearDown(self):
+        self.assert_(self.bucket._p_changed != 2)
+        self.assert_(self.set._p_changed != 2)
+
+    def assertUE(self, callable, *args):
+        self.assertRaises(UnicodeError, callable, *args)
+
+    def testBucketGet(self):
+        self.bucket[self.s] = 1
+        self.assertUE(self.bucket.get, self.u)
+
+    def testSetGet(self):
+        self.set.insert(self.s)
+        self.assertUE(self.set.remove, self.u)
+
+    def testBucketSet(self):
+        self.bucket[self.s] = 1
+        self.assertUE(self.bucket.__setitem__, self.u, 1)
+
+    def testSetSet(self):
+        self.set.insert(self.s)
+        self.assertUE(self.set.insert, self.u)
+
+    def testBucketMinKey(self):
+        self.bucket[self.s] = 1
+        self.assertUE(self.bucket.minKey, self.u)
+
+    def testSetMinKey(self):
+        self.set.insert(self.s)
+        self.assertUE(self.set.minKey, self.u)
+
+def test_suite():
+    return unittest.makeSuite(CompareTest)


=== Zope3/lib/python/Persistence/BTrees/tests/testConflict.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+import os
+
+from Persistence.BTrees.OOBTree import OOBTree, OOBucket, OOSet, OOTreeSet
+from Persistence.BTrees.IOBTree import IOBTree, IOBucket, IOSet, IOTreeSet
+from Persistence.BTrees.IIBTree import IIBTree, IIBucket, IISet, IITreeSet
+from Persistence.BTrees.OIBTree import OIBTree, OIBucket, OISet, OITreeSet
+from unittest import TestCase, TestSuite, TextTestRunner, makeSuite
+
+from Transaction.Exceptions import ConflictError
+
+class Base:
+    """ Tests common to all types: sets, buckets, and BTrees """
+    def tearDown(self):
+        self.t = None
+        del self.t
+
+    def _getRoot(self):
+        from ZODB.FileStorage import FileStorage
+        from ZODB.DB import DB
+        n = 'fs_tmp__%s' % os.getpid()
+        s = FileStorage(n)
+        db = DB(s)
+        root = db.open().root()
+        return root
+
+    def _closeDB(self, root):
+        root._p_jar._db.close()
+        root = None
+
+    def _delDB(self):
+        os.system('rm fs_tmp__*')
+
+class MappingBase(Base):
+    """ Tests common to mappings (buckets, btrees) """
+
+    def _deletefail(self):
+        del self.t[1]
+
+    def _setupConflict(self):
+        
+        l=[ -5124, -7377, 2274, 8801, -9901, 7327, 1565, 17, -679,
+            3686, -3607, 14, 6419, -5637, 6040, -4556, -8622, 3847, 7191,
+            -4067]
+
+
+        e1=[(-1704, 0), (5420, 1), (-239, 2), (4024, 3), (-6984, 4)]
+        e2=[(7745, 0), (4868, 1), (-2548, 2), (-2711, 3), (-3154, 4)]
+
+        
+        base=self.t
+        base.update([(i, i*i) for i in l[:20]])
+        b1=base.__class__(base)
+        b2=base.__class__(base)
+        bm=base.__class__(base)
+
+        items=base.items()
+
+        return  base, b1, b2, bm, e1, e2, items
+
+    def testMergeDelete(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+        del b1[items[0][0]]
+        del b2[items[5][0]]
+        del b1[items[-1][0]]
+        del b2[items[-2][0]]
+        del bm[items[0][0]]
+        del bm[items[5][0]]
+        del bm[items[-1][0]]
+        del bm[items[-2][0]]
+        test_merge(base, b1, b2, bm, 'merge  delete')
+
+    def testMergeDeleteAndUpdate(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+        del b1[items[0][0]]
+        b2[items[5][0]]=1
+        del b1[items[-1][0]]
+        b2[items[-2][0]]=2
+        del bm[items[0][0]]
+        bm[items[5][0]]=1
+        del bm[items[-1][0]]
+        bm[items[-2][0]]=2
+        test_merge(base, b1, b2, bm, 'merge update and delete')
+
+    def testMergeUpdate(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+        b1[items[0][0]]=1
+        b2[items[5][0]]=2
+        b1[items[-1][0]]=3
+        b2[items[-2][0]]=4
+        bm[items[0][0]]=1
+        bm[items[5][0]]=2
+        bm[items[-1][0]]=3
+        bm[items[-2][0]]=4
+        test_merge(base, b1, b2, bm, 'merge update')
+
+    def testFailMergeDelete(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+        del b1[items[0][0]]
+        del b2[items[0][0]]
+        test_merge(base, b1, b2, bm, 'merge conflicting delete',
+                   should_fail=1)
+
+    def testFailMergeUpdate(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+        b1[items[0][0]]=1
+        b2[items[0][0]]=2
+        test_merge(base, b1, b2, bm, 'merge conflicting update',
+                   should_fail=1)
+
+    def testFailMergeDeleteAndUpdate(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+        del b1[items[0][0]]
+        b2[items[0][0]]=-9
+        test_merge(base, b1, b2, bm, 'merge conflicting update and delete',
+                   should_fail=1)
+        
+    def testMergeInserts(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+
+        b1[-99999]=-99999
+        b1[e1[0][0]]=e1[0][1]
+        b2[99999]=99999
+        b2[e1[2][0]]=e1[2][1]
+
+        bm[-99999]=-99999
+        bm[e1[0][0]]=e1[0][1]
+        bm[99999]=99999
+        bm[e1[2][0]]=e1[2][1]
+        test_merge(base, b1, b2, bm, 'merge insert')
+        
+    def testMergeInsertsFromEmpty(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+
+        base.clear()
+        b1.clear()
+        b2.clear()
+        bm.clear()
+
+        b1.update(e1)
+        bm.update(e1)
+        b2.update(e2)
+        bm.update(e2)
+
+        test_merge(base, b1, b2, bm, 'merge insert from empty')
+        
+    def testMergeEmptyAndFill(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+
+        b1.clear()
+        bm.clear()
+        b2.update(e2)
+        bm.update(e2)
+
+        test_merge(base, b1, b2, bm, 'merge insert from empty')
+        
+    def testMergeEmpty(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+
+        b1.clear()
+        bm.clear()
+
+        test_merge(base, b1, b2, bm, 'empty one and not other')
+
+    def testFailMergeInsert(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+        b1[-99999]=-99999
+        b1[e1[0][0]]=e1[0][1]
+        b2[99999]=99999
+        b2[e1[0][0]]=e1[0][1]
+        test_merge(base, b1, b2, bm, 'merge conflicting inserts',
+                   should_fail=1)
+        
+
+class NormalSetTests(Base):
+    """ Test common to all set types """
+
+
+
+class ExtendedSetTests(NormalSetTests):
+    "Set (as opposed to TreeSet) specific tests."
+
+    def _setupConflict(self):        
+        l=[ -5124, -7377, 2274, 8801, -9901, 7327, 1565, 17, -679,
+            3686, -3607, 14, 6419, -5637, 6040, -4556, -8622, 3847, 7191,
+            -4067]
+
+        e1=[-1704, 5420, -239, 4024, -6984]
+        e2=[7745, 4868, -2548, -2711, -3154]
+
+        
+        base=self.t
+        base.update(l)
+        b1=base.__class__(base)
+        b2=base.__class__(base)
+        bm=base.__class__(base)
+
+        items=base.keys()
+
+        return  base, b1, b2, bm, e1, e2, items
+
+    def testMergeDelete(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+        b1.remove(items[0])
+        b2.remove(items[5])
+        b1.remove(items[-1])
+        b2.remove(items[-2])
+        bm.remove(items[0])
+        bm.remove(items[5])
+        bm.remove(items[-1])
+        bm.remove(items[-2])
+        test_merge(base, b1, b2, bm, 'merge  delete')
+
+    def testFailMergeDelete(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+        b1.remove(items[0])
+        b2.remove(items[0])
+        test_merge(base, b1, b2, bm, 'merge conflicting delete',
+                   should_fail=1)
+        
+    def testMergeInserts(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+
+        b1.insert(-99999)
+        b1.insert(e1[0])
+        b2.insert(99999)
+        b2.insert(e1[2])
+
+        bm.insert(-99999)
+        bm.insert(e1[0])
+        bm.insert(99999)
+        bm.insert(e1[2])
+        test_merge(base, b1, b2, bm, 'merge insert')
+        
+    def testMergeInsertsFromEmpty(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+
+        base.clear()
+        b1.clear()
+        b2.clear()
+        bm.clear()
+
+        b1.update(e1)
+        bm.update(e1)
+        b2.update(e2)
+        bm.update(e2)
+
+        test_merge(base, b1, b2, bm, 'merge insert from empty')
+        
+    def testMergeEmptyAndFill(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+
+        b1.clear()
+        bm.clear()
+        b2.update(e2)
+        bm.update(e2)
+
+        test_merge(base, b1, b2, bm, 'merge insert from empty')
+        
+    def testMergeEmpty(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+
+        b1.clear()
+        bm.clear()
+
+        test_merge(base, b1, b2, bm, 'empty one and not other')
+
+    def testFailMergeInsert(self):
+        base, b1, b2, bm, e1, e2, items = self._setupConflict()
+        b1.insert(-99999)
+        b1.insert(e1[0])
+        b2.insert(99999)
+        b2.insert(e1[0])
+        test_merge(base, b1, b2, bm, 'merge conflicting inserts',
+                   should_fail=1)
+        
+
+def test_merge(o1, o2, o3, expect, message='failed to merge', should_fail=0):
+    s1=o1.__getstate__()
+    s2=o2.__getstate__()
+    s3=o3.__getstate__()
+    expected=expect.__getstate__()
+    if expected is None: expected=((((),),),)
+
+    if should_fail:
+        try:
+            merged=o1._p_resolveConflict(s1, s2, s3)
+        except (ConflictError, ValueError), err:
+            pass # ConflictError is the only exception that should occur
+        else:
+            assert 0, message
+    else:
+        merged=o1._p_resolveConflict(s1, s2, s3)
+        assert merged==expected, message
+        
+class BucketTests(MappingBase):
+    """ Tests common to all buckets """
+    
+
+class BTreeTests(MappingBase):
+    """ Tests common to all BTrees """
+
+## BTree tests
+
+class TestIOBTrees(BTreeTests, TestCase):
+    def setUp(self):
+        self.t = IOBTree()
+
+class TestOOBTrees(BTreeTests, TestCase):
+    def setUp(self):
+        self.t = OOBTree()
+
+class TestOIBTrees(BTreeTests, TestCase):
+    def setUp(self):
+        self.t = OIBTree()
+
+class TestIIBTrees(BTreeTests, TestCase):
+    def setUp(self):
+        self.t = IIBTree()
+
+## Set tests
+
+class TestIOSets(ExtendedSetTests, TestCase):
+    def setUp(self):
+        self.t = IOSet()
+
+class TestOOSets(ExtendedSetTests, TestCase):
+    def setUp(self):
+        self.t = OOSet()
+
+class TestIISets(ExtendedSetTests, TestCase):
+    def setUp(self):
+        self.t = IISet()
+
+class TestOISets(ExtendedSetTests, TestCase):
+    def setUp(self):
+        self.t = OISet()
+
+class TestIOTreeSets(NormalSetTests, TestCase):
+    def setUp(self):
+        self.t = IOTreeSet()
+        
+class TestOOTreeSets(NormalSetTests, TestCase):
+    def setUp(self):
+        self.t = OOTreeSet()
+
+class TestIITreeSets(NormalSetTests, TestCase):
+    def setUp(self):
+        self.t = IITreeSet()
+
+class TestOITreeSets(NormalSetTests, TestCase):
+    def setUp(self):
+        self.t = OITreeSet()
+        
+## Bucket tests
+
+class TestIOBuckets(BucketTests, TestCase):
+    def setUp(self):
+        self.t = IOBucket()
+
+class TestOOBuckets(BucketTests, TestCase):
+    def setUp(self):
+        self.t = OOBucket()
+
+class TestIIBuckets(BucketTests, TestCase):
+    def setUp(self):
+        self.t = IIBucket()
+
+class TestOIBuckets(BucketTests, TestCase):
+    def setUp(self):
+        self.t = OIBucket()
+
+# XXX disable tests for now
+def test_suite():
+    TIOBTree = makeSuite(TestIOBTrees, 'test')
+    TOOBTree = makeSuite(TestOOBTrees, 'test')
+    TOIBTree = makeSuite(TestOIBTrees, 'test')
+    TIIBTree = makeSuite(TestIIBTrees, 'test')
+
+    TIOSet = makeSuite(TestIOSets, 'test')
+    TOOSet = makeSuite(TestOOSets, 'test')
+    TOISet = makeSuite(TestIOSets, 'test')
+    TIISet = makeSuite(TestOOSets, 'test')
+
+    TIOTreeSet = makeSuite(TestIOTreeSets, 'test')
+    TOOTreeSet = makeSuite(TestOOTreeSets, 'test')
+    TOITreeSet = makeSuite(TestIOTreeSets, 'test')
+    TIITreeSet = makeSuite(TestOOTreeSets, 'test')
+
+    TIOBucket = makeSuite(TestIOBuckets, 'test')
+    TOOBucket = makeSuite(TestOOBuckets, 'test')
+    TOIBucket = makeSuite(TestOIBuckets, 'test')
+    TIIBucket = makeSuite(TestIIBuckets, 'test')
+    
+    alltests = TestSuite((TIOSet, TOOSet, TOISet, TIISet,
+                          TIOTreeSet, TOOTreeSet, TOITreeSet, TIITreeSet,
+                          TIOBucket, TOOBucket, TOIBucket, TIIBucket,
+                          TOOBTree, TIOBTree, TOIBTree, TIIBTree))
+
+    return alltests
+
+## utility functions
+
+def lsubtract(l1, l2):
+   l1=list(l1)
+   l2=list(l2)
+   l = filter(lambda x, l1=l1: x not in l1, l2)
+   l = l + filter(lambda x, l2=l2: x not in l2, l1)
+   return l
+
+def realseq(itemsob):
+    return map(lambda x: x, itemsob)
+
+def main():
+    TextTestRunner().run(test_suite())
+
+if __name__ == '__main__':
+    main()
+


=== Zope3/lib/python/Persistence/BTrees/tests/testSetOps.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+import sys, os, time, random
+from unittest import TestCase, TestSuite, TextTestRunner, makeSuite
+
+from Persistence.BTrees.IIBTree import IIBTree, IIBucket, IISet, IITreeSet, \
+    union, intersection, difference, weightedUnion, weightedIntersection, \
+    multiunion
+
+# XXX TODO Needs more tests.
+# This file was created when multiunion was added.  The other set operations
+# don't appear to be tested anywhere yet.
+
+class TestMultiUnion(TestCase):
+
+    def testEmpty(self):
+        self.assertEqual(len(multiunion([])), 0)
+
+    def testOne(self):
+        for sequence in [3], range(20), range(-10, 0, 2) + range(1, 10, 2):
+            seq1 = sequence[:]
+            seq2 = sequence[:]
+            seq2.reverse()
+            seqsorted = sequence[:]
+            seqsorted.sort()
+            for seq in seq1, seq2, seqsorted:
+                for builder in IISet, IITreeSet:
+                    input = builder(seq)
+                    output = multiunion([input])
+                    self.assertEqual(len(seq), len(output))
+                    self.assertEqual(seqsorted, list(output))
+
+    def testValuesIgnored(self):
+        for builder in IIBucket, IIBTree:
+            input = builder([(1, 2), (3, 4), (5, 6)])
+            output = multiunion([input])
+            self.assertEqual([1, 3, 5], list(output))
+
+    def testBigInput(self):
+        N = 100000
+        input = IISet(range(N))
+        output = multiunion([input] * 10)
+        self.assertEqual(len(output), N)
+        self.assertEqual(output.minKey(), 0)
+        self.assertEqual(output.maxKey(), N-1)
+        self.assertEqual(list(output), range(N))
+
+    def testLotsOfLittleOnes(self):
+        from random import shuffle
+        N = 5000
+        inputs = []
+        for i in range(N):
+            base = i * 4 - N
+            inputs.append(IISet([base, base+1]))
+            inputs.append(IITreeSet([base+2, base+3]))
+        shuffle(inputs)
+        output = multiunion(inputs)
+        self.assertEqual(len(output), N*4)
+        self.assertEqual(list(output), range(-N, 3*N))
+
+    def testFunkyKeyIteration(self):
+        # The internal set iteration protocol allows "iterating over" a
+        # a single key as if it were a set.
+        N = 100
+        slow = IISet()
+        for i in range(N):
+            slow = union(slow, IISet([i]))
+        fast = multiunion(range(N))  # acts like N distinct singleton sets
+        self.assertEqual(len(slow), N)
+        self.assertEqual(len(fast), N)
+        self.assertEqual(list(slow.keys()), list(fast.keys()))
+        self.assertEqual(list(fast.keys()), range(N))
+
+def test_suite():
+    return makeSuite(TestMultiUnion, 'test')
+
+def main():
+  TextTestRunner().run(test_suite())
+
+if __name__ == '__main__':
+    main()