60
60
HypothesisDeprecationWarning ,
61
61
HypothesisWarning ,
62
62
InvalidArgument ,
63
- MultipleFailures ,
64
63
NoSuchExample ,
65
64
StopTest ,
66
65
Unsatisfiable ,
69
68
from hypothesis .executors import default_new_style_executor , new_style_executor
70
69
from hypothesis .internal .compat import (
71
70
PYPY ,
71
+ BaseExceptionGroup ,
72
72
bad_django_TestCase ,
73
73
get_type_hints ,
74
74
int_from_bytes ,
126
126
127
127
128
128
running_under_pytest = False
129
+ pytest_shows_exceptiongroups = True
129
130
global_force_seed = None
130
131
_hypothesis_global_random = None
131
132
@@ -436,7 +437,7 @@ def execute_explicit_examples(state, wrapped_test, arguments, kwargs, original_s
436
437
err = new
437
438
438
439
yield (fragments_reported , err )
439
- if state .settings .report_multiple_bugs :
440
+ if state .settings .report_multiple_bugs and pytest_shows_exceptiongroups :
440
441
continue
441
442
break
442
443
finally :
@@ -575,7 +576,6 @@ def __init__(
575
576
self .settings = settings
576
577
self .last_exception = None
577
578
self .falsifying_examples = ()
578
- self .__was_flaky = False
579
579
self .random = random
580
580
self .__test_runtime = None
581
581
self .ever_executed = False
@@ -710,11 +710,10 @@ def run(data):
710
710
)
711
711
else :
712
712
report ("Failed to reproduce exception. Expected: \n " + traceback )
713
- self .__flaky (
714
- f"Hypothesis { text_repr } produces unreliable results: Falsified"
715
- " on the first call but did not on a subsequent one" ,
716
- cause = exception ,
717
- )
713
+ raise Flaky (
714
+ f"Hypothesis { text_repr } produces unreliable results: "
715
+ "Falsified on the first call but did not on a subsequent one"
716
+ ) from exception
718
717
return result
719
718
720
719
def _execute_once_for_engine (self , data ):
@@ -842,64 +841,57 @@ def run_engine(self):
842
841
843
842
if not self .falsifying_examples :
844
843
return
845
- elif not self .settings .report_multiple_bugs :
844
+ elif not ( self .settings .report_multiple_bugs and pytest_shows_exceptiongroups ) :
846
845
# Pretend that we only found one failure, by discarding the others.
847
846
del self .falsifying_examples [:- 1 ]
848
847
849
848
# The engine found one or more failures, so we need to reproduce and
850
849
# report them.
851
850
852
- flaky = 0
851
+ errors_to_report = []
853
852
854
- if runner .best_observed_targets :
855
- for line in describe_targets (runner .best_observed_targets ):
856
- report (line )
857
- report ("" )
853
+ report_lines = describe_targets (runner .best_observed_targets )
854
+ if report_lines :
855
+ report_lines .append ("" )
858
856
859
857
explanations = explanatory_lines (self .explain_traces , self .settings )
860
858
for falsifying_example in self .falsifying_examples :
861
859
info = falsifying_example .extra_information
860
+ fragments = []
862
861
863
862
ran_example = ConjectureData .for_buffer (falsifying_example .buffer )
864
- self .__was_flaky = False
865
863
assert info .__expected_exception is not None
866
864
try :
867
- self .execute_once (
868
- ran_example ,
869
- print_example = not self .is_find ,
870
- is_final = True ,
871
- expected_failure = (
872
- info .__expected_exception ,
873
- info .__expected_traceback ,
874
- ),
875
- )
865
+ with with_reporter (fragments .append ):
866
+ self .execute_once (
867
+ ran_example ,
868
+ print_example = not self .is_find ,
869
+ is_final = True ,
870
+ expected_failure = (
871
+ info .__expected_exception ,
872
+ info .__expected_traceback ,
873
+ ),
874
+ )
876
875
except (UnsatisfiedAssumption , StopTest ) as e :
877
- report (format_exception (e , e .__traceback__ ))
878
- self .__flaky (
876
+ err = Flaky (
879
877
"Unreliable assumption: An example which satisfied "
880
878
"assumptions on the first run now fails it." ,
881
- cause = e ,
882
879
)
880
+ err .__cause__ = err .__context__ = e
881
+ errors_to_report .append ((fragments , err ))
883
882
except BaseException as e :
884
883
# If we have anything for explain-mode, this is the time to report.
885
884
for line in explanations [falsifying_example .interesting_origin ]:
886
- report (line )
887
-
888
- if len (self .falsifying_examples ) <= 1 :
889
- # There is only one failure, so we can report it by raising
890
- # it directly.
891
- raise
892
-
893
- # We are reporting multiple failures, so we need to manually
894
- # print each exception's stack trace and information.
895
- tb = get_trimmed_traceback ()
896
- report (format_exception (e , tb ))
885
+ fragments .append (line )
886
+ errors_to_report .append (
887
+ (fragments , e .with_traceback (get_trimmed_traceback ()))
888
+ )
897
889
898
890
finally :
899
891
# Whether or not replay actually raised the exception again, we want
900
892
# to print the reproduce_failure decorator for the failing example.
901
893
if self .settings .print_blob :
902
- report (
894
+ fragments . append (
903
895
"\n You can reproduce this example by temporarily adding "
904
896
"@reproduce_failure(%r, %r) as a decorator on your test case"
905
897
% (__version__ , encode_failure (falsifying_example .buffer ))
@@ -908,30 +900,38 @@ def run_engine(self):
908
900
# hold on to a reference to ``data`` know that it's now been
909
901
# finished and they can't draw more data from it.
910
902
ran_example .freeze ()
903
+ _raise_to_user (errors_to_report , self .settings , report_lines )
911
904
912
- if self .__was_flaky :
913
- flaky += 1
914
-
915
- # If we only have one example then we should have raised an error or
916
- # flaky prior to this point.
917
- assert len (self .falsifying_examples ) > 1
918
905
919
- if flaky > 0 :
920
- raise Flaky (
921
- f"Hypothesis found { len (self .falsifying_examples )} distinct failures, "
922
- f"but { flaky } of them exhibited some sort of flaky behaviour."
923
- )
924
- else :
925
- raise MultipleFailures (
926
- f"Hypothesis found { len (self .falsifying_examples )} distinct failures."
927
- )
906
+ def add_note (exc , note ):
907
+ try :
908
+ exc .add_note (note )
909
+ except AttributeError :
910
+ if not hasattr (exc , "__notes__" ):
911
+ exc .__notes__ = []
912
+ exc .__notes__ .append (note )
913
+
914
+
915
+ def _raise_to_user (errors_to_report , settings , target_lines , trailer = "" ):
916
+ """Helper function for attaching notes and grouping multiple errors."""
917
+ if settings .verbosity >= Verbosity .normal :
918
+ for fragments , err in errors_to_report :
919
+ for note in fragments :
920
+ add_note (err , note )
921
+
922
+ if len (errors_to_report ) == 1 :
923
+ _ , the_error_hypothesis_found = errors_to_report [0 ]
924
+ else :
925
+ assert errors_to_report
926
+ the_error_hypothesis_found = BaseExceptionGroup (
927
+ f"Hypothesis found { len (errors_to_report )} distinct failures{ trailer } ." ,
928
+ [e for _ , e in errors_to_report ],
929
+ )
928
930
929
- def __flaky (self , message , * , cause ):
930
- if len (self .falsifying_examples ) <= 1 :
931
- raise Flaky (message ) from cause
932
- else :
933
- self .__was_flaky = True
934
- report ("Flaky example! " + message )
931
+ if settings .verbosity >= Verbosity .normal :
932
+ for line in target_lines :
933
+ add_note (the_error_hypothesis_found , line )
934
+ raise the_error_hypothesis_found
935
935
936
936
937
937
@contextlib .contextmanager
@@ -1189,23 +1189,11 @@ def wrapped_test(*arguments, **kwargs):
1189
1189
state , wrapped_test , arguments , kwargs , original_sig
1190
1190
)
1191
1191
)
1192
- with local_settings (state .settings ):
1193
- if len (errors ) > 1 :
1194
- # If we're not going to report multiple bugs, we would have
1195
- # stopped running explicit examples at the first failure.
1196
- assert state .settings .report_multiple_bugs
1197
- for fragments , err in errors :
1198
- for f in fragments :
1199
- report (f )
1200
- report (format_exception (err , err .__traceback__ ))
1201
- raise MultipleFailures (
1202
- f"Hypothesis found { len (errors )} failures in explicit examples."
1203
- )
1204
- elif errors :
1205
- fragments , the_error_hypothesis_found = errors [0 ]
1206
- for f in fragments :
1207
- report (f )
1208
- raise the_error_hypothesis_found
1192
+ if errors :
1193
+ # If we're not going to report multiple bugs, we would have
1194
+ # stopped running explicit examples at the first failure.
1195
+ assert len (errors ) == 1 or state .settings .report_multiple_bugs
1196
+ _raise_to_user (errors , state .settings , [], " in explicit examples" )
1209
1197
1210
1198
# If there were any explicit examples, they all ran successfully.
1211
1199
# The next step is to use the Conjecture engine to run the test on
@@ -1236,7 +1224,7 @@ def wrapped_test(*arguments, **kwargs):
1236
1224
state .run_engine ()
1237
1225
except BaseException as e :
1238
1226
# The exception caught here should either be an actual test
1239
- # failure (or MultipleFailures ), or some kind of fatal error
1227
+ # failure (or BaseExceptionGroup ), or some kind of fatal error
1240
1228
# that caused the engine to stop.
1241
1229
1242
1230
generated_seed = wrapped_test ._hypothesis_internal_use_generated_seed
@@ -1262,7 +1250,9 @@ def wrapped_test(*arguments, **kwargs):
1262
1250
# which will actually appear in tracebacks is as clear as
1263
1251
# possible - "raise the_error_hypothesis_found".
1264
1252
the_error_hypothesis_found = e .with_traceback (
1265
- get_trimmed_traceback ()
1253
+ None
1254
+ if isinstance (e , BaseExceptionGroup )
1255
+ else get_trimmed_traceback ()
1266
1256
)
1267
1257
raise the_error_hypothesis_found
1268
1258
0 commit comments