-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathtest_anycast.py
More file actions
245 lines (178 loc) · 6.91 KB
/
Copy pathtest_anycast.py
File metadata and controls
245 lines (178 loc) · 6.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# License: MIT
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH
"""Tests for the Channel implementation."""
import asyncio
import pytest
from frequenz.channels import (
Anycast,
ChannelClosedError,
Receiver,
ReceiverStoppedError,
Sender,
SenderError,
)
async def test_anycast() -> None:
"""Ensure sent messages are received by one receiver."""
acast: Anycast[int] = Anycast(name="test")
num_receivers = 5
num_senders = 5
expected_sum = num_senders * num_receivers * (num_receivers + 1) / 2
# a list of `num_receivers` elements, where each element with get
# incremented by values the corresponding receiver receives. Once the run
# finishes, we will check if their sum equals `expected_sum`.
recv_trackers = [0] * num_receivers
async def send_msg(chan: Sender[int]) -> None:
# send one message for each receiver
for ctr in range(num_receivers):
await chan.send(ctr + 1)
async def update_tracker_on_receive(receiver_id: int, recv: Receiver[int]) -> None:
while True:
try:
msg = await recv.receive()
except ReceiverStoppedError as err:
assert err.receiver is recv
assert isinstance(err.__cause__, ChannelClosedError)
return
recv_trackers[receiver_id] += msg
# without the sleep, decommissioning receivers temporarily, all
# messages go to the first receiver.
await asyncio.sleep(0)
receivers = []
for ctr in range(num_receivers):
receivers.append(update_tracker_on_receive(ctr, acast.new_receiver()))
# get one more sender and receiver to test channel operations after the
# channel is closed.
after_close_receiver = acast.new_receiver()
after_close_sender = acast.new_sender()
receivers_runs = asyncio.gather(*receivers)
senders = []
for ctr in range(num_senders):
senders.append(send_msg(acast.new_sender()))
await asyncio.gather(*senders)
await acast.aclose()
await receivers_runs
with pytest.raises(SenderError):
await after_close_sender.send(5)
with pytest.raises(ReceiverStoppedError) as excinfo:
await after_close_receiver.receive()
assert excinfo.value.receiver is after_close_receiver
assert isinstance(excinfo.value.__cause__, ChannelClosedError)
assert excinfo.value.__cause__.channel is acast
actual_sum = 0
for ctr in recv_trackers:
# ensure all receivers have got messages
assert ctr > 0
actual_sum += ctr
assert actual_sum == expected_sum
async def test_anycast_after_close() -> None:
"""Ensure closed channels can't get new messages."""
acast: Anycast[int] = Anycast(name="test")
receiver = acast.new_receiver()
sender = acast.new_sender()
await sender.send(2)
await acast.aclose()
with pytest.raises(SenderError):
await sender.send(5)
assert await receiver.receive() == 2
with pytest.raises(ReceiverStoppedError):
await receiver.receive()
async def test_anycast_full() -> None:
"""Ensure send calls to a full channel are blocked."""
buffer_size = 10
timeout = 0.2
acast: Anycast[int] = Anycast(name="test", limit=buffer_size)
receiver = acast.new_receiver()
sender = acast.new_sender()
timeout_at = 0
for ctr in range(buffer_size + 1):
try:
await asyncio.wait_for(sender.send(ctr), timeout)
except asyncio.TimeoutError:
# expect timeout once the buffer is full
timeout_at = ctr
assert timeout_at == buffer_size
timeout_at = 0
for ctr in range(buffer_size + 1):
try:
msg = await asyncio.wait_for(receiver.receive(), timeout)
assert ctr == msg
except asyncio.TimeoutError:
# expect timeout once the buffer is empty
timeout_at = ctr
assert timeout_at == buffer_size
try:
await asyncio.wait_for(sender.send(100), timeout)
except asyncio.TimeoutError:
# should not timeout now, because we've just drained the channel
assert False
try:
msg = await asyncio.wait_for(receiver.receive(), timeout)
assert msg == 100
except asyncio.TimeoutError:
# should not timeout now, because we've just sent a message to the
# channel.
assert False
async def test_anycast_none_messages() -> None:
"""Ensure None messages can be sent and received."""
acast: Anycast[int | None] = Anycast(name="test")
sender = acast.new_sender()
receiver = acast.new_receiver()
await sender.send(5)
assert await receiver.receive() == 5
await sender.send(None)
assert await receiver.receive() is None
await sender.send(10)
assert await receiver.receive() == 10
async def test_anycast_async_iterator() -> None:
"""Check that the anycast receiver works as an async iterator."""
acast: Anycast[str] = Anycast(name="test")
sender = acast.new_sender()
receiver = acast.new_receiver()
async def send_messages() -> None:
for val in ["one", "two", "three", "four", "five"]:
await sender.send(val)
await acast.aclose()
sender_task = asyncio.create_task(send_messages())
received = []
async for recv in receiver:
received.append(recv)
assert received == ["one", "two", "three", "four", "five"]
await sender_task
async def test_anycast_map() -> None:
"""Ensure map runs on all incoming messages."""
chan: Anycast[int] = Anycast(name="test")
sender = chan.new_sender()
# transform int receiver into bool receiver.
receiver: Receiver[bool] = chan.new_receiver().map(lambda num: num > 10)
await sender.send(8)
await sender.send(12)
assert (await receiver.receive()) is False
assert (await receiver.receive()) is True
async def test_anycast_filter() -> None:
"""Ensure filter keeps only the messages that pass the filter."""
chan = Anycast[int](name="input-chan")
sender = chan.new_sender()
# filter out all numbers less than 10.
receiver: Receiver[int] = chan.new_receiver().filter(lambda num: num > 10)
await sender.send(8)
await sender.send(12)
await sender.send(5)
await sender.send(15)
assert (await receiver.receive()) == 12
assert (await receiver.receive()) == 15
async def test_anycast_close_receiver() -> None:
"""Ensure closing a receiver stops the receiver."""
chan = Anycast[int](name="input-chan")
sender = chan.new_sender()
receiver_1 = chan.new_receiver()
receiver_2 = chan.new_receiver()
await sender.send(1)
assert (await receiver_1.receive()) == 1
receiver_1.close()
await sender.send(2)
with pytest.raises(ReceiverStoppedError):
_ = await receiver_1.receive()
assert (await receiver_2.receive()) == 2
receiver_2.close()
with pytest.raises(ReceiverStoppedError):
_ = await receiver_2.receive()