From: Serhiy Storchaka Date: Thu, 15 Jan 2026 10:04:34 +0000 (+0200) Subject: [3.13] 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=eb6dbd7fec335defc927aafc55da597d686154dd;p=thirdparty%2FPython%2Fcpython.git [3.13] Add regression test for add() after remove() with hash collision in set (GH-143781) (GH-143859) (cherry picked from commit 565685f6e88fd333326baff6469f53cfff28e01e) --- diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 52ef0c14ea3a..164169b30fcd 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -11,6 +11,15 @@ from test import support from test.support import import_helper, get_c_recursion_limit +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): @@ -1701,6 +1710,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 2c2c8702b6c0..bc0fa5589603 100644 --- a/Lib/test/test_set.py +++ b/Lib/test/test_set.py @@ -19,6 +19,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 @@ -635,6 +643,38 @@ class TestSet(TestJointOps, unittest.TestCase): myset >= myobj self.assertTrue(myobj.le_called) + def test_set_membership(self): + myfrozenset = frozenset(range(3)) + myset = {myfrozenset, "abc", 1} + self.assertIn(set(range(3)), myset) + self.assertNotIn(set(range(1)), myset) + myset.discard(set(range(3))) + self.assertEqual(myset, {"abc", 1}) + self.assertRaises(KeyError, myset.remove, set(range(1))) + self.assertRaises(KeyError, myset.remove, set(range(3))) + + 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