Decorate a test as flaky. :param reason: the reason why the test is flaky :param max_runs: the maximum number of runs before raising an error :param min_passes: the minimum number of passing runs :param delay: the delay in seconds between retries :param affects_cpython_links: wh
(
*,
reason=None,
max_runs=2,
min_passes=1,
delay=1,
affects_cpython_linux=False,
func_name=None,
reset_func=None,
)
| 157 | |
| 158 | |
| 159 | def flaky( |
| 160 | *, |
| 161 | reason=None, |
| 162 | max_runs=2, |
| 163 | min_passes=1, |
| 164 | delay=1, |
| 165 | affects_cpython_linux=False, |
| 166 | func_name=None, |
| 167 | reset_func=None, |
| 168 | ): |
| 169 | """Decorate a test as flaky. |
| 170 | |
| 171 | :param reason: the reason why the test is flaky |
| 172 | :param max_runs: the maximum number of runs before raising an error |
| 173 | :param min_passes: the minimum number of passing runs |
| 174 | :param delay: the delay in seconds between retries |
| 175 | :param affects_cpython_links: whether the test is flaky on CPython on Linux |
| 176 | :param func_name: the name of the function, used for the rety message |
| 177 | :param reset_func: a function to call before retrying |
| 178 | |
| 179 | """ |
| 180 | if reason is None: |
| 181 | raise ValueError("flaky requires a reason input") |
| 182 | is_cpython_linux = sys.platform == "linux" and sys.implementation.name == "cpython" |
| 183 | disable_flaky = "DISABLE_FLAKY" in os.environ |
| 184 | if "CI" not in os.environ and "ENABLE_FLAKY" not in os.environ: |
| 185 | disable_flaky = True |
| 186 | |
| 187 | if disable_flaky or (is_cpython_linux and not affects_cpython_linux): |
| 188 | max_runs = 1 |
| 189 | min_passes = 1 |
| 190 | |
| 191 | def decorator(target_func): |
| 192 | @wraps(target_func) |
| 193 | def wrapper(*args, **kwargs): |
| 194 | passes = 0 |
| 195 | for i in range(max_runs): |
| 196 | try: |
| 197 | result = target_func(*args, **kwargs) |
| 198 | passes += 1 |
| 199 | if passes == min_passes: |
| 200 | return result |
| 201 | except Exception as e: |
| 202 | if i == max_runs - 1: |
| 203 | raise e |
| 204 | print( |
| 205 | f"Retrying after attempt {i+1} of {func_name or target_func.__name__} failed with ({reason})):\n" |
| 206 | f"{traceback.format_exc()}", |
| 207 | file=sys.stderr, |
| 208 | ) |
| 209 | time.sleep(delay) |
| 210 | if reset_func: |
| 211 | reset_func() |
| 212 | |
| 213 | return wrapper |
| 214 | |
| 215 | return decorator |
| 216 |