99 lines
3.2 KiB
Python
99 lines
3.2 KiB
Python
import asyncio
|
|
import json
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from k1link.viewer.node_local_media import acknowledge, admit_local, read_local, start_local
|
|
from k1link.viewer.node_media import NodeMediaPeers
|
|
|
|
|
|
def test_local_stream_fragments_ack_resume_and_releases_before_reconnect():
|
|
payload = b"RRF2" + bytes(range(256)) * 256
|
|
released = []
|
|
subscriptions = []
|
|
|
|
class Subscription:
|
|
pending = None
|
|
|
|
def __init__(self, after):
|
|
self.after = after
|
|
self.sent = False
|
|
|
|
def next_batch(self, **_):
|
|
if self.sent:
|
|
return b""
|
|
self.sent = True
|
|
self.pending = (self.after + 1, payload, None)
|
|
return self.pending
|
|
|
|
def snapshot(self, _):
|
|
return dict(sequence=self.after + 1, age_ms=0, points=5000)
|
|
|
|
def acknowledge(self, seq):
|
|
if seq == self.after + 1:
|
|
self.pending = None
|
|
|
|
def release(self):
|
|
released.append(self.after)
|
|
|
|
class Hub:
|
|
def subscribe(self, view_id, after):
|
|
sub = Subscription(after)
|
|
subscriptions.append((view_id, sub))
|
|
return sub
|
|
|
|
class Camera:
|
|
def snapshot(self):
|
|
return {"recording": {"source_end_expected": True}}
|
|
|
|
async def run():
|
|
peers = NodeMediaPeers(Hub(), Camera())
|
|
view = str(uuid4())
|
|
for after in (0, 1):
|
|
peer = await start_local(peers, dict(view_id=view, after=after))
|
|
if after:
|
|
assert released == [0] # Lost response/reconnect retires the old lease.
|
|
collected = []
|
|
while sum(map(len, collected)) < len(payload):
|
|
response = await asyncio.wait_for(read_local(peers, peer, after), 1.2)
|
|
assert len(response) <= 196608
|
|
offset = 0
|
|
while offset < len(response):
|
|
kind = response[offset]
|
|
size = int.from_bytes(response[offset + 1 : offset + 5], "big")
|
|
data = response[offset + 5 : offset + 5 + size]
|
|
if kind == 0:
|
|
assert json.loads(data)["peer_id"] == peer
|
|
if kind == 2:
|
|
if data.startswith(b"MCF1"):
|
|
assert int.from_bytes(data[4:], "big") == len(payload)
|
|
else:
|
|
collected.append(data)
|
|
offset += size + 5
|
|
assert b"".join(collected) == payload
|
|
acknowledge(peers, peer, after + 1)
|
|
assert subscriptions[-1][1].pending is None
|
|
await peers.close_all()
|
|
assert not peers.items and released == [0, 1]
|
|
assert subscriptions[0][0] == subscriptions[1][0] == view
|
|
|
|
asyncio.run(run())
|
|
|
|
|
|
@pytest.mark.parametrize("after", [True, -1, 2**53, "1"])
|
|
def test_local_cursor_rejects_noncanonical_values(after):
|
|
with pytest.raises(ValueError):
|
|
admit_local(dict(view_id=str(uuid4()), after=after))
|
|
|
|
|
|
def test_local_carrier_does_not_exceed_peer_capacity():
|
|
async def run():
|
|
class Peers:
|
|
items = {"a": {}, "b": {}}
|
|
|
|
with pytest.raises(ValueError):
|
|
await start_local(Peers(), dict(view_id=str(uuid4()), after=0))
|
|
|
|
asyncio.run(run())
|