Skip to content

Commit cacc248

Browse files
committed
add failing tests
1 parent 8780c9c commit cacc248

6 files changed

Lines changed: 123 additions & 25 deletions

File tree

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@ ignore_missing_imports = true
9292
[tool.pytest.ini_options]
9393
addopts = ["-rxXs", "--strict-config", "--strict-markers"]
9494
markers = ["copied_from(source, changes=None): mark test as copied from somewhere else, along with a description of changes made to accodomate e.g. our test setup"]
95-
filterwarnings = ["error"]
95+
filterwarnings = [
96+
"error",
97+
"ignore::trio.TrioDeprecationWarning", # due to trio.MultiError deprecation
98+
]
9699

97100
[tool.coverage.run]
98101
omit = [

tests/_async/test_connection_pool.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import hpack
55
import hyperframe.frame
66
import pytest
7-
import trio as concurrency
87

98
import httpcore
9+
from tests.concurrency import async_open_nursery as open_nursery
10+
from tests.current_time import async_current_time as current_time, async_sleep as sleep
1011

1112

1213
@pytest.mark.anyio
@@ -515,7 +516,7 @@ async def fetch(pool, domain, info_list):
515516
max_connections=1, network_backend=network_backend
516517
) as pool:
517518
info_list: typing.List[str] = []
518-
async with concurrency.open_nursery() as nursery:
519+
async with open_nursery() as nursery:
519520
for domain in ["a.com", "b.com", "c.com", "d.com", "e.com"]:
520521
nursery.start_soon(fetch, pool, domain, info_list)
521522

@@ -561,7 +562,7 @@ async def fetch(pool, domain, info_list):
561562
max_connections=1, network_backend=network_backend, http2=True
562563
) as pool:
563564
info_list: typing.List[str] = []
564-
async with concurrency.open_nursery() as nursery:
565+
async with open_nursery() as nursery:
565566
for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]:
566567
nursery.start_soon(fetch, pool, domain, info_list)
567568

@@ -603,7 +604,7 @@ async def fetch(pool, domain, info_list):
603604
max_connections=1, network_backend=network_backend, http2=True
604605
) as pool:
605606
info_list: typing.List[str] = []
606-
async with concurrency.open_nursery() as nursery:
607+
async with open_nursery() as nursery:
607608
for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]:
608609
nursery.start_soon(fetch, pool, domain, info_list)
609610

@@ -662,7 +663,14 @@ async def test_connection_pool_closed_while_request_in_flight():
662663
async def test_connection_pool_timeout():
663664
"""
664665
Ensure that exceeding max_connections can cause a request to timeout.
666+
667+
100 concurrent requests will be sent with a limit of 1 connection.
668+
The request time is fixed to 6ms, and there is a pool timeout of 50ms.
669+
670+
Less than 9 requests will succeed and the rest won't have a chance to get
671+
a connection before 50ms, PoolTimeout will be raised instead.
665672
"""
673+
666674
network_backend = httpcore.AsyncMockBackend(
667675
[
668676
b"HTTP/1.1 200 OK\r\n",
@@ -671,18 +679,41 @@ async def test_connection_pool_timeout():
671679
b"\r\n",
672680
b"Hello, world!",
673681
]
682+
* 100
674683
)
675684

685+
successes: int = 0
686+
max_wait: float = 0
687+
688+
async def fetch(pool):
689+
nonlocal successes, max_wait
690+
691+
start_time = current_time()
692+
extensions = {"timeout": {"pool": 0.05}}
693+
694+
try:
695+
async with pool.stream(
696+
"GET", "https://example.com/", extensions=extensions
697+
) as response:
698+
await sleep(0.006) # we block the connection for that time
699+
await response.aread()
700+
successes += 1
701+
except httpcore.PoolTimeout:
702+
pass
703+
finally:
704+
max_wait = max(current_time() - start_time, max_wait)
705+
676706
async with httpcore.AsyncConnectionPool(
677707
network_backend=network_backend, max_connections=1
678708
) as pool:
679-
# Send a request to a pool that is configured to only support a single
680-
# connection, and then ensure that a second concurrent request
681-
# fails with a timeout.
682-
async with pool.stream("GET", "https://example.com/"):
683-
with pytest.raises(httpcore.PoolTimeout):
684-
extensions = {"timeout": {"pool": 0.0001}}
685-
await pool.request("GET", "https://example.com/", extensions=extensions)
709+
async with open_nursery() as nursery:
710+
for _ in range(100):
711+
nursery.start_soon(fetch, pool)
712+
713+
assert 1 <= successes < 9, "less than 9 requests should succeed"
714+
715+
# Theoretically, the whole operation should take 50ms + 6ms; we should stay within that range
716+
assert max_wait <= 0.2
686717

687718

688719
@pytest.mark.anyio

tests/_sync/test_connection_pool.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import hpack
55
import hyperframe.frame
66
import pytest
7-
from tests import concurrency
87

98
import httpcore
9+
from tests.concurrency import sync_open_nursery as open_nursery
10+
from tests.current_time import sync_current_time as current_time, sync_sleep as sleep
1011

1112

1213

