• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / BTrees / 9258565736

27 May 2024 05:41PM UTC coverage: 93.83% (-0.05%) from 93.88%
9258565736

Pull #204

github

tseaver
fix: 'tox -e lint' passes
Pull Request #204: fix: 'tox -e lint' passes

1551 of 1653 branches covered (93.83%)

Branch coverage included in aggregate %.

212 of 220 new or added lines in 14 files covered. (96.36%)

2 existing lines in 2 files now uncovered.

7649 of 8152 relevant lines covered (93.83%)

7.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.9
/src/BTrees/tests/common.py
1
##############################################################################
2
#
3
# Copyright (c) 2001-2012 Zope Foundation and Contributors.
4
# All Rights Reserved.
5
#
6
# This software is subject to the provisions of the Zope Public License,
7
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11
# FOR A PARTICULAR PURPOSE
12
#
13
##############################################################################
14

15
import functools
8✔
16
import platform
8✔
17
import sys
8✔
18
import unittest
8✔
19
from unittest import skip
8✔
20

21
from BTrees._base import _tp_name
8✔
22
from BTrees._compat import PYPY
8✔
23
from BTrees._compat import _c_optimizations_ignored
8✔
24

25

26
def _no_op(test_method):
8✔
27
    return test_method
7✔
28

29

30
try:
8✔
31
    __import__('ZODB')
8✔
32
except ImportError:
33
    _skip_wo_ZODB = skip('ZODB not available')
34
else:
35
    _skip_wo_ZODB = _no_op
×
36

37
if platform.architecture()[0] == '32bit':
8!
38
    _skip_on_32_bits = skip("32-bit platform")
×
39
else:
40
    _skip_on_32_bits = _no_op
8✔
41

42
if _c_optimizations_ignored():
8✔
43
    skipOnPurePython = skip("Not on Pure Python")
8✔
44
else:
45
    skipOnPurePython = _no_op
7✔
46

47

48
def _skip_if_pure_py_and_py_test(self):
8✔
49
    if _c_optimizations_ignored() and 'Py' in type(self).__name__:
8✔
50
        # No need to run this again. The "C" tests will catch it.
51
        # This relies on the fact that we always define tests in pairs,
52
        # one normal/C and one with Py in the name for the Py test.
53
        raise unittest.SkipTest("Redundant with the C test")
54

55

56
#: The exceptions that can be raised by failed
57
#: unsigned conversions. The OverflowError is raised
58
#: by the interpreter and is nicer than the manual error.
59
UnsignedError = (TypeError, OverflowError)
8✔
60

61

62
def uses_negative_keys_and_values(func):
8✔
63
    """
64
    Apply this decorator to tests that use negative keys and values.
65

66
    If the underlying mapping doesn't support that, it will
67
    be expected to raise a TypeError or OverflowError.
68
    """
69
    @functools.wraps(func)
8✔
70
    def test(self):
7✔
71
        if not (self.SUPPORTS_NEGATIVE_KEYS and self.SUPPORTS_NEGATIVE_VALUES):
8✔
72
            with self.assertRaises(UnsignedError):
8✔
73
                func(self)
8✔
74
        else:
75
            func(self)
8✔
76
    return test
8✔
77

78

79
class SignedMixin:
8✔
80
    SUPPORTS_NEGATIVE_KEYS = True
8✔
81
    SUPPORTS_NEGATIVE_VALUES = True
8✔
82
    #: The values to pass to ``random.randrange()`` to generate
83
    #: valid keys.
84
    KEY_RANDRANGE_ARGS = (-2000, 2001)
8✔
85

86

87
class ZODBAccess:
8✔
88

89
    db = None
8✔
90

91
    def tearDown(self):
8✔
92
        if self.db is not None:
8!
93
            self.db.close()
×
94
            del self.db
×
95

96
    def _getRoot(self):
8✔
97
        from ZODB import DB
×
98
        from ZODB.MappingStorage import MappingStorage
×
99
        if self.db is None:
×
100
            # Unclear:  On the next line, the ZODB4 flavor of this routine
101
            # [asses a cache_size argument:
102
            #     self.db = DB(MappingStorage(), cache_size=1)
103
            # If that's done here, though, testLoadAndStore() and
104
            # testGhostUnghost() both nail the CPU and seemingly
105
            # never finish.
106
            self.db = DB(MappingStorage())
×
107
        return self.db.open().root()
×
108

109
    def _closeRoot(self, root):
8✔
110
        import transaction
×
111

112
        # If we don't commit/abort the transaction, then
113
        # closing the Connection tends to fail with
114
        # "Cannot close connection joined to transaction"
115
        transaction.abort()
×
116
        root._p_jar.close()
×
117

118

119
class Base(ZODBAccess, SignedMixin):
8✔
120
    # Tests common to all types: sets, buckets, and BTrees
121

122
    def _getTargetClass(self):
8✔
123
        raise NotImplementedError("subclass should return the target type")
124

125
    def _getTargetInterface(self):
8✔
126
        raise NotImplementedError(
127
            "subclass must return the expected interface "
128
        )
129

130
    def _makeOne(self):
8✔
131
        return self._getTargetClass()()
8✔
132

133
    def setUp(self):
8✔
134
        super().setUp()
8✔
135
        _skip_if_pure_py_and_py_test(self)
8✔
136

137
    def coerce_to_key(self, item):
8✔
138
        return item
×
139

140
    def coerce_to_value(self, value):
8✔
141
        return value
×
142

143
    # These are constant tuples. Indexing them produces a key/value
144
    # corresponding to the index.
145
    KEYS = tuple(range(2001))
8✔
146
    VALUES = tuple(range(2001))
8✔
147

148
    def testSubclassesCanHaveAttributes(self):
8✔
149
        # https://github.com/zopefoundation/BTrees/issues/168
150
        class Subclass(self._getTargetClass()):
8✔
151
            pass
8✔
152
        Subclass.foo = 1
8✔
153
        self.assertIn('foo', Subclass.__dict__)
8✔
154
        self.assertNotIn('foo', self._getTargetClass().__dict__)
8✔
155

156
    @skipOnPurePython
8✔
157
    def testCannotSetArbitraryAttributeOnBase(self):
7✔
158
        if 'Py' in self._getTargetClass().__name__:
7✔
159
            # pure-python classes can have arbitrary attributes
160
            self.skipTest("Not on Pure Python.")
7✔
161
        with self.assertRaises(TypeError):
7✔
162
            self._getTargetClass().foo = 1
7✔
163

164
    def testProvidesInterface(self):
8✔
165
        from zope.interface import providedBy
8✔
166
        from zope.interface.common.sequence import IMinimalSequence
8✔
167
        from zope.interface.verify import verifyObject
8✔
168
        t = self._makeOne()
8✔
169
        self._populate(t, 10)
8✔
170
        # reprs are usually the same in the Python and C implementations,
171
        # so you need the actual class to be sure of what you're dealing with
172
        __traceback_info__ = type(t)
8✔
173
        verifyObject(self._getTargetInterface(), t)
8✔
174

175
        for meth in ('keys', 'values'):
8✔
176
            if providedBy(t).get(meth):
8✔
177
                # The interface says it should be here,
178
                # make sure it is. This will be things
179
                # like Tree, Bucket, Set.
180
                seq = getattr(t, meth)()
8✔
181
                if type(seq) not in (tuple, list):
8✔
182
                    verifyObject(IMinimalSequence, seq)
8✔
183

184
    def _getColectionsABC(self):
8✔
185
        raise NotImplementedError("subclass should return the collection ABC")
186

187
    def testIsinstanceCollectionsABC(self):
8✔
188
        abc = self._getCollectionsABC()
8✔
189
        t = self._makeOne()
8✔
190

191
        self.assertIsInstance(t, abc)
8✔
192
        # Now make sure that it actually has the required methods.
193
        # First, get the required methods:
194
        abc_attrs = set(dir(abc))
8✔
195
        # If the method was None, that means it's not required;
196
        # if it's not callable, it's not a method (None is not callable)
197
        # If it's a private attribute (starting with only one _), it's
198
        # an implementation detail to ignore.
199
        abc_attrs -= {
8✔
200
            x for x in abc_attrs if
201
            (x[0] == '_' and x[1] != '_') or
202
            not callable(getattr(abc, x, None))
203
        }
204
        # Drop things from Python typing and zope.interface that may or may not
205
        # be present.
206
        abc_attrs -= {
8✔
207
            '__provides__',
208
            '__implemented__',
209
            '__providedBy__',
210
            '__class_getitem__',  # Python 3.9+
211
            # Also the equality and comparison operators;
212
            # we don't implement those methods, but the ABC does.
213
            '__lt__', '__le__', '__eq__', '__gt__', '__ge__', '__ne__',
214
        }
215
        btr_attrs = set(dir(type(t)))
8✔
216

217
        missing_attrs = abc_attrs - btr_attrs
8✔
218
        self.assertFalse(
8✔
219
            sorted(missing_attrs),
220
            "Class {!r} is missing these methods: {}".format(
221
                type(t), missing_attrs)
222
        )
223

224
    def testPersistentSubclass(self):
8✔
225
        # Can we subclass this and Persistent?
226
        # https://github.com/zopefoundation/BTrees/issues/78
227
        import persistent
8✔
228

229
        class PersistentSubclass(persistent.Persistent):
8✔
230
            pass
8✔
231

232
        __traceback_info__ = self._getTargetClass(), persistent.Persistent
8✔
233
        type('Subclass', (self._getTargetClass(), PersistentSubclass), {})
8✔
234

235
    def testPurePython(self):
8✔
236
        import importlib
8✔
237
        kind = self._getTargetClass()
8✔
238
        class_name = kind.__name__
8✔
239
        module_name = kind.__module__
8✔
240
        module = importlib.import_module(module_name)
8✔
241

242
        # If we're in pure python mode, our target class module
243
        # should not have an '_' in it (fix_pickle changes the name
244
        # to remove the 'Py')
245

246
        # If we're in the C extension mode, our target class
247
        # module still doesn't have the _ in it, but we should be able to find
248
        # a Py class that's different
249

250
        self.assertNotIn('_', module_name)
8✔
251
        self.assertIs(getattr(module, class_name), kind)
8✔
252

253
        if not _c_optimizations_ignored() and 'Py' not in type(self).__name__:
8✔
254
            self.assertIsNot(getattr(module, class_name + 'Py'), kind)
7✔
255

256
    @_skip_wo_ZODB
8✔
257
    def testLoadAndStore(self):
7✔
258
        import transaction
×
259
        for i in 0, 10, 1000:
×
260
            t = self._makeOne()
×
261
            self._populate(t, i)
×
262
            root = None
×
263
            root = self._getRoot()
×
264
            root[i] = t
×
265
            transaction.commit()
×
266

267
            root2 = self._getRoot()
×
268
            if hasattr(t, 'items'):
×
269
                self.assertEqual(list(root2[i].items()), list(t.items()))
×
270
            else:
271
                self.assertEqual(list(root2[i].keys()), list(t.keys()))
×
272

273
            self._closeRoot(root)
×
274
            self._closeRoot(root2)
×
275

276
    def testSetstateArgumentChecking(self):
8✔
277
        try:
8✔
278
            self._makeOne().__setstate__(('',))
8✔
279
        except TypeError as v:
8✔
280
            self.assertEqual(str(v), 'tuple required for first state element')
8✔
281
        else:
282
            raise AssertionError("Expected exception")
283

284
    @_skip_wo_ZODB
8✔
285
    def testGhostUnghost(self):
7✔
286
        import transaction
×
287
        for i in 0, 10, 1000:
×
288
            t = self._makeOne()
×
289
            self._populate(t, i)
×
290
            root = self._getRoot()
×
291
            root[i] = t
×
292
            transaction.commit()
×
293

294
            root2 = self._getRoot()
×
295
            root2[i]._p_deactivate()
×
296
            transaction.commit()
×
297
            if hasattr(t, 'items'):
×
298
                self.assertEqual(list(root2[i].items()), list(t.items()))
×
299
            else:
300
                self.assertEqual(list(root2[i].keys()), list(t.keys()))
×
301

302
            self._closeRoot(root)
×
303
            self._closeRoot(root2)
×
304

305
    def testSimpleExclusiveKeyRange(self):
8✔
306
        t = self._makeOne()
8✔
307
        K = self.KEYS
8✔
308
        self.assertEqual(list(t.keys()), [])
8✔
309
        self.assertEqual(list(t.keys(excludemin=True)), [])
8✔
310
        self.assertEqual(list(t.keys(excludemax=True)), [])
8✔
311
        self.assertEqual(list(t.keys(excludemin=True, excludemax=True)), [])
8✔
312

313
        self._populate(t, 1)
8✔
314
        self.assertEqual(list(t.keys()), [K[0]])
8✔
315
        self.assertEqual(list(t.keys(excludemin=True)), [])
8✔
316
        self.assertEqual(list(t.keys(excludemax=True)), [])
8✔
317
        self.assertEqual(list(t.keys(excludemin=True, excludemax=True)), [])
8✔
318

319
        t.clear()
8✔
320
        self._populate(t, 2)
8✔
321
        self.assertEqual(list(t.keys()), [K[0], K[1]])
8✔
322
        self.assertEqual(list(t.keys(excludemin=True)), [K[1]])
8✔
323
        self.assertEqual(list(t.keys(excludemax=True)), [K[0]])
8✔
324
        self.assertEqual(list(t.keys(excludemin=True, excludemax=True)), [])
8✔
325

326
        t.clear()
8✔
327
        self._populate(t, 3)
8✔
328
        self.assertEqual(list(t.keys()), [K[0], K[1], K[2]])
8✔
329
        self.assertEqual(list(t.keys(excludemin=True)), [K[1], K[2]])
8✔
330
        self.assertEqual(list(t.keys(excludemax=True)), [K[0], K[1]])
8✔
331
        self.assertEqual(
8✔
332
            list(t.keys(excludemin=True, excludemax=True)), [K[1]]
333
        )
334

335
        for low, high, expected in ((-1, 3, [0, 1, 2]), (-1, 2, [0, 1])):
8✔
336
            if self.SUPPORTS_NEGATIVE_KEYS:
8✔
337
                self.assertEqual(
8✔
338
                    list(t.keys(low, high, excludemin=True, excludemax=True)),
339
                    expected
340
                )
341
            else:
342
                with self.assertRaises(UnsignedError):
8✔
343
                    t.keys(low, high, excludemin=True, excludemax=True)
8✔
344

345
        self.assertEqual(
8✔
346
            list(t.keys(K[0], K[3], excludemin=True, excludemax=True)),
347
            [K[1], K[2]]
348
        )
349
        self.assertEqual(
8✔
350
            list(t.keys(K[0], K[2], excludemin=True, excludemax=True)),
351
            [K[1]]
352
        )
353

354
    @_skip_wo_ZODB
8✔
355
    def test_UpdatesDoReadChecksOnInternalNodes(self):
7✔
356
        import transaction
×
357
        from ZODB import DB
×
358
        from ZODB.MappingStorage import MappingStorage
×
359
        t = self._makeOne()
×
360
        K = self.KEYS
×
361
        if not hasattr(t, '_firstbucket'):
×
362
            return
×
363
        self._populate(t, 1000)
×
364
        store = MappingStorage()
×
365
        db = DB(store)
×
366
        conn = db.open()
×
367
        conn.root.t = t
×
368
        transaction.commit()
×
369

370
        read = []
×
371

372
        def readCurrent(ob):
×
373
            read.append(ob)
×
374
            conn.__class__.readCurrent(conn, ob)
×
375
            return 1
×
376

377
        conn.readCurrent = readCurrent
×
378

379
        try:
×
380
            _add = t.add
×
381
            _remove = t.remove
×
382
        except AttributeError:
×
383

384
            def add(i):
×
385
                t[self.coerce_to_key(i)] = self.coerce_to_value(i)
×
386

387
            def remove(i):
×
388
                del t[self.coerce_to_key(i)]
×
389

390
        else:
391

392
            def add(i):
×
393
                _add(self.coerce_to_key(i))
×
394

395
            def remove(i):
×
396
                _remove(self.coerce_to_key(i))
×
397

398
        # Modifying a thing
399
        remove(100)
×
400
        self.assertTrue(t in read)
×
401
        del read[:]
×
402
        add(100)
×
403
        self.assertTrue(t in read)
×
404
        del read[:]
×
405

406
        transaction.abort()
×
407
        conn.cacheMinimize()
×
408
        list(t)
×
409
        self.assertTrue(K[100] in t)
×
410
        self.assertTrue(not read)
×
411

412
    def test_impl_pickle(self):
8✔
413
        # Issue #2
414
        # Nothing we pickle should include the 'Py' suffix of
415
        # implementation classes, and unpickling should give us
416
        # back the best available type
417
        import pickle
8✔
418
        made_one = self._makeOne()
8✔
419

420
        for proto in range(1, pickle.HIGHEST_PROTOCOL + 1):
8✔
421
            dumped_str = pickle.dumps(made_one, proto)
8✔
422
            self.assertTrue(b'Py' not in dumped_str, repr(dumped_str))
8✔
423

424
            loaded_one = pickle.loads(dumped_str)
8✔
425

426
            # If we're testing the pure-Python version, but we have the
427
            # C extension available, then the loaded type will be the C
428
            # extension but the made type will be the Python version.
429
            # Otherwise, they match. (Note that if we don't have C extensions
430
            # available, the __name__ will be altered to not have Py in it.
431
            # See _fix_pickle)
432
            if 'Py' in type(made_one).__name__:
8✔
433
                self.assertTrue(type(loaded_one) is not type(made_one))
7✔
434
            else:
435
                self.assertTrue(type(loaded_one) is type(made_one))
8✔
436
                self.assertTrue(type(loaded_one) is self._getTargetClass())
8✔
437

438
            dumped_str2 = pickle.dumps(loaded_one, proto)
8✔
439
            self.assertEqual(dumped_str, dumped_str2)
8✔
440

441
    def test_pickle_empty(self):
8✔
442
        # Issue #2
443
        # Pickling an empty object and unpickling it should result
444
        # in an object that can be pickled, yielding an identical
445
        # pickle (and not an AttributeError)
446
        import pickle
8✔
447
        t = self._makeOne()
8✔
448

449
        s = pickle.dumps(t)
8✔
450
        t2 = pickle.loads(s)
8✔
451

452
        s2 = pickle.dumps(t2)
8✔
453
        self.assertEqual(s, s2)
8✔
454

455
        if hasattr(t2, '__len__'):
8!
456
            # checks for _firstbucket
457
            self.assertEqual(0, len(t2))
8✔
458

459
        # This doesn't hold for things like Bucket and Set, sadly
460
        # self.assertEqual(t, t2)
461

462
    def test_pickle_subclass(self):
8✔
463
        # Issue #2: Make sure our class swizzling doesn't break
464
        # pickling subclasses
465

466
        # We need a globally named subclass for pickle, but it needs
467
        # to be unique in case tests run in parallel
468
        base_class = type(self._makeOne())
8✔
469
        class_name = 'PickleSubclassOf' + base_class.__name__
8✔
470
        PickleSubclass = type(class_name, (base_class,), {})
8✔
471
        globals()[class_name] = PickleSubclass
8✔
472

473
        import pickle
8✔
474
        loaded = pickle.loads(pickle.dumps(PickleSubclass()))
8✔
475
        self.assertTrue(type(loaded) is PickleSubclass, type(loaded))
8✔
476
        self.assertTrue(PickleSubclass().__class__ is PickleSubclass)
8✔
477

478
    def test_isinstance_subclass(self):
8✔
479
        # Issue #2:
480
        # In some cases we define a __class__ attribute that gets
481
        # invoked for isinstance and *lies*. Check that isinstance still
482
        # works (almost) as expected.
483

484
        t = self._makeOne()
8✔
485
        # It's a little bit weird, but in the fibbing case,
486
        # we're an instance of two unrelated classes
487
        self.assertTrue(isinstance(t, type(t)), (t, type(t)))
8✔
488
        self.assertTrue(isinstance(t, t.__class__))
8✔
489

490
        class Sub(type(t)):
8✔
491
            pass
8✔
492

493
        self.assertTrue(issubclass(Sub, type(t)))
8✔
494

495
        if type(t) is not t.__class__:
8✔
496
            # We're fibbing; this breaks issubclass of itself,
497
            # contrary to the usual mechanism
498
            self.assertFalse(issubclass(t.__class__, type(t)))
7✔
499

500
        class NonSub:
8✔
501
            pass
8✔
502

503
        self.assertFalse(issubclass(NonSub, type(t)))
8✔
504
        self.assertFalse(isinstance(NonSub(), type(t)))
8✔
505

506

507
class MappingBase(Base):
8✔
508
    # Tests common to mappings (buckets, btrees)
509
    SUPPORTS_NEGATIVE_VALUES = True
8✔
510

511
    def _populate(self, t, largest):
