Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions trio/_core/_multierror.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,17 @@ def __exit__(self, etype, exc, tb):
if filtered_exc is None:
# Swallow the exception
return True
# We can't stop Python from setting __context__, but we can
# hide it. (Unfortunately Python *will* wipe out any existing
# __context__. Nothing we can do about it :-(.)
filtered_exc.__suppress_context__ = True
raise filtered_exc
# When we raise filtered_exc, Python will unconditionally blow
# away its __context__ attribute and replace it with the original
# exc we caught. So after we raise it, we have to pause it while
# it's in flight to put the correct __context__ back.
old_context = filtered_exc.__context__
try:
raise filtered_exc
finally:
_, value, _ = sys.exc_info()
assert value is filtered_exc
value.__context__ = old_context

class MultiError(BaseException):
"""An exception that contains other exceptions; also known as an
Expand Down
29 changes: 24 additions & 5 deletions trio/_core/tests/test_multierror.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,10 @@ def simple_filter(exc):
# ValueError disappeared & KeyError became RuntimeError, so now:
assert isinstance(new_m.exceptions[0], RuntimeError)
assert isinstance(new_m.exceptions[1], NameError)
# we can't stop Python from attaching the original MultiError to this as a
# __context__, but we can hide it:
assert new_m.__suppress_context__
# Make sure that Python did not successfully attach the old MultiError to
# our new MultiError's __context__
assert not new_m.__suppress_context__
assert new_m.__context__ is None

# check preservation of __cause__ and __context__
v = ValueError()
Expand All @@ -241,13 +242,31 @@ def simple_filter(exc):
assert isinstance(excinfo.value.__cause__, KeyError)

v = ValueError()
v.__context__ = KeyError()
context = KeyError()
v.__context__ = context
with pytest.raises(ValueError) as excinfo:
with MultiError.catch(lambda exc: exc):
raise v
assert isinstance(excinfo.value.__context__, KeyError)
assert excinfo.value.__context__ is context
assert not excinfo.value.__suppress_context__

for suppress_context in [True, False]:
v = ValueError()
context = KeyError()
v.__context__ = context
v.__suppress_context__ = suppress_context
distractor = RuntimeError()
with pytest.raises(ValueError) as excinfo:
def catch_RuntimeError(exc):
if isinstance(exc, RuntimeError):
return None
else:
return exc
with MultiError.catch(catch_RuntimeError):
raise MultiError([v, distractor])
assert excinfo.value.__context__ is context
assert excinfo.value.__suppress_context__ == suppress_context


def assert_match_in_seq(pattern_list, string):
offset = 0
Expand Down