def test_add_event(self): package = Package(Header(1, 2, "0" * 32)) event1 = Event("TEST", 1, 2, 3) event2 = Event("FOO", "Bar") package.add_event(event1) package.add_event(event2) assert len(package.events) == 2 assert event1 in package.events and event2 in package.events with pytest.raises(OverflowError): package.get_bytesize() package.add_event(Event("BIG", bytes(2030)))
def test_dispatch_multiple_events(self): send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) recv_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) recv_socket.bind(("localhost", 0)) connection = Connection(recv_socket.getsockname(), None) event = Event("TEST", 1, 2, 3, 4) another_event = Event("TEST", 3, 4, 5, 6) connection.dispatch_event(event) connection.dispatch_event(another_event) curio.run(connection._send_next_package, send_socket) data = curio.run(recv_socket.recv, Package._max_size) package = Package.from_datagram(data) assert package.events == [event, another_event]
def test_synchronous_event_handler(self): handler = UniversalEventHandler() testlist = [] def on_foo(bar): testlist.append(bar) assert not handler.has_event_type("FOO") handler.register_event_handler("FOO", on_foo) assert handler.has_event_type("FOO") curio.run(handler.handle, Event("FOO", "baz")) assert "baz" in testlist handler.register_event_handler("BAR", lambda: testlist.pop()) assert curio.run(handler.handle, Event("BAR")) == "baz" assert not testlist
def _read_out_event_block(event_block: bytes) -> list: events = [] while event_block: bytesize = int.from_bytes(event_block[:2], "big") events.append(Event.from_bytes(event_block[2:bytesize + 2])) event_block = event_block[bytesize + 2:] return events
def test_event_timeout_calbacks(self): async def sendto(*args): pass sock = type("socket", (), {"sendto": sendto})() class callback: count = 0 def __init__(self): callback.count += 1 with freeze_time("2012-01-14 12:00:01") as frozen_time: connection = Connection(("", 0), None) event = Event("TEST") connection.dispatch_event(event, timeout_callback=callback) curio.run(connection._send_next_package, sock) curio.run(connection._recv, Package(Header(1, 1, "0" * 32))) assert callback.count == 0 frozen_time.tick() frozen_time.tick() curio.run(connection._recv, Package(Header(2, 1, "0" * 32))) assert callback.count == 0 connection.dispatch_event(event, timeout_callback=callback) curio.run(connection._send_next_package, sock) frozen_time.tick() frozen_time.tick() curio.run(connection._recv, Package(Header(3, 1, "0" * 32))) assert callback.count == 1
def test_event_ack_callbacks(self): async def sendto(*args): pass sock = type("socket", (), {"sendto": sendto})() class callback: count = 0 def __init__(self): callback.count += 1 connection = Connection(("", 0), None) event = Event("TEST") connection.dispatch_event(event, ack_callback=callback) curio.run(connection._send_next_package, sock) curio.run(connection._recv, Package(Header(1, 1, "0" * 32))) assert callback.count == 1 curio.run(connection._recv, Package(Header(2, 1, "0" * 32))) assert callback.count == 1 connection.dispatch_event(event, ack_callback=callback) connection.dispatch_event(event) connection.dispatch_event(event, ack_callback=callback) curio.run(connection._send_next_package, sock) assert connection.local_sequence == 2 curio.run(connection._recv, Package(Header(3, 2, "1" + "0" * 31))) assert callback.count == 3
def test_receive_events(self): class EventHandler: call_count = 0 def __init__(self): self.events = [] async def handle(self, event, **kwargs): EventHandler.call_count += 1 self.events.append(event) def has_event_type(self, event_type): return True event_handler = EventHandler() connection = Connection(("", 0), event_handler) assert connection.event_handler == event_handler event = Event("TEST", 1, 2, 3, 4) package = Package(Header(1, 1, "1" * 32), [event, event]) assert package.events == [event, event] assert connection.remote_sequence == 0 curio.run(connection._recv, package) assert not connection._incoming_event_queue.empty() assert EventHandler.call_count == 0 curio.run(connection._handle_next_event) assert EventHandler.call_count == 1 assert not connection._incoming_event_queue.empty() assert event_handler.events == [event] curio.run(connection._handle_next_event) assert EventHandler.call_count == 2 assert connection._incoming_event_queue.empty() assert event_handler.events == [event, event]
def test_asynchronous_event_handler_with_kwarg(self): handler = UniversalEventHandler() testlist = [] async def on_foo(biz=0, bar="nobizbaz"): testlist.append(bar) handler.register_event_handler("FOO", on_foo) curio.run(handler.handle, Event("FOO", bar="bizbaz")) assert "bizbaz" in testlist
def dispatch_event(self, event_type: str, *args, target_client="all", retries: int = 0, ack_callback=None, **kwargs) -> None: """Send an event to one or all clients. # Arguments event_type (str): identifies the event and links it to a handler target_client (tuple, str): either `'all'` for an event broadcast, or a clients address as a tuple retries (int): number of times the event is to be resent in case it times out ack_callback (callable, coroutine): will be executed after the event was received and be passed a reference to the corresponding #pygase.connection.ServerConnection instance Additional positional and keyword arguments will be sent as event data and passed to the clients handler function. """ event = Event(event_type, *args, **kwargs) def get_ack_callback(connection): if ack_callback is not None: return lambda: ack_callback(connection) return None timeout_callback = None if retries > 0: timeout_callback = lambda: self.dispatch_event( # type: ignore event_type, *args, target_client=target_client, retries=retries - 1, ack_callback=ack_callback, **kwargs, ) or logger.warning( # type: ignore f"Event of type {event_type} timed out. Retrying to send event to server." ) if target_client == "all": for connection in self.connections.values(): connection.dispatch_event(event, get_ack_callback(connection), timeout_callback, **kwargs) else: self.connections[target_client].dispatch_event( event, get_ack_callback(self.connections[target_client]), timeout_callback, **kwargs)
def add_event(self, event: Event) -> None: """Add a PyGaSe event to the package. # Arguments event (pygase.event.Event): the event to be attached to this package # Raises OverflowError: if the package has previously been converted to a datagram and and its size with the added event would exceed #Package._max_size (2048 bytex) """ if self._datagram is not None: bytepack = event.to_bytes() if len(self._datagram) + len(bytepack) + 2 > self._max_size: raise OverflowError("Package exceeds the maximum size of " + str(self._max_size) + " bytes.") self._datagram += len(bytepack).to_bytes(2, "big") + bytepack self._events.append(event)
def dispatch_event(self, event_type: str, *args, retries: int = 0, ack_callback=None, **kwargs) -> None: """Send an event to the server. # Arguments event_type (str): event type identifier that links to a handler retries (int): number of times the event is to be resent in case it times out ack_callback (callable, coroutine): will be invoked after the event was received Additional positional and keyword arguments will be sent as event data and passed to the handler function. --- `ack_callback` should not perform any long-running blocking operations (say a `while True` loop), as that will block the connections asynchronous event loop. Use a coroutine instead, with appropriately placed `await`s. """ event = Event(event_type, *args, **kwargs) timeout_callback = None if retries > 0: timeout_callback = lambda: self.dispatch_event( # type: ignore event_type, *args, retries=retries - 1, ack_callback=ack_callback, **kwargs ) or logger.warning( # type: ignore f"Event of type {event_type} timed out. Retrying to send event to server." ) self.connection.dispatch_event(event, ack_callback, timeout_callback)
def test_bytepacking(self): package = ServerPackage(Header(4, 5, "10" * 16), GameStateUpdate(2), [Event("TEST", "Foo", "Bar")]) datagram = package.to_datagram() unpacked_package = ServerPackage.from_datagram(datagram) assert package == unpacked_package
def test_bytepacking(self): package = ClientPackage(Header(4, 5, "10" * 16), 1, [Event("TEST", "Foo", "Bar")]) datagram = package.to_datagram() unpacked_package = ClientPackage.from_datagram(datagram) assert package == unpacked_package
def test_size_restriction(self): with pytest.raises(OverflowError) as error: Package(Header(1, 4, "0" * 32), [Event("TEST", bytes(2048 - 13))]).to_datagram() assert str( error.value) == "Package exceeds the maximum size of 2048 bytes."
def test_bytepacking(self): event1 = Event("TEST", 1, 2, 3) event2 = Event.from_bytes(event1.to_bytes()) assert event1 == event2