8✔
512
        # Make some data
513
        to_key = self.coerce_to_key
8✔
514
        to_value = self.coerce_to_value
8✔
515
        for i in range(largest):
8✔
516
            t[to_key(i)] = to_value(i)
8✔
517

518
    def _getCollectionsABC(self):
8✔
519
        import collections.abc
8✔
520
        return collections.abc.MutableMapping
8✔
521

522
    def test_popitem(self):
8✔
523
        t = self._makeOne()
8✔
524
        K = self.KEYS
8✔
525
        V = self.VALUES
8✔
526
        # Empty
527
        with self.assertRaises(KeyError):
8✔
528
            t.popitem()
8✔
529

530
        self._populate(t, 2000)
8✔
531
        self.assertEqual(len(t), 2000)
8✔
532
        for i in range(2000):
8✔
533
            self.assertEqual(t.popitem(), (K[i], V[i]))
8✔
534
            self.assertEqual(len(t), 2000 - i - 1)
8✔
535

536
        # Back to empty
537
        self.assertEqual(len(t), 0)
8✔
538
        with self.assertRaises(KeyError):
8✔
539
            t.popitem()
8✔
540

541
    def testShortRepr(self):
8✔
542
        # test the repr because buckets have a complex repr implementation
543
        # internally the cutoff from a stack allocated buffer to a heap
544
        # allocated buffer is 10000.
545
        t = self._makeOne()
8✔
546
        to_key = self.coerce_to_key
8✔
547
        to_value = self.coerce_to_value
8✔
548
        for i in range(5):
8✔
549
            t[to_key(i)] = to_value(i)
8✔
550
        t._p_oid = b'12345678'
8✔
551
        r = repr(t)
8✔
552
        # Make sure the repr is **not* 10000 bytes long for a shrort bucket.
553
        # (the buffer must be terminated when copied).
554
        self.assertTrue(len(r) < 10000)
8✔
555
        # Make sure the repr is human readable if it's a bucket
556
        if 'Bucket' in r:
8✔
557
            self.assertTrue(r.startswith("BTrees"))
8✔
558
            self.assertTrue(r.endswith(repr(t.items()) + ')'), r)
8✔
559
        else:
560
            # persistent-4.4 changed the default reprs, adding
561
            # oid and jar reprs
562
            self.assertIn("<BTrees.", r)
8✔
563
            self.assertIn('BTree object at', r)
8✔
564
            self.assertIn('oid 0x3132333435363738', r)
8✔
565

566
        # Make sure it's the same between Python and C
567
        self.assertNotIn('Py', r)
8✔
568

569
    def testRepr(self):
8✔
570
        # test the repr because buckets have a complex repr implementation
571
        # internally the cutoff from a stack allocated buffer to a heap
572
        # allocated buffer is 10000.
573
        t = self._makeOne()
8✔
574
        to_key = self.coerce_to_key
8✔
575
        to_value = self.coerce_to_value
8✔
576
        for i in range(1000):
8✔
577
            t[to_key(i)] = to_value(i)
8✔
578
        r = repr(t)
8✔
579
        # Make sure the repr is 10000 bytes long for a bucket.
580
        # But since the test is also run for btrees, skip the length
581
        # check if the repr starts with '<'
582
        if not r.startswith('<'):
8✔
583
            self.assertTrue(len(r) > 10000)
8✔
584

585
    def testGetItemFails(self):
8✔
586
        self.assertRaises(KeyError, self._getitemfail)
8✔
587

588
    def _getitemfail(self):
8✔
589
        return self._makeOne()[1]
8✔
590

591
    def testGetReturnsDefault(self):
8✔
592
        self.assertEqual(self._makeOne().get(1), None)
8✔
593
        self.assertEqual(self._makeOne().get(1, 'foo'), 'foo')
8✔
594

595
    def testGetReturnsDefaultWrongTypes(self):
8✔
596
        self.assertIsNone(self._makeOne().get('abc'))
8✔
597
        self.assertEqual(self._makeOne().get('abc', 'def'), 'def')
8✔
598

599
    def testGetReturnsDefaultOverflowRanges(self):
8✔
600
        too_big = 2 ** 64 + 1
8✔
601
        self.assertIsNone(self._makeOne().get(too_big))
8✔
602
        self.assertEqual(self._makeOne().get(too_big, 'def'), 'def')
8✔
603

604
        too_small = -too_big
8✔
605
        self.assertIsNone(self._makeOne().get(too_small))
8✔
606
        self.assertEqual(self._makeOne().get(too_small, 'def'), 'def')
8✔
607

608
    def testSetItemGetItemWorks(self):
8✔
609
        t = self._makeOne()
8✔
610
        K = self.KEYS
8✔
611
        V = self.VALUES
8✔
612
        t[K[1]] = V[1]
8✔
613
        a = t[K[1]]
8✔
614
        self.assertEqual(a, V[1], repr(a))
8✔
615

616
    def testReplaceWorks(self):
8✔
617
        t = self._makeOne()
8✔
618
        K = self.KEYS
8✔
619
        V = self.VALUES
8✔
620
        t[K[1]] = V[1]
8✔
621
        self.assertEqual(t[K[1]], V[1], t[K[1]])
8✔
622
        t[K[1]] = V[2]
8✔
623
        self.assertEqual(t[K[1]], V[2], t[K[1]])
8✔
624

625
    def testLen(self):
8✔
626
        import random
8✔
627
        t = self._makeOne()
8✔
628
        added = {}
8✔
629
        r = list(range(1000))
8✔
630
        for x in r:
8✔
631
            k = random.choice(r)
8✔
632
            k = self.coerce_to_key(k)
8✔
633
            t[k] = self.coerce_to_value(x)
8✔
634
            added[k] = self.coerce_to_value(x)
8✔
635
        addl = added.keys()
8✔
636
        self.assertEqual(len(t), len(addl), len(t))
8✔
637

638
    def testHasKeyWorks(self):
8✔
639
        t = self._makeOne()
8✔
640
        K = self.KEYS
8✔
641
        V = self.VALUES
8✔
642
        t[K[1]] = V[1]
8✔
643
        self.assertTrue(t.has_key(K[1]))
8✔
644

645
        self.assertIn(K[1], t)
8✔
646
        self.assertNotIn(K[0], t)
8✔
647
        self.assertNotIn(K[2], t)
8✔
648

649
    def testHasKeyOverflowAndTypes(self):
8✔
650
        t = self._makeOne()
8✔
651

652
        too_big = 2 ** 64 + 1
8✔
653
        too_small = -too_big
8✔
654
        self.assertNotIn(too_big, t)
8✔
655
        self.assertNotIn(too_small, t)
8✔
656
        self.assertFalse(t.has_key(too_big))
8✔
657
        self.assertFalse(t.has_key(too_small))
8✔
658
        self.assertFalse(t.has_key('abc'))
8✔
659

660
    def testValuesWorks(self):
8✔
661
        t = self._makeOne()
8✔
662
        K = self.KEYS
8✔
663
        for x in range(100):
8✔
664
            t[K[x]] = self.coerce_to_value(x * x)
8✔
665
        values = t.values()
8✔
666
        for i in range(100):
8✔
667
            v = self.coerce_to_value(i * i)
8✔
668
            self.assertEqual(values[i], v)
8✔
669
        self.assertRaises(IndexError, lambda: values[i + 1])
8✔
670
        i = 0
8✔
671
        for value in t.itervalues():
8✔
672
            self.assertEqual(value, self.coerce_to_value(i * i))
8✔
673
            i += 1
8✔
674

675
    def testValuesWorks1(self):
8✔
676
        t = self._makeOne()
8✔
677
        K = self.KEYS
8✔
678
        V = self.VALUES
8✔
679
        for x in range(100):
8✔
680
            k = self.coerce_to_key(99 - x)
8✔
681
            t[k] = V[x]
8✔
682

683
        for x in range(40):
8✔
684
            lst = sorted(t.values(K[0 + x], K[99 - x]))
8✔
685
            self.assertEqual(lst, [V[i] for i in range(0 + x, 99 - x + 1)])
8✔
686

687
            lst = sorted(t.values(max=K[99 - x], min=K[0 + x]))
8✔
688
            self.assertEqual(lst, [V[i] for i in range(0 + x, 99 - x + 1)])
8✔
689

690
    @uses_negative_keys_and_values
8✔
691
    def testValuesNegativeIndex(self):
7✔
692
        t = self._makeOne()
8✔
693
        L = [-3, 6, -11, 4]
8✔
694
        for i in L:
8✔
695
            t[i] = self.coerce_to_value(i)
8✔
696
        L = sorted(L)
8✔
697
        vals = t.values()
8✔
698
        for i in range(-1, -5, -1):
8✔
699
            self.assertEqual(vals[i], L[i])
8✔
700
        self.assertRaises(IndexError, lambda: vals[-5])
8✔
701

702
    def testKeysWorks(self):
8✔
703
        t = self._makeOne()
8✔
704
        K = self.KEYS
8✔
705
        V = self.VALUES
8✔
706
        for x in range(100):
8✔
707
            t[K[x]] = V[x]
8✔
708
        v = t.keys()
8✔
709
        i = 0
8✔
710
        for x in v:
8✔
711
            self.assertEqual(x, K[i])
8✔
712
            i = i + 1
8✔
713
        self.assertRaises(IndexError, lambda: v[i])
8✔
714

715
        for x in range(40):
8✔
716
            lst = t.keys(K[0 + x], K[99 - x])
8✔
717
            self.assertEqual(
8✔
718
                list(lst),
719
                [K[x] for x in range(0 + x, 99 - x + 1)]
720
            )
721
            lst = t.keys(max=K[99-x], min=K[0+x])
8✔
722
            self.assertEqual(list(lst), [K[x] for x in range(0+x, 99-x+1)])
8✔
723

724
        self.assertEqual(len(v), 100)
8✔
725

726
    @uses_negative_keys_and_values
8✔
727
    def testKeysNegativeIndex(self):
7✔
728
        t = self._makeOne()
8✔
729
        L = [-3, 6, -11, 4]
8✔
730
        for i in L:
8✔
731
            t[i] = self.coerce_to_value(i)
8✔
732
        L = sorted(L)
8✔
733
        keys = t.keys()
8✔
734
        for i in range(-1, -5, -1):
8✔
735
            self.assertEqual(keys[i], L[i])
8✔
736
        self.assertRaises(IndexError, lambda: keys[-5])
8✔
737

738
    def testItemsWorks(self):
8✔
739
        t = self._makeOne()
8✔
740
        K = self.KEYS
8✔
741
        V = self.VALUES
8✔
742
        for x in range(100):
8✔
743
            t[K[x]] = V[2*x]
8✔
744
        v = t.items()
8✔
745
        i = 0
8✔
746
        for x in v:
8✔
747
            self.assertEqual(x[0], K[i])
8✔
748
            self.assertEqual(x[1], V[2*i])
8✔
749
            i += 1
8✔
750
        self.assertRaises(IndexError, lambda: v[i+1])
8✔
751

752
        i = 0
8✔
753
        for x in t.iteritems():
8✔
754
            self.assertEqual(x, (K[i], V[2*i]))
8✔
755
            i += 1
8✔
756

757
        items = list(t.items(min=K[12], max=K[20]))
8✔
758
        self.assertEqual(items, list(zip(
8✔
759
            (K[i] for i in range(12, 21)),
760
            (V[i] for i in range(24, 43, 2))
761
        )))
762

763
        items = list(t.iteritems(min=K[12], max=K[20]))
8✔
764
        self.assertEqual(items, list(zip(
8✔
765
            (K[i] for i in range(12, 21)),
766
            (V[i] for i in range(24, 43, 2))
767
        )))
768

769
    def testItemsNegativeIndex(self):
8✔
770
        if not (self.SUPPORTS_NEGATIVE_KEYS and self.SUPPORTS_NEGATIVE_VALUES):
8✔
771
            self.skipTest("Needs negative keys and values")
8✔
772
        t = self._makeOne()
8✔
773
        L = [-3, 6, -11, 4]
8✔
774
        for i in L:
8✔
775
            t[i] = self.coerce_to_value(i)
8✔
776
        L = sorted(L)
8✔
777
        items = t.items()
8✔
778
        for i in range(-1, -5, -1):
8✔
779
            self.assertEqual(items[i], (L[i], L[i]))
8✔
780
        self.assertRaises(IndexError, lambda: items[-5])
8✔
781

782
    def testDeleteInvalidKeyRaisesKeyError(self):
8✔
783
        self.assertRaises(KeyError, self._deletefail)
8✔
784

785
    def _deletefail(self):
8✔
786
        t = self._makeOne()
8✔
787
        del t[self.KEYS[1]]
8✔
788

789
    def testMaxKeyMinKey(self):
8✔
790
        t = self._makeOne()
8✔
791
        K = self.KEYS
8✔
792
        V = self.VALUES
8✔
793
        t[K[7]] = V[6]
8✔
794
        t[K[3]] = V[10]
8✔
795
        t[K[8]] = V[12]
8✔
796
        t[K[1]] = V[100]
8✔
797
        t[K[5]] = V[200]
8✔
798
        t[K[10]] = V[500]
8✔
799
        t[K[6]] = V[99]
8✔
800
        t[K[4]] = V[150]
8✔
801
        del t[K[7]]
8✔
802
        K = self.KEYS
8✔
803
        self.assertEqual(t.maxKey(), K[10])
8✔
804
        self.assertEqual(t.maxKey(None), K[10])
8✔
805
        self.assertEqual(t.maxKey(K[6]), K[6])
8✔
806
        self.assertEqual(t.maxKey(K[9]), K[8])
8✔
807
        self.assertEqual(t.minKey(), K[1])
8✔
808
        self.assertEqual(t.minKey(None), K[1])
8✔
809
        self.assertEqual(t.minKey(K[3]), K[3])
8✔
810
        self.assertEqual(t.minKey(K[9]), K[10])
8✔
811

812
        try:
8✔
813
            t.minKey() - 1
8✔
814
        except TypeError:
8✔
815
            # we can't do arithmetic with the key type;
816
            # must be fsBTree.
817
            return
8✔
818

819
        try:
8✔
820
            t.maxKey(t.minKey() - 1)
8✔
821
        except ValueError as err:
8✔
822
            self.assertEqual(str(err), "no key satisfies the conditions")
8✔
823
        else:
824
            self.fail("expected ValueError")
825

826
        try:
8✔
827
            t.minKey(t.maxKey() + 1)
8✔
828
        except ValueError as err:
8✔
829
            self.assertEqual(str(err), "no key satisfies the conditions")
8✔
830
        else:
831
            self.fail("expected ValueError")
832

833
    def testClear(self):
8✔
834
        import random
8✔
835
        t = self._makeOne()
8✔
836
        r = list(range(100))
8✔
837
        for x in r:
8✔
838
            rnd = random.choice(r)
8✔
839
            t[self.coerce_to_key(rnd)] = self.VALUES[0]
8✔
840
        t.clear()
8✔
841
        diff = lsubtract(list(t.keys()), [])
8✔
842
        self.assertEqual(diff, [])
8✔
843

844
    def testUpdate(self):
8✔
845
        import random
8✔
846
        t = self._makeOne()
8✔
847
        d = {}
8✔
848
        items = []
8✔
849
        for i in range(10000):
8✔
850
            k = random.randrange(*self.KEY_RANDRANGE_ARGS)
8✔
851
            k = self.coerce_to_key(k)
8✔
852
            v = self.coerce_to_value(i)
8✔
853
            d[k] = v
8✔
854
            items.append((k, v))
8✔
855

856
        items = sorted(d.items())
8✔
857

858
        t.update(d)
8✔
859
        self.assertEqual(list(t.items()), items)
8✔
860

861
        t.clear()
8✔
862
        self.assertEqual(list(t.items()), [])
8✔
863

864
        t.update(items)
8✔
865
        self.assertEqual(list(t.items()), items)
8✔
866

867
    # Before ZODB 3.4.2, update/construction from PersistentMapping failed.
868
    def testUpdateFromPersistentMapping(self):
8✔
869
        from persistent.mapping import PersistentMapping
8✔
870
        t = self._makeOne()
8✔
871
        K = self.KEYS
8✔
872
        V = self.VALUES
8✔
873
        pm = PersistentMapping({K[1]: V[2]})
8✔
874
        t.update(pm)
8✔
875
        self.assertEqual(list(t.items()), [(K[1], V[2])])
8✔
876

877
        # Construction goes thru the same internals as .update().
878
        t = t.__class__(pm)
8✔
879
        self.assertEqual(list(t.items()), [(K[1], V[2])])
8✔
880

881
    def testEmptyRangeSearches(self):
8✔
882
        t = self._makeOne()
8✔
883
        K = self.KEYS
8✔
884
        V = self.VALUES
8✔
885
        t.update([(K[1], V[1]), (K[5], V[5]), (K[9], V[9])])
8✔
886
        if self.SUPPORTS_NEGATIVE_KEYS and self.SUPPORTS_NEGATIVE_VALUES:
8✔
887
            self.assertEqual(
8✔
888
                list(t.keys(self.coerce_to_key(-6), self.coerce_to_key(-4))),
889
                []
890
            )
891

892
        self.assertEqual(list(t.keys(K[2], K[4])), [])
8✔
893
        self.assertEqual(list(t.keys(K[6], K[8])), [])
8✔
894
        self.assertEqual(list(t.keys(K[10], K[12])), [])
8✔
895
        self.assertEqual(list(t.keys(K[9], K[1])), [])
8✔
896

897
        # For IITreeSets, this one was returning 31 for len(keys), and
898
        # list(keys) produced a list with 100 elements.
899
        t.clear()
8✔
900
        t.update(list(zip(
8✔
901
            (self.coerce_to_key(x) for x in range(300)),
902
            (self.coerce_to_value(x) for x in range(300)))))
903
        two_hundred = K[200]
8✔
904
        fifty = K[50]
8✔
905
        keys = t.keys(two_hundred, fifty)
8✔
906
        self.assertEqual(len(keys), 0)
8✔
907
        self.assertEqual(list(keys), [])
8✔
908
        self.assertEqual(list(t.iterkeys(two_hundred, fifty)), [])
8✔
909

910
        keys = t.keys(max=fifty, min=two_hundred)
8✔
911
        self.assertEqual(len(keys), 0)
8✔
912
        self.assertEqual(list(keys), [])
8✔
913
        self.assertEqual(list(t.iterkeys(max=fifty, min=two_hundred)), [])
8✔
914

915
    def testSlicing(self):
8✔
916
        # Test that slicing of .keys()/.values()/.items() works exactly the
917
        # same way as slicing a Python list with the same contents.
918
        # This tests fixes to several bugs in this area, starting with
919
        # http://collector.zope.org/Zope/419,
920
        # "BTreeItems slice contains 1 too many elements".
921
        t = self._makeOne()
8✔
922
        val_multiplier = -2 if self.SUPPORTS_NEGATIVE_VALUES else 2
8✔
923
        K = self.KEYS
8✔
924
        V = self.VALUES
8✔
925
        for n in range(10):
8✔
926
            t.clear()
8✔
927
            self.assertEqual(len(t), 0)
8✔
928

929
            keys = []
8✔
930
            values = []
8✔
931
            items = []
8✔
932

933
            for key in range(n):
8✔
934
                value = key * val_multiplier
8✔
935
                key = K[key]
8✔
936
                value = self.coerce_to_value(value)
8✔
937
                t[key] = value
8✔
938
                keys.append(key)
8✔
939
                values.append(value)
8✔
940
                items.append((key, value))
8✔
941
            self.assertEqual(len(t), n)
8✔
942

943
            kslice = t.keys()
8✔
944
            vslice = t.values()
8✔
945
            islice = t.items()
8✔
946
            self.assertEqual(len(kslice), n)
8✔
947
            self.assertEqual(len(vslice), n)
8✔
948
            self.assertEqual(len(islice), n)
8✔
949

950
            # Test whole-structure slices.
951
            x = kslice[:]
8✔
952
            self.assertEqual(list(x), keys[:])
8✔
953

954
            x = vslice[:]
8✔
955
            self.assertEqual(list(x), values[:])
8✔
956

957
            x = islice[:]
8✔
958
            self.assertEqual(list(x), items[:])
8✔
959

960
            for lo in range(-2*n, 2*n+1):
8✔
961
                # Test one-sided slices.
962
                x = kslice[:lo]
8✔
963
                self.assertEqual(list(x), keys[:lo])
8✔
964
                x = kslice[lo:]
8✔
965
                self.assertEqual(list(x), keys[lo:])
8✔
966

967
                x = vslice[:lo]
8✔
968
                self.assertEqual(list(x), values[:lo])
8✔
969
                x = vslice[lo:]
8✔
970
                self.assertEqual(list(x), values[lo:])
8✔
971

972
                x = islice[:lo]
8✔
973
                self.assertEqual(list(x), items[:lo])
8✔
974
                x = islice[lo:]
