Skip to content

Commit e27b859

Browse files
committed
Improve test
1 parent 958f37e commit e27b859

File tree

1 file changed

+52
-40
lines changed

1 file changed

+52
-40
lines changed

hypothesis-python/tests/cover/test_deadline.py

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -161,59 +161,71 @@ def delay(phase, _info):
161161
gc.callbacks.remove(delay)
162162

163163

164-
@pytest.mark.skipif(not hasattr(gc, "callbacks"), reason="CPython specific")
165-
def test_gc_hooks_do_not_fail_due_to_recursionerror():
164+
@pytest.mark.skipif(not hasattr(gc, "callbacks"), reason="PyPy has weird stack-limit behaviour")
165+
def test_gc_hooks_do_not_cause_unraisable_recursionerror():
166166
# We were concerned in #3979 that we might see bad results from a RecursionError
167167
# inside the GC hook, if the stack was already deep and someone (e.g. Pytest)
168-
# had installed a sys.unraisablehook which raises that later. Even if there's
169-
# no such hook, we'd get the measured time wrong, so we set that to NaN.
168+
# had installed a sys.unraisablehook which raises that later.
169+
170+
NUM_CYCLES = 10_000
170171

171172
def probe_depth():
172173
try:
173174
return probe_depth() + 1
174175
except RecursionError:
175176
return 0
176177

177-
collections = 0
178-
179-
def collect_at(depth):
180-
if depth == 0:
181-
# We are at the recursion limit, no free stack frames. Generate reference
182-
# cycles until a background collection is observed. Beware: there is not
183-
# even room for raising new exceptions here.
184-
orig_collections = collections
185-
n_iter = 0
186-
while n_iter < 100_000:
187-
if collections > orig_collections:
188-
return True # successfully triggered collection at stack limit
189-
a = [None]
190-
b = [a]
191-
a[0] = b
192-
n_iter += 1
193-
return False
178+
def at_depth(depth, fn):
179+
if depth <= 1:
180+
return fn()
194181
else:
195-
return collect_at(depth - 1)
182+
# Recurse towards requested depth
183+
return at_depth(depth - 1, fn)
184+
185+
def gen_cycles():
186+
# We may be at the recursion limit, no free stack frames. Generate lots
187+
# of reference cycles. Beware: there may not even be room for raising new
188+
# exceptions here, anything will end up as a RecursionError.
189+
for i in range(NUM_CYCLES):
190+
a = [None]
191+
b = [a]
192+
a[0] = b
193+
194+
def gen_cycles_at_depth(depth, *, gc_disable):
195+
try:
196+
if gc_disable:
197+
gc.disable()
198+
at_depth(depth, gen_cycles)
199+
if gc_disable:
200+
assert gc.collect() >= 2 * NUM_CYCLES
201+
else:
202+
assert gc.collect() < 2 * NUM_CYCLES # collection was triggered
203+
finally:
204+
gc.enable()
196205

197206
@given(st.booleans())
198207
def inner_test(_):
199-
max_depth = probe_depth()
200-
assert collect_at(max_depth)
208+
max_depth = probe_depth() # would have to be called twice on PyPy
209+
210+
# Lower the limit to where we can successfully generate cycles
211+
while True:
212+
try:
213+
gen_cycles_at_depth(max_depth, gc_disable=True)
214+
except RecursionError:
215+
max_depth -= 1
216+
else:
217+
break
201218

202-
observations = []
203-
try:
204-
TESTCASE_CALLBACKS.append(observations.append)
205-
def gc_count_collections(*_):
206-
nonlocal collections
207-
collections += 1
208-
gc.callbacks.append(gc_count_collections)
209-
inner_test()
210-
finally:
211-
gc.callbacks.remove(gc_count_collections)
212-
popped = TESTCASE_CALLBACKS.pop()
213-
assert popped == observations.append, (popped, observations.append)
219+
# Verify limits w/o any gc interfering
220+
gen_cycles_at_depth(max_depth - 1, gc_disable=True) # this line raises RecursionError on Pypy
221+
gen_cycles_at_depth(max_depth, gc_disable=True) # while this line doesn't
222+
with pytest.raises(RecursionError):
223+
gen_cycles_at_depth(max_depth + 1, gc_disable=True)
214224

215-
timings = [t.get("timing", {}).get("overall:gc", 0.0) for t in observations]
216-
assert any(math.isnan(v) for v in timings), timings
225+
# Check that the limit is unchanged with gc enabled
226+
gen_cycles_at_depth(max_depth - 1, gc_disable=False)
227+
gen_cycles_at_depth(max_depth, gc_disable=False)
228+
with pytest.raises(RecursionError):
229+
gen_cycles_at_depth(max_depth + 1, gc_disable=False)
217230

218-
gc.collect() # should clear any NaN state
219-
assert not math.isnan(junkdrawer.gc_cumulative_time())
231+
inner_test()

0 commit comments

Comments
 (0)