def _unittest_transport_primitives() -> None: from pytest import raises from pyuavcan.transport import InputSessionSpecifier, OutputSessionSpecifier from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata with raises(ValueError): MessageDataSpecifier(-1) with raises(ValueError): MessageDataSpecifier(32768) with raises(ValueError): ServiceDataSpecifier(-1, ServiceDataSpecifier.Role.REQUEST) with raises(ValueError): InputSessionSpecifier(MessageDataSpecifier(123), -1) with raises(ValueError): OutputSessionSpecifier(ServiceDataSpecifier(100, ServiceDataSpecifier.Role.RESPONSE), None) with raises(ValueError): PayloadMetadata(-1, 0) with raises(ValueError): PayloadMetadata(2 ** 64, 0) with raises(ValueError): PayloadMetadata(0, -1)
async def _unittest_loopback_transport_service() -> None: from pyuavcan.transport import ServiceDataSpecifier, InputSessionSpecifier, OutputSessionSpecifier payload_metadata = pyuavcan.transport.PayloadMetadata( 0xdeadbeef0ddf00d, 1234) tr = pyuavcan.transport.loopback.LoopbackTransport(1234) inp = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(123, ServiceDataSpecifier.Role.REQUEST), 1234), payload_metadata) out = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(123, ServiceDataSpecifier.Role.REQUEST), 1234), payload_metadata) assert await out.send_until( pyuavcan.transport.Transfer( timestamp=pyuavcan.transport.Timestamp.now(), priority=pyuavcan.transport.Priority.IMMEDIATE, transfer_id=123, # mod 32 = 27 fragmented_payload=[memoryview(b'Hello world!')], ), tr.loop.time() + 1.0) assert None is not await inp.receive_until(0)
def _unittest_slow_input_dispatch_table_index() -> None: values: typing.Set[int] = set() for node_id in (*range(InputDispatchTable._NUM_NODE_IDS), None): for subj in range(InputDispatchTable._NUM_SUBJECTS): out = InputDispatchTable._compute_index( InputSessionSpecifier(MessageDataSpecifier(subj), node_id)) assert out not in values values.add(out) assert out < InputDispatchTable._TABLE_SIZE for serv in range(InputDispatchTable._NUM_SERVICES): for role in ServiceDataSpecifier.Role: out = InputDispatchTable._compute_index( InputSessionSpecifier(ServiceDataSpecifier(serv, role), node_id)) assert out not in values values.add(out) assert out < InputDispatchTable._TABLE_SIZE assert len(values) == InputDispatchTable._TABLE_SIZE
def _unittest_input_dispatch_table() -> None: import asyncio from pytest import raises from pyuavcan.transport import PayloadMetadata t = InputDispatchTable() assert len(list(t.items)) == 0 assert t.get(InputSessionSpecifier(MessageDataSpecifier(1234), None)) is None with raises(LookupError): t.remove(InputSessionSpecifier(MessageDataSpecifier(1234), 123)) a = CANInputSession( InputSessionSpecifier(MessageDataSpecifier(1234), None), PayloadMetadata(456, 789), asyncio.get_event_loop(), lambda: None) t.add(a) t.add(a) assert list(t.items) == [a] assert t.get(InputSessionSpecifier(MessageDataSpecifier(1234), None)) == a t.remove(InputSessionSpecifier(MessageDataSpecifier(1234), None)) assert len(list(t.items)) == 0
async def _unittest_can_transport_non_anon() -> None: from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer, TransferFrom from pyuavcan.transport import UnsupportedSessionConfigurationError, Priority, SessionStatistics, Timestamp from pyuavcan.transport import ResourceClosedError, InputSessionSpecifier, OutputSessionSpecifier # noinspection PyProtectedMember from pyuavcan.transport.can._identifier import MessageCANID, ServiceCANID # noinspection PyProtectedMember from pyuavcan.transport.can._frame import UAVCANFrame from .media.mock import MockMedia, FrameCollector peers: typing.Set[MockMedia] = set() media = MockMedia(peers, 64, 10) media2 = MockMedia(peers, 64, 3) peeper = MockMedia(peers, 64, 10) assert len(peers) == 3 tr = can.CANTransport(media, 5) tr2 = can.CANTransport(media2, 123) assert tr.protocol_parameters == pyuavcan.transport.ProtocolParameters( transfer_id_modulo=32, max_nodes=128, mtu=63) assert tr.local_node_id == 5 assert tr.protocol_parameters == tr2.protocol_parameters assert media.automatic_retransmission_enabled assert media2.automatic_retransmission_enabled # # Instantiate session objects # meta = PayloadMetadata(0x_bad_c0ffee_0dd_f00d, 10000) with pytest.raises(Exception): # Can't broadcast service calls tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(123, ServiceDataSpecifier.Role.RESPONSE), None), meta) with pytest.raises( UnsupportedSessionConfigurationError): # Can't unicast messages tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(1234), 123), meta) broadcaster = tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) assert broadcaster is tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) subscriber_promiscuous = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), None), meta) assert subscriber_promiscuous is tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), None), meta) subscriber_selective = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), 123), 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), 123), meta) client_requester = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 123), meta) client_listener = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 123), meta) assert set(tr.input_sessions) == { subscriber_promiscuous, subscriber_selective, server_listener, client_listener } assert set(tr.output_sessions) == { broadcaster, server_responder, client_requester } # # Basic exchange test, no one is listening # media2.configure_acceptance_filters( [can.media.FilterConfiguration.new_promiscuous()]) peeper.configure_acceptance_filters( [can.media.FilterConfiguration.new_promiscuous()]) collector = FrameCollector() peeper.start(collector.give, False) assert tr.sample_statistics() == can.CANTransportStatistics() assert tr2.sample_statistics() == can.CANTransportStatistics() ts = Timestamp.now() def validate_timestamp(timestamp: Timestamp) -> None: assert ts.monotonic_ns <= timestamp.monotonic_ns <= time.monotonic_ns() assert ts.system_ns <= timestamp.system_ns <= time.time_ns() assert await broadcaster.send_until( Transfer( timestamp=ts, priority=Priority.IMMEDIATE, transfer_id=32 + 11, # Modulus 11 fragmented_payload=[_mem('abc'), _mem('def')]), tr.loop.time() + 1.0) assert broadcaster.sample_statistics() == SessionStatistics( transfers=1, frames=1, payload_bytes=6) assert tr.sample_statistics() == can.CANTransportStatistics(out_frames=1) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=1, in_frames_uavcan=1) assert tr.sample_statistics( ).media_acceptance_filtering_efficiency == pytest.approx(1) assert tr2.sample_statistics( ).media_acceptance_filtering_efficiency == pytest.approx(0) assert tr.sample_statistics().lost_loopback_frames == 0 assert tr2.sample_statistics().lost_loopback_frames == 0 assert collector.pop().is_same_manifestation( UAVCANFrame( identifier=MessageCANID(Priority.IMMEDIATE, 5, 12345).compile( [_mem('abcdef')]), # payload fragments joined padded_payload=_mem('abcdef'), transfer_id=11, start_of_transfer=True, end_of_transfer=True, toggle_bit=True, loopback=False).compile()) assert collector.empty # # Broadcast exchange with input dispatch test # selective_m12345_5 = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), 5), meta) selective_m12345_9 = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), 9), meta) promiscuous_m12345 = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), None), meta) assert await broadcaster.send_until( Transfer( timestamp=ts, priority=Priority.IMMEDIATE, transfer_id=32 + 11, # Modulus 11 fragmented_payload=[_mem('abc'), _mem('def')]), tr.loop.time() + 1.0) assert broadcaster.sample_statistics() == SessionStatistics( transfers=2, frames=2, payload_bytes=12) assert tr.sample_statistics() == can.CANTransportStatistics(out_frames=2) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=2, in_frames_uavcan=2, in_frames_uavcan_accepted=1) received = await promiscuous_m12345.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 11 assert received.source_node_id == 5 assert received.priority == Priority.IMMEDIATE validate_timestamp(received.timestamp) assert received.fragmented_payload == [_mem('abcdef')] assert selective_m12345_5.sample_statistics() == SessionStatistics( ) # Nothing assert selective_m12345_9.sample_statistics() == SessionStatistics( ) # Nothing assert promiscuous_m12345.sample_statistics() == SessionStatistics( transfers=1, frames=1, payload_bytes=6) assert media.automatic_retransmission_enabled assert media2.automatic_retransmission_enabled feedback_collector = _FeedbackCollector() broadcaster.enable_feedback(feedback_collector.give) assert await broadcaster.send_until( Transfer( timestamp=ts, priority=Priority.SLOW, transfer_id=2, fragmented_payload=[_mem('qwe'), _mem('rty')] * 50 # Lots of data here, very multiframe ), tr.loop.time() + 1.0) assert broadcaster.sample_statistics() == SessionStatistics( transfers=3, frames=7, payload_bytes=312) broadcaster.disable_feedback() assert tr.sample_statistics() == can.CANTransportStatistics( out_frames=7, out_frames_loopback=1, in_frames_loopback=1) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=7, in_frames_uavcan=7, in_frames_uavcan_accepted=6) fb = feedback_collector.take() assert fb.original_transfer_timestamp == ts validate_timestamp(fb.first_frame_transmission_timestamp) received = await promiscuous_m12345.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 2 assert received.source_node_id == 5 assert received.priority == Priority.SLOW validate_timestamp(received.timestamp) assert b''.join( received.fragmented_payload ) == b'qwerty' * 50 + b'\x00' * 13 # The 0x00 at the end is padding assert await broadcaster.send_until( Transfer(timestamp=ts, priority=Priority.OPTIONAL, transfer_id=3, fragmented_payload=[_mem('qwe'), _mem('rty')]), tr.loop.time() + 1.0) assert broadcaster.sample_statistics() == SessionStatistics( transfers=4, frames=8, payload_bytes=318) received = await promiscuous_m12345.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 3 assert received.source_node_id == 5 assert received.priority == Priority.OPTIONAL validate_timestamp(received.timestamp) assert list(received.fragmented_payload) == [_mem('qwerty')] assert promiscuous_m12345.sample_statistics() == SessionStatistics( transfers=3, frames=7, payload_bytes=325) assert tr.sample_statistics() == can.CANTransportStatistics( out_frames=8, out_frames_loopback=1, in_frames_loopback=1) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=8, in_frames_uavcan=8, in_frames_uavcan_accepted=7) broadcaster.close() with pytest.raises(ResourceClosedError): assert await broadcaster.send_until( Transfer(timestamp=ts, priority=Priority.LOW, transfer_id=4, fragmented_payload=[]), tr.loop.time() + 1.0) broadcaster.close() # Does nothing # Final checks for the broadcaster - make sure nothing is left in the queue assert (await promiscuous_m12345.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None # The selective listener was not supposed to pick up anything because it's selective for node 9, not 5 assert (await selective_m12345_9.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None # Now, there are a bunch of items awaiting in the selective input for node 5, collect them and check the stats assert selective_m12345_5.source_node_id == 5 received = await selective_m12345_5.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 11 assert received.source_node_id == 5 assert received.priority == Priority.IMMEDIATE validate_timestamp(received.timestamp) assert received.fragmented_payload == [_mem('abcdef')] received = await selective_m12345_5.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 2 assert received.source_node_id == 5 assert received.priority == Priority.SLOW validate_timestamp(received.timestamp) assert b''.join( received.fragmented_payload ) == b'qwerty' * 50 + b'\x00' * 13 # The 0x00 at the end is padding received = await selective_m12345_5.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 3 assert received.source_node_id == 5 assert received.priority == Priority.OPTIONAL validate_timestamp(received.timestamp) assert list(received.fragmented_payload) == [_mem('qwerty')] assert selective_m12345_5.sample_statistics( ) == promiscuous_m12345.sample_statistics() # # Unicast exchange test # selective_server_s333_5 = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 5), meta) selective_server_s333_9 = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 9), meta) promiscuous_server_s333 = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), None), meta) selective_client_s333_5 = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 5), meta) selective_client_s333_9 = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 9), meta) promiscuous_client_s333 = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), None), meta) assert await client_requester.send_until( Transfer(timestamp=ts, priority=Priority.FAST, transfer_id=11, fragmented_payload=[]), tr.loop.time() + 1.0) assert client_requester.sample_statistics() == SessionStatistics( transfers=1, frames=1, payload_bytes=0) received = await selective_server_s333_5.receive_until( tr.loop.time() + 1.0) # Same thing here assert received is not None assert received.transfer_id == 11 assert received.priority == Priority.FAST validate_timestamp(received.timestamp) assert list(map(bytes, received.fragmented_payload)) == [b''] assert (await selective_server_s333_9.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None received = await promiscuous_server_s333.receive_until( tr.loop.time() + 1.0) # Same thing here assert received is not None assert received.transfer_id == 11 assert received.priority == Priority.FAST validate_timestamp(received.timestamp) assert list(map(bytes, received.fragmented_payload)) == [b''] assert selective_server_s333_5.sample_statistics() == SessionStatistics( transfers=1, frames=1) assert selective_server_s333_9.sample_statistics() == SessionStatistics() assert promiscuous_server_s333.sample_statistics() == SessionStatistics( transfers=1, frames=1) assert (await selective_client_s333_5.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert (await selective_client_s333_9.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert (await promiscuous_client_s333.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert selective_client_s333_5.sample_statistics() == SessionStatistics() assert selective_client_s333_9.sample_statistics() == SessionStatistics() assert promiscuous_client_s333.sample_statistics() == SessionStatistics() client_requester.enable_feedback( feedback_collector.give) # FEEDBACK ENABLED HERE # Will fail with an error; make sure it's counted properly. The feedback registry entry will remain pending! media.raise_on_send_once(RuntimeError('Induced failure')) with pytest.raises(RuntimeError, match='Induced failure'): assert await client_requester.send_until( Transfer(timestamp=ts, priority=Priority.FAST, transfer_id=12, fragmented_payload=[]), tr.loop.time() + 1.0) assert client_requester.sample_statistics() == SessionStatistics( transfers=1, frames=1, payload_bytes=0, errors=1) # Some malformed feedback frames which will be ignored media.inject_received([ UAVCANFrame( identifier=ServiceCANID(priority=Priority.FAST, source_node_id=5, destination_node_id=123, service_id=333, request_not_response=True).compile( [_mem('Ignored')]), padded_payload=_mem('Ignored'), start_of_transfer=False, # Ignored because not start-of-frame end_of_transfer=False, toggle_bit=True, transfer_id=12, loopback=True).compile() ]) media.inject_received([ UAVCANFrame( identifier=ServiceCANID(priority=Priority.FAST, source_node_id=5, destination_node_id=123, service_id=333, request_not_response=True).compile( [_mem('Ignored')]), padded_payload=_mem('Ignored'), start_of_transfer=True, end_of_transfer=False, toggle_bit=True, transfer_id= 9, # Ignored because there is no such transfer-ID in the registry loopback=True).compile() ]) # Now, this transmission will succeed, but a pending loopback registry entry will be overwritten, which will be # reflected in the error counter. assert await client_requester.send_until( Transfer( timestamp=ts, priority=Priority.FAST, transfer_id=12, fragmented_payload=[ _mem( 'Until philosophers are kings, or the kings and princes of this world have the spirit and power of ' 'philosophy, and political greatness and wisdom meet in one, and those commoner natures who pursue ' 'either to the exclusion of the other are compelled to stand aside, cities will never have rest from ' 'their evils '), _mem('- no, nor the human race, as I believe - '), _mem( 'and then only will this our State have a possibility of life and behold the light of day.' ), ]), tr.loop.time() + 1.0) client_requester.disable_feedback() assert client_requester.sample_statistics() == SessionStatistics( transfers=2, frames=8, payload_bytes=438, errors=2) # The feedback is disabled, but we will send a valid loopback frame anyway to make sure it is silently ignored media.inject_received([ UAVCANFrame(identifier=ServiceCANID(priority=Priority.FAST, source_node_id=5, destination_node_id=123, service_id=333, request_not_response=True).compile( [_mem('Ignored')]), padded_payload=_mem('Ignored'), start_of_transfer=True, end_of_transfer=False, toggle_bit=True, transfer_id=12, loopback=True).compile() ]) client_requester.close() with pytest.raises(ResourceClosedError): assert await client_requester.send_until( Transfer(timestamp=ts, priority=Priority.LOW, transfer_id=4, fragmented_payload=[]), tr.loop.time() + 1.0) fb = feedback_collector.take() assert fb.original_transfer_timestamp == ts validate_timestamp(fb.first_frame_transmission_timestamp) received = await promiscuous_server_s333.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.source_node_id == 5 assert received.transfer_id == 12 assert received.priority == Priority.FAST validate_timestamp(received.timestamp) assert len(received.fragmented_payload) == 7 # Equals the number of frames assert sum(map( len, received.fragmented_payload)) == 438 + 1 # Padding also included assert b'Until philosophers are kings' in bytes( received.fragmented_payload[0]) assert b'behold the light of day.' in bytes( received.fragmented_payload[-1]) received = await selective_server_s333_5.receive_until( tr.loop.time() + 1.0) # Same thing here assert received is not None assert received.transfer_id == 12 assert received.priority == Priority.FAST validate_timestamp(received.timestamp) assert len(received.fragmented_payload) == 7 # Equals the number of frames assert sum(map( len, received.fragmented_payload)) == 438 + 1 # Padding also included assert b'Until philosophers are kings' in bytes( received.fragmented_payload[0]) assert b'behold the light of day.' in bytes( received.fragmented_payload[-1]) # Nothing is received - non-matching node ID selector assert (await selective_server_s333_9.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None # Nothing is received - non-matching role (not server) assert (await selective_client_s333_5.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert (await selective_client_s333_9.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert (await promiscuous_client_s333.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert selective_client_s333_5.sample_statistics() == SessionStatistics() assert selective_client_s333_9.sample_statistics() == SessionStatistics() assert promiscuous_client_s333.sample_statistics() == SessionStatistics() # Final transport stats check; additional loopback frames are due to our manual tests above assert tr.sample_statistics() == can.CANTransportStatistics( out_frames=16, out_frames_loopback=2, in_frames_loopback=5) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=16, in_frames_uavcan=16, in_frames_uavcan_accepted=15) # # Drop non-UAVCAN frames silently # media.inject_received([ can.media.DataFrame( identifier=ServiceCANID(priority=Priority.FAST, source_node_id=5, destination_node_id=123, service_id=333, request_not_response=True).compile( [_mem('')]), data=bytearray( b'' ), # The CAN ID is valid for UAVCAN, but the payload is not - no tail byte format=can.media.FrameFormat.EXTENDED, loopback=False) ]) media.inject_received([ can.media.DataFrame( identifier=0, # The CAN ID is not valid for UAVCAN data=bytearray(b'123'), format=can.media.FrameFormat.BASE, loopback=False) ]) media.inject_received([ UAVCANFrame( identifier=ServiceCANID( priority=Priority.FAST, source_node_id=5, destination_node_id=123, service_id=444, # No such service request_not_response=True).compile([_mem('Ignored')]), padded_payload=_mem('Ignored'), start_of_transfer=True, end_of_transfer=False, toggle_bit=True, transfer_id=12, loopback=True).compile() ]) assert tr.sample_statistics() == can.CANTransportStatistics( out_frames=16, in_frames=2, out_frames_loopback=2, in_frames_loopback=6) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=16, in_frames_uavcan=16, in_frames_uavcan_accepted=15) # # Reception logic test. # pub_m2222 = tr2.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2222), None), meta) # Transfer ID timeout configuration - one of them will be configured very short for testing purposes subscriber_promiscuous.transfer_id_timeout = 1e-9 # Very low, basically zero timeout with pytest.raises(ValueError): subscriber_promiscuous.transfer_id_timeout = -1 with pytest.raises(ValueError): subscriber_promiscuous.transfer_id_timeout = float('nan') assert subscriber_promiscuous.transfer_id_timeout == pytest.approx(1e-9) subscriber_selective.transfer_id_timeout = 1.0 with pytest.raises(ValueError): subscriber_selective.transfer_id_timeout = -1 with pytest.raises(ValueError): subscriber_selective.transfer_id_timeout = float('nan') assert subscriber_selective.transfer_id_timeout == pytest.approx(1.0) # Queue capacity configuration assert subscriber_selective.frame_queue_capacity is None # Unlimited by default subscriber_selective.frame_queue_capacity = 2 with pytest.raises(ValueError): subscriber_selective.frame_queue_capacity = 0 assert subscriber_selective.frame_queue_capacity == 2 assert await pub_m2222.send_until( Transfer( timestamp=ts, priority=Priority.EXCEPTIONAL, transfer_id=7, fragmented_payload=[ _mem('Finally, from so little sleeping and so much reading, '), _mem( 'his brain dried up and he went completely out of his mind.' ), # Two frames. ]), tr.loop.time() + 1.0) assert tr.sample_statistics() == can.CANTransportStatistics( out_frames=16, in_frames=4, in_frames_uavcan=2, in_frames_uavcan_accepted=2, out_frames_loopback=2, in_frames_loopback=6) assert tr2.sample_statistics() == can.CANTransportStatistics( out_frames=2, in_frames=16, in_frames_uavcan=16, in_frames_uavcan_accepted=15) received = await subscriber_promiscuous.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.source_node_id == 123 assert received.priority == Priority.EXCEPTIONAL assert received.transfer_id == 7 validate_timestamp(received.timestamp) assert bytes(received.fragmented_payload[0]).startswith(b'Finally') assert bytes(received.fragmented_payload[-1]).rstrip(b'\x00').endswith( b'out of his mind.') received = await subscriber_selective.receive_until(tr.loop.time() + 1.0) assert received is not None assert received.priority == Priority.EXCEPTIONAL assert received.transfer_id == 7 validate_timestamp(received.timestamp) assert bytes(received.fragmented_payload[0]).startswith(b'Finally') assert bytes(received.fragmented_payload[-1]).rstrip(b'\x00').endswith( b'out of his mind.') assert subscriber_selective.sample_statistics( ) == subscriber_promiscuous.sample_statistics() assert subscriber_promiscuous.sample_statistics() == SessionStatistics( transfers=1, frames=2, payload_bytes=124) # Includes padding! assert await pub_m2222.send_until( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id= 7, # Same transfer ID, will be accepted only by the instance with low TID timeout fragmented_payload=[]), tr.loop.time() + 1.0) assert tr.sample_statistics() == can.CANTransportStatistics( out_frames=16, in_frames=5, in_frames_uavcan=3, in_frames_uavcan_accepted=3, out_frames_loopback=2, in_frames_loopback=6) assert tr2.sample_statistics() == can.CANTransportStatistics( out_frames=3, in_frames=16, in_frames_uavcan=16, in_frames_uavcan_accepted=15) received = await subscriber_promiscuous.receive_until(tr.loop.time() + 10.0) assert received is not None assert isinstance(received, TransferFrom) assert received.source_node_id == 123 assert received.priority == Priority.NOMINAL assert received.transfer_id == 7 validate_timestamp(received.timestamp) assert b''.join(received.fragmented_payload) == b'' assert subscriber_promiscuous.sample_statistics() == SessionStatistics( transfers=2, frames=3, payload_bytes=124) # Discarded because of the same transfer ID assert (await subscriber_selective.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert subscriber_selective.sample_statistics() == SessionStatistics( transfers=1, frames=3, payload_bytes=124, errors=1 # Error due to the repeated transfer ID ) assert await pub_m2222.send_until( Transfer( timestamp=ts, priority=Priority.HIGH, transfer_id=8, fragmented_payload=[ _mem('a' * 63), _mem('b' * 63), _mem('c' * 63), _mem( 'd' * 62 ), # Tricky case - one of the CRC bytes spills over into the fifth frame ]), tr.loop.time() + 1.0) # The promiscuous one is able to receive the transfer since its queue is large enough received = await subscriber_promiscuous.receive_until(tr.loop.time() + 1.0) assert received is not None assert received.priority == Priority.HIGH assert received.transfer_id == 8 validate_timestamp(received.timestamp) assert list(map(bytes, received.fragmented_payload)) == [ b'a' * 63, b'b' * 63, b'c' * 63, b'd' * 62, ] assert subscriber_promiscuous.sample_statistics() == SessionStatistics( transfers=3, frames=8, payload_bytes=375) # The selective one is unable to do so since its RX queue is too small; it is reflected in the error counter assert (await subscriber_selective.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert subscriber_selective.sample_statistics() == SessionStatistics( transfers=1, frames=5, payload_bytes=124, errors=1, drops=3) # Overruns! # # Finalization. # print('str(CANTransport):', tr) print('str(CANTransport):', tr2) client_listener.close() server_listener.close() subscriber_promiscuous.close() subscriber_selective.close() tr.close() tr2.close() # Double-close has no effect: client_listener.close() server_listener.close() subscriber_promiscuous.close() subscriber_selective.close() tr.close() tr2.close()
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_udp_transport() -> 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 with pytest.raises(ValueError): _ = UDPTransport(ip_address='127.0.0.111/8', mtu=10) with pytest.raises(ValueError): _ = UDPTransport(ip_address='127.0.0.111/8', service_transfer_multiplier=100) tr = UDPTransport('127.0.0.111/8', mtu=9000) tr2 = UDPTransport('127.0.0.222/8', service_transfer_multiplier=2) assert tr.local_ip_address_with_netmask == '127.0.0.111/8' assert tr2.local_ip_address_with_netmask == '127.0.0.222/8' assert tr.loop is asyncio.get_event_loop() assert tr.local_node_id == 111 assert tr2.local_node_id == 222 assert tr.input_sessions == [] assert tr.output_sessions == [] assert list(xml.etree.ElementTree.fromstring( tr.descriptor).itertext()) == ['127.0.0.111/8'] assert tr.protocol_parameters == ProtocolParameters( transfer_id_modulo=2**56, max_nodes=2**UDPTransport.NODE_ID_BIT_LENGTH, mtu=9000, ) assert list(xml.etree.ElementTree.fromstring( tr2.descriptor).itertext()) == ['127.0.0.222/8'] assert tr2.protocol_parameters == ProtocolParameters( transfer_id_modulo=2**56, max_nodes=2**UDPTransport.NODE_ID_BIT_LENGTH, mtu=UDPTransport.DEFAULT_MTU, ) assert tr.sample_statistics() == tr2.sample_statistics( ) == UDPTransportStatistics() payload_single = [_mem('qwertyui'), _mem('01234567') ] * (UDPTransport.DEFAULT_MTU // 16) assert sum(map(len, payload_single)) == UDPTransport.DEFAULT_MTU payload_x3 = (payload_single * 3)[:-1] payload_x3_size_bytes = UDPTransport.DEFAULT_MTU * 3 - 8 assert sum(map(len, payload_x3)) == payload_x3_size_bytes # # Instantiate session objects. # meta = PayloadMetadata(0x_bad_c0ffee_0dd_f00d, 10000) broadcaster = tr2.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) assert broadcaster is tr2.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) subscriber_promiscuous = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), None), meta) assert subscriber_promiscuous is tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), None), meta) subscriber_selective = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), 123), meta) assert subscriber_selective is tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), 123), meta) server_listener = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), None), meta) assert server_listener is tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), None), meta) server_responder = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 222), meta) assert server_responder is tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 222), meta) client_requester = tr2.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), 111), meta) assert client_requester is tr2.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), 111), meta) client_listener = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 111), meta) assert client_listener is tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 111), meta) print('tr :', tr.input_sessions, tr.output_sessions) assert set(tr.input_sessions) == { subscriber_promiscuous, subscriber_selective, server_listener } assert set(tr.output_sessions) == {server_responder} print('tr2:', tr2.input_sessions, tr2.output_sessions) assert set(tr2.input_sessions) == {client_listener} assert set(tr2.output_sessions) == {broadcaster, client_requester} assert tr.sample_statistics().demultiplexer[MessageDataSpecifier( 12345)].accepted_datagrams == {} assert tr.sample_statistics().demultiplexer[ServiceDataSpecifier( 444, ServiceDataSpecifier.Role.REQUEST)].accepted_datagrams == {} assert tr2.sample_statistics().demultiplexer[ServiceDataSpecifier( 444, ServiceDataSpecifier.Role.RESPONSE)].accepted_datagrams == {} # # Message exchange test. # assert await broadcaster.send_until( 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_until(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) ] # type: ignore print('tr :', tr.sample_statistics()) assert tr.sample_statistics().demultiplexer[MessageDataSpecifier( 12345)].accepted_datagrams == { 222: 1 } assert tr.sample_statistics().demultiplexer[ServiceDataSpecifier( 444, ServiceDataSpecifier.Role.REQUEST)].accepted_datagrams == {} print('tr2:', tr2.sample_statistics()) assert tr2.sample_statistics().demultiplexer[ServiceDataSpecifier( 444, ServiceDataSpecifier.Role.RESPONSE)].accepted_datagrams == {} assert None is await subscriber_selective.receive_until(get_monotonic() + 0.1) assert None is await subscriber_promiscuous.receive_until(get_monotonic() + 0.1) assert None is await server_listener.receive_until(get_monotonic() + 0.1) assert None is await client_listener.receive_until(get_monotonic() + 0.1) # # Service exchange test. # assert await client_requester.send_until( 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_until(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_until(get_monotonic() + 0.1) assert None is await subscriber_promiscuous.receive_until(get_monotonic() + 0.1) assert None is await server_listener.receive_until(get_monotonic() + 0.1) assert None is await client_listener.receive_until(get_monotonic() + 0.1) print('tr :', tr.sample_statistics()) assert tr.sample_statistics().demultiplexer[MessageDataSpecifier( 12345)].accepted_datagrams == { 222: 1 } assert tr.sample_statistics().demultiplexer[ServiceDataSpecifier( 444, ServiceDataSpecifier.Role.REQUEST )].accepted_datagrams == { 222: 3 * 2 } # Deterministic data loss mitigation is enabled, multiplication factor 2 print('tr2:', tr2.sample_statistics()) assert tr2.sample_statistics().demultiplexer[ServiceDataSpecifier( 444, ServiceDataSpecifier.Role.RESPONSE)].accepted_datagrams == {} # # Termination. # assert set(tr.input_sessions) == { subscriber_promiscuous, subscriber_selective, server_listener } assert set(tr.output_sessions) == {server_responder} assert set(tr2.input_sessions) == {client_listener} assert set(tr2.output_sessions) == {broadcaster, client_requester} subscriber_promiscuous.close() subscriber_promiscuous.close() # Idempotency. assert set(tr.input_sessions) == {subscriber_selective, server_listener} assert set(tr.output_sessions) == {server_responder} assert set(tr2.input_sessions) == {client_listener} assert set(tr2.output_sessions) == {broadcaster, client_requester} broadcaster.close() broadcaster.close() # Idempotency. assert set(tr.input_sessions) == {subscriber_selective, server_listener} assert set(tr.output_sessions) == {server_responder} assert set(tr2.input_sessions) == {client_listener} assert set(tr2.output_sessions) == {client_requester} tr.close() tr.close() # Idempotency. tr2.close() tr2.close() # Idempotency. assert not set(tr.input_sessions) assert not set(tr.output_sessions) assert not set(tr2.input_sessions) assert not set(tr2.output_sessions) with pytest.raises(pyuavcan.transport.ResourceClosedError): _ = tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) with pytest.raises(pyuavcan.transport.ResourceClosedError): _ = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), None), meta)
async def _unittest_can_transport_anon() -> None: from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer, TransferFrom from pyuavcan.transport import UnsupportedSessionConfigurationError, Priority, SessionStatistics, Timestamp from pyuavcan.transport import OperationNotDefinedForAnonymousNodeError from pyuavcan.transport import InputSessionSpecifier, OutputSessionSpecifier # noinspection PyProtectedMember from pyuavcan.transport.can._identifier import MessageCANID # noinspection PyProtectedMember from pyuavcan.transport.can._frame import UAVCANFrame from .media.mock import MockMedia, FrameCollector with pytest.raises(pyuavcan.transport.InvalidTransportConfigurationError): can.CANTransport(MockMedia(set(), 64, 0), None) with pytest.raises(pyuavcan.transport.InvalidTransportConfigurationError): can.CANTransport(MockMedia(set(), 7, 16), None) peers: typing.Set[MockMedia] = set() media = MockMedia(peers, 64, 10) media2 = MockMedia(peers, 64, 3) peeper = MockMedia(peers, 64, 10) assert len(peers) == 3 tr = can.CANTransport(media, None) tr2 = can.CANTransport(media2, None) assert tr.protocol_parameters == pyuavcan.transport.ProtocolParameters( transfer_id_modulo=32, max_nodes=128, mtu=63) assert tr.local_node_id is None assert tr.protocol_parameters == tr2.protocol_parameters assert not media.automatic_retransmission_enabled assert not media2.automatic_retransmission_enabled assert tr.descriptor == f'<can><mock mtu="64">mock@{id(peers):08x}</mock></can>' # # Instantiate session objects # meta = PayloadMetadata(0x_bad_c0ffee_0dd_f00d, 10000) with pytest.raises(Exception): # Can't broadcast service calls tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(123, ServiceDataSpecifier.Role.RESPONSE), None), meta) with pytest.raises( UnsupportedSessionConfigurationError): # Can't unicast messages tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(1234), 123), meta) broadcaster = tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) assert broadcaster is tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) subscriber_promiscuous = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), None), meta) assert subscriber_promiscuous is tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), None), meta) subscriber_selective = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), 123), meta) assert subscriber_selective is tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), 123), 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) server_responder = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 123), meta) assert server_responder is tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 123), meta) client_requester = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 123), meta) assert client_requester is tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 123), meta) client_listener = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 123), meta) assert client_listener is tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 123), meta) assert broadcaster.destination_node_id is None assert subscriber_promiscuous.source_node_id is None assert subscriber_selective.source_node_id == 123 assert server_listener.source_node_id is None assert client_listener.source_node_id == 123 base_ts = time.process_time() inputs = tr.input_sessions print( f'INPUTS (sampled in {time.process_time() - base_ts:.3f}s): {inputs}') assert set(inputs) == { subscriber_promiscuous, subscriber_selective, server_listener, client_listener } del inputs print('OUTPUTS:', tr.output_sessions) assert set(tr.output_sessions) == { broadcaster, server_responder, client_requester } # # Basic exchange test, no one is listening # media2.configure_acceptance_filters( [can.media.FilterConfiguration.new_promiscuous()]) peeper.configure_acceptance_filters( [can.media.FilterConfiguration.new_promiscuous()]) collector = FrameCollector() peeper.start(collector.give, False) assert tr.sample_statistics() == can.CANTransportStatistics() assert tr2.sample_statistics() == can.CANTransportStatistics() ts = Timestamp.now() def validate_timestamp(timestamp: Timestamp) -> None: assert ts.monotonic_ns <= timestamp.monotonic_ns <= time.monotonic_ns() assert ts.system_ns <= timestamp.system_ns <= time.time_ns() assert await broadcaster.send_until( Transfer( timestamp=ts, priority=Priority.IMMEDIATE, transfer_id=32 + 11, # Modulus 11 fragmented_payload=[_mem('abc'), _mem('def')]), tr.loop.time() + 1.0) assert broadcaster.sample_statistics() == SessionStatistics( transfers=1, frames=1, payload_bytes=6) assert tr.sample_statistics() == can.CANTransportStatistics(out_frames=1) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=1, in_frames_uavcan=1) assert tr.sample_statistics( ).media_acceptance_filtering_efficiency == pytest.approx(1) assert tr2.sample_statistics( ).media_acceptance_filtering_efficiency == pytest.approx(0) assert tr.sample_statistics().lost_loopback_frames == 0 assert tr2.sample_statistics().lost_loopback_frames == 0 assert collector.pop().is_same_manifestation( UAVCANFrame( identifier=MessageCANID(Priority.IMMEDIATE, None, 12345).compile( [_mem('abcdef')]), # payload fragments joined padded_payload=_mem('abcdef'), transfer_id=11, start_of_transfer=True, end_of_transfer=True, toggle_bit=True, loopback=False).compile()) assert collector.empty # Can't send anonymous service transfers with pytest.raises(OperationNotDefinedForAnonymousNodeError): assert await client_requester.send_until( Transfer(timestamp=ts, priority=Priority.IMMEDIATE, transfer_id=0, fragmented_payload=[]), tr.loop.time() + 1.0, ) assert client_requester.sample_statistics() == SessionStatistics( ) # Not incremented! # Can't send multiframe anonymous messages with pytest.raises(OperationNotDefinedForAnonymousNodeError): assert await broadcaster.send_until( Transfer( timestamp=ts, priority=Priority.SLOW, transfer_id=2, fragmented_payload=[_mem('qwe'), _mem('rty')] * 50 # Lots of data here, very multiframe ), tr.loop.time() + 1.0) # # Broadcast exchange with input dispatch test # selective_m12345_5 = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), 5), meta) selective_m12345_9 = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), 9), meta) promiscuous_m12345 = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), None), meta) assert await broadcaster.send_until( Transfer( timestamp=ts, priority=Priority.IMMEDIATE, transfer_id=32 + 11, # Modulus 11 fragmented_payload=[_mem('abc'), _mem('def')]), tr.loop.time() + 1.0) assert broadcaster.sample_statistics() == SessionStatistics( transfers=2, frames=2, payload_bytes=12) assert tr.sample_statistics() == can.CANTransportStatistics(out_frames=2) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=2, in_frames_uavcan=2, in_frames_uavcan_accepted=1) received = await promiscuous_m12345.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 11 assert received.source_node_id is None # The sender is anonymous assert received.priority == Priority.IMMEDIATE validate_timestamp(received.timestamp) assert received.fragmented_payload == [_mem('abcdef')] assert selective_m12345_5.sample_statistics() == SessionStatistics( ) # Nothing assert selective_m12345_9.sample_statistics() == SessionStatistics( ) # Nothing assert promiscuous_m12345.sample_statistics() == SessionStatistics( transfers=1, frames=1, payload_bytes=6) assert not media.automatic_retransmission_enabled assert not media2.automatic_retransmission_enabled # # Finalization. # print('str(CANTransport):', tr) print('str(CANTransport):', tr2) client_listener.close() server_listener.close() subscriber_promiscuous.close() subscriber_selective.close() tr.close() tr2.close() # Double-close has no effect: client_listener.close() server_listener.close() subscriber_promiscuous.close() subscriber_selective.close() tr.close() tr2.close()
def _unittest_input_session() -> None: import asyncio from pytest import raises, approx from pyuavcan.transport import InputSessionSpecifier, MessageDataSpecifier, Priority, TransferFrom from pyuavcan.transport import PayloadMetadata, Timestamp from pyuavcan.transport.commons.high_overhead_transport import TransferCRC ts = Timestamp.now() prio = Priority.SLOW dst_nid = 1234 run_until_complete = asyncio.get_event_loop().run_until_complete get_monotonic = asyncio.get_event_loop().time nihil_supernum = b'nihil supernum' finalized = False def do_finalize() -> None: nonlocal finalized finalized = True session_spec = InputSessionSpecifier(MessageDataSpecifier(12345), None) payload_meta = PayloadMetadata(0xdead_beef_bad_c0ffe, 100) sis = SerialInputSession(specifier=session_spec, payload_metadata=payload_meta, loop=asyncio.get_event_loop(), finalizer=do_finalize) assert sis.specifier == session_spec assert sis.payload_metadata == payload_meta assert sis.sample_statistics() == SerialInputSessionStatistics() assert sis.transfer_id_timeout == approx( SerialInputSession.DEFAULT_TRANSFER_ID_TIMEOUT) sis.transfer_id_timeout = 1.0 with raises(ValueError): sis.transfer_id_timeout = 0.0 assert sis.transfer_id_timeout == approx(1.0) assert run_until_complete(sis.receive_until(get_monotonic() + 0.1)) is None assert run_until_complete(sis.receive_until(0.0)) is None def mk_frame(transfer_id: int, index: int, end_of_transfer: bool, payload: typing.Union[bytes, memoryview], source_node_id: typing.Optional[int]) -> SerialFrame: return SerialFrame(timestamp=ts, priority=prio, transfer_id=transfer_id, index=index, end_of_transfer=end_of_transfer, payload=memoryview(payload), source_node_id=source_node_id, destination_node_id=dst_nid, data_specifier=session_spec.data_specifier, data_type_hash=payload_meta.data_type_hash) # ANONYMOUS TRANSFERS. sis._process_frame( mk_frame(transfer_id=0, index=0, end_of_transfer=False, payload=nihil_supernum, source_node_id=None)) assert sis.sample_statistics() == SerialInputSessionStatistics( frames=1, errors=1, ) sis._process_frame( mk_frame(transfer_id=0, index=1, end_of_transfer=True, payload=nihil_supernum, source_node_id=None)) assert sis.sample_statistics() == SerialInputSessionStatistics( frames=2, errors=2, ) sis._process_frame( mk_frame(transfer_id=0, index=0, end_of_transfer=True, payload=nihil_supernum, source_node_id=None)) assert sis.sample_statistics() == SerialInputSessionStatistics( transfers=1, frames=3, payload_bytes=len(nihil_supernum), errors=2, ) assert run_until_complete(sis.receive_until(0)) == \ TransferFrom(timestamp=ts, priority=prio, transfer_id=0, fragmented_payload=[memoryview(nihil_supernum)], source_node_id=None) assert run_until_complete(sis.receive_until(get_monotonic() + 0.1)) is None assert run_until_complete(sis.receive_until(0.0)) is None # BAD DATA TYPE HASH. sis._process_frame( SerialFrame(timestamp=ts, priority=prio, transfer_id=0, index=0, end_of_transfer=True, payload=memoryview(nihil_supernum), source_node_id=None, destination_node_id=None, data_specifier=session_spec.data_specifier, data_type_hash=0xbad_bad_bad_bad_bad)) assert sis.sample_statistics() == SerialInputSessionStatistics( transfers=1, frames=4, payload_bytes=len(nihil_supernum), errors=3, mismatched_data_type_hashes={0xbad_bad_bad_bad_bad: 1}, )
async def _unittest_input_session() -> None: ts = Timestamp.now() prio = Priority.SLOW dst_nid = 1234 get_monotonic = asyncio.get_event_loop().time nihil_supernum = b"nihil supernum" finalized = False def do_finalize() -> None: nonlocal finalized finalized = True session_spec = InputSessionSpecifier(MessageDataSpecifier(2345), None) payload_meta = PayloadMetadata(100) sis = SerialInputSession(specifier=session_spec, payload_metadata=payload_meta, finalizer=do_finalize) assert sis.specifier == session_spec assert sis.payload_metadata == payload_meta assert sis.sample_statistics() == SerialInputSessionStatistics() assert sis.transfer_id_timeout == approx( SerialInputSession.DEFAULT_TRANSFER_ID_TIMEOUT) sis.transfer_id_timeout = 1.0 with raises(ValueError): sis.transfer_id_timeout = 0.0 assert sis.transfer_id_timeout == approx(1.0) assert await (sis.receive(get_monotonic() + 0.1)) is None assert await (sis.receive(0.0)) is None def mk_frame( transfer_id: int, index: int, end_of_transfer: bool, payload: typing.Union[bytes, memoryview], source_node_id: typing.Optional[int], ) -> SerialFrame: return SerialFrame( priority=prio, transfer_id=transfer_id, index=index, end_of_transfer=end_of_transfer, payload=memoryview(payload), source_node_id=source_node_id, destination_node_id=dst_nid, data_specifier=session_spec.data_specifier, ) # ANONYMOUS TRANSFERS. sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=0, end_of_transfer=False, payload=nihil_supernum, source_node_id=None)) assert sis.sample_statistics() == SerialInputSessionStatistics( frames=1, errors=1, ) sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=1, end_of_transfer=True, payload=nihil_supernum, source_node_id=None)) assert sis.sample_statistics() == SerialInputSessionStatistics( frames=2, errors=2, ) sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=0, end_of_transfer=True, payload=nihil_supernum, source_node_id=None)) assert sis.sample_statistics() == SerialInputSessionStatistics( transfers=1, frames=3, payload_bytes=len(nihil_supernum), errors=2, ) assert await (sis.receive(0)) == TransferFrom( timestamp=ts, priority=prio, transfer_id=0, fragmented_payload=[memoryview(nihil_supernum)], source_node_id=None) assert await (sis.receive(get_monotonic() + 0.1)) is None assert await (sis.receive(0.0)) is None # VALID TRANSFERS. Notice that they are unordered on purpose. The reassembler can deal with that. sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=1, end_of_transfer=False, payload=nihil_supernum, source_node_id=1111)) sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=0, end_of_transfer=True, payload=nihil_supernum, source_node_id=2222)) # COMPLETED FIRST assert sis.sample_statistics() == SerialInputSessionStatistics( transfers=2, frames=5, payload_bytes=len(nihil_supernum) * 2, errors=2, reassembly_errors_per_source_node_id={ 1111: {}, 2222: {}, }, ) sis._process_frame( # pylint: disable=protected-access ts, mk_frame( transfer_id=0, index=3, end_of_transfer=True, payload=TransferCRC.new(nihil_supernum * 3).value_as_bytes, source_node_id=1111, ), ) sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=0, end_of_transfer=False, payload=nihil_supernum, source_node_id=1111)) sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=2, end_of_transfer=False, payload=nihil_supernum, source_node_id=1111)) # COMPLETED SECOND assert sis.sample_statistics() == SerialInputSessionStatistics( transfers=3, frames=8, payload_bytes=len(nihil_supernum) * 5, errors=2, reassembly_errors_per_source_node_id={ 1111: {}, 2222: {}, }, ) assert await (sis.receive(0)) == TransferFrom( timestamp=ts, priority=prio, transfer_id=0, fragmented_payload=[memoryview(nihil_supernum)], source_node_id=2222) assert await (sis.receive(0)) == TransferFrom( timestamp=ts, priority=prio, transfer_id=0, fragmented_payload=[memoryview(nihil_supernum)] * 3, source_node_id=1111, ) assert await (sis.receive(get_monotonic() + 0.1)) is None assert await (sis.receive(0.0)) is None # TRANSFERS WITH REASSEMBLY ERRORS. sis._process_frame( # pylint: disable=protected-access ts, mk_frame( transfer_id=1, index=0, end_of_transfer=False, payload=b"", source_node_id=1111 # EMPTY IN MULTIFRAME ), ) sis._process_frame( # pylint: disable=protected-access ts, mk_frame( transfer_id=2, index=0, end_of_transfer=False, payload=b"", source_node_id=1111 # EMPTY IN MULTIFRAME ), ) assert sis.sample_statistics() == SerialInputSessionStatistics( transfers=3, frames=10, payload_bytes=len(nihil_supernum) * 5, errors=4, reassembly_errors_per_source_node_id={ 1111: { TransferReassembler.Error.MULTIFRAME_EMPTY_FRAME: 2, }, 2222: {}, }, ) assert not finalized sis.close() assert finalized sis.close() # Idempotency check
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.
async def _unittest_udp_transport_ipv4() -> None: from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer, TransferFrom from pyuavcan.transport import Priority, Timestamp, InputSessionSpecifier, OutputSessionSpecifier from pyuavcan.transport import ProtocolParameters asyncio.get_running_loop().slow_callback_duration = 5.0 get_monotonic = asyncio.get_event_loop().time with pytest.raises(ValueError): _ = UDPTransport("127.0.0.111", mtu=10) with pytest.raises(ValueError): _ = UDPTransport("127.0.0.111", service_transfer_multiplier=100) tr = UDPTransport("127.0.0.111", mtu=9000) tr2 = UDPTransport("127.0.0.222", service_transfer_multiplier=2) assert tr.local_ip_address == ipaddress.ip_address("127.0.0.111") assert tr2.local_ip_address == ipaddress.ip_address("127.0.0.222") assert tr.loop is asyncio.get_event_loop() assert tr.local_node_id == 111 assert tr2.local_node_id == 222 assert tr.input_sessions == [] assert tr.output_sessions == [] assert "127.0.0.111" in repr(tr) assert tr.protocol_parameters == ProtocolParameters( transfer_id_modulo=2 ** 64, max_nodes=65535, mtu=9000, ) default_mtu = min(UDPTransport.VALID_MTU_RANGE) assert "127.0.0.222" in repr(tr2) assert tr2.protocol_parameters == ProtocolParameters( transfer_id_modulo=2 ** 64, max_nodes=65535, mtu=default_mtu, ) assert tr.sample_statistics() == tr2.sample_statistics() == UDPTransportStatistics() payload_single = [_mem("qwertyui"), _mem("01234567")] * (default_mtu // 16) assert sum(map(len, payload_single)) == default_mtu payload_x3 = (payload_single * 3)[:-1] payload_x3_size_bytes = default_mtu * 3 - 8 assert sum(map(len, payload_x3)) == payload_x3_size_bytes # # Instantiate session objects. # meta = PayloadMetadata(10000) broadcaster = tr2.get_output_session(OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) assert broadcaster is tr2.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), 123), meta) assert subscriber_selective is tr.get_input_session(InputSessionSpecifier(MessageDataSpecifier(2345), 123), meta) server_listener = tr.get_input_session( InputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), None), meta ) assert server_listener is tr.get_input_session( InputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), None), meta ) server_responder = tr.get_output_session( OutputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 222), meta ) assert server_responder is tr.get_output_session( OutputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 222), meta ) client_requester = tr2.get_output_session( OutputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), 111), meta ) assert client_requester is tr2.get_output_session( OutputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), 111), meta ) client_listener = tr2.get_input_session( InputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 111), meta ) assert client_listener is tr2.get_input_session( InputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 111), meta ) print("tr :", tr.input_sessions, tr.output_sessions) assert set(tr.input_sessions) == {subscriber_promiscuous, subscriber_selective, server_listener} assert set(tr.output_sessions) == {server_responder} print("tr2:", tr2.input_sessions, tr2.output_sessions) assert set(tr2.input_sessions) == {client_listener} assert set(tr2.output_sessions) == {broadcaster, client_requester} assert tr.sample_statistics().received_datagrams[MessageDataSpecifier(2345)].accepted_datagrams == {} assert ( tr.sample_statistics() .received_datagrams[ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST)] .accepted_datagrams == {} ) assert ( tr2.sample_statistics() .received_datagrams[ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE)] .accepted_datagrams == {} ) # # 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 :", tr.sample_statistics()) assert tr.sample_statistics().received_datagrams[MessageDataSpecifier(2345)].accepted_datagrams == {222: 1} assert ( tr.sample_statistics() .received_datagrams[ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST)] .accepted_datagrams == {} ) print("tr2:", tr2.sample_statistics()) assert ( tr2.sample_statistics() .received_datagrams[ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE)] .accepted_datagrams == {} ) 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. # 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 :", tr.sample_statistics()) assert tr.sample_statistics().received_datagrams[MessageDataSpecifier(2345)].accepted_datagrams == {222: 1} assert tr.sample_statistics().received_datagrams[ ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST) ].accepted_datagrams == { 222: 3 * 2 } # Deterministic data loss mitigation is enabled, multiplication factor 2 print("tr2:", tr2.sample_statistics()) assert ( tr2.sample_statistics() .received_datagrams[ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE)] .accepted_datagrams == {} ) # # Termination. # assert set(tr.input_sessions) == {subscriber_promiscuous, subscriber_selective, server_listener} assert set(tr.output_sessions) == {server_responder} assert set(tr2.input_sessions) == {client_listener} assert set(tr2.output_sessions) == {broadcaster, client_requester} subscriber_promiscuous.close() subscriber_promiscuous.close() # Idempotency. assert set(tr.input_sessions) == {subscriber_selective, server_listener} assert set(tr.output_sessions) == {server_responder} assert set(tr2.input_sessions) == {client_listener} assert set(tr2.output_sessions) == {broadcaster, client_requester} broadcaster.close() broadcaster.close() # Idempotency. assert set(tr.input_sessions) == {subscriber_selective, server_listener} assert set(tr.output_sessions) == {server_responder} assert set(tr2.input_sessions) == {client_listener} assert set(tr2.output_sessions) == {client_requester} tr.close() tr.close() # Idempotency. tr2.close() tr2.close() # Idempotency. assert not set(tr.input_sessions) assert not set(tr.output_sessions) assert not set(tr2.input_sessions) assert not set(tr2.output_sessions) with pytest.raises(pyuavcan.transport.ResourceClosedError): _ = tr.get_output_session(OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) with pytest.raises(pyuavcan.transport.ResourceClosedError): _ = tr2.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.
async def _unittest_can_capture_trace() -> None: from pyuavcan.transport import MessageDataSpecifier, PayloadMetadata, Transfer, Priority, Timestamp from pyuavcan.transport import InputSessionSpecifier, OutputSessionSpecifier, TransferTrace from .media.mock import MockMedia from pyuavcan.transport.can import CANCapture from pyuavcan.transport.can.media import FilterConfiguration, FrameFormat asyncio.get_running_loop().slow_callback_duration = 5.0 ts = Timestamp.now() peers: typing.Set[MockMedia] = set() media = MockMedia(peers, 64, 2) media2 = MockMedia(peers, 64, 2) tr = can.CANTransport(media, None) tr2 = can.CANTransport(media2, 51) captures: typing.List[CANCapture] = [] captures_other: typing.List[CANCapture] = [] def add_capture(cap: pyuavcan.transport.Capture) -> None: assert isinstance(cap, CANCapture) captures.append(cap) def add_capture_other(cap: pyuavcan.transport.Capture) -> None: assert isinstance(cap, CANCapture) captures_other.append(cap) tr.begin_capture(add_capture) tr.begin_capture(add_capture_other) assert media.acceptance_filters == [ FilterConfiguration.new_promiscuous(FrameFormat.BASE), FilterConfiguration.new_promiscuous(FrameFormat.EXTENDED), ] a_out = tr.get_output_session(OutputSessionSpecifier(MessageDataSpecifier(2345), None), PayloadMetadata(800)) b_out = tr2.get_output_session(OutputSessionSpecifier(MessageDataSpecifier(5432), None), PayloadMetadata(800)) # Ensure the filter configuration is not reset when creating new subscriptions. a_in = tr2.get_input_session(InputSessionSpecifier(MessageDataSpecifier(2345), None), PayloadMetadata(800)) assert media.acceptance_filters == [ FilterConfiguration.new_promiscuous(FrameFormat.BASE), FilterConfiguration.new_promiscuous(FrameFormat.EXTENDED), ] # Send transfers to collect some captures. assert await a_out.send( Transfer(ts, Priority.NOMINAL, transfer_id=11, fragmented_payload=[memoryview(b"first")]), monotonic_deadline=tr.loop.time() + 2.0, ) await asyncio.sleep(1.0) # Let messages propagate. assert await b_out.send( Transfer(ts, Priority.NOMINAL, transfer_id=22, fragmented_payload=[memoryview(b"second")]), monotonic_deadline=tr.loop.time() + 2.0, ) transfer = await a_in.receive(tr.loop.time() + 2.0) assert transfer assert transfer.transfer_id == 11 await asyncio.sleep(1.0) # Let messages propagate. # Validate the captures. assert captures == captures_other assert len(captures) == 2 # One sent, one received. assert captures[0].own assert b"first" in captures[0].frame.data assert not captures[1].own assert b"second" in captures[1].frame.data # Check the loopback stats. assert tr.sample_statistics().in_frames == 1 assert tr.sample_statistics().in_frames_loopback == 1 assert tr2.sample_statistics().in_frames == 1 assert tr2.sample_statistics().in_frames_loopback == 0 # Perform basic tracer test (the full test is implemented separately). tracer = tr.make_tracer() trc = tracer.update(captures[0]) assert isinstance(trc, TransferTrace) assert b"first" in trc.transfer.fragmented_payload[0].tobytes() trc = tracer.update(captures[1]) assert isinstance(trc, TransferTrace) assert b"second" in trc.transfer.fragmented_payload[0].tobytes()