1515Notably, the set of steps available at any point may depend on the
1616execution to date.
1717"""
18+
1819import collections
1920import dataclasses
2021import inspect
3536)
3637from hypothesis .control import _current_build_context , current_build_context
3738from hypothesis .core import TestFunc , given
38- from hypothesis .errors import InvalidArgument , InvalidDefinition
39+ from hypothesis .errors import (
40+ FlakyStrategyDefinition ,
41+ InvalidArgument ,
42+ InvalidDefinition ,
43+ )
3944from hypothesis .internal .compat import add_note , batched
4045from hypothesis .internal .conjecture .engine import BUFFER_SIZE
4146from hypothesis .internal .conjecture .junkdrawer import gc_cumulative_time
@@ -95,7 +100,11 @@ def __delete__(self, obj):
95100 raise AttributeError ("Cannot delete TestCase" )
96101
97102
98- def get_state_machine_test (state_machine_factory , * , settings = None , _min_steps = 0 ):
103+ def get_state_machine_test (
104+ state_machine_factory , * , settings = None , _min_steps = 0 , _flaky_state = None
105+ ):
106+ # This function is split out from run_state_machine_as_test so that
107+ # HypoFuzz can get and call the test function directly.
99108 if settings is None :
100109 try :
101110 settings = state_machine_factory .TestCase .settings
@@ -108,6 +117,7 @@ def get_state_machine_test(state_machine_factory, *, settings=None, _min_steps=0
108117 # Because settings can vary via e.g. profiles, settings.stateful_step_count
109118 # overrides this argument and we don't bother cross-validating.
110119 raise InvalidArgument (f"_min_steps={ _min_steps } must be non-negative." )
120+ _flaky_state = _flaky_state or {}
111121
112122 @settings
113123 @given (st .data ())
@@ -161,6 +171,7 @@ def output(s):
161171
162172 # Choose a rule to run, preferring an initialize rule if there are
163173 # any which have not been run yet.
174+ _flaky_state ["selecting_rule" ] = True
164175 if machine ._initialize_rules_to_run :
165176 init_rules = [
166177 st .tuples (st .just (rule ), st .fixed_dictionaries (rule .arguments ))
@@ -170,6 +181,7 @@ def output(s):
170181 machine ._initialize_rules_to_run .remove (rule )
171182 else :
172183 rule , data = cd .draw (machine ._rules_strategy )
184+ _flaky_state ["selecting_rule" ] = False
173185 draw_label = f"generate:rule:{ rule .function .__name__ } "
174186 cd .draw_times .setdefault (draw_label , 0.0 )
175187 in_gctime = gc_cumulative_time () - start_gc
@@ -250,10 +262,23 @@ def run_state_machine_as_test(state_machine_factory, *, settings=None, _min_step
250262 RuleBasedStateMachine when called with no arguments - it can be a class or a
251263 function. settings will be used to control the execution of the test.
252264 """
265+ flaky_state = {"selecting_rule" : False }
253266 state_machine_test = get_state_machine_test (
254- state_machine_factory , settings = settings , _min_steps = _min_steps
267+ state_machine_factory ,
268+ settings = settings ,
269+ _min_steps = _min_steps ,
270+ _flaky_state = flaky_state ,
255271 )
256- state_machine_test ()
272+ try :
273+ state_machine_test ()
274+ except FlakyStrategyDefinition as err :
275+ if flaky_state ["selecting_rule" ]:
276+ add_note (
277+ err ,
278+ "while selecting a rule to run. This is usually caused by "
279+ "a flaky precondition, or a bundle that was unexpectedly empty." ,
280+ )
281+ raise
257282
258283
259284class StateMachineMeta (type ):
0 commit comments