You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

658 lines
19 KiB
Python

# XX this should get broken up, like testing.py did
import tempfile
import pytest
from .._core.tests.tutil import can_bind_ipv6
from .. import sleep
from .. import _core
from .._highlevel_generic import aclose_forcefully
from ..testing import *
from ..testing._check_streams import _assert_raises
from ..testing._memory_streams import _UnboundedByteQueue
from .. import socket as tsocket
from .._highlevel_socket import SocketListener
async def test_wait_all_tasks_blocked():
record = []
async def busy_bee():
for _ in range(10):
await _core.checkpoint()
record.append("busy bee exhausted")
async def waiting_for_bee_to_leave():
await wait_all_tasks_blocked()
record.append("quiet at last!")
async with _core.open_nursery() as nursery:
nursery.start_soon(busy_bee)
nursery.start_soon(waiting_for_bee_to_leave)
nursery.start_soon(waiting_for_bee_to_leave)
# check cancellation
record = []
async def cancelled_while_waiting():
try:
await wait_all_tasks_blocked()
except _core.Cancelled:
record.append("ok")
async with _core.open_nursery() as nursery:
nursery.start_soon(cancelled_while_waiting)
nursery.cancel_scope.cancel()
assert record == ["ok"]
async def test_wait_all_tasks_blocked_with_timeouts(mock_clock):
record = []
async def timeout_task():
record.append("tt start")
await sleep(5)
record.append("tt finished")
async with _core.open_nursery() as nursery:
nursery.start_soon(timeout_task)
await wait_all_tasks_blocked()
assert record == ["tt start"]
mock_clock.jump(10)
await wait_all_tasks_blocked()
assert record == ["tt start", "tt finished"]
async def test_wait_all_tasks_blocked_with_cushion():
record = []
async def blink():
record.append("blink start")
await sleep(0.01)
await sleep(0.01)
await sleep(0.01)
record.append("blink end")
async def wait_no_cushion():
await wait_all_tasks_blocked()
record.append("wait_no_cushion end")
async def wait_small_cushion():
await wait_all_tasks_blocked(0.02)
record.append("wait_small_cushion end")
async def wait_big_cushion():
await wait_all_tasks_blocked(0.03)
record.append("wait_big_cushion end")
async with _core.open_nursery() as nursery:
nursery.start_soon(blink)
nursery.start_soon(wait_no_cushion)
nursery.start_soon(wait_small_cushion)
nursery.start_soon(wait_small_cushion)
nursery.start_soon(wait_big_cushion)
assert record == [
"blink start",
"wait_no_cushion end",
"blink end",
"wait_small_cushion end",
"wait_small_cushion end",
"wait_big_cushion end",
]
################################################################
async def test_assert_checkpoints(recwarn):
with assert_checkpoints():
await _core.checkpoint()
with pytest.raises(AssertionError):
with assert_checkpoints():
1 + 1
# partial yield cases
# if you have a schedule point but not a cancel point, or vice-versa, then
# that's not a checkpoint.
for partial_yield in [
_core.checkpoint_if_cancelled,
_core.cancel_shielded_checkpoint,
]:
print(partial_yield)
with pytest.raises(AssertionError):
with assert_checkpoints():
await partial_yield()
# But both together count as a checkpoint
with assert_checkpoints():
await _core.checkpoint_if_cancelled()
await _core.cancel_shielded_checkpoint()
async def test_assert_no_checkpoints(recwarn):
with assert_no_checkpoints():
1 + 1
with pytest.raises(AssertionError):
with assert_no_checkpoints():
await _core.checkpoint()
# partial yield cases
# if you have a schedule point but not a cancel point, or vice-versa, then
# that doesn't make *either* version of assert_{no_,}yields happy.
for partial_yield in [
_core.checkpoint_if_cancelled,
_core.cancel_shielded_checkpoint,
]:
print(partial_yield)
with pytest.raises(AssertionError):
with assert_no_checkpoints():
await partial_yield()
# And both together also count as a checkpoint
with pytest.raises(AssertionError):
with assert_no_checkpoints():
await _core.checkpoint_if_cancelled()
await _core.cancel_shielded_checkpoint()
################################################################
async def test_Sequencer():
record = []
def t(val):
print(val)
record.append(val)
async def f1(seq):
async with seq(1):
t(("f1", 1))
async with seq(3):
t(("f1", 3))
async with seq(4):
t(("f1", 4))
async def f2(seq):
async with seq(0):
t(("f2", 0))
async with seq(2):
t(("f2", 2))
seq = Sequencer()
async with _core.open_nursery() as nursery:
nursery.start_soon(f1, seq)
nursery.start_soon(f2, seq)
async with seq(5):
await wait_all_tasks_blocked()
assert record == [("f2", 0), ("f1", 1), ("f2", 2), ("f1", 3), ("f1", 4)]
seq = Sequencer()
# Catches us if we try to re-use a sequence point:
async with seq(0):
pass
with pytest.raises(RuntimeError):
async with seq(0):
pass # pragma: no cover
async def test_Sequencer_cancel():
# Killing a blocked task makes everything blow up
record = []
seq = Sequencer()
async def child(i):
with _core.CancelScope() as scope:
if i == 1:
scope.cancel()
try:
async with seq(i):
pass # pragma: no cover
except RuntimeError:
record.append("seq({}) RuntimeError".format(i))
async with _core.open_nursery() as nursery:
nursery.start_soon(child, 1)
nursery.start_soon(child, 2)
async with seq(0):
pass # pragma: no cover
assert record == ["seq(1) RuntimeError", "seq(2) RuntimeError"]
# Late arrivals also get errors
with pytest.raises(RuntimeError):
async with seq(3):
pass # pragma: no cover
################################################################
async def test__assert_raises():
with pytest.raises(AssertionError):
with _assert_raises(RuntimeError):
1 + 1
with pytest.raises(TypeError):
with _assert_raises(RuntimeError):
"foo" + 1
with _assert_raises(RuntimeError):
raise RuntimeError
# This is a private implementation detail, but it's complex enough to be worth
# testing directly
async def test__UnboundeByteQueue():
ubq = _UnboundedByteQueue()
ubq.put(b"123")
ubq.put(b"456")
assert ubq.get_nowait(1) == b"1"
assert ubq.get_nowait(10) == b"23456"
ubq.put(b"789")
assert ubq.get_nowait() == b"789"
with pytest.raises(_core.WouldBlock):
ubq.get_nowait(10)
with pytest.raises(_core.WouldBlock):
ubq.get_nowait()
with pytest.raises(TypeError):
ubq.put("string")
ubq.put(b"abc")
with assert_checkpoints():
assert await ubq.get(10) == b"abc"
ubq.put(b"def")
ubq.put(b"ghi")
with assert_checkpoints():
assert await ubq.get(1) == b"d"
with assert_checkpoints():
assert await ubq.get() == b"efghi"
async def putter(data):
await wait_all_tasks_blocked()
ubq.put(data)
async def getter(expect):
with assert_checkpoints():
assert await ubq.get() == expect
async with _core.open_nursery() as nursery:
nursery.start_soon(getter, b"xyz")
nursery.start_soon(putter, b"xyz")
# Two gets at the same time -> BusyResourceError
with pytest.raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(getter, b"asdf")
nursery.start_soon(getter, b"asdf")
# Closing
ubq.close()
with pytest.raises(_core.ClosedResourceError):
ubq.put(b"---")
assert ubq.get_nowait(10) == b""
assert ubq.get_nowait() == b""
assert await ubq.get(10) == b""
assert await ubq.get() == b""
# close is idempotent
ubq.close()
# close wakes up blocked getters
ubq2 = _UnboundedByteQueue()
async def closer():
await wait_all_tasks_blocked()
ubq2.close()
async with _core.open_nursery() as nursery:
nursery.start_soon(getter, b"")
nursery.start_soon(closer)
async def test_MemorySendStream():
mss = MemorySendStream()
async def do_send_all(data):
with assert_checkpoints():
await mss.send_all(data)
await do_send_all(b"123")
assert mss.get_data_nowait(1) == b"1"
assert mss.get_data_nowait() == b"23"
with assert_checkpoints():
await mss.wait_send_all_might_not_block()
with pytest.raises(_core.WouldBlock):
mss.get_data_nowait()
with pytest.raises(_core.WouldBlock):
mss.get_data_nowait(10)
await do_send_all(b"456")
with assert_checkpoints():
assert await mss.get_data() == b"456"
# Call send_all twice at once; one should get BusyResourceError and one
# should succeed. But we can't let the error propagate, because it might
# cause the other to be cancelled before it can finish doing its thing,
# and we don't know which one will get the error.
resource_busy_count = 0
async def do_send_all_count_resourcebusy():
nonlocal resource_busy_count
try:
await do_send_all(b"xxx")
except _core.BusyResourceError:
resource_busy_count += 1
async with _core.open_nursery() as nursery:
nursery.start_soon(do_send_all_count_resourcebusy)
nursery.start_soon(do_send_all_count_resourcebusy)
assert resource_busy_count == 1
with assert_checkpoints():
await mss.aclose()
assert await mss.get_data() == b"xxx"
assert await mss.get_data() == b""
with pytest.raises(_core.ClosedResourceError):
await do_send_all(b"---")
# hooks
assert mss.send_all_hook is None
assert mss.wait_send_all_might_not_block_hook is None
assert mss.close_hook is None
record = []
async def send_all_hook():
# hook runs after send_all does its work (can pull data out)
assert mss2.get_data_nowait() == b"abc"
record.append("send_all_hook")
async def wait_send_all_might_not_block_hook():
record.append("wait_send_all_might_not_block_hook")
def close_hook():
record.append("close_hook")
mss2 = MemorySendStream(
send_all_hook, wait_send_all_might_not_block_hook, close_hook
)
assert mss2.send_all_hook is send_all_hook
assert mss2.wait_send_all_might_not_block_hook is wait_send_all_might_not_block_hook
assert mss2.close_hook is close_hook
await mss2.send_all(b"abc")
await mss2.wait_send_all_might_not_block()
await aclose_forcefully(mss2)
mss2.close()
assert record == [
"send_all_hook",
"wait_send_all_might_not_block_hook",
"close_hook",
"close_hook",
]
async def test_MemoryReceiveStream():
mrs = MemoryReceiveStream()
async def do_receive_some(max_bytes):
with assert_checkpoints():
return await mrs.receive_some(max_bytes)
mrs.put_data(b"abc")
assert await do_receive_some(1) == b"a"
assert await do_receive_some(10) == b"bc"
mrs.put_data(b"abc")
assert await do_receive_some(None) == b"abc"
with pytest.raises(_core.BusyResourceError):
async with _core.open_nursery() as nursery:
nursery.start_soon(do_receive_some, 10)
nursery.start_soon(do_receive_some, 10)
assert mrs.receive_some_hook is None
mrs.put_data(b"def")
mrs.put_eof()
mrs.put_eof()
assert await do_receive_some(10) == b"def"
assert await do_receive_some(10) == b""
assert await do_receive_some(10) == b""
with pytest.raises(_core.ClosedResourceError):
mrs.put_data(b"---")
async def receive_some_hook():
mrs2.put_data(b"xxx")
record = []
def close_hook():
record.append("closed")
mrs2 = MemoryReceiveStream(receive_some_hook, close_hook)
assert mrs2.receive_some_hook is receive_some_hook
assert mrs2.close_hook is close_hook
mrs2.put_data(b"yyy")
assert await mrs2.receive_some(10) == b"yyyxxx"
assert await mrs2.receive_some(10) == b"xxx"
assert await mrs2.receive_some(10) == b"xxx"
mrs2.put_data(b"zzz")
mrs2.receive_some_hook = None
assert await mrs2.receive_some(10) == b"zzz"
mrs2.put_data(b"lost on close")
with assert_checkpoints():
await mrs2.aclose()
assert record == ["closed"]
with pytest.raises(_core.ClosedResourceError):
await mrs2.receive_some(10)
async def test_MemoryRecvStream_closing():
mrs = MemoryReceiveStream()
# close with no pending data
mrs.close()
with pytest.raises(_core.ClosedResourceError):
assert await mrs.receive_some(10) == b""
# repeated closes ok
mrs.close()
# put_data now fails
with pytest.raises(_core.ClosedResourceError):
mrs.put_data(b"123")
mrs2 = MemoryReceiveStream()
# close with pending data
mrs2.put_data(b"xyz")
mrs2.close()
with pytest.raises(_core.ClosedResourceError):
await mrs2.receive_some(10)
async def test_memory_stream_pump():
mss = MemorySendStream()
mrs = MemoryReceiveStream()
# no-op if no data present
memory_stream_pump(mss, mrs)
await mss.send_all(b"123")
memory_stream_pump(mss, mrs)
assert await mrs.receive_some(10) == b"123"
await mss.send_all(b"456")
assert memory_stream_pump(mss, mrs, max_bytes=1)
assert await mrs.receive_some(10) == b"4"
assert memory_stream_pump(mss, mrs, max_bytes=1)
assert memory_stream_pump(mss, mrs, max_bytes=1)
assert not memory_stream_pump(mss, mrs, max_bytes=1)
assert await mrs.receive_some(10) == b"56"
mss.close()
memory_stream_pump(mss, mrs)
assert await mrs.receive_some(10) == b""
async def test_memory_stream_one_way_pair():
s, r = memory_stream_one_way_pair()
assert s.send_all_hook is not None
assert s.wait_send_all_might_not_block_hook is None
assert s.close_hook is not None
assert r.receive_some_hook is None
await s.send_all(b"123")
assert await r.receive_some(10) == b"123"
async def receiver(expected):
assert await r.receive_some(10) == expected
# This fails if we pump on r.receive_some_hook; we need to pump on s.send_all_hook
async with _core.open_nursery() as nursery:
nursery.start_soon(receiver, b"abc")
await wait_all_tasks_blocked()
await s.send_all(b"abc")
# And this fails if we don't pump from close_hook
async with _core.open_nursery() as nursery:
nursery.start_soon(receiver, b"")
await wait_all_tasks_blocked()
await s.aclose()
s, r = memory_stream_one_way_pair()
async with _core.open_nursery() as nursery:
nursery.start_soon(receiver, b"")
await wait_all_tasks_blocked()
s.close()
s, r = memory_stream_one_way_pair()
old = s.send_all_hook
s.send_all_hook = None
await s.send_all(b"456")
async def cancel_after_idle(nursery):
await wait_all_tasks_blocked()
nursery.cancel_scope.cancel()
async def check_for_cancel():
with pytest.raises(_core.Cancelled):
# This should block forever... or until cancelled. Even though we
# sent some data on the send stream.
await r.receive_some(10)
async with _core.open_nursery() as nursery:
nursery.start_soon(cancel_after_idle, nursery)
nursery.start_soon(check_for_cancel)
s.send_all_hook = old
await s.send_all(b"789")
assert await r.receive_some(10) == b"456789"
async def test_memory_stream_pair():
a, b = memory_stream_pair()
await a.send_all(b"123")
await b.send_all(b"abc")
assert await b.receive_some(10) == b"123"
assert await a.receive_some(10) == b"abc"
await a.send_eof()
assert await b.receive_some(10) == b""
async def sender():
await wait_all_tasks_blocked()
await b.send_all(b"xyz")
async def receiver():
assert await a.receive_some(10) == b"xyz"
async with _core.open_nursery() as nursery:
nursery.start_soon(receiver)
nursery.start_soon(sender)
async def test_memory_streams_with_generic_tests():
async def one_way_stream_maker():
return memory_stream_one_way_pair()
await check_one_way_stream(one_way_stream_maker, None)
async def half_closeable_stream_maker():
return memory_stream_pair()
await check_half_closeable_stream(half_closeable_stream_maker, None)
async def test_lockstep_streams_with_generic_tests():
async def one_way_stream_maker():
return lockstep_stream_one_way_pair()
await check_one_way_stream(one_way_stream_maker, one_way_stream_maker)
async def two_way_stream_maker():
return lockstep_stream_pair()
await check_two_way_stream(two_way_stream_maker, two_way_stream_maker)
async def test_open_stream_to_socket_listener():
async def check(listener):
async with listener:
client_stream = await open_stream_to_socket_listener(listener)
async with client_stream:
server_stream = await listener.accept()
async with server_stream:
await client_stream.send_all(b"x")
await server_stream.receive_some(1) == b"x"
# Listener bound to localhost
sock = tsocket.socket()
await sock.bind(("127.0.0.1", 0))
sock.listen(10)
await check(SocketListener(sock))
# Listener bound to IPv4 wildcard (needs special handling)
sock = tsocket.socket()
await sock.bind(("0.0.0.0", 0))
sock.listen(10)
await check(SocketListener(sock))
if can_bind_ipv6:
# Listener bound to IPv6 wildcard (needs special handling)
sock = tsocket.socket(family=tsocket.AF_INET6)
await sock.bind(("::", 0))
sock.listen(10)
await check(SocketListener(sock))
if hasattr(tsocket, "AF_UNIX"):
# Listener bound to Unix-domain socket
sock = tsocket.socket(family=tsocket.AF_UNIX)
# can't use pytest's tmpdir; if we try then macOS says "OSError:
# AF_UNIX path too long"
with tempfile.TemporaryDirectory() as tmpdir:
path = "{}/sock".format(tmpdir)
await sock.bind(path)
sock.listen(10)
await check(SocketListener(sock))