From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:06:21 +0000 (+0100) Subject: [3.14] Add regression test for add() after remove() with hash collision in set (GH... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d210c7736bda9e4369a842e398c4420078667e79;p=thirdparty%2FPython%2Fcpython.git [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 --- diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index b761f68d1b3e..1c5a1d20daf5 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -12,6 +12,15 @@ from test import support 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'' + + class DictTest(unittest.TestCase): def test_invalid_keyword_arguments(self): @@ -1648,6 +1657,29 @@ class DictTest(unittest.TestCase): 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 5f38f8e319ef..34da01c34151 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'' + class BadCmp: def __hash__(self): return 1 @@ -675,6 +683,28 @@ class TestSet(TestJointOps, unittest.TestCase): 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