372
372
if self.module is _static_tuple_py:
374
374
self.assertIsNot(None, self.module._C_API)
378
class TestStaticTupleInterned(tests.TestCase):
380
_test_needs_features = [CompiledStaticTuple]
382
def assertIn(self, obj, container):
383
self.assertTrue(obj in container,
384
'%s not found in %s' % (obj, container))
386
def assertNotIn(self, obj, container):
387
self.assertTrue(obj not in container,
388
'We found %s in %s' % (obj, container))
390
def assertFillState(self, used, fill, mask, obj):
391
self.assertEqual((used, fill, mask), (obj.used, obj.fill, obj.mask))
393
def assertRefcount(self, count, obj):
394
"""Assert that the refcount for obj is what we expect.
396
Note that this automatically adjusts for the fact that calling
397
assertRefcount actually creates a new pointer, as does calling
398
sys.getrefcount. So pass the expected value *before* the call.
400
# I don't understand why it is count+3 here, but it seems to be
401
# correct. If I check in the calling function, with:
402
# self.assertEqual(count+1, sys.getrefcount(obj))
403
# Then it works fine. Something about passing it to assertRefcount is
404
# actually double-incrementing (and decrementing) the refcount
405
self.assertEqual(count+3, sys.getrefcount(obj))
407
def test_initial(self):
408
obj = _module.StaticTupleInterner()
409
self.assertEqual(0, len(obj))
410
st = StaticTuple('foo', 'bar')
411
self.assertFillState(0, 0, 0x3ff, obj)
413
def test__lookup(self):
414
# The tuple hash function is rather good at entropy. For all integers
415
# 0=>1023, hash((i,)) & 1023 maps to a unique output, and hash((i,j))
416
# maps to all 1024 fields evenly.
417
# However, hash((c,d))& 1023 for characters has an uneven distribution
418
# of collisions, for example:
419
# ('a', 'a'), ('f', '4'), ('p', 'r'), ('q', '1'), ('F', 'T'),
420
# ('Q', 'Q'), ('V', 'd'), ('7', 'C')
422
obj = _module.StaticTupleInterner()
423
offset, val = obj._test_lookup(StaticTuple('a', 'a'))
424
self.assertEqual(643, offset)
425
self.assertEqual('<null>', val)
426
offset, val = obj._test_lookup(StaticTuple('f', '4'))
427
self.assertEqual(643, offset)
428
self.assertEqual('<null>', val)
429
offset, val = obj._test_lookup(StaticTuple('p', 'r'))
430
self.assertEqual(643, offset)
431
self.assertEqual('<null>', val)
433
def test_get_set_del_with_collisions(self):
434
obj = _module.StaticTupleInterner()
435
k1 = StaticTuple('a', 'a')
436
k2 = StaticTuple('f', '4') # collides
437
k3 = StaticTuple('p', 'r')
438
k4 = StaticTuple('q', '1')
439
self.assertEqual((643, '<null>'), obj._test_lookup(k1))
440
self.assertEqual((643, '<null>'), obj._test_lookup(k2))
441
self.assertEqual((643, '<null>'), obj._test_lookup(k3))
442
self.assertEqual((643, '<null>'), obj._test_lookup(k4))
444
self.assertIn(k1, obj)
445
self.assertNotIn(k2, obj)
446
self.assertNotIn(k3, obj)
447
self.assertNotIn(k4, obj)
448
self.assertEqual((643, k1), obj._test_lookup(k1))
449
self.assertEqual((787, '<null>'), obj._test_lookup(k2))
450
self.assertEqual((787, '<null>'), obj._test_lookup(k3))
451
self.assertEqual((787, '<null>'), obj._test_lookup(k4))
452
self.assertIs(k1, obj[k1])
454
self.assertIs(k2, obj[k2])
455
self.assertEqual((643, k1), obj._test_lookup(k1))
456
self.assertEqual((787, k2), obj._test_lookup(k2))
457
self.assertEqual((660, '<null>'), obj._test_lookup(k3))
458
# Even though k4 collides for the first couple of iterations, the hash
459
# perturbation uses the full width hash (not just the masked value), so
461
self.assertEqual((180, '<null>'), obj._test_lookup(k4))
462
self.assertEqual((643, k1), obj._test_lookup(('a', 'a')))
463
self.assertEqual((787, k2), obj._test_lookup(('f', '4')))
464
self.assertEqual((660, '<null>'), obj._test_lookup(('p', 'r')))
465
self.assertEqual((180, '<null>'), obj._test_lookup(('q', '1')))
467
self.assertIs(k3, obj[k3])
468
self.assertIn(k1, obj)
469
self.assertIn(k2, obj)
470
self.assertIn(k3, obj)
471
self.assertNotIn(k4, obj)
474
self.assertEqual((643, '<dummy>'), obj._test_lookup(k1))
475
self.assertEqual((787, k2), obj._test_lookup(k2))
476
self.assertEqual((660, k3), obj._test_lookup(k3))
477
self.assertEqual((643, '<dummy>'), obj._test_lookup(k4))
478
self.assertNotIn(k1, obj)
479
self.assertIn(k2, obj)
480
self.assertIn(k3, obj)
481
self.assertNotIn(k4, obj)
484
obj = _module.StaticTupleInterner()
485
self.assertFillState(0, 0, 0x3ff, obj)
486
k1 = StaticTuple('foo')
487
self.assertRefcount(1, k1)
488
self.assertIs(k1, obj.add(k1))
489
self.assertFillState(1, 1, 0x3ff, obj)
490
self.assertRefcount(2, k1)
492
self.assertRefcount(3, k1)
493
self.assertIs(k1, ktest)
495
self.assertRefcount(2, k1)
496
k2 = StaticTuple('foo')
497
self.assertRefcount(1, k2)
498
self.assertIsNot(k1, k2)
499
# doesn't add anything, so the counters shouldn't be adjusted
500
self.assertIs(k1, obj.add(k2))
501
self.assertFillState(1, 1, 0x3ff, obj)
502
self.assertRefcount(2, k1) # not changed
503
self.assertRefcount(1, k2) # not incremented
504
self.assertIs(k1, obj[k1])
505
self.assertIs(k1, obj[k2])
506
self.assertRefcount(2, k1)
507
self.assertRefcount(1, k2)
508
# Deleting an entry should remove the fill, but not the used
510
self.assertFillState(0, 1, 0x3ff, obj)
511
self.assertRefcount(1, k1)
512
k3 = StaticTuple('bar')
513
self.assertRefcount(1, k3)
514
self.assertIs(k3, obj.add(k3))
515
self.assertFillState(1, 2, 0x3ff, obj)
516
self.assertRefcount(2, k3)
517
self.assertIs(k2, obj.add(k2))
518
self.assertFillState(2, 2, 0x3ff, obj)
519
self.assertRefcount(1, k1)
520
self.assertRefcount(2, k2)
521
self.assertRefcount(2, k3)
523
def test_discard(self):
524
obj = _module.StaticTupleInterner()
525
k1 = StaticTuple('foo')
526
k2 = StaticTuple('foo')
527
k3 = StaticTuple('bar')
528
self.assertRefcount(1, k1)
529
self.assertRefcount(1, k2)
530
self.assertRefcount(1, k3)
532
self.assertRefcount(2, k1)
533
self.assertEqual(0, obj.discard(k3))
534
self.assertRefcount(1, k3)
536
self.assertRefcount(2, k3)
537
self.assertEqual(1, obj.discard(k3))
538
self.assertRefcount(1, k3)
540
def test__delitem__(self):
541
obj = _module.StaticTupleInterner()
542
k1 = StaticTuple('foo')
543
k2 = StaticTuple('foo')
544
k3 = StaticTuple('bar')
545
self.assertRefcount(1, k1)
546
self.assertRefcount(1, k2)
547
self.assertRefcount(1, k3)
549
self.assertRefcount(2, k1)
550
self.assertRaises(KeyError, obj.__delitem__, k3)
551
self.assertRefcount(1, k3)
553
self.assertRefcount(2, k3)
555
self.assertRefcount(1, k3)
557
def test__resize(self):
558
obj = _module.StaticTupleInterner()
559
k1 = StaticTuple('foo')
560
k2 = StaticTuple('bar')
561
k3 = StaticTuple('baz')
566
self.assertFillState(2, 3, 0x3ff, obj)
567
self.assertEqual(1024, obj._resize(500))
568
# Doesn't change the size, but does change the content
569
self.assertFillState(2, 2, 0x3ff, obj)
572
self.assertFillState(2, 3, 0x3ff, obj)
573
self.assertEqual(4096, obj._resize(4095))
574
self.assertFillState(2, 2, 0xfff, obj)
575
self.assertIn(k1, obj)
576
self.assertIn(k2, obj)
577
self.assertNotIn(k3, obj)
579
self.assertIn(k2, obj)
581
self.assertEqual((591, '<dummy>'), obj._test_lookup(k2))
582
self.assertFillState(1, 2, 0xfff, obj)
583
self.assertEqual(2048, obj._resize(1024))
584
self.assertFillState(1, 1, 0x7ff, obj)
585
self.assertEqual((591, '<null>'), obj._test_lookup(k2))
587
def test_add_and_remove_lots_of_items(self):
588
obj = _module.StaticTupleInterner()
589
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'
592
k = StaticTuple(i, j)
594
num = len(chars)*len(chars)
595
self.assertFillState(num, num, 0x1fff, obj)
596
# Now delete all of the entries and it should shrink again
599
k = StaticTuple(i, j)
601
# It should be back to 1024 wide mask, though there may still be some
602
# dummy values in there
603
self.assertFillState(0, obj.fill, 0x3ff, obj)
604
# but there should be fewer than 1/5th dummy entries
605
self.assertTrue(obj.fill < 1024 / 5)
607
def test__iter__(self):
608
obj = _module.StaticTupleInterner()
609
k1 = StaticTuple('1')
610
k2 = StaticTuple('1', '2')
611
k3 = StaticTuple('3', '4')
618
self.assertEqual(sorted([k1, k2, k3]), sorted(all))
621
obj.add(StaticTuple('foo'))
623
self.assertRaises(RuntimeError, iterator.next)
624
# And even removing an item still causes it to fail
626
self.assertRaises(RuntimeError, iterator.next)