Skip to content

Commit b8e4abc

Browse files
committed
Add decorator that xfails on select exception(s), retrying otherwise.
1 parent 05cee68 commit b8e4abc

File tree

2 files changed

+53
-13
lines changed

2 files changed

+53
-13
lines changed

web3/_utils/module_testing/eth_module.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
assert_contains_log,
5959
async_mock_offchain_lookup_request_response,
6060
flaky_geth_dev_mining,
61+
flaky_with_xfail_on_exception,
6162
mock_offchain_lookup_request_response,
6263
)
6364
from web3._utils.module_testing.utils import (
@@ -2359,10 +2360,9 @@ async def test_async_eth_replace_transaction_gas_price_too_low(
23592360
with pytest.raises(Web3ValueError):
23602361
await async_w3.eth.replace_transaction(txn_hash, txn_params)
23612362

2362-
@pytest.mark.xfail(
2363-
reason="Very flaky on CI runs, hard to reproduce locally",
2364-
strict=False,
2365-
raises=(RequestTimedOut, asyncio.TimeoutError, Web3ValueError),
2363+
@flaky_with_xfail_on_exception(
2364+
reason="Very flaky on CI runs, hard to reproduce locally.",
2365+
exception=RequestTimedOut,
23662366
)
23672367
@pytest.mark.asyncio
23682368
async def test_async_eth_replace_transaction_gas_price_defaulting_minimum(
@@ -2387,10 +2387,9 @@ async def test_async_eth_replace_transaction_gas_price_defaulting_minimum(
23872387
gas_price * 1.125
23882388
) # minimum gas price
23892389

2390-
@pytest.mark.xfail(
2391-
reason="Very flaky on CI runs, hard to reproduce locally",
2392-
strict=False,
2393-
raises=(RequestTimedOut, asyncio.TimeoutError, Web3ValueError),
2390+
@flaky_with_xfail_on_exception(
2391+
reason="Very flaky on CI runs, hard to reproduce locally.",
2392+
exception=RequestTimedOut,
23942393
)
23952394
@pytest.mark.asyncio
23962395
async def test_async_eth_replace_transaction_gas_price_defaulting_strategy_higher(
@@ -2420,10 +2419,9 @@ def higher_gas_price_strategy(async_w3: "AsyncWeb3", txn: TxParams) -> Wei:
24202419
) # Strategy provides higher gas price
24212420
async_w3.eth.set_gas_price_strategy(None) # reset strategy
24222421

2423-
@pytest.mark.xfail(
2424-
reason="Very flaky on CI runs, hard to reproduce locally",
2425-
strict=False,
2426-
raises=(RequestTimedOut, asyncio.TimeoutError, Web3ValueError),
2422+
@flaky_with_xfail_on_exception(
2423+
reason="Very flaky on CI runs, hard to reproduce locally.",
2424+
exception=RequestTimedOut,
24272425
)
24282426
@pytest.mark.asyncio
24292427
async def test_async_eth_replace_transaction_gas_price_defaulting_strategy_lower(

web3/_utils/module_testing/module_testing_utils.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import asyncio
2+
import functools
3+
import pytest
24
from typing import (
35
TYPE_CHECKING,
46
Any,
7+
Callable,
58
Collection,
69
Dict,
710
Generator,
811
Literal,
912
Sequence,
13+
Tuple,
14+
Type,
1015
Union,
1116
)
1217

@@ -55,7 +60,44 @@
5560
for the duration of the test. This behavior can be flaky
5661
due to timing of the test running as a block is mined.
5762
"""
58-
flaky_geth_dev_mining = flaky(max_runs=3)
63+
flaky_geth_dev_mining = flaky(max_runs=3, min_passes=1)
64+
65+
66+
def flaky_with_xfail_on_exception(
67+
reason: str,
68+
exception: Union[Type[Exception], Tuple[Type[Exception], ...]],
69+
max_runs: int = 3,
70+
min_passes: int = 1,
71+
) -> Callable[[Any], Any]:
72+
"""
73+
Some tests inconsistently fail hard with a particular exception and retrying
74+
these tests often times does not get them "unstuck". If we've exhausted all flaky
75+
retries and this expected exception is raised, `xfail` the test with the given
76+
reason.
77+
"""
78+
runs = max_runs
79+
80+
def decorator(func: Any) -> Any:
81+
@flaky(max_runs=max_runs, min_passes=min_passes)
82+
@functools.wraps(func)
83+
async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
84+
nonlocal runs
85+
try:
86+
return await func(self, *args, **kwargs)
87+
except exception:
88+
# xfail the test only if the exception is raised and we have exhausted
89+
# all flaky retries
90+
if runs == 1:
91+
pytest.xfail(reason)
92+
runs -= 1
93+
pytest.fail(f"xfailed but {runs} run(s) remaining with flaky...")
94+
except Exception as e:
95+
# let flaky handle it
96+
raise e
97+
98+
return wrapper
99+
100+
return decorator
59101

60102

61103
def assert_contains_log(

0 commit comments

Comments
 (0)