8✔
975
                self.assertEqual(list(x), items[lo:])
8✔
976

977
                for hi in range(-2*n, 2*n+1):
8✔
978
                    # Test two-sided slices.
979
                    x = kslice[lo:hi]
8✔
980
                    self.assertEqual(list(x), keys[lo:hi])
8✔
981

982
                    x = vslice[lo:hi]
8✔
983
                    self.assertEqual(list(x), values[lo:hi])
8✔
984

985
                    x = islice[lo:hi]
8✔
986
                    self.assertEqual(list(x), items[lo:hi])
8✔
987

988
        # The specific test case from Zope collector 419.
989
        t.clear()
8✔
990
        for i in range(100):
8✔
991
            t[K[i]] = self.VALUES[1]
8✔
992
        tslice = t.items()[20:80]
8✔
993
        self.assertEqual(len(tslice), 60)
8✔
994
        self.assertEqual(list(tslice), list(zip(
8✔
995
            [K[x] for x in range(20, 80)],
996
            [V[1]] * 60
997
        )))
998

999
    @uses_negative_keys_and_values
8✔
1000
    def testIterators(self):
7✔
1001
        t = self._makeOne()
8✔
1002

1003
        for keys in [], [-2], [1, 4], list(range(-170, 2000, 6)):
8✔
1004
            t.clear()
8✔
1005
            for k in keys:
8✔
1006
                val = -3 * k
8✔
1007
                t[k] = self.coerce_to_value(val)
8✔
1008

1009
            self.assertEqual(list(t), keys)
8✔
1010

1011
            x = []
8✔
1012
            for k in t:
8✔
1013
                x.append(k)
8✔
1014
            self.assertEqual(x, keys)
8✔
1015

1016
            it = iter(t)
8✔
1017
            self.assertTrue(it is iter(it))
8✔
1018
            x = []
8✔
1019
            try:
8✔
1020
                while 1:
6✔
1021
                    x.append(next(it))
8✔
1022
            except StopIteration:
8✔
1023
                pass
8✔
1024
            self.assertEqual(x, keys)
8✔
1025

1026
            self.assertEqual(list(t.iterkeys()), keys)
8✔
1027
            self.assertEqual(list(t.itervalues()), list(t.values()))
8✔
1028
            self.assertEqual(list(t.iteritems()), list(t.items()))
8✔
1029

1030
    @uses_negative_keys_and_values
8✔
1031
    def testRangedIterators(self):
7✔
1032
        t = self._makeOne()
8✔
1033

1034
        for keys in [], [-2], [1, 4], list(range(-170, 2000, 13)):
8✔
1035
            t.clear()
8✔
1036
            values = []
8✔
1037
            for k in keys:
8✔
1038
                value = -3 * k
8✔
1039
                t[k] = self.coerce_to_value(value)
8✔
1040
                values.append(value)
8✔
1041
            items = list(zip(keys, values))
8✔
1042

1043
            self.assertEqual(list(t.iterkeys()), keys)
8✔
1044
            self.assertEqual(list(t.itervalues()), values)
8✔
1045
            self.assertEqual(list(t.iteritems()), items)
8✔
1046

1047
            if not keys:
8✔
1048
                continue
8✔
1049

1050
            min_mid_max = (keys[0], keys[len(keys) >> 1], keys[-1])
8✔
1051
            for key1 in min_mid_max:
8✔
1052
                for lo in range(key1 - 1, key1 + 2):
8✔
1053
                    # Test one-sided range iterators.
1054
                    goodkeys = [k for k in keys if lo <= k]
8✔
1055
                    got = t.iterkeys(lo)
8✔
1056
                    self.assertEqual(goodkeys, list(got))
8✔
1057

1058
                    goodvalues = [t[k] for k in goodkeys]
8✔
1059
                    got = t.itervalues(lo)
8✔
1060
                    self.assertEqual(goodvalues, list(got))
8✔
1061

1062
                    gooditems = list(zip(goodkeys, goodvalues))
8✔
1063
                    got = t.iteritems(lo)
8✔
1064
                    self.assertEqual(gooditems, list(got))
8✔
1065

1066
                    for key2 in min_mid_max:
8✔
1067
                        for hi in range(key2 - 1, key2 + 2):
8✔
1068
                            goodkeys = [k for k in keys if lo <= k <= hi]
8✔
1069
                            got = t.iterkeys(min=lo, max=hi)
8✔
1070
                            self.assertEqual(goodkeys, list(got))
8✔
1071

1072
                            goodvalues = [t[k] for k in goodkeys]
8✔
1073
                            got = t.itervalues(lo, max=hi)
8✔
1074
                            self.assertEqual(goodvalues, list(got))
8✔
1075

1076
                            gooditems = list(zip(goodkeys, goodvalues))
8✔
1077
                            got = t.iteritems(max=hi, min=lo)
8✔
1078
                            self.assertEqual(gooditems, list(got))
8✔
1079

1080
    def testBadUpdateTupleSize(self):
8✔
1081
        t = self._makeOne()
8✔
1082
        # This one silently ignored the excess in Zope3.
1083
        key = self.KEYS[1]
8✔
1084
        value = self.VALUES[2]
8✔
1085
        self.assertRaises(TypeError, t.update, [(key, value, value)])
8✔
1086

1087
        # This one dumped core in Zope3.
1088
        self.assertRaises(TypeError, t.update, [(key,)])
8✔
1089

1090
        # This one should simply succeed.
1091
        t.update([(key, value)])
8✔
1092
        self.assertEqual(list(t.items()), [(key, value)])
8✔
1093

1094
    def testSimpleExclusivRanges(self):
8✔
1095
        K = self.KEYS
8✔
1096
        V = self.VALUES
8✔
1097

1098
        def list_keys(x):
8✔
1099
            return [K[k] for k in x]
8✔
1100

1101
        def list_values(x):
8✔
1102
            return [V[k] for k in x]
8✔
1103

1104
        def as_items(x):
8✔
1105
            return [(K[k], V[k]) for k in x]
8✔
1106

1107
        for methodname, f in (("keys", list_keys),
8✔
1108
                              ("values", list_values),
1109
                              ("items", as_items),
1110
                              ("iterkeys", list_keys),
1111
                              ("itervalues", list_values),
1112
                              ("iteritems", as_items)):
1113

1114
            t = self._makeOne()
8✔
1115
            meth = getattr(t, methodname, None)
8✔
1116
            if meth is None:
8!
1117
                continue
×
1118

1119
            __traceback_info__ = meth
8✔
1120

1121
            supports_negative = self.SUPPORTS_NEGATIVE_KEYS
8✔
1122

1123
            self.assertEqual(list(meth()), [])
8✔
1124
            self.assertEqual(list(meth(excludemin=True)), [])
8✔
1125
            self.assertEqual(list(meth(excludemax=True)), [])
8✔
1126
            self.assertEqual(list(meth(excludemin=True, excludemax=True)), [])
8✔
1127

1128
            self._populate(t, 1)
8✔
1129
            self.assertEqual(list(meth()), f([0]))
8✔
1130
            self.assertEqual(list(meth(excludemin=True)), [])
8✔
1131
            self.assertEqual(list(meth(excludemax=True)), [])
8✔
1132
            self.assertEqual(list(meth(excludemin=True, excludemax=True)), [])
8✔
1133

1134
            t.clear()
8✔
1135
            self._populate(t, 2)
8✔
1136
            self.assertEqual(list(meth()), f([0, 1]))
8✔
1137
            self.assertEqual(list(meth(excludemin=True)), f([1]))
8✔
1138
            self.assertEqual(list(meth(excludemax=True)), f([0]))
8✔
1139
            self.assertEqual(list(meth(excludemin=True, excludemax=True)), [])
8✔
1140

1141
            t.clear()
8✔
1142
            self._populate(t, 3)
8✔
1143
            self.assertEqual(list(meth()), f([0, 1, 2]))
8✔
1144
            self.assertEqual(list(meth(excludemin=True)), f([1, 2]))
8✔
1145
            self.assertEqual(list(meth(excludemax=True)), f([0, 1]))
8✔
1146
            self.assertEqual(list(meth(excludemin=True, excludemax=True)),
8✔
1147
                             f([1]))
1148
            if supports_negative:
8✔
1149
                self.assertEqual(
8✔
1150
                    list(meth(
1151
                        self.coerce_to_key(-1),
1152
                        K[2],
1153
                        excludemin=True,
1154
                        excludemax=True,
1155
                    )), f([0, 1])
1156
                )
1157

1158
                self.assertEqual(
8✔
1159
                    list(meth(
1160
                        self.coerce_to_key(-1),
1161
                        K[3], excludemin=True,
1162
                        excludemax=True
1163
                    )), f([0, 1, 2])
1164
                )
1165

1166
            self.assertEqual(
8✔
1167
                list(meth(K[0], K[3], excludemin=True, excludemax=True)),
1168
                f([1, 2])
1169
            )
1170
            self.assertEqual(
8✔
1171
                list(meth(K[0], K[2], excludemin=True, excludemax=True)),
1172
                f([1])
1173
            )
1174

1175
    def testSetdefault(self):
8✔
1176
        t = self._makeOne()
8✔
1177
        K = self.KEYS
8✔
1178
        V = self.VALUES
8✔
1179
        self.assertEqual(t.setdefault(K[1], V[2]), V[2])
8✔
1180
        # That should also have associated 1 with 2 in the tree.
1181
        self.assertTrue(K[1] in t)
8✔
1182
        self.assertEqual(t[K[1]], V[2])
8✔
1183
        # And trying to change it again should have no effect.
1184
        self.assertEqual(t.setdefault(K[1], self.coerce_to_value(666)), V[2])
8✔
1185
        self.assertEqual(t[K[1]], V[2])
8✔
1186

1187
        # Not enough arguments.
1188
        self.assertRaises(TypeError, t.setdefault)
8✔
1189
        self.assertRaises(TypeError, t.setdefault, K[1])
8✔
1190
        # Too many arguments.
1191
        self.assertRaises(TypeError, t.setdefault, K[1], V[2], V[3])
8✔
1192

1193
    def testPop(self):
8✔
1194
        t = self._makeOne()
8✔
1195
        K = self.KEYS
8✔
1196
        V = self.VALUES
8✔
1197
        # Empty container.
1198
        # If no default given, raises KeyError.
1199
        self.assertRaises(KeyError, t.pop, K[1])
8✔
1200
        # But if default given, returns that instead.
1201
        self.assertEqual(t.pop(K[1], 42), 42)
8✔
1202

1203
        t[K[1]] = V[3]
8✔
1204
        # KeyError when key is not in container and default is not passed.
1205
        self.assertRaises(KeyError, t.pop, K[5])
8✔
1206
        self.assertEqual(list(t.items()), [(K[1], V[3])])
8✔
1207
        # If key is in container, returns the value and deletes the key.
1208
        self.assertEqual(t.pop(K[1]), V[3])
8✔
1209
        self.assertEqual(len(t), 0)
8✔
1210

1211
        # If key is present, return value bypassing default.
1212
        t[K[1]] = V[3]
8✔
1213
        self.assertEqual(t.pop(K[1], 7), V[3])
8✔
1214
        self.assertEqual(len(t), 0)
8✔
1215

1216
        # Pop only one item.
1217
        t[K[1]] = V[3]
8✔
1218
        t[K[2]] = V[4]
8✔
1219
        self.assertEqual(len(t), 2)
8✔
1220
        self.assertEqual(t.pop(K[1]), V[3])
8✔
1221
        self.assertEqual(len(t), 1)
8✔
1222
        self.assertEqual(t[K[2]], V[4])
8✔
1223
        self.assertEqual(t.pop(K[1], 3), 3)
8✔
1224

1225
        # Too few arguments.
1226
        self.assertRaises(TypeError, t.pop)
8✔
1227
        # Too many arguments.
1228
        self.assertRaises(TypeError, t.pop, K[1], 2, 3)
8✔
1229

1230
    def __test_key_or_value_type(self, k, v, to_test, kvtype):
8✔
1231
        try:
8✔
1232
            kvtype(to_test)
8✔
1233
        except Exception as e:
8✔
1234
            with self.assertRaises(type(e)):
8✔
1235
                self._makeOne()[k] = self.coerce_to_value(v)
8✔
1236
        else:
1237
            self._makeOne()[k] = self.coerce_to_value(v)
8✔
1238

1239
    def __test_key(self, k):
8✔
1240
        v = self.getTwoValues()[0]
8✔
1241
        self.__test_key_or_value_type(k, v, k, self.key_type)
8✔
1242

1243
    def __test_value(self, v):
8✔
1244
        k = self.getTwoKeys()[0]
8✔
1245
        self.__test_key_or_value_type(k, v, v, self.value_type)
8✔
1246

1247
    def test_assign_key_type_str(self):
8✔
1248
        self.__test_key('c')
8✔
1249

1250
    # Assigning a str may or may not work; but querying for
1251
    # one will always return a correct answer, not raise
1252
    # a TypeError.
1253
    def testStringAllowedInContains(self):
8✔
1254
        self.assertFalse('key' in self._makeOne())
8✔
1255

1256
    def testStringKeyRaisesKeyErrorWhenMissing(self):
8✔
1257
        self.assertRaises(KeyError, self._makeOne().__getitem__, 'key')
8✔
1258

1259
    def testStringKeyReturnsDefaultFromGetWhenMissing(self):
8✔
1260
        self.assertEqual(self._makeOne().get('key', 42), 42)
8✔
1261

1262
    def test_assign_key_type_float(self):
8✔
1263
        self.__test_key(2.5)
8✔
1264

1265
    def test_assign_key_type_None(self):
8✔
1266
        self.__test_key(None)
8✔
1267

1268
    def test_assign_value_type_str(self):
8✔
1269
        self.__test_value('c')
8✔
1270

1271
    def test_assign_value_type_float(self):
8✔
1272
        self.__test_value(2.5)
8✔
1273

1274
    def test_assign_value_type_None(self):
8✔
1275
        self.__test_value(None)
8✔
1276

1277
    def testNewStyleClassAsKeyNotAllowed(self):
8✔
1278
        m = self._makeOne()
8✔
1279

1280
        class New:
8✔
1281
            pass
8✔
1282

1283
        with self.assertRaises(TypeError):
8✔
1284
            m[New] = self.getTwoValues()[0]
8✔
1285

1286
    def testClassAsKeyNotAllowed(self):
8✔
1287
        m = self._makeOne()
8✔
1288

1289
        class Cls:
8✔
1290
            pass
8✔
1291

1292
        with self.assertRaises(TypeError):
8✔
1293
            m[Cls] = self.getTwoValues()[0]
8✔
1294

1295
    def testNewStyleClassWithCustomMetaClassNotAllowed(self):
8✔
1296

1297
        class Meta(type):
8✔
1298
            pass
8✔
1299

1300
        cls = Meta('Class', (object,), {})
8✔
1301
        m = self._makeOne()
8✔
1302
        with self.assertRaises(TypeError):
8✔
1303
            m[cls] = self.getTwoValues()[0]
8✔
1304

1305
    def testEmptyFirstBucketReportedByGuido(self):
8✔
1306
        # This was for Integer keys
1307
        b = self._makeOne()
8✔
1308
        for i in range(29972):  # reduce to 29971 and it works
8✔
1309
            b[self.coerce_to_key(i)] = self.coerce_to_value(i)
8✔
1310
        for i in range(30):  # reduce to 29 and it works
8✔
1311
            del b[self.coerce_to_key(i)]
8✔
1312
            try:
8✔
1313
                big_key = self.coerce_to_key(i + 40000)
8✔
1314
            except TypeError:
8✔
1315
                # fsBtree only has a two-byte key
1316
                self.skipTest('Key to big')
8✔
1317
            b[big_key] = self.coerce_to_value(i)
8✔
1318

1319
        self.assertEqual(b.keys()[0], self.KEYS[30])
8✔
1320

1321
    def testKeyAndValueOverflow(self):
8✔
1322
        if (
8✔
1323
            self.key_type.get_upper_bound() is None or
1324
            self.value_type.get_upper_bound() is None
1325
        ):
1326
            self.skipTest("Needs bounded key and value")
8✔
1327

1328
        import struct
8✔
1329

1330
        good = set()
8✔
1331
        b = self._makeOne()
8✔
1332

1333
        # Some platforms (Windows) use a 32-bit value for long,
1334
        # meaning that PyInt_AsLong and such can throw OverflowError
1335
        # for values that are in range on most other platforms. And on Python
1336
        # 2, PyInt_Check can fail with a TypeError starting at small values
1337
        # like 2147483648. So we look for small longs and catch those errors
1338
        # even when we think we should be in range. In all cases, our code
1339
        # catches the unexpected error (OverflowError) and turns it into
1340
        # TypeError.
1341
        long_is_32_bit = struct.calcsize('@l') < 8
8✔
1342
        in_range_errors = TypeError
8✔
1343
        out_of_range_errors = TypeError
8✔
1344

1345
        V = self.VALUES
8✔
1346

1347
        def trial(i):
8✔
1348
            i = int(i)
8✔
1349
            __traceback_info__ = i, type(i)
8✔
1350
            # As key
1351
            if i > self.key_type.get_upper_bound():
8✔
1352
                with self.assertRaises(out_of_range_errors):
8✔
1353
                    b[i] = V[0]
8✔
1354
            elif i < self.key_type.get_lower_bound():
8✔
1355
                with self.assertRaises(out_of_range_errors):
8✔
1356
                    b[i] = V[0]
8✔
1357
            else:
1358
                try:
8✔
1359
                    b[i] = V[0]
8✔
1360
                except in_range_errors:
×
1361
                    pass
×
1362
                else:
1363
                    good.add(i)
8✔
1364
                    self.assertEqual(b[i], 0)
8✔
1365

1366
            # As value
1367
            if i > self.value_type.get_upper_bound():
8✔
1368
                with self.assertRaises(out_of_range_errors):
8✔
1369
                    b[V[0]] = self.coerce_to_value(i)
8✔
1370
            elif i < self.value_type.get_lower_bound():
8✔
1371
                with self.assertRaises(out_of_range_errors):
8✔
1372
                    b[V[0]] = self.coerce_to_value(i)
8✔
1373
            else:
1374
                try:
8✔
1375
                    b[V[0]] = self.coerce_to_value(i)
8✔
1376
                except in_range_errors:
×
1377
                    pass
×
1378
                else:
1379
                    self.assertEqual(b[V[0]], i)
8✔
1380

1381
        for i in range(self.key_type.get_upper_bound() - 3,
8✔
1382
                       self.key_type.get_upper_bound() + 3):
1383

1384
            trial(i)
8✔
1385
            trial(-i)
8✔
1386

1387
        if 0 in b:
8✔
1388
            del b[0]
8✔
1389
        self.assertEqual(sorted(good), sorted(b))
8✔
1390
        if not long_is_32_bit:
8!
1391
            if self.key_type.get_lower_bound() == 0:
8✔
1392
                # None of the negative values got in
1393
                self.assertEqual(4, len(b))
8✔
1394
            else:
1395
                # 9, not 4 * 2, because of the asymmetry
1396
                # of twos complement binary integers
1397
                self.assertEqual(9, len(b))
8✔
1398

1399
    @_skip_wo_ZODB
8✔
1400
    def testAccessRaisesPOSKeyErrorOnSelf(self):
7✔
1401
        # We don't hide a POSKeyError that happens when
1402
        # accessing the object itself in `get()`.
1403
        # See https://github.com/zopefoundation/BTrees/issues/161
1404
        import transaction
×
1405
        from ZODB.POSException import POSKeyError
×
1406
        transaction.begin()
×
1407
        m = self._makeOne()
×
1408
        root = self._getRoot()
×
1409
        root.m = m
×
1410
        transaction.commit()
×
1411
        conn = root._p_jar
×
1412
        # Ghost the object
1413
        conn.cacheMinimize()
×
1414
        self.assertEqual(m._p_status, "ghost")
×
1415
        # Remove the backing data
1416
        self.db._storage._data.clear()
×
1417

1418
        transaction.begin()
×
1419
        K = self.KEYS
×
1420

1421
        try:
×
1422
            with self.assertRaises(POSKeyError):
×
1423
                m.get(K[1])
×
1424

1425
            with self.assertRaises(POSKeyError):
×
1426
                m.setdefault(K[1], self.VALUES[1])
×
1427

1428
            with self.assertRaises(POSKeyError):
×
1429
                _ = K[1] in m
×
1430

1431
            with self.assertRaises(POSKeyError):
×
1432
                m.pop(K[1])
×
1433
        finally:
1434
            self._closeRoot(root)
×
1435

1436

1437
class BTreeTests(MappingBase):
8✔
1438
    # Tests common to all BTrees
1439

1440
    def _getTargetClass(self):
8✔
1441
        # Most of the subclasses override _makeOne and not
1442
        # _getTargetClass, so we can get the type that way.
