From 18d3284b8e2b04962b0c6ad325a4636ed0ed4d18 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Wed, 21 Oct 2026 11:07:03 -0400 Subject: [PATCH] test: drive collect_until's late-finaliser test off a fake clock (#2016) The test called the engine's real gc.collect under a wall-clock grace of 5 s, so GraalPy on a loaded runner could not fit the three collections the predicate needs. Patch gc.collect, time.monotonic and time.sleep with fakes (each collection costing a fake second), so the test checks the contract rather than engine speed, and cover the two edges it left untested: a predicate that holds with no grace, and one that never holds and must stop once the grace is spent. --- tests/foundation/test_extraction_ownership.py | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/tests/foundation/test_extraction_ownership.py b/tests/foundation/test_extraction_ownership.py index bfd38e92f..c62a8dce0 100644 --- a/tests/foundation/test_extraction_ownership.py +++ b/tests/foundation/test_extraction_ownership.py @@ -520,19 +520,47 @@ def test_collect_until_waits_out_a_finaliser_that_runs_late(self) -> None: The stand-in finaliser runs on the third collection. With no grace the helper reports it as never run, the shape of the GraalPy leg's failure; with a - grace it waits it out. + grace it waits it out, and one that never runs still fails once the grace is + spent. + + The collector and the clock are both fakes, with each collection costing a + fake second, so the outcome does not depend on how fast the engine's own + :func:`gc.collect` is (:issue:`2016`). """ - collections = [] + clock = [0.0] + collections: 'list[None]' = [] - def late() -> bool: + def collect() -> int: + clock[0] += 1.0 collections.append(None) + return 0 + + def sleep(seconds: float) -> None: + clock[0] += seconds + + def late() -> bool: return len(collections) >= 3 - self.assertFalse(collect_until(late, grace=0.0)) - collections.clear() - self.assertTrue(collect_until(late, grace=5.0)) - self.assertEqual(len(collections), 3) + with mock.patch.object(gc, 'collect', collect), \ + mock.patch.object(time, 'monotonic', lambda: clock[0]), \ + mock.patch.object(time, 'sleep', sleep): + # CPython's case: no grace, and the finaliser ran inside the collection. + self.assertTrue(collect_until(lambda: True, grace=0.0)) + + collections.clear() + self.assertFalse(collect_until(late, grace=0.0)) + self.assertEqual(len(collections), 1) + + collections.clear() + self.assertTrue(collect_until(late, grace=5.0)) + self.assertEqual(len(collections), 3) + + collections.clear() + self.assertFalse(collect_until(lambda: False, grace=5.0)) + # Collections end at fake 1, 2.05, 3.1, 4.15 and 5.2 s; the fifth is + # the first to finish past the deadline. + self.assertEqual(len(collections), 5) def test_finished_extraction_detaches_its_finaliser(self) -> None: """``_cleanup`` and the context manager unregister it, so nothing closes twice."""