Skip to content

Commit 123289a

Browse files
fixes pytest-dev#2208 by introducing a iteration limit
1 parent 6c011f4 commit 123289a

File tree

3 files changed

+44
-6
lines changed

3 files changed

+44
-6
lines changed

CHANGELOG.rst

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ Changes
4141
* fix `#2013`_: turn RecordedWarning into namedtupe,
4242
to give it a comprehensible repr while preventing unwarranted modification
4343

44+
* fix `#2208`_: ensure a iteration limit for _pytest.compat.get_real_func.
45+
Thanks `@RonnyPfannschmidt`_ for the Report and PR
46+
47+
4448
.. _@davidszotten: https://github.com/davidszotten
4549
.. _@fushi: https://github.com/fushi
4650
.. _@mattduck: https://github.com/mattduck
@@ -57,6 +61,7 @@ Changes
5761
.. _#2101: https://github.com/pytest-dev/pytest/pull/2101
5862
.. _#2166: https://github.com/pytest-dev/pytest/pull/2166
5963
.. _#2147: https://github.com/pytest-dev/pytest/issues/2147
64+
.. _#2208: https://github.com/pytest-dev/pytest/issues/2208
6065

6166
3.0.6.dev0 (unreleased)
6267
=======================

_pytest/compat.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,16 @@ def get_real_func(obj):
180180
""" gets the real function object of the (possibly) wrapped object by
181181
functools.wraps or functools.partial.
182182
"""
183-
while hasattr(obj, "__wrapped__"):
184-
obj = obj.__wrapped__
183+
start_obj = obj
184+
for i in range(100):
185+
new_obj = getattr(obj, '__wrapped__', None)
186+
if new_obj is None:
187+
break
188+
obj = new_obj
189+
else:
190+
raise ValueError(
191+
("could not find real function of {start}"
192+
"\nstopped at {current}").format(start=start_obj, current=obj))
185193
if isinstance(obj, functools.partial):
186194
obj = obj.func
187195
return obj

testing/test_compat.py

+29-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import sys
22

33
import pytest
4-
from _pytest.compat import is_generator
4+
from _pytest.compat import is_generator, get_real_func
55

66

77
def test_is_generator():
@@ -15,7 +15,30 @@ def foo():
1515
assert not is_generator(foo)
1616

1717

18-
@pytest.mark.skipif(sys.version_info < (3, 4), reason='asyncio available in Python 3.4+')
18+
def test_real_func_loop_limit():
19+
20+
class Evil(object):
21+
def __init__(self):
22+
self.left = 1000
23+
24+
def __repr__(self):
25+
return "<Evil left={left}>".format(left=self.left)
26+
27+
def __getattr__(self, attr):
28+
if not self.left:
29+
raise RuntimeError('its over')
30+
self.left -= 1
31+
return self
32+
33+
evil = Evil()
34+
35+
with pytest.raises(ValueError):
36+
res = get_real_func(evil)
37+
print(res)
38+
39+
40+
@pytest.mark.skipif(sys.version_info < (3, 4),
41+
reason='asyncio available in Python 3.4+')
1942
def test_is_generator_asyncio(testdir):
2043
testdir.makepyfile("""
2144
from _pytest.compat import is_generator
@@ -27,12 +50,14 @@ def baz():
2750
def test_is_generator_asyncio():
2851
assert not is_generator(baz)
2952
""")
30-
# avoid importing asyncio into pytest's own process, which in turn imports logging (#8)
53+
# avoid importing asyncio into pytest's own process,
54+
# which in turn imports logging (#8)
3155
result = testdir.runpytest_subprocess()
3256
result.stdout.fnmatch_lines(['*1 passed*'])
3357

3458

35-
@pytest.mark.skipif(sys.version_info < (3, 5), reason='async syntax available in Python 3.5+')
59+
@pytest.mark.skipif(sys.version_info < (3, 5),
60+
reason='async syntax available in Python 3.5+')
3661
def test_is_generator_async_syntax(testdir):
3762
testdir.makepyfile("""
3863
from _pytest.compat import is_generator

0 commit comments

Comments
 (0)