1443
        # TODO: This could change for less repetition in the subclasses,
1444
        # using the name of the class to import the module and find
1445
        # the type.
1446
        if type(self)._makeOne is not BTreeTests._makeOne:
×
1447
            return type(self._makeOne())
×
1448
        raise NotImplementedError()
1449

1450
    def _makeOne(self, *args):
8✔
1451
        return self._getTargetClass()(*args)
8✔
1452

1453
    def _checkIt(self, t):
8✔
1454
        from BTrees.check import check
8✔
1455
        t._check()
8✔
1456
        check(t)
8✔
1457

1458
    @_skip_wo_ZODB
8✔
1459
    def testAccessRaisesPOSKeyErrorOnNested(self):
7✔
1460
        # We don't hide a POSKeyError that happens when
1461
        # accessing sub objects in `get()`.
1462
        # See https://github.com/zopefoundation/BTrees/issues/161
1463
        import transaction
×
1464
        from ZODB.POSException import POSKeyError
×
1465
        transaction.begin()
×
1466
        m = self._makeOne()
×
1467
        root = self._getRoot()
×
1468
        root.m = m
×
1469
        self._populate(m, 1000)
×
1470
        transaction.commit()
×
1471
        conn = root._p_jar
×
1472
        # Ghost the objects...
1473
        conn.cacheMinimize()
×
1474
        # ...but activate the tree itself, leaving the
1475
        # buckets ghosted
1476
        m._p_activate()
×
1477

1478
        # Remove the backing data
1479
        self.db._storage._data.clear()
×
1480
        to_key = self.coerce_to_key
×
1481
        to_value = self.coerce_to_value
×
1482
        transaction.begin()
×
1483
        try:
×
1484
            with self.assertRaises(POSKeyError):
×
1485
                m.get(to_key(1))
×
1486

1487
            with self.assertRaises(POSKeyError):
×
1488
                m.setdefault(to_key(1), to_value(1))
×
1489

1490
            with self.assertRaises(POSKeyError):
×
1491
                _ = to_key(1) in m
×
1492

1493
            with self.assertRaises(POSKeyError):
×
1494
                m.pop(to_key(1))
×
1495
        finally:
1496
            self._closeRoot(root)
×
1497

1498
    def testDeleteNoChildrenWorks(self):
8✔
1499
        t = self._makeOne()
8✔
1500
        K = self.KEYS
8✔
1501
        V = self.VALUES
8✔
1502
        t[K[5]] = V[6]
8✔
1503
        t[K[2]] = V[10]
8✔
1504
        t[K[6]] = V[12]
8✔
1505
        t[K[1]] = V[100]
8✔
1506
        t[K[3]] = V[200]
8✔
1507
        t[K[10]] = self.coerce_to_value(500)
8✔
1508
        t[K[4]] = V[99]
8✔
1509
        del t[K[4]]
8✔
1510
        keys = [
8✔
1511
            self.coerce_to_key(x)
1512
            for x in (1, 2, 3, 5, 6, 10)
1513
        ]
1514
        diff = lsubtract(t.keys(), keys)
8✔
1515
        self.assertEqual(diff, [], diff)
8✔
1516
        self._checkIt(t)
8✔
1517

1518
    def testDeleteOneChildWorks(self):
8✔
1519
        t = self._makeOne()
8✔
1520
        K = self.KEYS
8✔
1521
        V = self.VALUES
8✔
1522
        t[K[5]] = V[6]
8✔
1523
        t[K[2]] = V[10]
8✔
1524
        t[K[6]] = V[12]
8✔
1525
        t[K[1]] = V[100]
8✔
1526
        t[K[3]] = V[200]
8✔
1527
        t[K[10]] = self.coerce_to_value(500)
8✔
1528
        t[K[4]] = V[99]
8✔
1529
        del t[K[3]]
8✔
1530
        keys = [
8✔
1531
            self.coerce_to_key(x)
1532
            for x in (1, 2, 4, 5, 6, 10)
1533
        ]
1534
        diff = lsubtract(t.keys(), keys)
8✔
1535
        self.assertEqual(diff, [], diff)
8✔
1536
        self._checkIt(t)
8✔
1537

1538
    def testDeleteTwoChildrenNoInorderSuccessorWorks(self):
8✔
1539
        t = self._makeOne()
8✔
1540
        K = self.KEYS
8✔
1541
        V = self.VALUES
8✔
1542
        t[K[5]] = V[6]
8✔
1543
        t[K[2]] = V[10]
8✔
1544
        t[K[6]] = V[12]
8✔
1545
        t[K[1]] = V[100]
8✔
1546
        t[K[3]] = V[200]
8✔
1547
        t[K[10]] = self.coerce_to_value(500)
8✔
1548
        t[K[4]] = V[99]
8✔
1549
        del t[K[2]]
8✔
1550
        keys = [
8✔
1551
            self.coerce_to_key(x)
1552
            for x in (1, 3, 4, 5, 6, 10)
1553
        ]
1554
        diff = lsubtract(t.keys(), keys)
8✔
1555
        self.assertEqual(diff, [], diff)
8✔
1556
        self._checkIt(t)
8✔
1557

1558
    def testDeleteTwoChildrenInorderSuccessorWorks(self):
8✔
1559
        # 7, 3, 8, 1, 5, 10, 6, 4 -- del 3
1560
        t = self._makeOne()
8✔
1561
        K = self.KEYS
8✔
1562
        V = self.VALUES
8✔
1563
        t[K[7]] = V[6]
8✔
1564
        t[K[3]] = V[10]
8✔
1565
        t[K[8]] = V[12]
8✔
1566
        t[K[1]] = V[100]
8✔
1567
        t[K[5]] = V[200]
8✔
1568
        t[K[10]] = self.coerce_to_value(500)
8✔
1569
        t[K[6]] = V[99]
8✔
1570
        t[K[4]] = V[150]
8✔
1571
        del t[K[3]]
8✔
1572
        keys = [
8✔
1573
            self.coerce_to_key(x)
1574
            for x in (1, 4, 5, 6, 7, 8, 10)
1575
        ]
1576
        diff = lsubtract(t.keys(), keys)
8✔
1577
        self.assertEqual(diff, [], diff)
8✔
1578
        self._checkIt(t)
8✔
1579

1580
    def testDeleteRootWorks(self):
8✔
1581
        # 7, 3, 8, 1, 5, 10, 6, 4 -- del 7
1582
        t = self._makeOne()
8✔
1583
        K = self.KEYS
8✔
1584
        V = self.VALUES
8✔
1585
        t[K[7]] = V[6]
8✔
1586
        t[K[3]] = V[10]
8✔
1587
        t[K[8]] = V[12]
8✔
1588
        t[K[1]] = V[100]
8✔
1589
        t[K[5]] = V[200]
8✔
1590
        t[K[10]] = self.coerce_to_value(500)
8✔
1591
        t[K[6]] = V[99]
8✔
1592
        t[K[4]] = V[150]
8✔
1593
        del t[K[7]]
8✔
1594
        keys = [
8✔
1595
            self.coerce_to_key(x)
1596
            for x in (1, 3, 4, 5, 6, 8, 10)
1597
        ]
1598
        diff = lsubtract(t.keys(), keys)
8✔
1599
        self.assertEqual(diff, [], diff)
8✔
1600
        self._checkIt(t)
8✔
1601

1602
    def testRandomNonOverlappingInserts(self):
8✔
1603
        import random
8✔
1604
        t = self._makeOne()
8✔
1605
        added = {}
8✔
1606
        r = list(range(100))
8✔
1607
        K = self.KEYS
8✔
1608
        V = self.VALUES
8✔
1609
        for x in r:
8✔
1610
            k = random.choice(r)
8✔
1611
            k = K[k]
8✔
1612
            if k not in added:
8✔
1613
                t[k] = V[x]
8✔
1614
                added[k] = V[1]
8✔
1615
        addl = sorted(added.keys())
8✔
1616
        diff = lsubtract(list(t.keys()), addl)
8✔
1617
        self.assertEqual(diff, [], (diff, addl, list(t.keys())))
8✔
1618
        self._checkIt(t)
8✔
1619

1620
    def testRandomOverlappingInserts(self):
8✔
1621
        import random
8✔
1622
        t = self._makeOne()
8✔
1623
        added = {}
8✔
1624
        r = list(range(100))
8✔
1625
        K = self.KEYS
8✔
1626
        V = self.VALUES
8✔
1627
        for x in r:
8✔
1628
            k = random.choice(r)
8✔
1629
            k = K[k]
8✔
1630
            t[k] = V[x]
8✔
1631
            added[k] = V[1]
8✔
1632
        addl = sorted(added.keys())
8✔
1633
        diff = lsubtract(t.keys(), addl)
8✔
1634
        self.assertEqual(diff, [], diff)
8✔
1635
        self._checkIt(t)
8✔
1636

1637
    def testRandomDeletes(self):
8✔
1638
        import random
8✔
1639
        t = self._makeOne()
8✔
1640
        K = self.KEYS
8✔
1641
        V = self.VALUES
8✔
1642
        r = list(range(1000))
8✔
1643

1644
        added = []
8✔
1645
        for x in r:
8✔
1646
            k = random.choice(r)
8✔
1647
            k = K[k]
8✔
1648
            t[k] = V[x]
8✔
1649
            added.append(k)
8✔
1650
        deleted = []
8✔
1651
        for x in r:
8✔
1652
            k = random.choice(r)
8✔
1653
            k = K[k]
8✔
1654
            if k in t:
8✔
1655
                self.assertTrue(k in t)
8✔
1656
                del t[k]
8✔
1657
                deleted.append(k)
8✔
1658
                if k in t:
8✔
1659
                    self.fail("had problems deleting %s" % k)
1660
        badones = []
8✔
1661
        for x in deleted:
8✔
1662
            if x in t:
8!
1663
                badones.append(x)
×
1664
        self.assertEqual(badones, [], (badones, added, deleted))
8✔
1665
        self._checkIt(t)
8✔
1666

1667
    def testTargetedDeletes(self):
8✔
1668
        import random
8✔
1669
        t = self._makeOne()
8✔
1670
        r = list(range(1000))
8✔
1671
        K = self.KEYS
8✔
1672
        V = self.VALUES
8✔
1673
        for x in r:
8✔
1674
            k = random.choice(r)
8✔
1675
            k = K[k]
8✔
1676
            v = V[x]
8✔
1677
            t[k] = v
8✔
1678
        for x in r:
8✔
1679
            k = K[x]
8✔
1680
            try:
8✔
1681
                del t[k]
8✔
1682
            except KeyError:
8✔
1683
                pass
8✔
1684
        self.assertEqual(realseq(t.keys()), [], realseq(t.keys()))
8✔
1685
        self._checkIt(t)
8✔
1686

1687
    def testPathologicalRightBranching(self):
8✔
1688
        t = self._makeOne()
8✔
1689
        K = self.KEYS
8✔
1690
        V = self.VALUES
8✔
1691
        r = [K[k] for k in range(1000)]
8✔
1692
        for x in r:
8✔
1693
            t[x] = V[1]
8✔
1694
        self.assertEqual(realseq(t.keys()), r, realseq(t.keys()))
8✔
1695
        for x in r:
8✔
1696
            del t[x]
8✔
1697
        self.assertEqual(realseq(t.keys()), [], realseq(t.keys()))
8✔
1698
        self._checkIt(t)
8✔
1699

1700
    def testPathologicalLeftBranching(self):
8✔
1701
        t = self._makeOne()
8✔
1702
        r = [self.coerce_to_key(k) for k in range(1000)]
8✔
1703
        revr = list(reversed(r[:]))
8✔
1704
        for x in revr:
8✔
1705
            t[x] = self.VALUES[1]
8✔
1706
        self.assertEqual(realseq(t.keys()), r, realseq(t.keys()))
8✔
1707

1708
        for x in revr:
8✔
1709
            del t[x]
8✔
1710
        self.assertEqual(realseq(t.keys()), [], realseq(t.keys()))
8✔
1711
        self._checkIt(t)
8✔
1712

1713
    def testSuccessorChildParentRewriteExerciseCase(self):
8✔
1714
        t = self._makeOne()
8✔
1715
        K = self.KEYS
8✔
1716
        V = self.VALUES
8✔
1717
        add_order = [
8✔
1718
            85, 73, 165, 273, 215, 142, 233, 67, 86, 166, 235, 225, 255,
1719
            73, 175, 171, 285, 162, 108, 28, 283, 258, 232, 199, 260,
1720
            298, 275, 44, 261, 291, 4, 181, 285, 289, 216, 212, 129,
1721
            243, 97, 48, 48, 159, 22, 285, 92, 110, 27, 55, 202, 294,
1722
            113, 251, 193, 290, 55, 58, 239, 71, 4, 75, 129, 91, 111,
1723
            271, 101, 289, 194, 218, 77, 142, 94, 100, 115, 101, 226,
1724
            17, 94, 56, 18, 163, 93, 199, 286, 213, 126, 240, 245, 190,
1725
            195, 204, 100, 199, 161, 292, 202, 48, 165, 6, 173, 40, 218,
1726
            271, 228, 7, 166, 173, 138, 93, 22, 140, 41, 234, 17, 249,
1727
            215, 12, 292, 246, 272, 260, 140, 58, 2, 91, 246, 189, 116,
1728
            72, 259, 34, 120, 263, 168, 298, 118, 18, 28, 299, 192, 252,
1729
            112, 60, 277, 273, 286, 15, 263, 141, 241, 172, 255, 52, 89,
1730
            127, 119, 255, 184, 213, 44, 116, 231, 173, 298, 178, 196,
1731
            89, 184, 289, 98, 216, 115, 35, 132, 278, 238, 20, 241, 128,
1732
            179, 159, 107, 206, 194, 31, 260, 122, 56, 144, 118, 283,
1733
            183, 215, 214, 87, 33, 205, 183, 212, 221, 216, 296, 40,
1734
            108, 45, 188, 139, 38, 256, 276, 114, 270, 112, 214, 191,
1735
            147, 111, 299, 107, 101, 43, 84, 127, 67, 205, 251, 38, 91,
1736
            297, 26, 165, 187, 19, 6, 73, 4, 176, 195, 90, 71, 30, 82,
1737
            139, 210, 8, 41, 253, 127, 190, 102, 280, 26, 233, 32, 257,
1738
            194, 263, 203, 190, 111, 218, 199, 29, 81, 207, 18, 180,
1739
            157, 172, 192, 135, 163, 275, 74, 296, 298, 265, 105, 191,
1740
            282, 277, 83, 188, 144, 259, 6, 173, 81, 107, 292, 231,
1741
            129, 65, 161, 113, 103, 136, 255, 285, 289, 1
1742
            ]
1743
        delete_order = [
8✔
1744
            276, 273, 12, 275, 2, 286, 127, 83, 92, 33, 101, 195,
1745
            299, 191, 22, 232, 291, 226, 110, 94, 257, 233, 215, 184,
1746
            35, 178, 18, 74, 296, 210, 298, 81, 265, 175, 116, 261,
1747
            212, 277, 260, 234, 6, 129, 31, 4, 235, 249, 34, 289, 105,
1748
            259, 91, 93, 119, 7, 183, 240, 41, 253, 290, 136, 75, 292,
1749
            67, 112, 111, 256, 163, 38, 126, 139, 98, 56, 282, 60, 26,
1750
            55, 245, 225, 32, 52, 40, 271, 29, 252, 239, 89, 87, 205,
1751
            213, 180, 97, 108, 120, 218, 44, 187, 196, 251, 202, 203,
1752
            172, 28, 188, 77, 90, 199, 297, 282, 141, 100, 161, 216,
1753
            73, 19, 17, 189, 30, 258
1754
            ]
1755
        for x in add_order:
8✔
1756
            t[K[x]] = V[1]
8✔
1757
        for x in delete_order:
8✔
1758
            try:
8✔
1759
                del t[K[x]]
8✔
1760
            except KeyError:
8✔
1761
                if K[x] in t:
8!
1762
                    self.assertEqual(1, 2, "failed to delete %s" % x)
×
1763
        self._checkIt(t)
8✔
1764

1765
    def testRangeSearchAfterSequentialInsert(self):
8✔
1766
        t = self._makeOne()
8✔
1767
        K = self.KEYS
8✔
1768
        V = self.VALUES
8✔
1769
        r = [K[k] for k in range(100)]
8✔
1770
        for x in r:
8✔
1771
            t[x] = V[0]
8✔
1772
        diff = lsubtract(list(t.keys(K[0], K[100])), r)
8✔
1773
        self.assertEqual(diff, [], diff)
8✔
1774
        # The same thing with no bounds
1775
        diff = lsubtract(list(t.keys(None, None)), r)
8✔
1776
        self.assertEqual(diff, [], diff)
8✔
1777
        # The same thing with each bound set and the other
1778
        # explicitly None
1779
        diff = lsubtract(list(t.keys(K[0], None)), r)
8✔
1780
        self.assertEqual(diff, [], diff)
8✔
1781
        diff = lsubtract(list(t.keys(None, K[100])), r)
8✔
1782
        self.assertEqual(diff, [], diff)
8✔
1783
        self._checkIt(t)
8✔
1784

1785
    def testRangeSearchAfterRandomInsert(self):
8✔
1786
        import random
8✔
1787
        t = self._makeOne()
8✔
1788
        r = range(100)
8✔
1789
        a = {}
8✔
1790
        V = self.VALUES
8✔
1791
        K = self.KEYS
8✔
1792
        for x in r:
8✔
1793
            rnd = random.choice(r)
8✔
1794
            rnd = K[rnd]
8✔
1795
            t[rnd] = V[0]
8✔
1796
            a[rnd] = V[0]
8✔
1797
        diff = lsubtract(list(t.keys(K[0], K[100])), a.keys())
8✔
1798
        self.assertEqual(diff, [], diff)
8✔
1799
        self._checkIt(t)
8✔
1800

1801
    def testPathologicalRangeSearch(self):
8✔
1802
        # XXX: This test needs some work to be able to handle fsBTree
1803
        # objects. It makes assumptions about bucket sizes and key ordering
1804
        # that doesn't hold.
1805
        if self._getTargetClass().__name__[:2] == 'fs':
8✔
1806
            self.skipTest("XXX: Needs ported for fsBTree")
8✔
1807
        t = self._makeOne()
8✔
1808
        # Build a 2-level tree with at least two buckets.
1809
        if self.SUPPORTS_NEGATIVE_KEYS:
8✔
1810
            range_begin = 0
8✔
1811
            firstkey_offset = 1
8✔
1812
        else:
1813
            range_begin = 1
8✔
1814
            firstkey_offset = 0
8✔
1815

1816
        before_range_begin = range_begin - 1
8✔
1817
        for i in range(range_begin, 200 + range_begin):
8✔
1818
            t[self.coerce_to_key(i)] = self.coerce_to_value(i)
8✔
1819
        items, dummy = t.__getstate__()
8✔
1820
        self.assertTrue(len(items) > 2)   # at least two buckets and a key
8✔
1821
        # All values in the first bucket are < firstkey.  All in the
1822
        # second bucket are >= firstkey, and firstkey is the first key in
1823
        # the second bucket.
1824
        firstkey = items[1]
8✔
1825
        therange = t.keys(self.coerce_to_key(before_range_begin), firstkey)
8✔
1826
        self.assertEqual(len(therange), firstkey + firstkey_offset)
8✔
1827
        self.assertEqual(
8✔
1828
            list(therange), list(range(range_begin, firstkey + 1))
1829
        )
1830

1831
        # Now for the tricky part.  If we delete firstkey, the second bucket
1832
        # loses its smallest key, but firstkey remains in the BTree node.
1833
        # If we then do a high-end range search on firstkey, the BTree node
1834
        # directs us to look in the second bucket, but there's no longer any
1835
        # key <= firstkey in that bucket.  The correct answer points to the
1836
        # end of the *first* bucket.  The algorithm has to be smart enough
1837
        # to "go backwards" in the BTree then; if it doesn't, it will
1838
        # erroneously claim that the range is empty.
1839
        del t[firstkey]
8✔
1840
        therange = t.keys(min=before_range_begin, max=firstkey)
8✔
1841
        self.assertEqual(len(therange), firstkey - range_begin)
8✔
1842
        self.assertEqual(list(therange), list(range(range_begin, firstkey)))
8✔
1843
        self._checkIt(t)
8✔
1844

1845
    def testInsertMethod(self):
8✔
1846
        t = self._makeOne()
8✔
1847
        K = self.KEYS
8✔
1848
        V = self.VALUES
8✔
1849
        t[K[0]] = V[1]
8✔
1850
        self.assertEqual(t.insert(K[0], V[1]), 0)
8✔
1851
        self.assertEqual(t.insert(K[1], V[1]), 1)
8✔
1852
        self.assertEqual(lsubtract(list(t.keys()), [K[0], K[1]]), [])
8✔
1853
        self._checkIt(t)
