fix add_method_controller to deal properly in the event of exceptions.
add a docstring as well.
This commit is contained in:
parent
c45b7012f5
commit
68f3818562
|
@ -67,20 +67,39 @@ class TagTracerSub:
|
||||||
def get(self, name):
|
def get(self, name):
|
||||||
return self.__class__(self.root, self.tags + (name,))
|
return self.__class__(self.root, self.tags + (name,))
|
||||||
|
|
||||||
|
|
||||||
def add_method_controller(cls, func):
|
def add_method_controller(cls, func):
|
||||||
|
""" Use func as the method controler for the method found
|
||||||
|
at the class named func.__name__.
|
||||||
|
|
||||||
|
A method controler is invoked with the same arguments
|
||||||
|
as the function it substitutes and is required to yield once
|
||||||
|
which will trigger calling the controlled method.
|
||||||
|
If it yields a second value, the value will be returned
|
||||||
|
as the result of the invocation. Errors in the controlled function
|
||||||
|
are re-raised to the controller during the first yield.
|
||||||
|
"""
|
||||||
name = func.__name__
|
name = func.__name__
|
||||||
oldcall = getattr(cls, name)
|
oldcall = getattr(cls, name)
|
||||||
def wrap_exec(*args, **kwargs):
|
def wrap_exec(*args, **kwargs):
|
||||||
gen = func(*args, **kwargs)
|
gen = func(*args, **kwargs)
|
||||||
next(gen) # first yield
|
next(gen) # first yield
|
||||||
res = oldcall(*args, **kwargs)
|
|
||||||
try:
|
try:
|
||||||
gen.send(res)
|
res = oldcall(*args, **kwargs)
|
||||||
|
except Exception:
|
||||||
|
excinfo = sys.exc_info()
|
||||||
|
try:
|
||||||
|
# reraise exception to controller
|
||||||
|
res = gen.throw(*excinfo)
|
||||||
|
except StopIteration:
|
||||||
|
py.builtin._reraise(*excinfo)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
res = gen.send(res)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
pass
|
pass
|
||||||
else:
|
|
||||||
raise ValueError("expected StopIteration")
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
setattr(cls, name, wrap_exec)
|
setattr(cls, name, wrap_exec)
|
||||||
return lambda: setattr(cls, name, oldcall)
|
return lambda: setattr(cls, name, oldcall)
|
||||||
|
|
||||||
|
|
|
@ -765,8 +765,8 @@ def test_importplugin_issue375(testdir):
|
||||||
assert "qwe" not in str(excinfo.value)
|
assert "qwe" not in str(excinfo.value)
|
||||||
assert "aaaa" in str(excinfo.value)
|
assert "aaaa" in str(excinfo.value)
|
||||||
|
|
||||||
|
class TestWrapMethod:
|
||||||
def test_wrapping():
|
def test_basic_happypath(self):
|
||||||
class A:
|
class A:
|
||||||
def f(self):
|
def f(self):
|
||||||
return "A.f"
|
return "A.f"
|
||||||
|
@ -784,3 +784,77 @@ def test_wrapping():
|
||||||
l[:] = []
|
l[:] = []
|
||||||
assert A().f() == "A.f"
|
assert A().f() == "A.f"
|
||||||
assert l == []
|
assert l == []
|
||||||
|
|
||||||
|
def test_method_raises(self):
|
||||||
|
class A:
|
||||||
|
def error(self, val):
|
||||||
|
raise ValueError(val)
|
||||||
|
|
||||||
|
l = []
|
||||||
|
def error(self, val):
|
||||||
|
l.append(val)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except ValueError:
|
||||||
|
l.append(None)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
undo = add_method_controller(A, error)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
A().error(42)
|
||||||
|
assert l == [42, None]
|
||||||
|
undo()
|
||||||
|
l[:] = []
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
A().error(42)
|
||||||
|
assert l == []
|
||||||
|
|
||||||
|
def test_controller_swallows_method_raises(self):
|
||||||
|
class A:
|
||||||
|
def error(self, val):
|
||||||
|
raise ValueError(val)
|
||||||
|
|
||||||
|
def error(self, val):
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except ValueError:
|
||||||
|
yield 2
|
||||||
|
|
||||||
|
add_method_controller(A, error)
|
||||||
|
assert A().error(42) == 2
|
||||||
|
|
||||||
|
def test_reraise_on_controller_StopIteration(self):
|
||||||
|
class A:
|
||||||
|
def error(self, val):
|
||||||
|
raise ValueError(val)
|
||||||
|
|
||||||
|
def error(self, val):
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
add_method_controller(A, error)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
A().error(42)
|
||||||
|
|
||||||
|
@pytest.mark.xfail(reason="if needed later")
|
||||||
|
def test_modify_call_args(self):
|
||||||
|
class A:
|
||||||
|
def error(self, val1, val2):
|
||||||
|
raise ValueError(val1+val2)
|
||||||
|
|
||||||
|
l = []
|
||||||
|
def error(self):
|
||||||
|
try:
|
||||||
|
yield (1,), {'val2': 2}
|
||||||
|
except ValueError as ex:
|
||||||
|
assert ex.args == (3,)
|
||||||
|
l.append(1)
|
||||||
|
|
||||||
|
add_method_controller(A, error)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
A().error()
|
||||||
|
assert l == [1]
|
||||||
|
|
Loading…
Reference in New Issue