async def _unittest_serial_transport_capture(caplog: typing.Any) -> None: from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer from pyuavcan.transport import Priority, Timestamp, OutputSessionSpecifier get_monotonic = asyncio.get_event_loop().time tr = SerialTransport(serial_port="loop://", local_node_id=42, mtu=1024, service_transfer_multiplier=2) sft_capacity = 1024 payload_single = [_mem("qwertyui"), _mem("01234567") ] * (sft_capacity // 16) assert sum(map(len, payload_single)) == sft_capacity payload_x3 = (payload_single * 3)[:-1] payload_x3_size_bytes = sft_capacity * 3 - 8 assert sum(map(len, payload_x3)) == payload_x3_size_bytes broadcaster = tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), PayloadMetadata(10000)) client_requester = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 3210), PayloadMetadata(10000), ) events: typing.List[SerialCapture] = [] events2: typing.List[pyuavcan.transport.Capture] = [] def append_events(cap: pyuavcan.transport.Capture) -> None: assert isinstance(cap, SerialCapture) events.append(cap) tr.begin_capture(append_events) tr.begin_capture(events2.append) assert events == [] assert events2 == [] # # Multi-frame message. # ts = Timestamp.now() assert await broadcaster.send( Transfer(timestamp=ts, priority=Priority.LOW, transfer_id=777, fragmented_payload=payload_x3), monotonic_deadline=get_monotonic() + 5.0, ) await asyncio.sleep(0.1) assert events == events2 # Send three, receive three. # Sorting is required because the ordering of the events in the middle is not defined: arrival events # may or may not be registered before the emission event depending on how the serial loopback is operating. a, b, c, d, e, f = sorted(events, key=lambda x: not x.own) assert isinstance(a, SerialCapture) and a.own assert isinstance(b, SerialCapture) and b.own assert isinstance(c, SerialCapture) and c.own assert isinstance(d, SerialCapture) and not d.own assert isinstance(e, SerialCapture) and not e.own assert isinstance(f, SerialCapture) and not f.own def parse(x: SerialCapture) -> SerialFrame: out = SerialFrame.parse_from_cobs_image(x.fragment) assert out is not None return out assert parse(a).transfer_id == 777 assert parse(b).transfer_id == 777 assert parse(c).transfer_id == 777 assert a.timestamp.monotonic >= ts.monotonic assert b.timestamp.monotonic >= ts.monotonic assert c.timestamp.monotonic >= ts.monotonic assert parse(a).index == 0 assert parse(b).index == 1 assert parse(c).index == 2 assert not parse(a).end_of_transfer assert not parse(b).end_of_transfer assert parse(c).end_of_transfer assert a.fragment.tobytes().strip(b"\x00") == d.fragment.tobytes().strip( b"\x00") assert b.fragment.tobytes().strip(b"\x00") == e.fragment.tobytes().strip( b"\x00") assert c.fragment.tobytes().strip(b"\x00") == f.fragment.tobytes().strip( b"\x00") events.clear() events2.clear() # # Single-frame service request with dual frame duplication. # ts = Timestamp.now() assert await client_requester.send( Transfer(timestamp=ts, priority=Priority.HIGH, transfer_id=888, fragmented_payload=payload_single), monotonic_deadline=get_monotonic() + 5.0, ) await asyncio.sleep(0.1) assert events == events2 # Send two, receive two. # Sorting is required because the order of the two events in the middle is not defined: the arrival event # may or may not be registered before the emission event depending on how the serial loopback is operating. a, b, c, d = sorted(events, key=lambda x: not x.own) assert isinstance(a, SerialCapture) and a.own assert isinstance(b, SerialCapture) and b.own assert isinstance(c, SerialCapture) and not c.own assert isinstance(d, SerialCapture) and not d.own assert parse(a).transfer_id == 888 assert parse(b).transfer_id == 888 assert a.timestamp.monotonic >= ts.monotonic assert b.timestamp.monotonic >= ts.monotonic assert parse(a).index == 0 assert parse(b).index == 0 assert parse(a).end_of_transfer assert parse(b).end_of_transfer assert a.fragment.tobytes().strip(b"\x00") == c.fragment.tobytes().strip( b"\x00") assert b.fragment.tobytes().strip(b"\x00") == d.fragment.tobytes().strip( b"\x00") events.clear() events2.clear() # # Out-of-band data. # grownups = b"Aren't there any grownups at all? - No grownups!\x00" with caplog.at_level(logging.CRITICAL, logger=pyuavcan.transport.serial.__name__): # The frame delimiter is needed to force new frame into the state machine. tr.serial_port.write(grownups) await asyncio.sleep(1) assert events == events2 (oob, ) = events assert isinstance(oob, SerialCapture) assert not oob.own assert bytes(oob.fragment) == grownups events.clear() events2.clear()
async def _unittest_redundant_transport(caplog: typing.Any) -> None: from pyuavcan.transport import MessageDataSpecifier, PayloadMetadata, Transfer from pyuavcan.transport import Priority, Timestamp, InputSessionSpecifier, OutputSessionSpecifier from pyuavcan.transport import ProtocolParameters loop = asyncio.get_event_loop() loop.slow_callback_duration = 1.0 tr_a = RedundantTransport() tr_b = RedundantTransport(loop) assert tr_a.sample_statistics() == RedundantTransportStatistics([]) assert tr_a.inferiors == [] assert tr_a.local_node_id is None assert tr_a.loop is asyncio.get_event_loop() assert tr_a.local_node_id is None assert tr_a.protocol_parameters == ProtocolParameters( transfer_id_modulo=0, max_nodes=0, mtu=0, ) assert tr_a.descriptor == '<redundant></redundant>' # Empty, no inferiors. assert tr_a.input_sessions == [] assert tr_a.output_sessions == [] assert tr_a.loop == tr_b.loop # # Instantiate session objects. # meta = PayloadMetadata(10_240) pub_a = tr_a.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) sub_any_a = tr_a.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), None), meta) assert pub_a is tr_a.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) assert set(tr_a.input_sessions) == {sub_any_a} assert set(tr_a.output_sessions) == {pub_a} assert tr_a.sample_statistics() == RedundantTransportStatistics() pub_b = tr_b.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) sub_any_b = tr_b.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), None), meta) sub_sel_b = tr_b.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), 3210), meta) assert sub_sel_b is tr_b.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), 3210), meta) assert set(tr_b.input_sessions) == {sub_any_b, sub_sel_b} assert set(tr_b.output_sessions) == {pub_b} assert tr_b.sample_statistics() == RedundantTransportStatistics() # # Exchange test with no inferiors, expected to fail. # assert len(pub_a.inferiors) == 0 assert len(sub_any_a.inferiors) == 0 assert not await pub_a.send_until(Transfer( timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=1, fragmented_payload=[memoryview(b'abc')]), monotonic_deadline=loop.time() + 1.0) assert not await sub_any_a.receive_until(loop.time() + 0.1) assert not await sub_any_b.receive_until(loop.time() + 0.1) assert tr_a.sample_statistics() == RedundantTransportStatistics() assert tr_b.sample_statistics() == RedundantTransportStatistics() # # Adding inferiors - loopback, transport A only. # with pytest.raises(InconsistentInferiorConfigurationError, match='(?i).*loop.*'): tr_a.attach_inferior( LoopbackTransport( 111, loop=asyncio.new_event_loop())) # Wrong event loop. assert len(pub_a.inferiors) == 0 assert len(sub_any_a.inferiors) == 0 lo_mono_0 = LoopbackTransport(111) lo_mono_1 = LoopbackTransport(111) tr_a.attach_inferior(lo_mono_0) assert len(pub_a.inferiors) == 1 assert len(sub_any_a.inferiors) == 1 with pytest.raises(ValueError): tr_a.detach_inferior(lo_mono_1) # Not a registered inferior (yet). tr_a.attach_inferior(lo_mono_1) assert len(pub_a.inferiors) == 2 assert len(sub_any_a.inferiors) == 2 with pytest.raises(ValueError): tr_a.attach_inferior(lo_mono_0) # Double-add not allowed. with pytest.raises(InconsistentInferiorConfigurationError, match='(?i).*node-id.*'): tr_a.attach_inferior(LoopbackTransport(None)) # Wrong node-ID. with pytest.raises(InconsistentInferiorConfigurationError, match='(?i).*node-id.*'): tr_a.attach_inferior(LoopbackTransport(1230)) # Wrong node-ID. assert tr_a.inferiors == [lo_mono_0, lo_mono_1] assert len(pub_a.inferiors) == 2 assert len(sub_any_a.inferiors) == 2 assert tr_a.sample_statistics() == RedundantTransportStatistics(inferiors=[ lo_mono_0.sample_statistics(), lo_mono_1.sample_statistics(), ]) assert tr_a.local_node_id == 111 assert tr_a.descriptor == '<redundant><loopback/><loopback/></redundant>' assert await pub_a.send_until(Transfer( timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=2, fragmented_payload=[memoryview(b'def')]), monotonic_deadline=loop.time() + 1.0) rx = await sub_any_a.receive_until(loop.time() + 1.0) assert rx is not None assert rx.fragmented_payload == [memoryview(b'def')] assert rx.transfer_id == 2 assert not await sub_any_b.receive_until(loop.time() + 0.1) # # Incapacitate one inferior, ensure things are still OK. # with caplog.at_level(logging.CRITICAL, logger=pyuavcan.transport.redundant.__name__): for s in lo_mono_0.output_sessions: s.exception = RuntimeError('INTENDED EXCEPTION') assert await pub_a.send_until(Transfer( timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=3, fragmented_payload=[memoryview(b'qwe')]), monotonic_deadline=loop.time() + 1.0) rx = await sub_any_a.receive_until(loop.time() + 1.0) assert rx is not None assert rx.fragmented_payload == [memoryview(b'qwe')] assert rx.transfer_id == 3 # # Remove old loopback transports. Configure new ones with cyclic TID. # lo_cyc_0 = LoopbackTransport(111) lo_cyc_1 = LoopbackTransport(111) cyc_proto_params = ProtocolParameters( transfer_id_modulo=32, # Like CAN max_nodes=128, # Like CAN mtu=63, # Like CAN ) lo_cyc_0.protocol_parameters = cyc_proto_params lo_cyc_1.protocol_parameters = cyc_proto_params assert lo_cyc_0.protocol_parameters == lo_cyc_1.protocol_parameters == cyc_proto_params assert tr_a.protocol_parameters.transfer_id_modulo >= 2**56 with pytest.raises(InconsistentInferiorConfigurationError, match='(?i).*transfer-id.*'): tr_a.attach_inferior(lo_cyc_0) # Transfer-ID modulo mismatch tr_a.detach_inferior(lo_mono_0) tr_a.detach_inferior(lo_mono_1) del lo_mono_0 # Prevent accidental reuse. del lo_mono_1 assert tr_a.inferiors == [] # All removed, okay. assert pub_a.inferiors == [] assert sub_any_a.inferiors == [] assert tr_a.local_node_id is None # Back to the roots assert tr_a.descriptor == '<redundant></redundant>' # Yes yes # Now we can add our cyclic transports safely. tr_a.attach_inferior(lo_cyc_0) assert tr_a.protocol_parameters.transfer_id_modulo == 32 tr_a.attach_inferior(lo_cyc_1) assert tr_a.protocol_parameters == cyc_proto_params, 'Protocol parameter mismatch' assert tr_a.local_node_id == 111 assert tr_a.descriptor == '<redundant><loopback/><loopback/></redundant>' # Exchange test. assert await pub_a.send_until(Transfer( timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=4, fragmented_payload=[memoryview(b'rty')]), monotonic_deadline=loop.time() + 1.0) rx = await sub_any_a.receive_until(loop.time() + 1.0) assert rx is not None assert rx.fragmented_payload == [memoryview(b'rty')] assert rx.transfer_id == 4 # # Real heterogeneous transport test. # tr_a.detach_inferior(lo_cyc_0) tr_a.detach_inferior(lo_cyc_1) del lo_cyc_0 # Prevent accidental reuse. del lo_cyc_1 udp_a = UDPTransport('127.0.0.111/8') udp_b = UDPTransport('127.0.0.222/8') serial_a = SerialTransport(SERIAL_URI, 111) serial_b = SerialTransport(SERIAL_URI, 222, mtu=2048) # Heterogeneous. tr_a.attach_inferior(udp_a) tr_a.attach_inferior(serial_a) tr_b.attach_inferior(udp_b) tr_b.attach_inferior(serial_b) print('tr_a.descriptor', tr_a.descriptor) print('tr_b.descriptor', tr_b.descriptor) assert tr_a.protocol_parameters == ProtocolParameters( transfer_id_modulo=2**64, max_nodes=4096, mtu=1024, ) assert tr_a.local_node_id == 111 assert tr_a.descriptor == f'<redundant>{udp_a.descriptor}{serial_a.descriptor}</redundant>' assert tr_b.protocol_parameters == ProtocolParameters( transfer_id_modulo=2**64, max_nodes=4096, mtu=1024, ) assert tr_b.local_node_id == 222 assert tr_b.descriptor == f'<redundant>{udp_b.descriptor}{serial_b.descriptor}</redundant>' assert await pub_a.send_until(Transfer( timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=5, fragmented_payload=[memoryview(b'uio')]), monotonic_deadline=loop.time() + 1.0) rx = await sub_any_b.receive_until(loop.time() + 1.0) assert rx is not None assert rx.fragmented_payload == [memoryview(b'uio')] assert rx.transfer_id == 5 assert not await sub_any_a.receive_until(loop.time() + 0.1) assert not await sub_any_b.receive_until(loop.time() + 0.1) assert not await sub_sel_b.receive_until(loop.time() + 0.1) # # Construct new session with the transports configured. # pub_a_new = tr_a.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), 222), meta) assert pub_a_new is tr_a.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), 222), meta) assert set(tr_a.output_sessions) == {pub_a, pub_a_new} assert await pub_a_new.send_until(Transfer( timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=6, fragmented_payload=[memoryview(b'asd')]), monotonic_deadline=loop.time() + 1.0) rx = await sub_any_b.receive_until(loop.time() + 1.0) assert rx is not None assert rx.fragmented_payload == [memoryview(b'asd')] assert rx.transfer_id == 6 # # Termination. # tr_a.close() tr_a.close() # Idempotency tr_b.close() tr_b.close() # Idempotency with pytest.raises(pyuavcan.transport.ResourceClosedError ): # Make sure the inferiors are closed. udp_a.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) with pytest.raises(pyuavcan.transport.ResourceClosedError ): # Make sure the inferiors are closed. serial_b.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) with pytest.raises(pyuavcan.transport.ResourceClosedError ): # Make sure the sessions are closed. await pub_a.send_until(Transfer(timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=100, fragmented_payload=[]), monotonic_deadline=loop.time() + 1.0) await asyncio.sleep( 1 ) # Let all pending tasks finalize properly to avoid stack traces in the output.
async def _unittest_serial_transport(caplog: typing.Any) -> None: from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer, TransferFrom from pyuavcan.transport import Priority, Timestamp, InputSessionSpecifier, OutputSessionSpecifier from pyuavcan.transport import ProtocolParameters get_monotonic = asyncio.get_event_loop().time service_multiplication_factor = 2 with pytest.raises(ValueError): _ = SerialTransport(serial_port="loop://", local_node_id=None, mtu=1) with pytest.raises(ValueError): _ = SerialTransport(serial_port="loop://", local_node_id=None, service_transfer_multiplier=10000) with pytest.raises(pyuavcan.transport.InvalidMediaConfigurationError): _ = SerialTransport(serial_port=serial.serial_for_url( "loop://", do_not_open=True), local_node_id=None) tr = SerialTransport(serial_port="loop://", local_node_id=None, mtu=1024) assert tr.local_node_id is None assert tr.serial_port.is_open assert tr.input_sessions == [] assert tr.output_sessions == [] assert tr.protocol_parameters == ProtocolParameters( transfer_id_modulo=2**64, max_nodes=4096, mtu=1024, ) assert tr.sample_statistics() == SerialTransportStatistics() sft_capacity = 1024 payload_single = [_mem("qwertyui"), _mem("01234567") ] * (sft_capacity // 16) assert sum(map(len, payload_single)) == sft_capacity payload_x3 = (payload_single * 3)[:-1] payload_x3_size_bytes = sft_capacity * 3 - 8 assert sum(map(len, payload_x3)) == payload_x3_size_bytes # # Instantiate session objects. # meta = PayloadMetadata(10000) broadcaster = tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) assert broadcaster is tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) subscriber_promiscuous = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), None), meta) assert subscriber_promiscuous is tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), None), meta) subscriber_selective = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), 3210), meta) assert subscriber_selective is tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), 3210), meta) server_listener = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), None), meta) assert server_listener is tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), None), meta) client_listener = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 3210), meta) assert client_listener is tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 3210), meta) print("INPUTS:", tr.input_sessions) print("OUTPUTS:", tr.output_sessions) assert set(tr.input_sessions) == { subscriber_promiscuous, subscriber_selective, server_listener, client_listener } assert set(tr.output_sessions) == {broadcaster} assert tr.sample_statistics() == SerialTransportStatistics() # # Message exchange test. # assert await broadcaster.send( Transfer(timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=77777, fragmented_payload=payload_single), monotonic_deadline=get_monotonic() + 5.0, ) rx_transfer = await subscriber_promiscuous.receive(get_monotonic() + 5.0) print("PROMISCUOUS SUBSCRIBER TRANSFER:", rx_transfer) assert isinstance(rx_transfer, TransferFrom) assert rx_transfer.priority == Priority.LOW assert rx_transfer.transfer_id == 77777 assert rx_transfer.fragmented_payload == [b"".join(payload_single)] print(tr.sample_statistics()) assert tr.sample_statistics().in_bytes >= 32 + sft_capacity + 2 assert tr.sample_statistics().in_frames == 1 assert tr.sample_statistics().in_out_of_band_bytes == 0 assert tr.sample_statistics().out_bytes == tr.sample_statistics().in_bytes assert tr.sample_statistics().out_frames == 1 assert tr.sample_statistics().out_transfers == 1 assert tr.sample_statistics().out_incomplete == 0 with pytest.raises( pyuavcan.transport.OperationNotDefinedForAnonymousNodeError): # Anonymous nodes can't send multiframe transfers. assert await broadcaster.send( Transfer(timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=77777, fragmented_payload=payload_x3), monotonic_deadline=get_monotonic() + 5.0, ) assert None is await subscriber_selective.receive(get_monotonic() + 0.1) assert None is await subscriber_promiscuous.receive(get_monotonic() + 0.1) assert None is await server_listener.receive(get_monotonic() + 0.1) assert None is await client_listener.receive(get_monotonic() + 0.1) # # Service exchange test. # with pytest.raises( pyuavcan.transport.OperationNotDefinedForAnonymousNodeError): # Anonymous nodes can't emit service transfers. tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 3210), meta) # # Replace the transport with a different one where the local node-ID is not None. # tr = SerialTransport(serial_port="loop://", local_node_id=3210, mtu=1024) assert tr.local_node_id == 3210 # # Re-instantiate session objects because the transport instances have been replaced. # broadcaster = tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) assert broadcaster is tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) subscriber_promiscuous = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), None), meta) subscriber_selective = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), 3210), meta) server_listener = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), None), meta) server_responder = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 3210), meta) assert server_responder is tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 3210), meta) client_requester = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 3210), meta) assert client_requester is tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 3210), meta) client_listener = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 3210), meta) assert client_listener is tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 3210), meta) assert set(tr.input_sessions) == { subscriber_promiscuous, subscriber_selective, server_listener, client_listener } assert set(tr.output_sessions) == { broadcaster, server_responder, client_requester } assert tr.sample_statistics() == SerialTransportStatistics() assert await client_requester.send( Transfer(timestamp=Timestamp.now(), priority=Priority.HIGH, transfer_id=88888, fragmented_payload=payload_x3), monotonic_deadline=get_monotonic() + 5.0, ) rx_transfer = await server_listener.receive(get_monotonic() + 5.0) print("SERVER LISTENER TRANSFER:", rx_transfer) assert isinstance(rx_transfer, TransferFrom) assert rx_transfer.priority == Priority.HIGH assert rx_transfer.transfer_id == 88888 assert len(rx_transfer.fragmented_payload) == 3 assert b"".join(rx_transfer.fragmented_payload) == b"".join(payload_x3) assert None is await subscriber_selective.receive(get_monotonic() + 0.1) assert None is await subscriber_promiscuous.receive(get_monotonic() + 0.1) assert None is await server_listener.receive(get_monotonic() + 0.1) assert None is await client_listener.receive(get_monotonic() + 0.1) print(tr.sample_statistics()) assert tr.sample_statistics().in_bytes >= ( 32 * 3 + payload_x3_size_bytes + 2) * service_multiplication_factor assert tr.sample_statistics( ).in_frames == 3 * service_multiplication_factor assert tr.sample_statistics().in_out_of_band_bytes == 0 assert tr.sample_statistics().out_bytes == tr.sample_statistics().in_bytes assert tr.sample_statistics( ).out_frames == 3 * service_multiplication_factor assert tr.sample_statistics( ).out_transfers == 1 * service_multiplication_factor assert tr.sample_statistics().out_incomplete == 0 # # Write timeout test. # assert not await broadcaster.send( Transfer(timestamp=Timestamp.now(), priority=Priority.IMMEDIATE, transfer_id=99999, fragmented_payload=payload_x3), monotonic_deadline=get_monotonic() - 5.0, # The deadline is in the past. ) assert None is await subscriber_selective.receive(get_monotonic() + 0.1) assert None is await subscriber_promiscuous.receive(get_monotonic() + 0.1) assert None is await server_listener.receive(get_monotonic() + 0.1) assert None is await client_listener.receive(get_monotonic() + 0.1) print(tr.sample_statistics()) assert tr.sample_statistics().in_bytes >= ( 32 * 3 + payload_x3_size_bytes + 2) * service_multiplication_factor assert tr.sample_statistics( ).in_frames == 3 * service_multiplication_factor assert tr.sample_statistics().in_out_of_band_bytes == 0 assert tr.sample_statistics().out_bytes == tr.sample_statistics().in_bytes assert tr.sample_statistics( ).out_frames == 3 * service_multiplication_factor assert tr.sample_statistics( ).out_transfers == 1 * service_multiplication_factor assert tr.sample_statistics().out_incomplete == 1 # INCREMENTED HERE # # Selective message exchange test. # assert await broadcaster.send( Transfer(timestamp=Timestamp.now(), priority=Priority.IMMEDIATE, transfer_id=99999, fragmented_payload=payload_x3), monotonic_deadline=get_monotonic() + 5.0, ) rx_transfer = await subscriber_promiscuous.receive(get_monotonic() + 5.0) print("PROMISCUOUS SUBSCRIBER TRANSFER:", rx_transfer) assert isinstance(rx_transfer, TransferFrom) assert rx_transfer.priority == Priority.IMMEDIATE assert rx_transfer.transfer_id == 99999 assert b"".join(rx_transfer.fragmented_payload) == b"".join(payload_x3) rx_transfer = await subscriber_selective.receive(get_monotonic() + 1.0) print("SELECTIVE SUBSCRIBER TRANSFER:", rx_transfer) assert isinstance(rx_transfer, TransferFrom) assert rx_transfer.priority == Priority.IMMEDIATE assert rx_transfer.transfer_id == 99999 assert b"".join(rx_transfer.fragmented_payload) == b"".join(payload_x3) assert None is await subscriber_selective.receive(get_monotonic() + 0.1) assert None is await subscriber_promiscuous.receive(get_monotonic() + 0.1) assert None is await server_listener.receive(get_monotonic() + 0.1) assert None is await client_listener.receive(get_monotonic() + 0.1) # # Out-of-band data test. # with caplog.at_level(logging.CRITICAL, logger=pyuavcan.transport.serial.__name__): stats_reference = tr.sample_statistics() # The frame delimiter is needed to force new frame into the state machine. grownups = b"Aren't there any grownups at all? - No grownups!\x00" tr.serial_port.write(grownups) stats_reference.in_bytes += len(grownups) stats_reference.in_out_of_band_bytes += len(grownups) # Wait for the reader thread to catch up. assert None is await subscriber_selective.receive(get_monotonic() + 0.2) assert None is await subscriber_promiscuous.receive(get_monotonic() + 0.2) assert None is await server_listener.receive(get_monotonic() + 0.2) assert None is await client_listener.receive(get_monotonic() + 0.2) print(tr.sample_statistics()) assert tr.sample_statistics() == stats_reference # The frame delimiter is needed to force new frame into the state machine. tr.serial_port.write( bytes([0xFF, 0xFF, SerialFrame.FRAME_DELIMITER_BYTE])) stats_reference.in_bytes += 3 stats_reference.in_out_of_band_bytes += 3 # Wait for the reader thread to catch up. assert None is await subscriber_selective.receive(get_monotonic() + 0.2) assert None is await subscriber_promiscuous.receive(get_monotonic() + 0.2) assert None is await server_listener.receive(get_monotonic() + 0.2) assert None is await client_listener.receive(get_monotonic() + 0.2) print(tr.sample_statistics()) assert tr.sample_statistics() == stats_reference # # Termination. # assert set(tr.input_sessions) == { subscriber_promiscuous, subscriber_selective, server_listener, client_listener } assert set(tr.output_sessions) == { broadcaster, server_responder, client_requester } subscriber_promiscuous.close() subscriber_promiscuous.close() # Idempotency. assert set(tr.input_sessions) == { subscriber_selective, server_listener, client_listener } assert set(tr.output_sessions) == { broadcaster, server_responder, client_requester } broadcaster.close() broadcaster.close() # Idempotency. assert set(tr.input_sessions) == { subscriber_selective, server_listener, client_listener } assert set(tr.output_sessions) == {server_responder, client_requester} tr.close() tr.close() # Idempotency. assert not set(tr.input_sessions) assert not set(tr.output_sessions) with pytest.raises(pyuavcan.transport.ResourceClosedError): _ = tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) with pytest.raises(pyuavcan.transport.ResourceClosedError): _ = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), None), meta) await asyncio.sleep( 1 ) # Let all pending tasks finalize properly to avoid stack traces in the output.