8✔
1854

1855
    def testDamagedIterator(self):
8✔
1856
        # A cute one from Steve Alexander.  This caused the BTreeItems
1857
        # object to go insane, accessing memory beyond the allocated part
1858
        # of the bucket.  If it fails, the symptom is either a C-level
1859
        # assertion error (if the BTree code was compiled without NDEBUG),
1860
        # or most likely a segfault (if the BTree code was compiled with
1861
        # NDEBUG).
1862
        t = self._makeOne()
8✔
1863
        self._populate(t, 10)
8✔
1864
        # In order for this to fail, it's important that k be a "lazy"
1865
        # iterator, referring to the BTree by indirect position (index)
1866
        # instead of a fully materialized list.  Then the position can
1867
        # end up pointing into trash memory, if the bucket pointed to
1868
        # shrinks.
1869
        k = t.keys()
8✔
1870
        for dummy in range(20):
8✔
1871
            try:
8✔
1872
                del t[k[0]]
8✔
1873
            except RuntimeError as detail:
8✔
1874
                self.assertEqual(str(detail), "the bucket being iterated "
7✔
1875
                                              "changed size")
1876
                break
7✔
1877
            except KeyError as v:
8✔
1878
                # The Python implementation behaves very differently and
1879
                # gives a key error in this situation. It can't mess up
1880
                # memory and can't readily detect changes to underlying buckets
1881
                # in any sane way.
1882
                self.assertEqual(v.args[0], k[0])
8✔
1883
        self._checkIt(t)
8✔
1884

1885
    def testAddTwoSetsChanged(self):
8✔
1886
        # A bug in the BTree Python implementation once
1887
        # caused adding a second item to a tree to fail
1888
        # to set _p_changed (adding the first item sets it because
1889
        # the _firstbucket gets set, but the second item only grew the
1890
        # existing bucket)
1891
        t = self._makeOne()
8✔
1892
        # Note that for the property to actually hold, we have to fake a
1893
        # _p_jar and _p_oid
1894
        t._p_oid = b'\0\0\0\0\0'
8✔
1895

1896
        class Jar:
8✔
1897
            def __init__(self):
8✔
1898
                self._cache = self
8✔
1899
                self.registered = None
8✔
1900

1901
            def mru(self, arg):
8✔
1902
                pass
8✔
1903

1904
            def readCurrent(self, arg):
8✔
1905
                pass
8✔
1906

1907
            def register(self, arg):
8✔
1908
                self.registered = arg
8✔
1909

1910
        t._p_jar = Jar()
8✔
1911
        K = self.KEYS
8✔
1912
        V = self.VALUES
8✔
1913
        t[K[1]] = V[3]
8✔
1914
        # reset these, setting _firstbucket triggered a change
1915
        t._p_changed = False
8✔
1916
        t._p_jar.registered = None
8✔
1917
        t[K[2]] = V[4]
8✔
1918
        self.assertTrue(t._p_changed)
8✔
1919
        self.assertEqual(t, t._p_jar.registered)
8✔
1920

1921
        # Setting the same key to a different value also triggers a change
1922
        t._p_changed = False
8✔
1923
        t._p_jar.registered = None
8✔
1924
        t[K[2]] = V[5]
8✔
1925
        self.assertTrue(t._p_changed)
8✔
1926
        self.assertEqual(t, t._p_jar.registered)
8✔
1927

1928
        # Likewise with only a single value
1929
        t = self._makeOne()
8✔
1930
        t._p_oid = b'\0\0\0\0\0'
8✔
1931
        t._p_jar = Jar()
8✔
1932
        t[K[1]] = V[3]
8✔
1933
        # reset these, setting _firstbucket triggered a change
1934
        t._p_changed = False
8✔
1935
        t._p_jar.registered = None
8✔
1936

1937
        t[K[1]] = V[6]
8✔
1938
        self.assertTrue(t._p_changed)
8✔
1939
        self.assertEqual(t, t._p_jar.registered)
8✔
1940

1941
    def testRemoveInSmallMapSetsChanged(self):
8✔
1942
        # A bug in the BTree Python implementation once caused
1943
        # deleting from a small btree to set _p_changed.
1944
        # There must be at least two objects so that _firstbucket doesn't
1945
        # get set
1946
        t = self._makeOne()
8✔
1947
        K = self.KEYS
8✔
1948
        V = self.VALUES
8✔
1949
        # Note that for the property to actually hold, we have to fake a
1950
        # _p_jar and _p_oid
1951
        t._p_oid = b'\0\0\0\0\0'
8✔
1952

1953
        class Jar:
8✔
1954
            def __init__(self):
8✔
1955
                self._cache = self
8✔
1956
                self.registered = None
8✔
1957

1958
            def mru(self, arg):
8✔
1959
                pass
8✔
1960

1961
            def readCurrent(self, arg):
8✔
1962
                pass
8✔
1963

1964
            def register(self, arg):
8✔
1965
                self.registered = arg
8✔
1966

1967
        t._p_jar = Jar()
8✔
1968
        t[K[0]] = V[1]
8✔
1969
        t[K[1]] = V[2]
8✔
1970
        # reset these, setting _firstbucket triggered a change
1971
        t._p_changed = False
8✔
1972
        t._p_jar.registered = None
8✔
1973

1974
        # now remove the second value
1975
        del t[K[1]]
8✔
1976
        self.assertTrue(t._p_changed)
8✔
1977
        self.assertEqual(t, t._p_jar.registered)
8✔
1978

1979
    def test_legacy_py_pickle(self):
8✔
1980
        # Issue #2
1981
        # If we have a pickle that includes the 'Py' suffix,
1982
        # it (unfortunately) unpickles to the python type. But
1983
        # new pickles never produce that.
1984
        import pickle
8✔
1985
        made_one = self._makeOne()
8✔
1986

1987
        for proto in (1, 2):
8✔
1988
            s = pickle.dumps(made_one, proto)
8✔
1989
            # It's not legacy
1990
            assert b'TreePy\n' not in s, repr(s)
8✔
1991
            # \np for protocol 1, \nq for proto 2,
1992
            assert b'Tree\np' in s or b'Tree\nq' in s, repr(s)
8✔
1993

1994
            # Now make it pseudo-legacy
1995
            legacys = s.replace(b'Tree\np', b'TreePy\np')
8✔
1996
            legacys = legacys.replace(b'Tree\nq', b'TreePy\nq')
8✔
1997

1998
            # It loads up as the specified class
1999
            loaded_one = pickle.loads(legacys)
8✔
2000

2001
            # It still functions and can be dumped again, as the original class
2002
            s2 = pickle.dumps(loaded_one, proto)
8✔
2003
            self.assertTrue(b'Py' not in s2)
8✔
2004
            self.assertEqual(s2, s)
8✔
2005

2006
    def testSetstateBadChild(self):
8✔
2007
        # tree used to allow to pass in non (tree or bucket) node as a child
2008
        # via __setstate__. This was leading to crashes when using C mode.
2009
        t = self._makeOne()
8✔
2010
        b = t._bucket_type()
8✔
2011
        K = self.KEYS
8✔
2012
        V = self.VALUES
8✔
2013
        if isaset(b):
8!
2014
            b.add(K[1])
×
2015
        else:
2016
            b[K[1]] = V[11]
8✔
2017

2018
        # xchild is non-BTree class deriving from Persistent
2019
        import persistent
8✔
2020
        xchild = persistent.Persistent()
8✔
2021
        self.assertIs(xchild._p_oid, None)
8✔
2022

2023
        typeErrOK = "tree child {} is neither {} nor {}".format(
8✔
2024
            _tp_name(type(xchild)),
2025
            _tp_name(type(t)),
2026
            _tp_name(t._bucket_type)
2027
        )
2028

2029
        # if the following is allowed, e.g.
2030
        # t.__getstate__(), or t[0]=1 corrupt memory and crash.
2031
        with self.assertRaises(TypeError) as exc:
8✔
2032
            t.__setstate__(
8✔
2033
                (
2034
                    (xchild,),  # child0 is neither tree nor bucket
2035
                    b
2036
                )
2037
            )
2038
        self.assertEqual(str(exc.exception), typeErrOK)
8✔
2039

2040
        # if the following is allowed, e.g. t[5]=1 corrupts memory and crash.
2041
        with self.assertRaises(TypeError) as exc:
8✔
2042
            t.__setstate__(
8✔
2043
                (
2044
                    (b, K[4], xchild),
2045
                    b
2046
                )
2047
            )
2048
        self.assertEqual(str(exc.exception), typeErrOK)
8✔
2049

2050

2051
class NormalSetTests(Base):
8✔
2052
    # Test common to all set types
2053

2054
    def _getCollectionsABC(self):
8✔
2055
        import collections.abc
8✔
2056
        return collections.abc.MutableSet
8✔
2057

2058
    def _populate(self, t, largest):
8✔
2059
        # Make some data
2060
        t.update(self.coerce_to_key(k) for k in range(largest))
8✔
2061

2062
    def test_isdisjoint(self):
8✔
2063
        t = self._makeOne()
8✔
2064
        K = self.KEYS
8✔
2065
        # The empty set is disjoint with itself
2066
        self.assertTrue(t.isdisjoint(t))
8✔
2067
        # Empty sequences
2068
        self.assertTrue(t.isdisjoint(()))
8✔
2069
        self.assertTrue(t.isdisjoint([]))
8✔
2070
        self.assertTrue(t.isdisjoint(set()))
8✔
2071
        # non-empty sequences but empty set
2072
        self.assertTrue(t.isdisjoint((K[1], K[2])))
8✔
2073
        self.assertTrue(t.isdisjoint([K[1], K[2]]))
8✔
2074
        self.assertTrue(t.isdisjoint({K[1], K[2]}))
8✔
2075
        self.assertTrue(t.isdisjoint(K[k] for k in range(10)))
8✔
2076

2077
        # non-empty sequences and non-empty set, null intersection
2078
        self._populate(t, 2)
8✔
2079
        self.assertEqual(set(t), {K[0], K[1]})
8✔
2080

2081
        self.assertTrue(t.isdisjoint((K[2], K[3])))
8✔
2082
        self.assertTrue(t.isdisjoint([K[2], K[3]]))
8✔
2083
        self.assertTrue(t.isdisjoint({K[2], K[3]}))
8✔
2084
        self.assertTrue(t.isdisjoint(K[k] for k in range(2, 10)))
8✔
2085

2086
        # non-null intersection
2087
        self.assertFalse(t.isdisjoint(t))
8✔
2088
        self.assertFalse(t.isdisjoint((K[0],)))
8✔
2089
        self.assertFalse(t.isdisjoint((K[1],)))
8✔
2090
        self.assertFalse(t.isdisjoint([K[2], K[3], K[0]]))
8✔
2091
        self.assertFalse(t.isdisjoint({K[2], K[3], K[1]}))
8✔
2092
        self.assertFalse(t.isdisjoint(K[k] for k in range(1, 10)))
8!
2093

2094
    def test_discard(self):
8✔
2095
        t = self._makeOne()
8✔
2096
        K = self.KEYS
8✔
2097
        # empty set, raises no error, even if the key is
2098
        # of the wrong type
2099
        t.discard(K[1])
8✔
2100
        t.discard(object())
8✔
2101
        self.assertEqual(set(t), set())
8✔
2102

2103
        # non-empty set, discarding absent key
2104
        self._populate(t, 2)
8✔
2105
        self.assertEqual(set(t), {K[0], K[1]})
8✔
2106
        t.discard(K[3])
8✔
2107
        t.discard(object())
8✔
2108
        self.assertEqual(set(t), {K[0], K[1]})
8✔
2109

2110
        t.discard(K[1])
8✔
2111
        self.assertEqual(set(t), {K[0]})
8✔
2112
        t.discard(K[0])
8✔
2113
        self.assertEqual(set(t), set())
8✔
2114

2115
    def test_pop(self):
8✔
2116
        t = self._makeOne()
8✔
2117
        K = self.KEYS
8✔
2118
        with self.assertRaises(KeyError):
8✔
2119
            t.pop()
8✔
2120

2121
        self._populate(t, 2)
8✔
2122
        self.assertEqual(K[0], t.pop())
8✔
2123
        self.assertEqual(K[1], t.pop())
8✔
2124
        self.assertEqual(set(t), set())
8✔
2125
        with self.assertRaises(KeyError):
8✔
2126
            t.pop()
8✔
2127

2128
    def test___ior__(self):
8✔
2129
        t = self._makeOne()
8✔
2130
        K = self.KEYS
8✔
2131
        orig_t = t
8✔
2132
        t |= (K[1],)
8✔
2133
        t |= [K[2],]
8✔
2134
        t |= {K[1], K[2], K[3]}
8✔
2135
        t |= (K[k] for k in range(10))
8✔
2136
        t |= t
8✔
2137
        self.assertIs(t, orig_t)
8✔
2138
        self.assertEqual(set(t), {K[k] for k in range(10)})
8✔
2139

2140
    def test___iand__(self):
8✔
2141
        t = self._makeOne()
8✔
2142
        K = self.KEYS
8✔
2143
        orig_t = t
8✔
2144
        t &= (K[1],)
8✔
2145
        t &= [K[2],]
8✔
2146
        t &= {K[3], K[4]}
8✔
2147
        self.assertIs(t, orig_t)
8✔
2148
        self.assertEqual(set(t), set())
8✔
2149

2150
        self._populate(t, 10)
8✔
2151
        self.assertEqual(set(t), {K[k] for k in range(10)})
8✔
2152

2153
        t &= (K[1], K[2], K[3])
8✔
2154
        self.assertIs(t, orig_t)
8✔
2155
        self.assertEqual(set(t), {K[1], K[2], K[3]})
8✔
2156

2157
    def test___isub__(self):
8✔
2158
        t = self._makeOne()
8✔
2159
        K = self.KEYS
8✔
2160
        orig_t = t
8✔
2161
        t -= (K[1],)
8✔
2162
        t -= [K[2],]
8✔
2163
        t -= {K[3], K[4]}
8✔
2164
        self.assertIs(t, orig_t)
8✔
2165
        self.assertEqual(set(t), set())
8✔
2166

2167
        self._populate(t, 10)
8✔
2168
        self.assertEqual(set(t), {K[k] for k in range(10)})
8✔
2169

2170
        t -= (K[1], K[2], K[3])
8✔
2171
        self.assertIs(t, orig_t)
8✔
2172
        self.assertEqual(set(t), {K[0], K[4], K[5], K[6], K[7], K[8], K[9]})
8✔
2173

2174
        t -= t
8✔
2175
        self.assertIs(t, orig_t)
8✔
2176
        self.assertEqual(set(t), set())
8✔
2177

2178
    def test___ixor__(self):
8✔
2179
        t = self._makeOne()
8✔
2180
        K = self.KEYS
8✔
2181
        t ^= (K[1],)
8✔
2182
        self.assertEqual(set(t), {K[1]})
8✔
2183
        t ^= t
8✔
2184
        self.assertEqual(set(t), set())
8✔
2185

2186
        t ^= (K[1], K[2], K[3])
8✔
2187
        self.assertEqual(set(t), {K[1], K[2], K[3]})
8✔
2188
        t ^= [K[2], K[3], K[4]]
8✔
2189
        self.assertEqual(set(t), {K[1], K[4]})
8✔
2190

2191
    def test___xor__(self):
8✔
2192
        t = self._makeOne()
8✔
2193
        K = self.KEYS
8✔
2194
        u = t ^ (K[1],)
8✔
2195
        self.assertEqual(set(u), {K[1]})
8✔
2196
        u = t ^ t
8✔
2197
        self.assertEqual(set(u), set())
8✔
2198

2199
        u = t ^ (K[1], K[2], K[3])
8✔
2200
        self.assertEqual(set(u), {K[1], K[2], K[3]})
8✔
2201
        t.update(u)
8✔
2202
        u = t ^ [K[2], K[3], K[4]]
8✔
2203
        self.assertEqual(set(u), {K[1], K[4]})
8✔
2204

2205
    def testShortRepr(self):
8✔
2206
        t = self._makeOne()
8✔
2207
        K = self.KEYS
8✔
2208
        for i in range(5):
8✔
2209
            t.add(K[i])
8✔
2210
        t._p_oid = b'12345678'
8✔
2211
        r = repr(t)
8✔
2212
        # Make sure the repr is **not* 10000 bytes long for a shrort bucket.
2213
        # (the buffer must be terminated when copied).
2214
        self.assertTrue(len(r) < 10000)
8✔
2215
        # Make sure the repr is human readable, unless it's a tree
2216
        if 'TreeSet' not in r:
8✔
2217
            self.assertTrue(r.endswith("Set(%r)" % t.keys()))
8✔
2218
        else:
2219
            # persistent-4.4 changed the default reprs, adding
2220
            # oid and jar reprs
2221
            self.assertIn("<BTrees.", r)
8✔
2222
            self.assertIn('TreeSet object at', r)
8✔
2223
            self.assertIn('oid 0x3132333435363738', r)
8✔
2224

2225
        # Make sure it's the same between Python and C
2226
        self.assertNotIn('Py', r)
8✔
2227

2228
    def testInsertReturnsValue(self):
8✔
2229
        t = self._makeOne()
8✔
2230
        K = self.KEYS
8✔
2231
        self.assertEqual(t.insert(K[5]), 1)
8✔
2232
        self.assertEqual(t.add(K[4]), 1)
8✔
2233

2234
    def testDuplicateInsert(self):
8✔
2235
        t = self._makeOne()
8✔
2236
        k = self.KEYS[5]
8✔
2237
        t.insert(k)
8✔
2238
        self.assertEqual(t.insert(k), 0)
8✔
2239
        self.assertEqual(t.add(k), 0)
8✔
2240

2241
    def testInsert(self):
8✔
2242
        t = self._makeOne()
8✔
2243
        K = self.KEYS
8✔
2244
        t.insert(K[1])
8✔
2245
        self.assertTrue(K[1] in t)
8✔
2246
        self.assertTrue(K[2] not in t)
8✔
2247

2248
    def testBigInsert(self):
8✔
2249
        t = self._makeOne()
8✔
2250
        r = range(10000)
8✔
2251
        to_key = self.coerce_to_key
8✔
2252
        for x in r:
8✔
2253
            t.insert(to_key(x))
8✔
2254
        for x in r:
8✔
2255
            x = to_key(x)
8✔
2256
            self.assertTrue(x in t)
8✔
2257

2258
    def testRemoveSucceeds(self):
8✔
2259
        t = self._makeOne()
8✔
2260
        r = range(10000)
8✔
2261
        to_key = self.coerce_to_key
8✔
2262
        for x in r:
8✔
2263
            t.insert(to_key(x))
8✔
2264
        for x in r:
8✔
2265
            t.remove(to_key(x))
8✔
2266

2267
    def testRemoveFails(self):
8✔
2268
        self.assertRaises(KeyError, self._removenonexistent)
8✔
2269

2270
    def _removenonexistent(self):
8✔
2271
        self._makeOne().remove(self.KEYS[1])
8✔
2272

2273
    def testHasKeyFails(self):
8✔
2274
        t = self._makeOne()
8✔
2275
        self.assertTrue(1 not in t)
8✔
2276

2277
    def testKeys(self):
8✔
2278
        t = self._makeOne()
8✔
2279
        r = [self.KEYS[x] for x in range(1000)]
8✔
2280
        for x in r:
8✔
2281
            t.insert(x)
8✔
2282
        diff = lsubtract(t.keys(), r)
8✔
2283
        self.assertEqual(diff, [])
8✔
2284
        diff = lsubtract(t.keys(None, None), r)
8✔
2285
        self.assertEqual(diff, [])
8✔
2286

2287
    def testClear(self):
8✔
2288
        t = self._makeOne()
8✔
2289
        r = range(1000)
8✔
2290
        K = self.KEYS
8✔
2291
        for x in r:
8✔
2292
            t.insert(K[x])
8✔
2293
        t.clear()
8✔
2294
        diff = lsubtract(t.keys(), [])
8✔
2295
        self.assertEqual(diff, [], diff)
8✔
2296

2297
    def testMaxKeyMinKey(self):
8✔
2298
        t = self._makeOne()
8✔
2299
        K = self.KEYS
8✔
2300
        t.insert(K[1])
8✔
2301
        t.insert(K[2])
8✔
2302
        t.insert(K[3])
8✔
2303
        t.insert(K[8])
8✔
2304
        t.insert(K[5])
8✔
2305
        t.insert(K[10])
8✔
2306
        t.insert(K[6])
8✔
2307
        t.insert(K[4])
8✔
2308
        self.assertEqual(t.maxKey(), K[10])
8✔
2309
        self.assertEqual(t.maxKey(None), K[10])
8✔
2310
        self.assertEqual(t.maxKey(K[6]), K[6])
8✔
2311
        self.assertEqual(t.maxKey(K[9]), K[8])