@@ -515,7 +516,7 @@ def fetch(pool, domain, info_list):
515516
max_connections=1, network_backend=network_backend
516517
) as pool:
517518
info_list: typing.List[str] = []
518-
with concurrency.open_nursery() as nursery:
519+
with open_nursery() as nursery:
519520
for domain in ["a.com", "b.com", "c.com", "d.com", "e.com"]:
520521
nursery.start_soon(fetch, pool, domain, info_list)
521522

@@ -561,7 +562,7 @@ def fetch(pool, domain, info_list):
561562
max_connections=1, network_backend=network_backend, http2=True
562563
) as pool:
563564
info_list: typing.List[str] = []
564-
with concurrency.open_nursery() as nursery:
565+
with open_nursery() as nursery:
565566
for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]:
566567
nursery.start_soon(fetch, pool, domain, info_list)
567568

@@ -603,7 +604,7 @@ def fetch(pool, domain, info_list):
603604
max_connections=1, network_backend=network_backend, http2=True
604605
) as pool:
605606
info_list: typing.List[str] = []
606-
with concurrency.open_nursery() as nursery:
607+
with open_nursery() as nursery:
607608
for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]:
608609
nursery.start_soon(fetch, pool, domain, info_list)
609610

@@ -662,7 +663,14 @@ def test_connection_pool_closed_while_request_in_flight():
662663
def test_connection_pool_timeout():
663664
"""
664665
Ensure that exceeding max_connections can cause a request to timeout.
666+
667+
100 concurrent requests will be sent with a limit of 1 connection.
668+
The request time is fixed to 6ms, and there is a pool timeout of 50ms.
669+
670+
Less than 9 requests will succeed and the rest won't have a chance to get
671+
a connection before 50ms, PoolTimeout will be raised instead.
665672
"""
673+
666674
network_backend = httpcore.MockBackend(
667675
[
668676
b"HTTP/1.1 200 OK\r\n",
@@ -671,18 +679,41 @@ def test_connection_pool_timeout():
671679
b"\r\n",
672680
b"Hello, world!",
673681
]
682+
* 100
674683
)
675684

685+
successes: int = 0
686+
max_wait: float = 0
687+
688+
def fetch(pool):
689+
nonlocal successes, max_wait
690+
691+
start_time = current_time()
692+
extensions = {"timeout": {"pool": 0.05}}
693+
694+
try:
695+
with pool.stream(
696+
"GET", "https://example.com/", extensions=extensions
697+
) as response:
698+
sleep(0.006) # we block the connection for that time
699+
response.read()
700+
successes += 1
701+
except httpcore.PoolTimeout:
702+
pass
703+
finally:
704+
max_wait = max(current_time() - start_time, max_wait)
705+
676706
with httpcore.ConnectionPool(
677707
network_backend=network_backend, max_connections=1
678708
) as pool:
679-
# Send a request to a pool that is configured to only support a single
680-
# connection, and then ensure that a second concurrent request
681-
# fails with a timeout.
682-
with pool.stream("GET", "https://example.com/"):
683-
with pytest.raises(httpcore.PoolTimeout):
684-
extensions = {"timeout": {"pool": 0.0001}}
685-
pool.request("GET", "https://example.com/", extensions=extensions)
709+
with open_nursery() as nursery:
710+
for _ in range(100):
711+
nursery.start_soon(fetch, pool)
712+
713+
assert 1 <= successes < 9, "less than 9 requests should succeed"
714+
715+
# Theoretically, the whole operation should take 50ms + 6ms; we should stay within that range
716+
assert max_wait <= 0.2
686717

687718

688719

tests/concurrency.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
from types import TracebackType
1313
from typing import Any, Callable, List, Optional, Type
1414

15+
from anyio import create_task_group
16+
from anyio.abc import TaskGroup
17+
1518

1619
class Nursery:
1720
def __init__(self) -> None:
@@ -36,5 +39,9 @@ def start_soon(self, func: Callable[..., object], *args: Any) -> None:
3639
self._threads.append(thread)
3740

3841

39-
def open_nursery() -> Nursery:
42+
def sync_open_nursery() -> Nursery:
4043
return Nursery()
44+
45+
46+
def async_open_nursery() -> TaskGroup:
47+
return create_task_group()

tests/current_time.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import time
2+
3+
import anyio
4+
5+
6+
def async_current_time() -> float:
7+
return anyio.current_time()
8+
9+
10+
def sync_current_time() -> float:
11+
return time.monotonic()
12+
13+
14+
async def async_sleep(delay: float) -> None:
15+
await anyio.sleep(delay)
16+
17+
18+
def sync_sleep(delay: float) -> None:
19+
time.sleep(delay)

unasync.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66

77
SUBS = [
88
('from .._backends.auto import AutoBackend', 'from .._backends.sync import SyncBackend'),
9-
('import trio as concurrency', 'from tests import concurrency'),
9+
(
10+
'from tests.current_time import async_current_time as current_time, async_sleep as sleep',
11+
'from tests.current_time import sync_current_time as current_time, sync_sleep as sleep',
12+
),
13+
(
14+
'from tests.concurrency import async_open_nursery as open_nursery',
15+
'from tests.concurrency import sync_open_nursery as open_nursery',
16+
),
1017
('AsyncIterator', 'Iterator'),
1118
('Async([A-Z][A-Za-z0-9_]*)', r'\2'),
1219
('async def', 'def'),

0 commit comments

Comments
 (0)