Skip to content

Commit 97161bf

Browse files
Ensure tests are run correctly by verifying call order. (#239)
* Ensure tests are run correctly by verifying call order. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Cleanup * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent c3a4f8e commit 97161bf

File tree

1 file changed

+48
-27
lines changed

1 file changed

+48
-27
lines changed

tests/test_timeout.py

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
11
import asyncio
22
import sys
33
import time
4+
from functools import wraps
5+
from typing import Any, Callable, List, TypeVar
46

57
import pytest
68

79
from async_timeout import Timeout, timeout, timeout_at
810

911

12+
_Func = TypeVar("_Func", bound=Callable[..., Any])
13+
14+
15+
def log_func(func: _Func, msg: str, call_order: List[str]) -> _Func:
16+
"""Simple wrapper to add a log to call_order when the function is called."""
17+
18+
@wraps(func)
19+
def wrapper(*args: Any, **kwargs: Any) -> Any: # type: ignore[misc]
20+
call_order.append(msg)
21+
return func(*args, **kwargs)
22+
23+
return wrapper # type: ignore[return-value]
24+
25+
1026
@pytest.mark.asyncio
1127
async def test_timeout() -> None:
1228
canceled_raised = False
@@ -347,15 +363,8 @@ async def test_deprecated_with() -> None:
347363
await asyncio.sleep(0)
348364

349365

350-
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Not supported in 3.6")
351-
@pytest.mark.asyncio
352-
async def test_race_condition_cancel_before() -> None:
353-
"""Test race condition when cancelling before timeout.
354-
355-
If cancel happens immediately before the timeout, then
356-
the timeout may overrule the cancellation, making it
357-
impossible to cancel some tasks.
358-
"""
366+
async def race_condition(offset: float = 0) -> List[str]:
367+
"""Common code for below race condition tests."""
359368

360369
async def test_task(deadline: float, loop: asyncio.AbstractEventLoop) -> None:
361370
# We need the internal Timeout class to specify the deadline (not delay).
@@ -364,39 +373,51 @@ async def test_task(deadline: float, loop: asyncio.AbstractEventLoop) -> None:
364373
with Timeout(deadline, loop):
365374
await asyncio.sleep(10)
366375

376+
call_order: List[str] = []
377+
f_exit = log_func(Timeout._do_exit, "exit", call_order)
378+
Timeout._do_exit = f_exit # type: ignore[assignment]
379+
f_timeout = log_func(Timeout._on_timeout, "timeout", call_order)
380+
Timeout._on_timeout = f_timeout # type: ignore[assignment]
381+
367382
loop = asyncio.get_running_loop()
368383
deadline = loop.time() + 1
369384
t = asyncio.create_task(test_task(deadline, loop))
370-
loop.call_at(deadline, t.cancel)
385+
loop.call_at(deadline + offset, log_func(t.cancel, "cancel", call_order))
371386
# If we get a TimeoutError, then the code is broken.
372387
with pytest.raises(asyncio.CancelledError):
373388
await t
374389

390+
return call_order
391+
375392

376-
@pytest.mark.xfail(
377-
reason="The test is CPU performance sensitive, might fail on slow CI box"
378-
)
379393
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Not supported in 3.6")
380394
@pytest.mark.asyncio
395+
async def test_race_condition_cancel_before() -> None:
396+
"""Test race condition when cancelling before timeout.
397+
398+
If cancel happens immediately before the timeout, then
399+
the timeout may overrule the cancellation, making it
400+
impossible to cancel some tasks.
401+
"""
402+
call_order = await race_condition()
403+
404+
# This test is very timing dependant, so we check the order that calls
405+
# happened to be sure the test itself ran correctly.
406+
assert call_order == ["cancel", "timeout", "exit"]
407+
408+
409+
@pytest.mark.xfail(reason="Can't see a way to fix this currently.")
410+
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Can't be fixed in <3.9.")
411+
@pytest.mark.asyncio
381412
async def test_race_condition_cancel_after() -> None:
382413
"""Test race condition when cancelling after timeout.
383414
384415
Similarly to the previous test, if a cancel happens
385416
immediately after the timeout (but before the __exit__),
386417
then the explicit cancel can get overruled again.
387418
"""
419+
call_order = await race_condition(0.000001)
388420

389-
async def test_task(deadline: float, loop: asyncio.AbstractEventLoop) -> None:
390-
# We need the internal Timeout class to specify the deadline (not delay).
391-
# This is needed to create the precise timing to reproduce the race condition.
392-
with pytest.warns(DeprecationWarning):
393-
with Timeout(deadline, loop):
394-
await asyncio.sleep(10)
395-
396-
loop = asyncio.get_running_loop()
397-
deadline = loop.time() + 1
398-
t = asyncio.create_task(test_task(deadline, loop))
399-
loop.call_at(deadline + 0.0000000000001, t.cancel)
400-
# If we get a TimeoutError, then the code is broken.
401-
with pytest.raises(asyncio.CancelledError):
402-
await t
421+
# This test is very timing dependant, so we check the order that calls
422+
# happened to be sure the test itself ran correctly.
423+
assert call_order == ["timeout", "cancel", "exit"]

0 commit comments

Comments
 (0)