8✔
2312
        self.assertEqual(t.minKey(), K[1])
8✔
2313
        self.assertEqual(t.minKey(None), K[1])
8✔
2314
        self.assertEqual(t.minKey(K[3]), K[3])
8✔
2315
        self.assertEqual(t.minKey(K[9]), K[10])
8✔
2316
        self.assertTrue(t.minKey() in t)
8✔
2317
        self.assertTrue(t.maxKey() in t)
8✔
2318
        try:
8✔
2319
            bigger = t.maxKey() + 1
8✔
2320
        except TypeError:
8✔
2321
            assert 'fs' in type(t).__name__
8✔
2322
        else:
2323
            self.assertTrue(bigger not in t)
8✔
2324
            self.assertTrue(t.minKey() - 1 not in t)
8✔
2325
            try:
8✔
2326
                t.maxKey(t.minKey() - 1)
8✔
2327
            except ValueError as err:
8✔
2328
                self.assertEqual(str(err), "no key satisfies the conditions")
8✔
2329
            else:
2330
                self.fail("expected ValueError")
2331

2332
            try:
8✔
2333
                t.minKey(t.maxKey() + 1)
8✔
2334
            except ValueError as err:
8✔
2335
                self.assertEqual(str(err), "no key satisfies the conditions")
8✔
2336
            else:
2337
                self.fail("expected ValueError")
2338

2339
    def testUpdate(self):
8✔
2340
        import random
8✔
2341
        t = self._makeOne()
8✔
2342
        d = {}
8✔
2343
        keys = []
8✔
2344
        for i in range(10000):
8✔
2345
            k = random.randrange(*self.KEY_RANDRANGE_ARGS)
8✔
2346
            k = self.coerce_to_key(k)
8✔
2347
            d[k] = self.coerce_to_value(i)
8✔
2348
            keys.append(k)
8✔
2349

2350
        items = sorted(d.keys())
8✔
2351

2352
        t.update(keys)
8✔
2353
        self.assertEqual(list(t.keys()), items)
8✔
2354

2355
    def testEmptyRangeSearches(self):
8✔
2356
        t = self._makeOne()
8✔
2357
        K = self.KEYS
8✔
2358
        t.update([K[1], K[5], K[9]])
8✔
2359
        if self.SUPPORTS_NEGATIVE_KEYS:
8✔
2360
            self.assertEqual(list(t.keys(-6, -4)), [])
8✔
2361

2362
        self.assertEqual(list(t.keys(K[2], K[4])), [])
8✔
2363
        self.assertEqual(list(t.keys(K[6], K[8])), [])
8✔
2364
        self.assertEqual(list(t.keys(K[10], K[12])), [])
8✔
2365
        self.assertEqual(list(t.keys(K[9], K[1])), [])
8✔
2366

2367
        # For IITreeSets, this one was returning 31 for len(keys), and
2368
        # list(keys) produced a list with 100 elements.
2369
        t.clear()
8✔
2370
        t.update(K[k] for k in range(300))
8✔
2371
        keys = t.keys(K[200], K[50])
8✔
2372
        self.assertEqual(len(keys), 0)
8✔
2373
        self.assertEqual(list(keys), [])
8✔
2374

2375
        keys = t.keys(max=K[50], min=K[200])
8✔
2376
        self.assertEqual(len(keys), 0)
8✔
2377
        self.assertEqual(list(keys), [])
8✔
2378

2379
    def testSlicing(self):
8✔
2380
        # Test that slicing of .keys() works exactly the same way as slicing
2381
        # a Python list with the same contents.
2382
        t = self._makeOne()
8✔
2383
        for n in range(10):
8✔
2384
            t.clear()
8✔
2385
            self.assertEqual(len(t), 0)
8✔
2386

2387
            keys = [self.coerce_to_key(x) for x in range(10 * n, 11 * n)]
8✔
2388
            t.update(keys)
8✔
2389
            self.assertEqual(len(t), n)
8✔
2390

2391
            kslice = t.keys()
8✔
2392
            self.assertEqual(len(list(kslice)), n)
8✔
2393

2394
            # Test whole-structure slices.
2395
            x = kslice[:]
8✔
2396
            self.assertEqual(list(x), list(keys[:]))
8✔
2397

2398
            for lo in range(-2 * n, 2 * n + 1):
8✔
2399
                # Test one-sided slices.
2400
                x = kslice[:lo]
8✔
2401
                self.assertEqual(list(x), list(keys[:lo]))
8✔
2402
                x = kslice[lo:]
8✔
2403
                self.assertEqual(list(x), list(keys[lo:]))
8✔
2404

2405
                for hi in range(-2 * n, 2 * n + 1):
8✔
2406
                    # Test two-sided slices.
2407
                    x = kslice[lo:hi]
8✔
2408
                    self.assertEqual(list(x), list(keys[lo:hi]))
8✔
2409

2410
    def testIterator(self):
8✔
2411
        t = self._makeOne()
8✔
2412

2413
        for keys in [], [-2], [1, 4], list(range(-170, 2000, 6)):
8✔
2414
            t.clear()
8✔
2415
            if keys and keys[0] < 0 and not self.SUPPORTS_NEGATIVE_KEYS:
8✔
2416
                with self.assertRaises(UnsignedError):
8✔
2417
                    t.update(keys)
8✔
2418
                continue
8✔
2419

2420
            keys = [self.coerce_to_key(k) for k in keys]
8✔
2421
            t.update(keys)
8✔
2422

2423
            self.assertEqual(list(t), keys)
8✔
2424

2425
            x = []
8✔
2426
            for k in t:
8✔
2427
                x.append(k)
8✔
2428
            self.assertEqual(x, keys)
8✔
2429

2430
            it = iter(t)
8✔
2431
            self.assertTrue(it is iter(it))
8✔
2432
            x = []
8✔
2433
            try:
8✔
2434
                while 1:
6✔
2435
                    x.append(next(it))
8✔
2436
            except StopIteration:
8✔
2437
                pass
8✔
2438
            self.assertEqual(x, keys)
8✔
2439

2440
    def testRemoveInSmallSetSetsChanged(self):
8✔
2441
        # A bug in the BTree TreeSet Python implementation once caused
2442
        # deleting an item in a small set to fail to set _p_changed.
2443
        # There must be at least two objects so that _firstbucket doesn't
2444
        # get set
2445
        t = self._makeOne()
8✔
2446
        # Note that for the property to actually hold, we have to fake a
2447
        # _p_jar and _p_oid
2448
        t._p_oid = b'\0\0\0\0\0'
8✔
2449

2450
        class Jar:
8✔
2451
            def __init__(self):
8✔
2452
                self._cache = self
8✔
2453
                self.registered = None
8✔
2454

2455
            def mru(self, arg):
8✔
2456
                pass
8✔
2457

2458
            def readCurrent(self, arg):
8✔
2459
                pass
8✔
2460

2461
            def register(self, arg):
8✔
2462
                self.registered = arg
8✔
2463

2464
        t._p_jar = Jar()
8✔
2465
        t.add(self.KEYS[0])
8✔
2466
        t.add(self.KEYS[1])
8✔
2467
        # reset these, setting _firstbucket triggered a change
2468
        t._p_changed = False
8✔
2469
        t._p_jar.registered = None
8✔
2470

2471
        # now remove the second value
2472
        t.remove(self.KEYS[1])
8✔
2473
        self.assertTrue(t._p_changed)
8✔
2474
        self.assertEqual(t, t._p_jar.registered)
8✔
2475

2476
    def testAddingOneSetsChanged(self):
8✔
2477
        # A bug in the BTree Set Python implementation once caused
2478
        # adding an object not to set _p_changed
2479
        t = self._makeOne()
8✔
2480
        # Note that for the property to actually hold, we have to fake a
2481
        # _p_jar and _p_oid
2482
        t._p_oid = b'\0\0\0\0\0'
8✔
2483

2484
        class Jar:
8✔
2485
            def __init__(self):
8✔
2486
                self._cache = self
8✔
2487
                self.registered = None
8✔
2488

2489
            def mru(self, arg):
8✔
2490
                pass
8✔
2491

2492
            def readCurrent(self, arg):
8✔
2493
                pass
8✔
2494

2495
            def register(self, arg):
8✔
2496
                self.registered = arg
8✔
2497

2498
        t._p_jar = Jar()
8✔
2499
        t.add(self.KEYS[0])
8✔
2500
        self.assertTrue(t._p_changed)
8✔
2501
        self.assertEqual(t, t._p_jar.registered)
8✔
2502

2503
        # Whether or not doing `t.add(0)` again would result in _p_changed
2504
        # being set depends on whether this is a TreeSet or a plain Set
2505

2506

2507
class ExtendedSetTests(NormalSetTests):
8✔
2508

2509
    def testLen(self):
8✔
2510
        t = self._makeOne()
8✔
2511
        r = range(10000)
8✔
2512
        to_key = self.coerce_to_key
8✔
2513
        for x in r:
8✔
2514
            t.insert(to_key(x))
8✔
2515
        self.assertEqual(len(t), 10000, len(t))
8✔
2516

2517
    def testGetItem(self):
8✔
2518
        t = self._makeOne()
8✔
2519
        r = range(10000)
8✔
2520
        to_key = self.coerce_to_key
8✔
2521
        for x in r:
8✔
2522
            t.insert(to_key(x))
8✔
2523
        for x in r:
8✔
2524
            self.assertEqual(t[x], to_key(x))
8✔
2525

2526

2527
class KeyCoercionFailed(Exception):
8✔
2528
    """Raised when we use a static key that we expect to be able to fit."""
2529

2530

2531
class InternalKeysMappingTest:
8✔
2532
    # There must not be any internal keys not in the BTree
2533

2534
    def _makeOne(self):
8✔
2535
        return self._getTargetClass()()
×
2536

2537
    def add_key(self, tree, i):
8✔
2538
        value = self.coerce_to_value(i)
×
2539
        try:
×
2540
            key = self.coerce_to_key(i)
×
2541
        except TypeError:
×
2542
            raise KeyCoercionFailed(i)
×
2543

2544
        tree[key] = value
×
2545

2546
    @_skip_wo_ZODB
8✔
2547
    def test_internal_keys_after_deletion(self):
7✔
2548
        # Make sure when a key's deleted, it's not an internal key
2549
        #
2550
        # We'll leverage __getstate__ to introspect the internal structures.
2551
        #
2552
        # We need to check BTrees with BTree children as well as BTrees
2553
        # with bucket children.
2554
        import transaction
×
2555
        from ZODB.MappingStorage import DB
×
2556
        db = DB()
×
2557
        conn = db.open()
×
2558

2559
        tree = conn.root.tree = self._makeOne()
×
2560
        i = 0
×
2561

2562
        # Grow the btree until we have multiple buckets.
2563
        # (Calling ``__getstate__`` to check the internals is expensive,
2564
        # especially with the Python implementation, so only do so when we hit
2565
        # the threshold we expect the tree to grow. This makes the difference
2566
        # between a 6s test and a 0.6s test.)
2567
        bucket_size = self.key_type.bucket_size_for_value(self.value_type)
×
2568
        tree_size = self.key_type.tree_size
×
2569
        while 1:
×
2570
            i += 1
×
2571
            self.add_key(tree, i)
×
2572
            if i >= bucket_size:
×
2573
                data = tree.__getstate__()[0]
×
2574
                if len(data) >= 3:
×
2575
                    break
×
2576

2577
        transaction.commit()
×
2578

2579
        # Now, delete the internal key and make sure it's really gone
2580
        key = data[1]
×
2581
        del tree[key]
×
2582
        data = tree.__getstate__()[0]
×
2583
        self.assertTrue(data[1] != key)
×
2584

2585
        # The tree should have changed:
2586
        self.assertTrue(tree._p_changed)
×
2587

2588
        # Grow the btree until we have multiple levels
2589
        while 1:
×
2590
            i += 1
×
2591
            try:
×
2592
                self.add_key(tree, i)
×
2593
            except KeyCoercionFailed:
×
2594
                # Only expected in fsbtree
2595
                assert i == 32768 and type(tree).__name__.startswith('fs')
×
2596
                break
×
2597
            if i >= tree_size * bucket_size:
×
2598
                data = tree.__getstate__()[0]
×
2599
                if data[0].__class__ == tree.__class__:
×
2600
                    assert len(data[2].__getstate__()[0]) >= 3
×
2601
                    break
×
2602

2603
        # Now, delete the internal key and make sure it's really gone
2604
        key = data[1]
×
2605
        del tree[key]
×
2606
        data = tree.__getstate__()[0]
×
2607
        self.assertTrue(data[1] != key)
×
2608

2609
        transaction.abort()
×
2610
        db.close()
×
2611

2612

2613
class ModuleTest:
8✔
2614
    # test for presence of generic names in module
2615
    prefix = ''
8✔
2616
    key_type = None
8✔
2617
    value_type = None
8✔
2618

2619
    def _getModule(self):
8✔
2620
        raise NotImplementedError
2621

2622
    def testNames(self):
8✔
2623
        names = ['Bucket', 'BTree', 'Set', 'TreeSet']
8✔
2624
        mod = self._getModule()
8✔
2625
        mod_all = mod.__all__
8✔
2626
        for name in names:
8✔
2627
            klass = getattr(mod, name)
8✔
2628
            self.assertEqual(klass.__module__, mod.__name__)
8✔
2629
            self.assertIs(klass, getattr(mod,
8✔
2630
                                         self.prefix + name))
2631
            self.assertIn(name, mod_all)
8✔
2632
            self.assertIn(self.prefix + name, mod_all)
8✔
2633

2634
        # BBB for zope.app.security ZCML :(
2635
        pfx_iter = self.prefix + 'TreeIterator'
8✔
2636
        klass = getattr(mod, pfx_iter)
8✔
2637
        self.assertEqual(klass.__module__, mod.__name__)
8✔
2638

2639
    def testModuleProvides(self):
8✔
2640
        from zope.interface.verify import verifyObject
8✔
2641
        verifyObject(self._getInterface(), self._getModule())
8✔
2642

2643
    def testFamily(self):
8✔
2644
        import BTrees
8✔
2645
        if self.prefix == 'OO':
8✔
2646
            self.assertTrue(
8✔
2647
                getattr(self._getModule(), 'family', self) is self)
2648
        elif 'L' in self.prefix:
8✔
2649
            self.assertTrue(self._getModule().family is BTrees.family64)
8✔
2650
        elif 'I' in self.prefix:
8✔
2651
            self.assertTrue(self._getModule().family is BTrees.family32)
8✔
2652

2653
    def _check_union_presence(self, datatype, name):
8✔
2654
        mod = self._getModule()
8✔
2655
        if datatype.supports_value_union():
8✔
2656
            in_ = self.assertIn
8✔
2657
            has = self.assertTrue
8✔
2658
        else:
2659
            in_ = self.assertNotIn
8✔
2660
            has = self.assertFalse
8✔
2661

2662
        in_(name, dir(mod))
8✔
2663
        has(hasattr(mod, name))
8✔
2664
        in_(name, mod.__all__)
8✔
2665

2666
    # The weighted* functions require the value type to support unions.
2667
    def test_weightedUnion_presence(self):
8✔
2668
        self._check_union_presence(self.value_type, 'weightedUnion')
8✔
2669

2670
    def test_weightedIntersection_presence(self):
8✔
2671
        self._check_union_presence(self.value_type, 'weightedIntersection')
8✔
2672

2673
    # The multiunion function requires the key type to support unions
2674
    def test_multiunion_presence(self):
8✔
2675
        self._check_union_presence(self.key_type, 'multiunion')
8✔
2676

2677

2678
class I_SetsBase:
8✔
2679

2680
    def setUp(self):
8✔
2681
        super().setUp()
8✔
2682
        _skip_if_pure_py_and_py_test(self)
8✔
2683

2684
    def _getTargetClass(self):
8✔
2685
        raise NotImplementedError
2686

2687
    def _makeOne(self):
8✔
2688
        return self._getTargetClass()()
8✔
2689

2690
    def testBadBadKeyAfterFirst(self):
8✔
2691
        with self.assertRaises(TypeError):
8✔
2692
            self._getTargetClass()([1, object()])
8✔
2693

2694
        t = self._makeOne()
8✔
2695
        with self.assertRaises(TypeError):
8✔
2696
            t.update([1, object()])
8✔
2697

2698
    def __test_key(self, k):
8✔
2699
        try:
8✔
2700
            self.key_type(k)
8✔
2701
        except Exception as e:
8✔
2702
            with self.assertRaises(type(e)):
8✔
2703
                self._makeOne().insert(k)
8✔
2704
        else:
2705
            self._makeOne().insert(k)
8✔
2706

2707
    def test_key_type_str(self):
8✔
2708
        self.__test_key('c')
8✔
2709

2710
    def test_key_type_float(self):
8✔
2711
        self.__test_key(2.5)
8✔
2712

2713
    def test_key_type_None(self):
8✔
2714
        self.__test_key(None)
8✔
2715

2716

2717
LARGEST_32_BITS = 2147483647
8✔
2718
SMALLEST_32_BITS = -LARGEST_32_BITS - 1
8✔
2719

2720
SMALLEST_POSITIVE_33_BITS = LARGEST_32_BITS + 1
8✔
2721
LARGEST_NEGATIVE_33_BITS = SMALLEST_32_BITS - 1
8✔
2722

2723
LARGEST_64_BITS = 0x7fffffffffffffff  # Signed. 2**63 - 1
8✔
2724
SMALLEST_64_BITS = -LARGEST_64_BITS - 1
8✔
2725

2726
SMALLEST_POSITIVE_65_BITS = LARGEST_64_BITS + 1
8✔
2727
LARGEST_NEGATIVE_65_BITS = SMALLEST_64_BITS - 1
8✔
2728

2729

2730
class TestLongIntSupport:
8✔
2731

2732
    def getTwoValues(self):
8✔
2733
        # Return two distinct values; these must compare as un-equal.
2734
        #
2735
        # These values must be usable as values.
2736
        return object(), object()
×
2737

2738
    def getTwoKeys(self):
8✔
2739
        # Return two distinct values, these must compare as un-equal.
2740
        #
2741
        # These values must be usable as keys.
2742
        return 0, 1
×
2743

2744
    def _skip_if_not_64bit(self):
8✔
2745
        mod = sys.modules[self._getTargetClass().__module__]
8✔
2746
        if not mod.using64bits:
8✔
2747
            self.skipTest("Needs 64 bit support.")  # pragma: no cover
2748

2749

2750
class TestLongIntKeys(TestLongIntSupport):
8✔
2751
    SUPPORTS_NEGATIVE_KEYS = True
8✔
2752

2753
    def _makeLong(self, v):
8✔
2754
        try:
8✔
2755
            return long(v)
8✔
2756
        except NameError:  # pragma: no cover
2757
            return int(v)
2758

2759
    def testLongIntKeysWork(self):
8✔
2760
        self._skip_if_not_64bit()
8✔
2761
        t = self._makeOne()
8✔
2762
        K = self.KEYS
8✔
2763
        o1, o2 = self.getTwoValues()
8✔
2764
        assert o1 != o2
8✔
2765

2766
        # Test some small key values first:
2767
        one_long = self._makeLong(1)
8✔
2768
        t[one_long] = o1
8✔
2769
        self.assertEqual(t[K[1]], o1)
8✔
2770
        t[K[1]] = o2
8✔
2771
        self.assertEqual(t[one_long], o2)
8✔
2772
        self.assertEqual(list(t.keys()), [1])
8✔
2773
        self.assertEqual(list(t.keys(None, None)), [1])
8✔
2774

2775
        # Test some large key values too:
2776
        k1 = SMALLEST_POSITIVE_33_BITS
8✔
2777
        k2 = LARGEST_64_BITS
8✔
2778
        k3 = SMALLEST_64_BITS if self.SUPPORTS_NEGATIVE_KEYS else 0
8✔
2779
        t[k1] = o1
8✔
2780
        t[k2] = o2
8✔
2781
        t[k3] = o1
8✔
2782
        self.assertEqual(t[k1], o1)
8✔
2783
        self.assertEqual(t[k2], o2)
8✔
2784
        self.assertEqual(t[k3], o1)
8✔
2785
        self.assertEqual(list(t.keys()), [k3, 1, k1, k2])
8✔
2786
        self.assertEqual(list(t.keys(k3, None)), [k3, 1, k1, k2])
8✔
2787
        self.assertEqual(list(t.keys(None, k2)), [k3, 1, k1, k2])
8✔
2788

2789
    def testLongIntKeysOutOfRange(self):
8✔
2790
        self._skip_if_not_64bit()
8✔
2791
        o1, o2 = self.getTwoValues()
8✔
2792
        t = self._makeOne()
8✔
2793
        k1 = (
8✔
2794
            SMALLEST_POSITIVE_65_BITS
2795
            if self.SUPPORTS_NEGATIVE_KEYS
2796
            else 2**64 + 1
2797
        )
