-
Notifications
You must be signed in to change notification settings - Fork 586
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
first draft of handling marker exceptions wrapped in exceptiongroup #4110
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice, thanks! 👍
I think most of the build errors will go away with some of the minor commented changes, the remaining will hopefully be manageable :-)
# multiple frozen exceptions | ||
# TODO: raise a group? The first one? None of them? | ||
raise frozen_exceptions[0] from excgroup | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, the semantics here (generally) are a good question.
I'd opine:
- If all exceptions are
Frozen
, raise the first (as above). - Else, if exactly one exception is non-
Frozen
,raise
that one [*]. - Otherwise, reconstruct and raise a group with just the non-frozen ones (maybe
.with_traceback(eg.__traceback__)
).
[*] hm, I see, that would cause an undesired unwrapping if user code raises a single-exception ExceptionGroup
. Maybe the second step should be "if exactly one ... and that exception is a StopException
or HypothesisException
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If all exceptions are Frozen, raise the first (as above).
Actually, I think this could cause confusion. If I spawn three tasks and I only get one Frozen
, I will assume that the-thing-that-caused-Frozen only happened in one of the tasks. We should probably err on the side of not unwrapping unless necessary to get internal exception handling to work.
edit: nvm, I thought frozen didn't get swallowed by @given
edit2: I'm confused about how hypothesis works 😅
edit3: Aaaahhh.
hypothesis/hypothesis-python/src/hypothesis/core.py
Lines 1077 to 1089 in 4d079f7
except failure_exceptions_to_catch() as e: | |
# If an unhandled (i.e., non-Hypothesis) error was raised by | |
# Hypothesis-internal code, re-raise it as a fatal error instead | |
# of treating it as a test failure. | |
filepath = traceback.extract_tb(e.__traceback__)[-1][0] | |
if is_hypothesis_file(filepath) and not isinstance(e, HypothesisException): | |
raise | |
if data.frozen: | |
# This can happen if an error occurred in a finally | |
# block somewhere, suppressing our original StopTest. | |
# We raise a new one here to resume normal operation. | |
raise StopTest(data.testcounter) from e |
This means that if we got our data frozen, any exceptions (no matter if inside a group or not) get disregarded. So it doesn't matter what we do if there's only frozen exceptions
edit4: and this is very bad, because it can suppress TypeError
and anything. Gotta rejig some logic here
edit5: except that's what the code currently does in sync code, so... ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another question is if we get multiple HypothesisException
/StopException
, if that's possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we get multiple StopTest
, re-raise the one with the lowest _testcounter i.e. the first one to occur.
def test_single_exc_group(data): | ||
raise ExceptionGroup("important message for user", [ValueError()]) | ||
|
||
with RaisesGroup(ValueError, match="important message for user"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just catch BaseExceptionGroup
(via hypothesis.internal.compat.BaseExceptionGroup
) and check the subexceptions explicitly, to avoid the trio
dependency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ye that's probably the way to go. I'm incredibly biased on the matter as the person who wrote trio.testing.RaisesGroup
and is trying to get it added to pytest 😇
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(and as a dual maintainer of Pytest and Trio, among other things, I'm pretty keen to support that when I have time 😅)
It should be possible to provoke most of these situations by playing around with multiple tasks failing in different ways. But I think it's fine to create the non-obvious situations explicitly, too. |
Sorry: "most" should probably be replaced by "some" 😁 |
Oh, and one other tip: hypothesis will transform/trim the tracebacks by default, which may be confusing sometimes when working on exceptions and not seeing what you'd expect. See |
actually, make that none of the situations if we 1. Want tests to run on py<3.11 and 2. not add a dependency on trio or anyio. |
I think for testing we'll want |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(partial review is better than no review, right?)
I've also merged a fix for #4115 so you might want to rebase on master 🙂
# TODO: this should maybe inspect the stacktrace to see that the above | ||
# mentioned story is true? I.e. reraise as StopTest iff there is a | ||
# StopTest somewhere in the tree of e.__context__ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alas, raise Whatever() from None
would defeat this inspection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that would only override the __cause__
no? And the comment specifically talks about errors happening in finally
, where you rarely raise explicit exceptions.
It seems pretty dangerous to ignore all exceptions in case the data is frozen, shouldn't the user be informed if there's errors happening in their teardown code?
Although thinking about it more, the problem is perhaps that the StopTest.__cause__
is not getting reraised/logged?
@@ -9,6 +9,7 @@ | |||
# obtain one at https://mozilla.org/MPL/2.0/. | |||
|
|||
"""This module provides the core primitives of Hypothesis, such as given.""" | |||
from __future__ import annotations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because Hypothesis does a lot of runtime introspection, we prefer to avoid from __future__ import annotations
- which will eventually go away anyway, c.f. PEPs 649 and 749.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you also want to avoid it in test files?
#4115 made me realize that unwrapping exceptions inside groups is really tricky to do right without losing the context/cause of the original exception. Though I'm unsure if/how much this matters for hypothesis, as we only try to unwrap specific marker exceptions which users wouldn't see anyway. |
I have barely used Hypothesis, and never worked with the codebase, so there are several gaps in code coverage and question marks on implementation decisions. If somebody more familiar wants to take a quick pass and opine it'd be very helpful :)
I can get code coverage by manually raising the specific
ExceptionGroup
s that'll trigger the different branches, but it'd likely be better to do the actual things that causes those exceptions. So would likewise appreciate quick pointers on how to, if at all possible, cause e.g. multipleStopTest
, multipleFrozen
, a singleFrozen
, etc.fixes #4106