Skip to content

Commit d210c77

Browse files
[3.14] Add regression test for add() after remove() with hash collision in set (GH-143781) (GH-143858)
(cherry picked from commit 565685f) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 2e49323 commit d210c77

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

Lib/test/test_dict.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212
from test.support import import_helper
1313

1414

15+
class CustomHash:
16+
def __init__(self, hash):
17+
self.hash = hash
18+
def __hash__(self):
19+
return self.hash
20+
def __repr__(self):
21+
return f'<CustomHash {self.hash} at {id(self):#x}>'
22+
23+
1524
class DictTest(unittest.TestCase):
1625

1726
def test_invalid_keyword_arguments(self):
@@ -1648,6 +1657,29 @@ class MyClass: pass
16481657
d[MyStr("attr1")] = 2
16491658
self.assertIsInstance(list(d)[0], MyStr)
16501659

1660+
def test_hash_collision_remove_add(self):
1661+
self.maxDiff = None
1662+
# There should be enough space, so all elements with unique hash
1663+
# will be placed in corresponding cells without collision.
1664+
n = 64
1665+
items = [(CustomHash(h), h) for h in range(n)]
1666+
# Keys with hash collision.
1667+
a = CustomHash(n)
1668+
b = CustomHash(n)
1669+
items += [(a, 'a'), (b, 'b')]
1670+
d = dict(items)
1671+
self.assertEqual(len(d), len(items), d)
1672+
del d[a]
1673+
# "a" has been replaced with a dummy.
1674+
del items[n]
1675+
self.assertEqual(len(d), len(items), d)
1676+
self.assertEqual(d, dict(items))
1677+
d[b] = 'c'
1678+
# "b" should not replace the dummy.
1679+
items[n] = (b, 'c')
1680+
self.assertEqual(len(d), len(items), d)
1681+
self.assertEqual(d, dict(items))
1682+
16511683

16521684
class CAPITest(unittest.TestCase):
16531685

Lib/test/test_set.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ def check_pass_thru():
2020
raise PassThru
2121
yield 1
2222

23+
class CustomHash:
24+
def __init__(self, hash):
25+
self.hash = hash
26+
def __hash__(self):
27+
return self.hash
28+
def __repr__(self):
29+
return f'<CustomHash {self.hash} at {id(self):#x}>'
30+
2331
class BadCmp:
2432
def __hash__(self):
2533
return 1
@@ -675,6 +683,28 @@ def __hash__(self):
675683
with self.assertRaises(KeyError):
676684
myset.discard(elem2)
677685

686+
def test_hash_collision_remove_add(self):
687+
self.maxDiff = None
688+
# There should be enough space, so all elements with unique hash
689+
# will be placed in corresponding cells without collision.
690+
n = 64
691+
elems = [CustomHash(h) for h in range(n)]
692+
# Elements with hash collision.
693+
a = CustomHash(n)
694+
b = CustomHash(n)
695+
elems += [a, b]
696+
s = self.thetype(elems)
697+
self.assertEqual(len(s), len(elems), s)
698+
s.remove(a)
699+
# "a" has been replaced with a dummy.
700+
del elems[n]
701+
self.assertEqual(len(s), len(elems), s)
702+
self.assertEqual(s, set(elems))
703+
s.add(b)
704+
# "b" should not replace the dummy.
705+
self.assertEqual(len(s), len(elems), s)
706+
self.assertEqual(s, set(elems))
707+
678708

679709
class SetSubclass(set):
680710
pass

0 commit comments

Comments
 (0)