2798
        with self.assertRaises(TypeError):
8✔
2799
            t[k1] = self.coerce_to_value(o1)
8✔
2800

2801
        t = self._makeOne()
8✔
2802
        with self.assertRaises(TypeError):
8✔
2803
            t[LARGEST_NEGATIVE_65_BITS] = self.coerce_to_value(o1)
8✔
2804

2805

2806
class TestLongIntValues(TestLongIntSupport):
8✔
2807
    SUPPORTS_NEGATIVE_VALUES = True
8✔
2808

2809
    def testLongIntValuesWork(self):
8✔
2810
        self._skip_if_not_64bit()
8✔
2811
        t = self._makeOne()
8✔
2812
        keys = sorted(self.getTwoKeys())
8✔
2813
        k1, k2 = keys
8✔
2814
        assert k1 != k2
8✔
2815

2816
        # This is the smallest positive integer that requires 33 bits:
2817
        v1 = SMALLEST_POSITIVE_33_BITS
8✔
2818
        v2 = v1 + 1
8✔
2819

2820
        t[k1] = self.coerce_to_value(v1)
8✔
2821
        t[k2] = self.coerce_to_value(v2)
8✔
2822
        self.assertEqual(t[k1], v1)
8✔
2823
        self.assertEqual(t[k2], v2)
8✔
2824
        self.assertEqual(list(t.values()), [v1, v2])
8✔
2825
        self.assertEqual(list(t.values(None, None)), [v1, v2])
8✔
2826

2827
    def testLongIntValuesOutOfRange(self):
8✔
2828
        self._skip_if_not_64bit()
8✔
2829
        k1, k2 = self.getTwoKeys()
8✔
2830
        t = self._makeOne()
8✔
2831
        v1 = (
8✔
2832
            SMALLEST_POSITIVE_65_BITS
2833
            if self.SUPPORTS_NEGATIVE_VALUES
2834
            else 2**64 + 1
2835
        )
2836
        with self.assertRaises(TypeError):
8✔
2837
            t[k1] = self.coerce_to_value(v1)
8✔
2838

2839
        t = self._makeOne()
8✔
2840
        with self.assertRaises(TypeError):
8✔
2841
            t[k1] = self.coerce_to_value(LARGEST_NEGATIVE_65_BITS)
8✔
2842

2843

2844
def makeMapBuilder(self, mapbuilder):
8✔
2845
    # Given a mapping builder (IIBTree, OOBucket, etc), return a function
2846
    # that builds an object of that type given only a list of keys.
2847
    def result(keys=(), mapbuilder=mapbuilder, self=self):
8✔
2848
        return mapbuilder(list(zip(
8✔
2849
            (self.KEYS[k] for k in keys),
2850
            (self.VALUES[k] for k in keys)
2851
        )))
2852
    return result
8✔
2853

2854

2855
def makeSetBuilder(self, setbuilder):
8✔
2856
    def result(keys=(), setbuilder=setbuilder, self=self):
8✔
2857
        return setbuilder(self.KEYS[k] for k in keys)
8✔
2858
    return result
8✔
2859

2860

2861
class SetResult:
8✔
2862
    # Subclasses have to set up:
2863
    #     builders() - function returning functions to build inputs,
2864
    #     each returned callable takes an optional keys arg
2865
    #     intersection, union, difference - set to the type-correct versions
2866
    def setUp(self):
8✔
2867
        super().setUp()
8✔
2868
        _skip_if_pure_py_and_py_test(self)
8✔
2869

2870
        rawAkeys = [1,    3,    5, 6]
8✔
2871
        rawBkeys = [   2, 3, 4,    6, 7]  # noqa #201
8✔
2872
        self.Akeys = [self.KEYS[k] for k in rawAkeys]
8✔
2873
        self.Bkeys = [self.KEYS[k] for k in rawBkeys]
8✔
2874
        self.As = [makeset(rawAkeys) for makeset in self.builders()]
8✔
2875
        self.Bs = [makeset(rawBkeys) for makeset in self.builders()]
8✔
2876
        self.emptys = [makeset() for makeset in self.builders()]
8✔
2877

2878
    # Slow but obviously correct Python implementations of basic ops.
2879
    def _union(self, x, y):
8✔
2880
        result = list(x)
8✔
2881
        for e in y:
8✔
2882
            if e not in result:
8✔
2883
                result.append(e)
8✔
2884
        return sorted(result)
8✔
2885

2886
    def _intersection(self, x, y):
8✔
2887
        result = []
8✔
2888
        for e in x:
8✔
2889
            if e in y:
8✔
2890
                result.append(e)
8✔
2891
        return result
8✔
2892

2893
    def _difference(self, x, y):
8✔
2894
        result = list(x)
8✔
2895
        for e in y:
8✔
2896
            if e in result:
8✔
2897
                result.remove(e)
8✔
2898
        # Difference preserves LHS values.
2899
        if hasattr(x, "values"):
8✔
2900
            result = [(k, x[k]) for k in result]
8✔
2901
        return result
8✔
2902

2903
    def testNone(self):
8✔
2904
        for op in self.union, self.intersection, self.difference:
8✔
2905
            C = op(None, None)
8✔
2906
            self.assertIsNone(C)
8✔
2907

2908
        for op in self.union, self.intersection, self.difference:
8✔
2909
            for A in self.As:
8✔
2910
                C = op(A, None)
8✔
2911
                self.assertIs(C, A)
8✔
2912

2913
                C = op(None, A)
8✔
2914
                if op == self.difference:
8✔
2915
                    self.assertIsNone(C, None)
8✔
2916
                else:
2917
                    self.assertIs(C, A)
8✔
2918

2919
    def testEmptyUnion(self):
8✔
2920
        for A in self.As:
8✔
2921
            for E in self.emptys:
8✔
2922
                C = self.union(A, E)
8✔
2923
                self.assertTrue(not hasattr(C, "values"))
8✔
2924
                self.assertEqual(list(C), self.Akeys)
8✔
2925

2926
                C = self.union(E, A)
8✔
2927
                self.assertTrue(not hasattr(C, "values"))
8✔
2928
                self.assertEqual(list(C), self.Akeys)
8✔
2929

2930
    def testEmptyIntersection(self):
8✔
2931
        for A in self.As:
8✔
2932
            for E in self.emptys:
8✔
2933
                C = self.intersection(A, E)
8✔
2934
                self.assertTrue(not hasattr(C, "values"))
8✔
2935
                self.assertEqual(list(C), [])
8✔
2936

2937
                C = self.intersection(E, A)
8✔
2938
                self.assertTrue(not hasattr(C, "values"))
8✔
2939
                self.assertEqual(list(C), [])
8✔
2940

2941
    def testEmptyDifference(self):
8✔
2942
        for A in self.As:
8✔
2943
            for E in self.emptys:
8✔
2944
                C = self.difference(A, E)
8✔
2945
                # Difference preserves LHS values.
2946
                self.assertEqual(hasattr(C, "values"), hasattr(A, "values"))
8✔
2947
                if hasattr(A, "values"):
8✔
2948
                    self.assertEqual(list(C.items()), list(A.items()))
8✔
2949
                else:
2950
                    self.assertEqual(list(C), self.Akeys)
8✔
2951

2952
                C = self.difference(E, A)
8✔
2953
                self.assertEqual(hasattr(C, "values"), hasattr(E, "values"))
8✔
2954
                self.assertEqual(list(C), [])
8✔
2955

2956
    def _reversed(self, x):
8✔
2957
        x = list(x)
8✔
2958
        x.reverse()
8✔
2959
        return x
8✔
2960

2961
    def testUnion(self):
8✔
2962
        inputs = self.As + self.Bs
8✔
2963
        for A in inputs:
8✔
2964
            for B in inputs:
8✔
2965
                for convert in lambda x: x, self._reversed, list, tuple, set:
8✔
2966
                    # For all of these tests, we need to be sure we have at
2967
                    # least one value that is *not* sorted relative to the
2968
                    # other.  See
2969
                    # https://github.com/zopefoundation/BTrees/issues/171
2970
                    a = convert(A)
8✔
2971
                    b = convert(B)
8✔
2972
                    if hasattr(b, 'reverse'):
8✔
2973
                        b.reverse()
8✔
2974
                    __traceback_info__ = a, b
8✔
2975
                    C = self.union(a, b)
8✔
2976
                    self.assertTrue(not hasattr(C, "values"))
8✔
2977
                    self.assertEqual(list(C), self._union(a, b))
8✔
2978
                    self.assertEqual(set(A) | set(B), set(A | B))
8✔
2979

2980
    def testIntersection(self):
8✔
2981
        inputs = self.As + self.Bs
8✔
2982
        for A in inputs:
8✔
2983
            for B in inputs:
8✔
2984
                for convert in lambda x: x, self._reversed, list, tuple, set:
8✔
2985
                    a = convert(A)
8✔
2986
                    b = convert(B)
8✔
2987
                    if hasattr(b, 'reverse'):
8✔
2988
                        b.reverse()
8✔
2989
                    __traceback_info__ = a, b
8✔
2990
                    C = self.intersection(a, b)
8✔
2991
                    self.assertTrue(not hasattr(C, "values"))
8✔
2992
                    self.assertEqual(list(C), self._intersection(A, B))
8✔
2993
                    self.assertEqual(set(A) & set(B), set(A & B))
8✔
2994

2995
    def testDifference(self):
8✔
2996
        inputs = self.As + self.Bs
8✔
2997
        for A in inputs:
8✔
2998
            for B in inputs:
8✔
2999
                for convert in lambda x: x, self._reversed, list, tuple, set:
8✔
3000
                    # Difference is unlike the others: The first argument
3001
                    # must be a BTree type, in both C and Python.
3002
                    C = self.difference(A, convert(B))
8✔
3003
                    # Difference preserves LHS values.
3004
                    self.assertEqual(
8✔
3005
                        hasattr(C, "values"), hasattr(A, "values")
3006
                    )
3007
                    want = self._difference(A, B)
8✔
3008
                    if hasattr(A, "values"):
8✔
3009
                        self.assertEqual(list(C.items()), want)
8✔
3010
                    else:
3011
                        self.assertEqual(list(C), want)
8✔
3012
                    self.assertEqual(set(A) - set(B), set(A - B))
8✔
3013

3014
    def testLargerInputs(self):
8✔
3015
        from random import randint
8✔
3016

3017
        from BTrees.IIBTree import IISet
8✔
3018
        MAXSIZE = 200
8✔
3019
        MAXVAL = 400
8✔
3020
        K = self.KEYS
8✔
3021
        for i in range(3):
8✔
3022
            n = randint(0, MAXSIZE)
8✔
3023
            Akeys = [randint(1, MAXVAL) for j in range(n)]
8✔
3024
            As = [makeset(Akeys) for makeset in self.builders()]
8✔
3025
            Akeys = IISet(Akeys)
8✔
3026

3027
            n = randint(0, MAXSIZE)
8✔
3028
            Bkeys = [randint(1, MAXVAL) for j in range(n)]
8✔
3029
            Bs = [makeset(Bkeys) for makeset in self.builders()]
8✔
3030
            Bkeys = IISet(Bkeys)
8✔
3031

3032
            Akeys = [K[k] for k in Akeys]
8✔
3033
            Bkeys = [K[k] for k in Bkeys]
8✔
3034

3035
            for op, simulator in ((self.union, self._union),
8✔
3036
                                  (self.intersection, self._intersection),
3037
                                  (self.difference, self._difference)):
3038
                for A in As:
8✔
3039
                    for B in Bs:
8✔
3040
                        got = op(A, B)
8✔
3041
                        want = simulator(Akeys, Bkeys)
8✔
3042
                        self.assertEqual(
8✔
3043
                            list(got), want,
3044
                            (A, B, Akeys, Bkeys, list(got), want)
3045
                        )
3046

3047

3048
class Weighted(SignedMixin):
8✔
3049
    # Subclasses must set up (as class variables):
3050
    #     weightedUnion, weightedIntersection
3051
    #     builders -- sequence of constructors, taking items
3052
    #     union, intersection -- the module routines of those names
3053
    #     mkbucket -- the module bucket builder
3054

3055
    def setUp(self):
8✔
3056
        self.Aitems = [(1, 10), (3, 30), (5, 50), (6, 60)]
8✔
3057
        self.Bitems = [(2, 21), (3, 31), (4, 41), (6, 61), (7, 71)]
8✔
3058

3059
        self.As = [make(self.Aitems) for make in self.builders()]
8✔
3060
        self.Bs = [make(self.Bitems) for make in self.builders()]
8✔
3061
        self.emptys = [make([]) for make in self.builders()]
8✔
3062

3063
        weights = []
8✔
3064
        for w1 in -3, -1, 0, 1, 7:
8✔
3065
            for w2 in -3, -1, 0, 1, 7:
8✔
3066
                weights.append((w1, w2))
8✔
3067
        self.weights = weights
8✔
3068

3069
    def testBothNone(self):
8✔
3070
        for op in self.weightedUnion(), self.weightedIntersection():
8✔
3071
            w, C = op(None, None)
8✔
3072
            self.assertTrue(C is None)
8✔
3073
            self.assertEqual(w, 0)
8✔
3074

3075
            w, C = op(None, None, 42, 666)
8✔
3076
            self.assertTrue(C is None)
8✔
3077
            self.assertEqual(w, 0)
8✔
3078

3079
    def testLeftNone(self):
8✔
3080
        for op in self.weightedUnion(), self.weightedIntersection():
8✔
3081
            for A in self.As + self.emptys:
8✔
3082
                w, C = op(None, A)
8✔
3083
                self.assertTrue(C is A)
8✔
3084
                self.assertEqual(w, 1)
8✔
3085

3086
                w, C = op(None, A, 42, 666)
8✔
3087
                self.assertTrue(C is A)
8✔
3088
                self.assertEqual(w, 666)
8✔
3089

3090
    def testRightNone(self):
8✔
3091
        for op in self.weightedUnion(), self.weightedIntersection():
8✔
3092
            for A in self.As + self.emptys:
8✔
3093
                w, C = op(A, None)
8✔
3094
                self.assertTrue(C is A)
8✔
3095
                self.assertEqual(w, 1)
8✔
3096

3097
                w, C = op(A, None, 42, 666)
8✔
3098
                self.assertTrue(C is A)
8✔
3099
                self.assertEqual(w, 42)
8✔
3100

3101
    # If obj is a set, return a bucket with values all 1; else return obj.
3102
    def _normalize(self, obj):
8✔
3103
        if isaset(obj):
8✔
3104
            obj = self.mkbucket(list(zip(obj, [1] * len(obj))))
8✔
3105
        return obj
8✔
3106

3107
    # Python simulation of weightedUnion.
3108
    def _wunion(self, A, B, w1=1, w2=1):
8✔
3109
        if isaset(A) and isaset(B):
8✔
3110
            return 1, self.union()(A, B).keys()
8✔
3111
        A = self._normalize(A)
8✔
3112
        B = self._normalize(B)
8✔
3113
        result = []
8✔
3114
        for key in self.union()(A, B):
8✔
3115
            v1 = A.get(key, 0)
8✔
3116
            v2 = B.get(key, 0)
8✔
3117
            result.append((key, v1*w1 + v2*w2))
8✔
3118
        return 1, result
8✔
3119

3120
    def testUnion(self):
8✔
3121
        inputs = self.As + self.Bs + self.emptys
8✔
3122
        for A in inputs:
8✔
3123
            for B in inputs:
8✔
3124
                want_w, want_s = self._wunion(A, B)
8✔
3125
                got_w, got_s = self.weightedUnion()(A, B)
8✔
3126
                self.assertEqual(got_w, want_w)
8✔
3127
                if isaset(got_s):
8✔
3128
                    self.assertEqual(got_s.keys(), want_s)
8✔
3129
                else:
3130
                    self.assertEqual(got_s.items(), want_s)
8✔
3131

3132
                for w1, w2 in self.weights:
8✔
3133
                    if (
8✔
3134
                        (w1 < 0 or w2 < 0) and
3135
                        not self.SUPPORTS_NEGATIVE_VALUES
3136
                    ):
3137
                        continue
8✔
3138
                    want_w, want_s = self._wunion(A, B, w1, w2)
8✔
3139
                    got_w, got_s = self.weightedUnion()(A, B, w1, w2)
8✔
3140
                    self.assertEqual(got_w, want_w)
8✔
3141
                    if isaset(got_s):
8✔
3142
                        self.assertEqual(got_s.keys(), want_s)
8✔
3143
                    else:
3144
                        self.assertEqual(got_s.items(), want_s)
8✔
3145

3146
    # Python simulation weightedIntersection.
3147
    def _wintersection(self, A, B, w1=1, w2=1):
8✔
3148
        if isaset(A) and isaset(B):
8✔
3149
            return w1 + w2, self.intersection()(A, B).keys()
8✔
3150
        A = self._normalize(A)
8✔
3151
        B = self._normalize(B)
8✔
3152
        result = []
8✔
3153
        for key in self.intersection()(A, B):
8✔
3154
            result.append((key, A[key]*w1 + B[key]*w2))
8✔
3155
        return 1, result
8✔
3156

3157
    def testIntersection(self):
8✔
3158
        inputs = self.As + self.Bs + self.emptys
8✔
3159
        for A in inputs:
8✔
3160
            for B in inputs:
8✔
3161
                want_w, want_s = self._wintersection(A, B)
8✔
3162
                got_w, got_s = self.weightedIntersection()(A, B)
8✔
3163
                self.assertEqual(got_w, want_w)
8✔
3164
                if isaset(got_s):
8✔
3165
                    self.assertEqual(got_s.keys(), want_s)
8✔
3166
                else:
3167
                    self.assertEqual(got_s.items(), want_s)
8✔
3168

3169
                for w1, w2 in self.weights:
8✔
3170
                    if (
8✔
3171
                        (w1 < 0 or w2 < 0) and
3172
                        not self.SUPPORTS_NEGATIVE_VALUES
3173
                    ):
3174
                        continue
8✔
3175
                    want_w, want_s = self._wintersection(A, B, w1, w2)
8✔
3176
                    got_w, got_s = self.weightedIntersection()(A, B, w1, w2)
8✔
3177
                    self.assertEqual(got_w, want_w)
8✔
3178
                    if isaset(got_s):
8✔
3179
                        self.assertEqual(got_s.keys(), want_s)
8✔
3180
                    else:
3181
                        self.assertEqual(got_s.items(), want_s)
8✔
3182

3183

3184
# Given a set builder (like OITreeSet or OISet), return a function that
3185
# takes a list of (key, value) pairs and builds a set out of the keys.
3186
def itemsToSet(setbuilder):
8✔
3187
    def result(items, setbuilder=setbuilder):
8✔
3188
        return setbuilder([key for key, value in items])
8✔
3189
    return result
8✔
3190

3191

3192
# 'thing' is a bucket, btree, set or treeset.  Return true iff it's one of the
3193
# latter two.
3194
def isaset(thing):
8✔
3195
    return not hasattr(thing, 'values')
8✔
3196

3197

3198
class MultiUnion(SignedMixin):
8✔
3199
    # Subclasses must set up (as class variables):
3200
    #     multiunion, union
3201
    #     mkset, mktreeset
3202
    #     mkbucket, mkbtree
3203

3204
    def setUp(self):
8✔
3205
        super().setUp()
8✔
3206
        _skip_if_pure_py_and_py_test(self)
8✔
3207

3208
    def testEmpty(self):
8✔
3209
        self.assertEqual(len(self.multiunion([])), 0)
8✔
3210

3211
    def _testOne(self, builder):
8✔
3212
        for sequence in (
8✔
3213
                [3],
3214
                list(range(20)),
3215
                list(range(-10, 0, 2)) + list(range(1, 10, 2)),
3216
        ):
3217
            if min(sequence) < 0 and not self.SUPPORTS_NEGATIVE_KEYS:
8✔
3218
                continue
8✔
3219
            seq1 = sequence[:]
8✔
3220
            seq2 = list(reversed(sequence[:]))
8✔
3221
            seqsorted = sorted(sequence[:])
8✔
3222
            for seq in seq1, seq2, seqsorted:
8✔
3223
                input = builder(seq)
8✔
3224
                output = self.multiunion([input])
8✔
3225
                self.assertEqual(len(seq), len(output))
8✔
3226
                self.assertEqual(seqsorted, list(output))
8✔
3227

3228
    def testOneBTSet(self):
8✔
3229
        self._testOne(self.mkset)
8✔
3230

3231
    def testOneBTTreeSet(self):
8✔
3232
        self._testOne(self.mktreeset)
8✔
3233

3234
    def testOneList(self):
8✔
3235
        self._testOne(list)
8✔
3236

3237
    def testOneTuple(self):
8✔
3238
        self._testOne(tuple)
8✔
3239

3240
    def testOneSet(self):
8✔
3241
        self._testOne(set)
8✔
3242

3243
    def testOneGenerator(self):
