diff options
author | Antoine Pitrou <solipsis@pitrou.net> | 2012-03-01 15:28:14 (GMT) |
---|---|---|
committer | Antoine Pitrou <solipsis@pitrou.net> | 2012-03-01 15:28:14 (GMT) |
commit | 0836baa46af9349b79976f751ac8d569cb256edf (patch) | |
tree | eecf806dbbb66d846e869ccd7665aa1438a12c02 /Lib/test | |
parent | d7aff2d696bd4b0e074cb57544443e6706da9a52 (diff) | |
parent | bbe2f60b3c19ecaa02ca07be14474eaacfcb59a0 (diff) | |
download | cpython-0836baa46af9349b79976f751ac8d569cb256edf.zip cpython-0836baa46af9349b79976f751ac8d569cb256edf.tar.gz cpython-0836baa46af9349b79976f751ac8d569cb256edf.tar.bz2 |
Issue #14159: Fix the len() of weak containers (WeakSet, WeakKeyDictionary, WeakValueDictionary) to return a better approximation when some objects are dead or dying.
Moreover, the implementation is now O(1) rather than O(n).
Thanks to Yury Selivanov for reporting.
Diffstat (limited to 'Lib/test')
-rw-r--r-- | Lib/test/test_weakref.py | 60 | ||||
-rw-r--r-- | Lib/test/test_weakset.py | 47 |
2 files changed, 107 insertions, 0 deletions
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 8b5bbc3..74b9a87 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -812,11 +812,71 @@ class Object: def __hash__(self): return hash(self.arg) +class RefCycle: + def __init__(self): + self.cycle = self + class MappingTestCase(TestBase): COUNT = 10 + def check_len_cycles(self, dict_type, cons): + N = 20 + items = [RefCycle() for i in range(N)] + dct = dict_type(cons(o) for o in items) + # Keep an iterator alive + it = dct.items() + try: + next(it) + except StopIteration: + pass + del items + gc.collect() + n1 = len(dct) + del it + gc.collect() + n2 = len(dct) + # one item may be kept alive inside the iterator + self.assertIn(n1, (0, 1)) + self.assertEqual(n2, 0) + + def test_weak_keyed_len_cycles(self): + self.check_len_cycles(weakref.WeakKeyDictionary, lambda k: (k, 1)) + + def test_weak_valued_len_cycles(self): + self.check_len_cycles(weakref.WeakValueDictionary, lambda k: (1, k)) + + def check_len_race(self, dict_type, cons): + # Extended sanity checks for len() in the face of cyclic collection + self.addCleanup(gc.set_threshold, *gc.get_threshold()) + for th in range(1, 100): + N = 20 + gc.collect(0) + gc.set_threshold(th, th, th) + items = [RefCycle() for i in range(N)] + dct = dict_type(cons(o) for o in items) + del items + # All items will be collected at next garbage collection pass + it = dct.items() + try: + next(it) + except StopIteration: + pass + n1 = len(dct) + del it + n2 = len(dct) + self.assertGreaterEqual(n1, 0) + self.assertLessEqual(n1, N) + self.assertGreaterEqual(n2, 0) + self.assertLessEqual(n2, n1) + + def test_weak_keyed_len_race(self): + self.check_len_race(weakref.WeakKeyDictionary, lambda k: (k, 1)) + + def test_weak_valued_len_race(self): + self.check_len_race(weakref.WeakValueDictionary, lambda k: (1, k)) + def test_weak_values(self): # # This exercises d.copy(), d.items(), d[], del d[], len(d). diff --git a/Lib/test/test_weakset.py b/Lib/test/test_weakset.py index 3cddf40..35db7a6 100644 --- a/Lib/test/test_weakset.py +++ b/Lib/test/test_weakset.py @@ -17,6 +17,10 @@ import contextlib class Foo: pass +class RefCycle: + def __init__(self): + self.cycle = self + class TestWeakSet(unittest.TestCase): @@ -359,6 +363,49 @@ class TestWeakSet(unittest.TestCase): s.clear() self.assertEqual(len(s), 0) + def test_len_cycles(self): + N = 20 + items = [RefCycle() for i in range(N)] + s = WeakSet(items) + del items + it = iter(s) + try: + next(it) + except StopIteration: + pass + gc.collect() + n1 = len(s) + del it + gc.collect() + n2 = len(s) + # one item may be kept alive inside the iterator + self.assertIn(n1, (0, 1)) + self.assertEqual(n2, 0) + + def test_len_race(self): + # Extended sanity checks for len() in the face of cyclic collection + self.addCleanup(gc.set_threshold, *gc.get_threshold()) + for th in range(1, 100): + N = 20 + gc.collect(0) + gc.set_threshold(th, th, th) + items = [RefCycle() for i in range(N)] + s = WeakSet(items) + del items + # All items will be collected at next garbage collection pass + it = iter(s) + try: + next(it) + except StopIteration: + pass + n1 = len(s) + del it + n2 = len(s) + self.assertGreaterEqual(n1, 0) + self.assertLessEqual(n1, N) + self.assertGreaterEqual(n2, 0) + self.assertLessEqual(n2, n1) + def test_main(verbose=None): support.run_unittest(TestWeakSet) |