https://github.com/python/cpython/commit/d210c7736bda9e4369a842e398c4420078667e79
commit: d210c7736bda9e4369a842e398c4420078667e79
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-01-15T09:06:21Z
summary:

[3.14] Add regression test for add() after remove() with hash collision in set 
(GH-143781) (GH-143858)

(cherry picked from commit 565685f6e88fd333326baff6469f53cfff28e01e)

Co-authored-by: Serhiy Storchaka <[email protected]>

files:
M Lib/test/test_dict.py
M Lib/test/test_set.py

diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py
index b761f68d1b3e70..1c5a1d20daf5b2 100644
--- a/Lib/test/test_dict.py
+++ b/Lib/test/test_dict.py
@@ -12,6 +12,15 @@
 from test.support import import_helper
 
 
+class CustomHash:
+    def __init__(self, hash):
+        self.hash = hash
+    def __hash__(self):
+        return self.hash
+    def __repr__(self):
+        return f'<CustomHash {self.hash} at {id(self):#x}>'
+
+
 class DictTest(unittest.TestCase):
 
     def test_invalid_keyword_arguments(self):
@@ -1648,6 +1657,29 @@ class MyClass: pass
         d[MyStr("attr1")] = 2
         self.assertIsInstance(list(d)[0], MyStr)
 
+    def test_hash_collision_remove_add(self):
+        self.maxDiff = None
+        # There should be enough space, so all elements with unique hash
+        # will be placed in corresponding cells without collision.
+        n = 64
+        items = [(CustomHash(h), h) for h in range(n)]
+        # Keys with hash collision.
+        a = CustomHash(n)
+        b = CustomHash(n)
+        items += [(a, 'a'), (b, 'b')]
+        d = dict(items)
+        self.assertEqual(len(d), len(items), d)
+        del d[a]
+        # "a" has been replaced with a dummy.
+        del items[n]
+        self.assertEqual(len(d), len(items), d)
+        self.assertEqual(d, dict(items))
+        d[b] = 'c'
+        # "b" should not replace the dummy.
+        items[n] = (b, 'c')
+        self.assertEqual(len(d), len(items), d)
+        self.assertEqual(d, dict(items))
+
 
 class CAPITest(unittest.TestCase):
 
diff --git a/Lib/test/test_set.py b/Lib/test/test_set.py
index 5f38f8e319efc3..34da01c3415128 100644
--- a/Lib/test/test_set.py
+++ b/Lib/test/test_set.py
@@ -20,6 +20,14 @@ def check_pass_thru():
     raise PassThru
     yield 1
 
+class CustomHash:
+    def __init__(self, hash):
+        self.hash = hash
+    def __hash__(self):
+        return self.hash
+    def __repr__(self):
+        return f'<CustomHash {self.hash} at {id(self):#x}>'
+
 class BadCmp:
     def __hash__(self):
         return 1
@@ -675,6 +683,28 @@ def __hash__(self):
         with self.assertRaises(KeyError):
             myset.discard(elem2)
 
+    def test_hash_collision_remove_add(self):
+        self.maxDiff = None
+        # There should be enough space, so all elements with unique hash
+        # will be placed in corresponding cells without collision.
+        n = 64
+        elems = [CustomHash(h) for h in range(n)]
+        # Elements with hash collision.
+        a = CustomHash(n)
+        b = CustomHash(n)
+        elems += [a, b]
+        s = self.thetype(elems)
+        self.assertEqual(len(s), len(elems), s)
+        s.remove(a)
+        # "a" has been replaced with a dummy.
+        del elems[n]
+        self.assertEqual(len(s), len(elems), s)
+        self.assertEqual(s, set(elems))
+        s.add(b)
+        # "b" should not replace the dummy.
+        self.assertEqual(len(s), len(elems), s)
+        self.assertEqual(s, set(elems))
+
 
 class SetSubclass(set):
     pass

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to