8✔
3244
        def generator(seq):
8✔
3245
            yield from seq
8✔
3246

3247
        self._testOne(generator)
8✔
3248

3249
    def testValuesIgnored(self):
8✔
3250
        for builder in self.mkbucket, self.mkbtree, dict:
8✔
3251
            input = builder([(1, 2), (3, 4), (5, 6)])
8✔
3252
            output = self.multiunion([input])
8✔
3253
            self.assertEqual([1, 3, 5], list(output))
8✔
3254

3255
    def testValuesIgnoredNonInteger(self):
8✔
3256
        # This only uses a dict because the bucket and tree can't
3257
        # hold non-integers.
3258
        i1 = {1: 'a', 2: 'b'}
8✔
3259
        i2 = {1: 'c', 3: 'd'}
8✔
3260

3261
        output = self.multiunion((i1, i2))
8✔
3262
        self.assertEqual([1, 2, 3], list(output))
8✔
3263

3264
    def testRangeInputs(self):
8✔
3265
        i1 = range(3)
8✔
3266
        i2 = range(7)
8✔
3267

3268
        output = self.multiunion((i1, i2))
8✔
3269
        self.assertEqual([0, 1, 2, 3, 4, 5, 6], list(output))
8✔
3270

3271
    def testNegativeKeys(self):
8✔
3272
        i1 = (-1, -2, -3)
8✔
3273
        i2 = (0, 1, 2)
8✔
3274

3275
        if not self.SUPPORTS_NEGATIVE_KEYS:
8✔
3276
            with self.assertRaises(TypeError):
8✔
3277
                self.multiunion((i2, i1))
8✔
3278
        else:
3279
            output = self.multiunion((i2, i1))
8✔
3280
            self.assertEqual([-3, -2, -1, 0, 1, 2], list(output))
8✔
3281

3282
    def testOneIterableWithBadKeys(self):
8✔
3283
        i1 = [1, 2, 3, 'a']
8✔
3284
        for kind in list, tuple:
8✔
3285
            with self.assertRaises(TypeError):
8✔
3286
                self.multiunion((kind(i1),))
8✔
3287

3288
    def testBadIterable(self):
8✔
3289
        class MyException(Exception):
8✔
3290
            pass
8✔
3291

3292
        def gen():
8✔
3293
            yield from range(3)
8✔
3294
            raise MyException
8✔
3295

3296
        with self.assertRaises(MyException):
8✔
3297
            self.multiunion((gen(),))
8✔
3298

3299
    def testBigInput(self):
8✔
3300
        N = 100000
8✔
3301
        if (
8✔
3302
            (_c_optimizations_ignored() or 'Py' in type(self).__name__) and
3303
            not PYPY
3304
        ):
3305
            # This is extremely slow in CPython implemented in Python,
3306
            # taking 20s or more on a 2015-era laptop
3307
            N = N // 10
7✔
3308
        input = self.mkset(list(range(N)))
8✔
3309
        output = self.multiunion([input] * 10)
8✔
3310
        self.assertEqual(len(output), N)
8✔
3311
        self.assertEqual(output.minKey(), 0)
8✔
3312
        self.assertEqual(output.maxKey(), N-1)
8✔
3313
        self.assertEqual(list(output), list(range(N)))
8✔
3314

3315
    def testLotsOfLittleOnes(self):
8✔
3316
        from random import shuffle
8✔
3317
        N = 5000
8✔
3318
        inputs = []
8✔
3319
        mkset, mktreeset = self.mkset, self.mktreeset
8✔
3320
        for i in range(N):
8✔
3321
            if self.SUPPORTS_NEGATIVE_KEYS:
8✔
3322
                base = i * 4 - N
8✔
3323
            else:
3324
                base = i * 4
8✔
3325
            inputs.append(mkset([base, base + 1]))
8✔
3326
            inputs.append(mktreeset([base + 2, base + 3]))
8✔
3327
        shuffle(inputs)
8✔
3328
        output = self.multiunion(inputs)
8✔
3329
        self.assertEqual(len(output), N * 4)
8✔
3330
        if self.SUPPORTS_NEGATIVE_KEYS:
8✔
3331
            self.assertEqual(list(output), list(range(-N, 3 * N)))
8✔
3332
        else:
3333
            self.assertEqual(list(output), list(range(N * 4)))
8✔
3334

3335
    def testFunkyKeyIteration(self):
8✔
3336
        # The internal set iteration protocol allows "iterating over" a
3337
        # a single key as if it were a set.
3338
        N = 100
8✔
3339
        union, mkset = self.union, self.mkset
8✔
3340
        slow = mkset()
8✔
3341
        for i in range(N):
8✔
3342
            slow = union(slow, mkset([i]))
8✔
3343
        fast = self.multiunion(list(range(N)))  # ~ N distinct singleton sets
8✔
3344
        self.assertEqual(len(slow), N)
8✔
3345
        self.assertEqual(len(fast), N)
8✔
3346
        self.assertEqual(list(slow), list(fast))
8✔
3347
        self.assertEqual(list(fast), list(range(N)))
8✔
3348

3349

3350
class ConflictTestBase(SignedMixin):
8✔
3351
    # Tests common to all types: sets, buckets, and BTrees
3352

3353
    storage = None
8✔
3354
    db = None
8✔
3355

3356
    def setUp(self):
8✔
3357
        super().setUp()
8✔
3358
        _skip_if_pure_py_and_py_test(self)
8✔
3359

3360
        def identity(x):
8✔
3361
            return x
8✔
3362

3363
        self.key_tx = abs if not self.SUPPORTS_NEGATIVE_KEYS else identity
8✔
3364
        self.val_tx = abs if not self.SUPPORTS_NEGATIVE_VALUES else identity
8✔
3365

3366
    def tearDown(self):
8✔
3367
        import transaction
8✔
3368
        transaction.abort()
8✔
3369
        if self.storage is not None:
8!
3370
            self.storage.close()
×
3371
            self.storage.cleanup()
×
3372

3373
    def _makeOne(self):
8✔
3374
        return self._getTargetClass()()
8✔
3375

3376
    def openDB(self):
8✔
3377
        import os
×
3378

3379
        from ZODB.DB import DB
×
3380
        from ZODB.FileStorage import FileStorage
×
3381
        n = 'fs_tmp__%s' % os.getpid()
×
3382
        self.storage = FileStorage(n)
×
3383
        self.db = DB(self.storage)
×
3384
        return self.db
×
3385

3386
    def _test_merge(
8✔
3387
        self, o1, o2, o3, expect, message='failed to merge', should_fail=False,
3388
    ):
3389
        from BTrees.Interfaces import BTreesConflictError
8✔
3390
        s1 = o1.__getstate__()
8✔
3391
        s2 = o2.__getstate__()
8✔
3392
        s3 = o3.__getstate__()
8✔
3393
        expected = expect.__getstate__()
8✔
3394
        if expected is None:
8✔
3395
            expected = ((((),),),)
8✔
3396

3397
        if should_fail:
8✔
3398
            with self.assertRaises(BTreesConflictError):
8✔
3399
                __traceback_info__ = message
8✔
3400
                o1._p_resolveConflict(s1, s2, s3)
8✔
3401
        else:
3402
            merged = o1._p_resolveConflict(s1, s2, s3)
8✔
3403
            self.assertEqual(merged, expected, message)
8✔
3404

3405

3406
class MappingConflictTestBase(ConflictTestBase):
8✔
3407
    # Tests common to mappings (buckets, btrees).
3408

3409
    def _skip_if_only_small_keys(self):
8✔
3410
        try:
8✔
3411
            self.coerce_to_key(99999)
8✔
3412
        except TypeError:
8✔
3413
            assert 'fs' in self._getTargetClass().__name__
8✔
3414
            self.skipTest("Uses keys too large for fsBTree")
8✔
3415

3416
    def _deletefail(self):
8✔
UNCOV
3417
        t = self._makeOne()
×
3418
        del t[self.KEYS[1]]
×
3419

3420
    def _setupConflict(self):
8✔
3421
        if self._getTargetClass().__name__.startswith('fs'):
8✔
3422
            # Too many negative numbers, could be done with a little work
3423
            # though.
3424
            self.skipTest("Needs ported to fsBTree")
8✔
3425
        key_tx = self.key_tx
8✔
3426
        keys = [
8✔
3427
            -5124, -7377, 2274, 8801, -9901, 7327, 1565, 17, -679,
3428
            3686, -3607, 14, 6419, -5637, 6040, -4556, -8622, 3847, 7191,
3429
            -4067
3430
        ]
3431
        keys = [key_tx(v) for v in keys]
8✔
3432

3433
        e1 = [(-1704, 0), (5420, 1), (-239, 2), (4024, 3), (-6984, 4)]
8✔
3434
        e1 = [(key_tx(k), v) for k, v in e1]
8✔
3435
        e2 = [(7745, 0), (4868, 1), (-2548, 2), (-2711, 3), (-3154, 4)]
8✔
3436
        e2 = [(key_tx(k), v) for k, v in e2]
8✔
3437

3438
        base = self._makeOne()
8✔
3439
        base.update([
8✔
3440
            (self.coerce_to_key(i), self.coerce_to_value(i * i))
3441
            for i in keys[:20]
3442
        ])
3443
        b1 = type(base)(base)
8✔
3444
        b2 = type(base)(base)
8✔
3445
        bm = type(base)(base)
8✔
3446

3447
        items = base.items()
8✔
3448

3449
        return base, b1, b2, bm, e1, e2, items
8✔
3450

3451
    def testMergeDelete(self):
8✔
3452
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3453
        del b1[items[1][0]]
8✔
3454
        del b2[items[5][0]]
8✔
3455
        del b1[items[-1][0]]
8✔
3456
        del b2[items[-2][0]]
8✔
3457
        del bm[items[1][0]]
8✔
3458
        del bm[items[5][0]]
8✔
3459
        del bm[items[-1][0]]
8✔
3460
        del bm[items[-2][0]]
8✔
3461
        self._test_merge(base, b1, b2, bm, 'merge  delete')
8✔
3462

3463
    def testMergeDeleteAndUpdate(self):
8✔
3464
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3465
        V = self.VALUES
8✔
3466
        del b1[items[1][0]]
8✔
3467
        b2[items[5][0]] = V[1]
8✔
3468
        del b1[items[-1][0]]
8✔
3469
        b2[items[-2][0]] = V[2]
8✔
3470
        del bm[items[1][0]]
8✔
3471
        bm[items[5][0]] = V[1]
8✔
3472
        del bm[items[-1][0]]
8✔
3473
        bm[items[-2][0]] = V[2]
8✔
3474
        self._test_merge(base, b1, b2, bm, 'merge update and delete')
8✔
3475

3476
    def testMergeUpdate(self):
8✔
3477
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3478
        V = self.VALUES
8✔
3479
        b1[items[0][0]] = V[1]
8✔
3480
        b2[items[5][0]] = V[2]
8✔
3481
        b1[items[-1][0]] = V[3]
8✔
3482
        b2[items[-2][0]] = V[4]
8✔
3483
        bm[items[0][0]] = V[1]
8✔
3484
        bm[items[5][0]] = V[2]
8✔
3485
        bm[items[-1][0]] = V[3]
8✔
3486
        bm[items[-2][0]] = V[4]
8✔
3487
        self._test_merge(base, b1, b2, bm, 'merge update')
8✔
3488

3489
    def testFailMergeDelete(self):
8✔
3490
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3491
        del b1[items[0][0]]
8✔
3492
        del b2[items[0][0]]
8✔
3493
        self._test_merge(base, b1, b2, bm, 'merge conflicting delete',
8✔
3494
                         should_fail=1)
3495

3496
    def testFailMergeUpdate(self):
8✔
3497
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3498
        V = self.VALUES
8✔
3499
        b1[items[0][0]] = V[1]
8✔
3500
        b2[items[0][0]] = V[2]
8✔
3501
        self._test_merge(base, b1, b2, bm, 'merge conflicting update',
8✔
3502
                         should_fail=1)
3503

3504
    def testFailMergeDeleteAndUpdate(self):
8✔
3505
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3506
        del b1[items[0][0]]
8✔
3507
        b2[items[0][0]] = self.val_tx(-9)
8✔
3508
        self._test_merge(
8✔
3509
            base,
3510
            b1,
3511
            b2,
3512
            bm,
3513
            'merge conflicting update and delete',
3514
            should_fail=1,
3515
        )
3516

3517
    def testMergeInserts(self):
8✔
3518
        self._skip_if_only_small_keys()
8✔
3519
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3520
        b1[self.key_tx(-99999)] = self.val_tx(-99999)
8✔
3521
        b1[e1[0][0]] = e1[0][1]
8✔
3522
        b2[99999] = self.coerce_to_value(99999)
8✔
3523
        b2[e1[2][0]] = e1[2][1]
8✔
3524

3525
        bm[self.key_tx(-99999)] = self.val_tx(-99999)
8✔
3526
        bm[e1[0][0]] = e1[0][1]
8✔
3527
        bm[99999] = self.coerce_to_value(99999)
8✔
3528
        bm[e1[2][0]] = e1[2][1]
8✔
3529
        self._test_merge(base, b1, b2, bm, 'merge insert',
8✔
3530
                         should_fail=not self.SUPPORTS_NEGATIVE_KEYS)
3531

3532
    def testMergeInsertsFromEmpty(self):
8✔
3533
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3534

3535
        base.clear()
8✔
3536
        b1.clear()
8✔
3537
        b2.clear()
8✔
3538
        bm.clear()
8✔
3539

3540
        b1.update(e1)
8✔
3541
        bm.update(e1)
8✔
3542
        b2.update(e2)
8✔
3543
        bm.update(e2)
8✔
3544

3545
        self._test_merge(base, b1, b2, bm, 'merge insert from empty')
8✔
3546

3547
    def testFailMergeEmptyAndFill(self):
8✔
3548
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3549

3550
        b1.clear()
8✔
3551
        bm.clear()
8✔
3552
        b2.update(e2)
8✔
3553
        bm.update(e2)
8✔
3554

3555
        self._test_merge(
8✔
3556
            base, b1, b2, bm, 'merge insert from empty', should_fail=1,
3557
        )
3558

3559
    def testMergeEmpty(self):
8✔
3560
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3561

3562
        b1.clear()
8✔
3563
        bm.clear()
8✔
3564

3565
        self._test_merge(
8✔
3566
            base, b1, b2, bm, 'empty one and not other', should_fail=1,
3567
        )
3568

3569
    def testFailMergeInsert(self):
8✔
3570
        self._skip_if_only_small_keys()
8✔
3571
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3572
        b1[self.key_tx(-99999)] = self.val_tx(-99999)
8✔
3573
        b1[e1[0][0]] = e1[0][1]
8✔
3574
        b2[99999] = self.coerce_to_value(99999)
8✔
3575
        b2[e1[0][0]] = e1[0][1]
8✔
3576
        self._test_merge(base, b1, b2, bm, 'merge conflicting inserts',
8✔
3577
                         should_fail=1)
3578

3579

3580
class SetConflictTestBase(ConflictTestBase):
8✔
3581
    "Set (as opposed to TreeSet) specific tests."
3582

3583
    def _skip_if_only_small_keys(self):
8✔
3584
        try:
8✔
3585
            self.coerce_to_key(99999)
8✔
3586
        except TypeError:
8✔
3587
            assert 'fs' in self._getTargetClass().__name__
8✔
3588
            self.skipTest("Uses keys too large for fsBTree")
8✔
3589

3590
    def _setupConflict(self):
8✔
3591

3592
        def to_key(x):
8✔
3593
            return self.coerce_to_key(self.key_tx(x))
8✔
3594

3595
        items = [
8✔
3596
            to_key(x)for x in [
3597
                -5124, -7377, 2274, 8801, -9901, 7327, 1565, 17, -679,
3598
                3686, -3607, 14, 6419, -5637, 6040, -4556, -8622, 3847, 7191,
3599
                -4067,
3600
            ]
3601
        ]
3602

3603
        e1 = [to_key(x) for x in
8✔
3604
              [-1704, 5420, -239, 4024, -6984]]
3605
        e2 = [to_key(x) for x in
8✔
3606
              [7745, 4868, -2548, -2711, -3154]]
3607

3608
        base = self._makeOne()
8✔
3609
        base.update(items)
8✔
3610
        b1 = base.__class__(base)
8✔
3611
        b2 = base.__class__(base)
8✔
3612
        bm = base.__class__(base)
8✔
3613

3614
        items = base.keys()
8✔
3615

3616
        return base, b1, b2, bm, e1, e2, items
8✔
3617

3618
    def testMergeDelete(self):
8✔
3619
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3620
        b1.remove(items[1])
8✔
3621
        b2.remove(items[5])
8✔
3622
        b1.remove(items[-1])
8✔
3623
        b2.remove(items[-2])
8✔
3624
        bm.remove(items[1])
8✔
3625
        bm.remove(items[5])
8✔
3626
        bm.remove(items[-1])
8✔
3627
        bm.remove(items[-2])
8✔
3628
        self._test_merge(base, b1, b2, bm, 'merge  delete')
8✔
3629

3630
    def testFailMergeDelete(self):
8✔
3631
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3632
        b1.remove(items[0])
8✔
3633
        b2.remove(items[0])
8✔
3634
        self._test_merge(base, b1, b2, bm, 'merge conflicting delete',
8✔
3635
                         should_fail=1)
3636

3637
    def testMergeInserts(self):
8✔
3638
        self._skip_if_only_small_keys()
8✔
3639
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3640

3641
        b1.insert(self.key_tx(-99999))
8✔
3642
        b1.insert(e1[0])
8✔
3643
        b2.insert(99999)
8✔
3644
        b2.insert(e1[2])
8✔
3645

3646
        bm.insert(self.key_tx(-99999))
8✔
3647
        bm.insert(e1[0])
8✔
3648
        bm.insert(99999)
8✔
3649
        bm.insert(e1[2])
8✔
3650
        self._test_merge(base, b1, b2, bm, 'merge insert',
8✔
3651
                         should_fail=not self.SUPPORTS_NEGATIVE_KEYS)
3652

3653
    def testMergeInsertsFromEmpty(self):
8✔
3654
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3655

3656
        base.clear()
8✔
3657
        b1.clear()
8✔
3658
        b2.clear()
8✔
3659
        bm.clear()
8✔
3660

3661
        b1.update(e1)
8✔
3662
        bm.update(e1)
8✔
3663
        b2.update(e2)
8✔
3664
        bm.update(e2)
8✔
3665

3666
        self._test_merge(base, b1, b2, bm, 'merge insert from empty')
8✔
3667

3668
    def testFailMergeEmptyAndFill(self):
8✔
3669
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3670

3671
        b1.clear()
8✔
3672
        bm.clear()
8✔
3673
        b2.update(e2)
8✔
3674
        bm.update(e2)
8✔
3675

3676
        self._test_merge(
8✔
3677
            base, b1, b2, bm, 'merge insert from empty', should_fail=1,
3678
        )
3679

3680
    def testMergeEmpty(self):
8✔
3681
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3682

3683
        b1.clear()
8✔
3684
        bm.clear()
8✔
3685

3686
        self._test_merge(
8✔
3687
            base, b1, b2, bm, 'empty one and not other', should_fail=1,
3688
        )
3689

3690
    def testFailMergeInsert(self):
8✔
3691
        self._skip_if_only_small_keys()
8✔
3692
        base, b1, b2, bm, e1, e2, items = self._setupConflict()
8✔
3693
        b1.insert(self.coerce_to_key(self.key_tx(-99999)))
8✔
3694
        b1.insert(e1[0])
8✔
3695
        b2.insert(99999)
8✔
3696
        b2.insert(e1[0])
8✔
3697
        self._test_merge(base, b1, b2, bm, 'merge conflicting inserts',
8✔
3698
                         should_fail=1)
3699

3700
# utility functions
3701

3702

3703
def lsubtract(l1, l2):
8✔
3704
    l1 = list(l1)
8✔
3705
    l2 = list(l2)
8✔
3706
    return (list(filter(lambda x, l1=l1: x not in l1, l2)) +
8✔
3707
            list(filter(lambda x, l2=l2: x not in l2, l1)))
3708

3709

3710
def realseq(itemsob):
8✔
3711
    return list(itemsob)
8✔
3712

3713

3714
def permutations(x):
8✔
3715
    # Return a list of all permutations of list x.
3716
    n = len(x)
8✔
3717
    if n <= 1:
8✔
3718
        return [x]
8✔
3719
    result = []
8✔
3720
    x0 = x[0]
8✔
3721
    for i in range(n):
8✔
3722
        # Build the (n-1)! permutations with x[i] in the first position.
3723
        xcopy = x[:]
8✔
3724
        first, xcopy[i] = xcopy[i], x0
8✔
3725
        result.extend([[first] + p for p in permutations(xcopy[1:])])
8✔
3726
    return result
8✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc