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()
def _unittest_output_session() -> None: from pytest import raises from pyuavcan.transport import OutputSessionSpecifier, MessageDataSpecifier, ServiceDataSpecifier, Priority from pyuavcan.transport import PayloadMetadata, SessionStatistics, Timestamp, Feedback, Transfer ts = Timestamp.now() loop = asyncio.get_event_loop() run_until_complete = loop.run_until_complete finalized = False def do_finalize() -> None: nonlocal finalized finalized = True def check_timestamp(t: pyuavcan.transport.Timestamp) -> bool: now = pyuavcan.transport.Timestamp.now() s = ts.system_ns <= t.system_ns <= now.system_ns m = ts.monotonic_ns <= t.monotonic_ns <= now.system_ns return s and m destination_endpoint = '127.100.0.1', 25406 sock_rx = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM) sock_rx.bind(destination_endpoint) sock_rx.settimeout(1.0) def make_sock() -> socket_.socket: sock = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM) sock.bind(('127.100.0.2', 0)) sock.connect(destination_endpoint) sock.setblocking(False) return sock sos = UDPOutputSession( specifier=OutputSessionSpecifier(MessageDataSpecifier(3210), None), payload_metadata=PayloadMetadata(0xdead_beef_badc0ffe, 1024), mtu=11, multiplier=1, sock=make_sock(), loop=asyncio.get_event_loop(), finalizer=do_finalize, ) assert sos.specifier == OutputSessionSpecifier(MessageDataSpecifier(3210), None) assert sos.destination_node_id is None assert sos.payload_metadata == PayloadMetadata(0xdead_beef_badc0ffe, 1024) assert sos.sample_statistics() == SessionStatistics() assert run_until_complete( sos.send_until( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b'one'), memoryview(b'two'), memoryview(b'three') ]), loop.time() + 10.0)) rx_data, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == '127.100.0.2' assert rx_data == ( b'\x00\x04\x00\x00\x00\x00\x00\x8040\x00\x00\x00\x00\x00\x00\xfe\x0f\xdc\xba\xef\xbe\xad\xde' + b'one' b'two' b'three') with raises(socket_.timeout): sock_rx.recvfrom(1000) last_feedback: typing.Optional[Feedback] = None def feedback_handler(feedback: Feedback) -> None: nonlocal last_feedback last_feedback = feedback sos.enable_feedback(feedback_handler) assert last_feedback is None assert run_until_complete( sos.send_until( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[]), loop.time() + 10.0)) assert last_feedback is not None assert last_feedback.original_transfer_timestamp == ts assert check_timestamp(last_feedback.first_frame_transmission_timestamp) sos.disable_feedback() sos.disable_feedback() # Idempotency check _, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == '127.100.0.2' with raises(socket_.timeout): sock_rx.recvfrom(1000) assert sos.sample_statistics() == SessionStatistics(transfers=2, frames=2, payload_bytes=11, errors=0, drops=0) assert sos.socket.fileno() >= 0 assert not finalized sos.close() assert finalized assert sos.socket.fileno() < 0 # The socket is supposed to be disposed of. finalized = False # Multi-frame with multiplication sos = UDPOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 2222), payload_metadata=PayloadMetadata(0xdead_beef_badc0ffe, 1024), mtu=10, multiplier=2, sock=make_sock(), loop=asyncio.get_event_loop(), finalizer=do_finalize, ) assert run_until_complete( sos.send_until( Transfer(timestamp=ts, priority=Priority.OPTIONAL, transfer_id=54321, fragmented_payload=[ memoryview(b'one'), memoryview(b'two'), memoryview(b'three') ]), loop.time() + 10.0)) data_main_a, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == '127.100.0.2' data_main_b, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == '127.100.0.2' data_redundant_a, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == '127.100.0.2' data_redundant_b, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == '127.100.0.2' with raises(socket_.timeout): sock_rx.recvfrom(1000) print('data_main_a', data_main_a) print('data_main_b', data_main_b) print('data_redundant_a', data_redundant_a) print('data_redundant_b', data_redundant_b) assert data_main_a == data_redundant_a assert data_main_b == data_redundant_b assert data_main_a == ( b'\x00\x07\x00\x00\x00\x00\x00\x001\xd4\x00\x00\x00\x00\x00\x00\xfe\x0f\xdc\xba\xef\xbe\xad\xde' + b'one' b'two' b'three'[:-1]) assert data_main_b == ( b'\x00\x07\x00\x00\x01\x00\x00\x801\xd4\x00\x00\x00\x00\x00\x00\xfe\x0f\xdc\xba\xef\xbe\xad\xde' + b'e' + pyuavcan.transport.commons.crc.CRC32C.new( b'one', b'two', b'three').value_as_bytes) sos = UDPOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 2222), payload_metadata=PayloadMetadata(0xdead_beef_badc0ffe, 1024), mtu=10, multiplier=1, sock=make_sock(), loop=asyncio.get_event_loop(), finalizer=do_finalize, ) # Induced timeout assert not run_until_complete( sos.send_until( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b'one'), memoryview(b'two'), memoryview(b'three') ]), loop.time() - 0.1 # Expired on arrival )) assert sos.sample_statistics() == SessionStatistics( transfers=0, frames=0, payload_bytes=0, errors=0, drops=2 # Because multiframe ) # Induced failure sos.socket.close() with raises(OSError): assert not run_until_complete( sos.send_until( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b'one'), memoryview(b'two'), memoryview(b'three') ]), loop.time() + 10.0)) assert sos.sample_statistics() == SessionStatistics(transfers=0, frames=0, payload_bytes=0, errors=1, drops=2) assert not finalized sos.close() assert finalized sos.close() # Idempotency with raises(pyuavcan.transport.ResourceClosedError): run_until_complete( sos.send_until( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b'one'), memoryview(b'two'), memoryview(b'three') ]), loop.time() + 10.0)) sock_rx.close()
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()
async def _unittest_output_session() -> None: ts = Timestamp.now() loop = asyncio.get_event_loop() tx_timestamp: typing.Optional[Timestamp] = Timestamp.now() tx_exception: typing.Optional[Exception] = None last_sent_frames: typing.List[SerialFrame] = [] last_monotonic_deadline = 0.0 finalized = False async def do_send(frames: typing.Sequence[SerialFrame], monotonic_deadline: float) -> typing.Optional[Timestamp]: nonlocal last_sent_frames nonlocal last_monotonic_deadline last_sent_frames = list(frames) last_monotonic_deadline = monotonic_deadline if tx_exception: raise tx_exception return tx_timestamp def do_finalize() -> None: nonlocal finalized finalized = True with raises(pyuavcan.transport.OperationNotDefinedForAnonymousNodeError): SerialOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 1111), payload_metadata=PayloadMetadata(1024), mtu=10, local_node_id=None, send_handler=do_send, finalizer=do_finalize, ) sos = SerialOutputSession( specifier=OutputSessionSpecifier(MessageDataSpecifier(3210), None), payload_metadata=PayloadMetadata(1024), mtu=11, local_node_id=None, send_handler=do_send, finalizer=do_finalize, ) assert sos.specifier == OutputSessionSpecifier(MessageDataSpecifier(3210), None) assert sos.destination_node_id is None assert sos.payload_metadata == PayloadMetadata(1024) assert sos.sample_statistics() == SessionStatistics() assert await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), 999999999.999, )) assert last_monotonic_deadline == approx(999999999.999) assert len(last_sent_frames) == 1 with raises(pyuavcan.transport.OperationNotDefinedForAnonymousNodeError): await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three four five") ], ), loop.time() + 10.0, )) last_feedback: typing.Optional[Feedback] = None def feedback_handler(feedback: Feedback) -> None: nonlocal last_feedback last_feedback = feedback sos.enable_feedback(feedback_handler) assert last_feedback is None assert await (sos.send( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[]), 999999999.999)) assert last_monotonic_deadline == approx(999999999.999) assert len(last_sent_frames) == 1 assert last_feedback is not None assert last_feedback.original_transfer_timestamp == ts assert last_feedback.first_frame_transmission_timestamp == tx_timestamp sos.disable_feedback() sos.disable_feedback() # Idempotency check assert sos.sample_statistics() == SessionStatistics(transfers=2, frames=2, payload_bytes=11, errors=0, drops=0) assert not finalized sos.close() assert finalized finalized = False sos = SerialOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 2222), payload_metadata=PayloadMetadata(1024), mtu=10, local_node_id=1234, send_handler=do_send, finalizer=do_finalize, ) # Induced failure tx_timestamp = None assert not await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), 999999999.999, )) assert last_monotonic_deadline == approx(999999999.999) assert len(last_sent_frames) == 2 assert sos.sample_statistics() == SessionStatistics(transfers=0, frames=0, payload_bytes=0, errors=0, drops=2) tx_exception = RuntimeError() with raises(RuntimeError): _ = await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, )) assert sos.sample_statistics() == SessionStatistics(transfers=0, frames=0, payload_bytes=0, errors=1, drops=2) assert not finalized sos.close() assert finalized sos.close() # Idempotency with raises(pyuavcan.transport.ResourceClosedError): await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, ))
async def _unittest_output_session() -> None: ts = Timestamp.now() loop = asyncio.get_event_loop() loop.slow_callback_duration = 5.0 # TODO use asyncio socket read and remove this thing. finalized = False def do_finalize() -> None: nonlocal finalized finalized = True def check_timestamp(t: Timestamp) -> bool: now = Timestamp.now() s = ts.system_ns <= t.system_ns <= now.system_ns m = ts.monotonic_ns <= t.monotonic_ns <= now.system_ns return s and m destination_endpoint = "127.100.0.1", 25406 sock_rx = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM) sock_rx.bind(destination_endpoint) sock_rx.settimeout(1.0) def make_sock() -> socket_.socket: sock = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM) sock.bind(("127.100.0.2", 0)) sock.connect(destination_endpoint) sock.setblocking(False) return sock sos = UDPOutputSession( specifier=OutputSessionSpecifier(MessageDataSpecifier(3210), None), payload_metadata=PayloadMetadata(1024), mtu=11, multiplier=1, sock=make_sock(), finalizer=do_finalize, ) assert sos.specifier == OutputSessionSpecifier(MessageDataSpecifier(3210), None) assert sos.destination_node_id is None assert sos.payload_metadata == PayloadMetadata(1024) assert sos.sample_statistics() == SessionStatistics() assert await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, )) rx_data, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == "127.100.0.2" assert rx_data == ( b"\x00\x04\x00\x00\x00\x00\x00\x8040\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"one" + b"two" + b"three") with raises(socket_.timeout): sock_rx.recvfrom(1000) last_feedback: typing.Optional[Feedback] = None def feedback_handler(feedback: Feedback) -> None: nonlocal last_feedback last_feedback = feedback sos.enable_feedback(feedback_handler) assert last_feedback is None assert await (sos.send( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[]), loop.time() + 10.0, )) assert last_feedback is not None assert last_feedback.original_transfer_timestamp == ts assert check_timestamp(last_feedback.first_frame_transmission_timestamp) sos.disable_feedback() sos.disable_feedback() # Idempotency check _, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == "127.100.0.2" with raises(socket_.timeout): sock_rx.recvfrom(1000) assert sos.sample_statistics() == SessionStatistics(transfers=2, frames=2, payload_bytes=11, errors=0, drops=0) assert sos.socket.fileno() >= 0 assert not finalized sos.close() assert finalized assert sos.socket.fileno() < 0 # The socket is supposed to be disposed of. finalized = False # Multi-frame with multiplication sos = UDPOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 2222), payload_metadata=PayloadMetadata(1024), mtu=10, multiplier=2, sock=make_sock(), finalizer=do_finalize, ) assert await (sos.send( Transfer( timestamp=ts, priority=Priority.OPTIONAL, transfer_id=54321, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, )) data_main_a, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == "127.100.0.2" data_main_b, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == "127.100.0.2" data_redundant_a, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == "127.100.0.2" data_redundant_b, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == "127.100.0.2" with raises(socket_.timeout): sock_rx.recvfrom(1000) print("data_main_a", data_main_a) print("data_main_b", data_main_b) print("data_redundant_a", data_redundant_a) print("data_redundant_b", data_redundant_b) assert data_main_a == data_redundant_a assert data_main_b == data_redundant_b assert data_main_a == ( b"\x00\x07\x00\x00\x00\x00\x00\x001\xd4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"one" + b"two" + b"three"[:-1]) assert data_main_b == ( b"\x00\x07\x00\x00\x01\x00\x00\x801\xd4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"e" + pyuavcan.transport.commons.crc.CRC32C.new( b"one", b"two", b"three").value_as_bytes) sos.socket.close() # This is to prevent resource warning sos = UDPOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 2222), payload_metadata=PayloadMetadata(1024), mtu=10, multiplier=1, sock=make_sock(), finalizer=do_finalize, ) # Induced timeout assert not await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() - 0.1, # Expired on arrival )) assert sos.sample_statistics() == SessionStatistics( transfers=0, frames=0, payload_bytes=0, errors=0, drops=2 # Because multiframe ) # Induced failure sos.socket.close() with raises(OSError): assert not await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, )) assert sos.sample_statistics() == SessionStatistics(transfers=0, frames=0, payload_bytes=0, errors=1, drops=2) assert not finalized sos.close() assert finalized sos.close() # Idempotency with raises(pyuavcan.transport.ResourceClosedError): await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, )) sock_rx.close()
async def _unittest_redundant_output_exceptions(caplog: typing.Any) -> None: loop = asyncio.get_event_loop() spec = pyuavcan.transport.OutputSessionSpecifier( pyuavcan.transport.MessageDataSpecifier(4321), None) spec_rx = pyuavcan.transport.InputSessionSpecifier(spec.data_specifier, None) meta = pyuavcan.transport.PayloadMetadata(30 * 1024 * 1024) ts = Timestamp.now() is_retired = False def retire() -> None: nonlocal is_retired is_retired = True ses = RedundantOutputSession(spec, meta, finalizer=retire) assert not is_retired assert ses.specifier is spec assert ses.payload_metadata is meta assert not ses.inferiors assert ses.sample_statistics() == RedundantSessionStatistics() tr_a = LoopbackTransport(111) tr_b = LoopbackTransport(111) inf_a = tr_a.get_output_session(spec, meta) inf_b = tr_b.get_output_session(spec, meta) rx_a = tr_a.get_input_session(spec_rx, meta) rx_b = tr_b.get_input_session(spec_rx, meta) ses._add_inferior(inf_a) # pylint: disable=protected-access ses._add_inferior(inf_b) # pylint: disable=protected-access # Transmission with exceptions. # If at least one transmission succeeds, the call succeeds. with caplog.at_level(logging.CRITICAL, logger=__name__): inf_a.exception = RuntimeError("INTENDED EXCEPTION") assert await (ses.send( Transfer( timestamp=ts, priority=Priority.FAST, transfer_id=444444444444, fragmented_payload=[memoryview(b"INTENDED EXCEPTION")], ), loop.time() + 1.0, )) assert ses.sample_statistics() == RedundantSessionStatistics( transfers=1, frames=1, payload_bytes=len("INTENDED EXCEPTION"), errors=0, drops=0, inferiors=[ SessionStatistics( transfers=0, frames=0, payload_bytes=0, ), SessionStatistics( transfers=1, frames=1, payload_bytes=len("INTENDED EXCEPTION"), ), ], ) assert None is await (rx_a.receive(loop.time() + 1)) tf_rx = await (rx_b.receive(loop.time() + 1)) assert isinstance(tf_rx, TransferFrom) assert tf_rx.transfer_id == 444444444444 assert tf_rx.fragmented_payload == [memoryview(b"INTENDED EXCEPTION")] # Transmission timeout. # One times out, one raises an exception --> the result is timeout. inf_b.should_timeout = True assert not await (ses.send( Transfer( timestamp=ts, priority=Priority.FAST, transfer_id=2222222222222, fragmented_payload=[memoryview(b"INTENDED EXCEPTION")], ), loop.time() + 1.0, )) assert ses.sample_statistics().transfers == 1 assert ses.sample_statistics().payload_bytes == len( "INTENDED EXCEPTION") assert ses.sample_statistics().errors == 0 assert ses.sample_statistics().drops == 1 assert None is await (rx_a.receive(loop.time() + 1)) assert None is await (rx_b.receive(loop.time() + 1)) # Transmission with exceptions. # If all transmissions fail, the call fails. inf_b.exception = RuntimeError("INTENDED EXCEPTION") with pytest.raises(RuntimeError, match="INTENDED EXCEPTION"): assert await (ses.send( Transfer( timestamp=ts, priority=Priority.FAST, transfer_id=3333333333333, fragmented_payload=[memoryview(b"INTENDED EXCEPTION")], ), loop.time() + 1.0, )) assert ses.sample_statistics().transfers == 1 assert ses.sample_statistics().payload_bytes == len( "INTENDED EXCEPTION") assert ses.sample_statistics().errors == 1 assert ses.sample_statistics().drops == 1 assert None is await (rx_a.receive(loop.time() + 1)) assert None is await (rx_b.receive(loop.time() + 1)) # Retirement. assert not is_retired ses.close() assert is_retired # Make sure the inferiors have been closed. assert not tr_a.output_sessions assert not tr_b.output_sessions # Idempotency. is_retired = False ses.close() assert not is_retired await asyncio.sleep(2.0)
async def _unittest_redundant_output() -> None: loop = asyncio.get_event_loop() spec = pyuavcan.transport.OutputSessionSpecifier( pyuavcan.transport.MessageDataSpecifier(4321), None) spec_rx = pyuavcan.transport.InputSessionSpecifier(spec.data_specifier, None) meta = pyuavcan.transport.PayloadMetadata(30 * 1024 * 1024) ts = Timestamp.now() is_retired = False def retire() -> None: nonlocal is_retired is_retired = True ses = RedundantOutputSession(spec, meta, finalizer=retire) assert not is_retired assert ses.specifier is spec assert ses.payload_metadata is meta assert not ses.inferiors assert ses.sample_statistics() == RedundantSessionStatistics() # Transmit with an empty set of inferiors. time_before = loop.time() assert not await (ses.send( Transfer( timestamp=ts, priority=Priority.IMMEDIATE, transfer_id=1234567890, fragmented_payload=[memoryview(b"abc")], ), loop.time() + 2.0, )) assert 1.0 < loop.time( ) - time_before < 5.0, "The method should have returned in about two seconds." assert ses.sample_statistics() == RedundantSessionStatistics(drops=1, ) # Create inferiors. tr_a = LoopbackTransport(111) tr_b = LoopbackTransport(111) inf_a = tr_a.get_output_session(spec, meta) inf_b = tr_b.get_output_session(spec, meta) rx_a = tr_a.get_input_session(spec_rx, meta) rx_b = tr_b.get_input_session(spec_rx, meta) # Begin transmission, then add an inferior while it is in progress. async def add_inferior(inferior: pyuavcan.transport.OutputSession) -> None: print("sleeping before adding the inferior...") await asyncio.sleep(2.0) print("adding the inferior...") ses._add_inferior(inferior) # pylint: disable=protected-access print("inferior has been added.") assert await (asyncio.gather( # Start transmission here. It would stall for up to five seconds because no inferiors. ses.send( Transfer( timestamp=ts, priority=Priority.IMMEDIATE, transfer_id=9876543210, fragmented_payload=[memoryview(b"def")], ), loop.time() + 5.0, ), # While the transmission is stalled, add one inferior with a 2-sec delay. It will unlock the stalled task. add_inferior(inf_a), # Then make sure that the transmission has actually taken place about after two seconds from the start. )), "Transmission should have succeeded" assert 1.0 < loop.time( ) - time_before < 5.0, "The method should have returned in about two seconds." assert ses.sample_statistics() == RedundantSessionStatistics( transfers=1, frames=1, payload_bytes=3, drops=1, inferiors=[ SessionStatistics( transfers=1, frames=1, payload_bytes=3, ), ], ) tf_rx = await (rx_a.receive(loop.time() + 1)) assert isinstance(tf_rx, TransferFrom) assert tf_rx.transfer_id == 9876543210 assert tf_rx.fragmented_payload == [memoryview(b"def")] assert None is await (rx_b.receive(loop.time() + 0.1)) # Enable feedback. feedback: typing.List[RedundantFeedback] = [] ses.enable_feedback(feedback.append) assert await (ses.send( Transfer( timestamp=ts, priority=Priority.LOW, transfer_id=555555555555, fragmented_payload=[memoryview(b"qwerty")], ), loop.time() + 1.0, )) assert ses.sample_statistics() == RedundantSessionStatistics( transfers=2, frames=2, payload_bytes=9, drops=1, inferiors=[ SessionStatistics( transfers=2, frames=2, payload_bytes=9, ), ], ) assert len(feedback) == 1 assert feedback[0].inferior_session is inf_a assert feedback[0].original_transfer_timestamp == ts assert ts.system <= feedback[ 0].first_frame_transmission_timestamp.system <= time.time() assert ts.monotonic <= feedback[ 0].first_frame_transmission_timestamp.monotonic <= time.monotonic() assert isinstance(feedback[0].inferior_feedback, LoopbackFeedback) feedback.pop() assert not feedback tf_rx = await (rx_a.receive(loop.time() + 1)) assert isinstance(tf_rx, TransferFrom) assert tf_rx.transfer_id == 555555555555 assert tf_rx.fragmented_payload == [memoryview(b"qwerty")] assert None is await (rx_b.receive(loop.time() + 0.1)) # Add a new inferior and ensure that its feedback is auto-enabled! ses._add_inferior(inf_b) # pylint: disable=protected-access assert ses.inferiors == [ inf_a, inf_b, ] # Double-add has no effect. ses._add_inferior(inf_b) # pylint: disable=protected-access assert ses.inferiors == [ inf_a, inf_b, ] assert await (ses.send( Transfer( timestamp=ts, priority=Priority.FAST, transfer_id=777777777777, fragmented_payload=[memoryview(b"fgsfds")], ), loop.time() + 1.0, )) assert ses.sample_statistics() == RedundantSessionStatistics( transfers=3, frames=3 + 1, payload_bytes=15, drops=1, inferiors=[ SessionStatistics( transfers=3, frames=3, payload_bytes=15, ), SessionStatistics( transfers=1, frames=1, payload_bytes=6, ), ], ) assert len(feedback) == 2 feedback.sort(key=lambda x: x.inferior_session is not inf_a ) # Ensure consistent ordering assert feedback[0].inferior_session is inf_a assert feedback[0].original_transfer_timestamp == ts assert ts.system <= feedback[ 0].first_frame_transmission_timestamp.system <= time.time() assert ts.monotonic <= feedback[ 0].first_frame_transmission_timestamp.monotonic <= time.monotonic() assert isinstance(feedback[0].inferior_feedback, LoopbackFeedback) feedback.pop(0) assert len(feedback) == 1 assert feedback[0].inferior_session is inf_b assert feedback[0].original_transfer_timestamp == ts assert ts.system <= feedback[ 0].first_frame_transmission_timestamp.system <= time.time() assert ts.monotonic <= feedback[ 0].first_frame_transmission_timestamp.monotonic <= time.monotonic() assert isinstance(feedback[0].inferior_feedback, LoopbackFeedback) feedback.pop() assert not feedback tf_rx = await (rx_a.receive(loop.time() + 1)) assert isinstance(tf_rx, TransferFrom) assert tf_rx.transfer_id == 777777777777 assert tf_rx.fragmented_payload == [memoryview(b"fgsfds")] tf_rx = await (rx_b.receive(loop.time() + 1)) assert isinstance(tf_rx, TransferFrom) assert tf_rx.transfer_id == 777777777777 assert tf_rx.fragmented_payload == [memoryview(b"fgsfds")] # Remove the first inferior. ses._close_inferior(0) # pylint: disable=protected-access assert ses.inferiors == [inf_b] ses._close_inferior(1) # Out of range, no effect. # pylint: disable=protected-access assert ses.inferiors == [inf_b] # Make sure the removed inferior has been closed. assert not tr_a.output_sessions # Transmission test with the last inferior. assert await (ses.send( Transfer( timestamp=ts, priority=Priority.HIGH, transfer_id=88888888888888, fragmented_payload=[memoryview(b"hedgehog")], ), loop.time() + 1.0, )) assert ses.sample_statistics().transfers == 4 # We don't check frames because this stat metric is computed quite clumsily atm, this may change later. assert ses.sample_statistics().payload_bytes == 23 assert ses.sample_statistics().drops == 1 assert ses.sample_statistics().inferiors == [ SessionStatistics( transfers=2, frames=2, payload_bytes=14, ), ] assert len(feedback) == 1 assert feedback[0].inferior_session is inf_b assert feedback[0].original_transfer_timestamp == ts assert ts.system <= feedback[ 0].first_frame_transmission_timestamp.system <= time.time() assert ts.monotonic <= feedback[ 0].first_frame_transmission_timestamp.monotonic <= time.monotonic() assert isinstance(feedback[0].inferior_feedback, LoopbackFeedback) feedback.pop() assert not feedback assert None is await (rx_a.receive(loop.time() + 1)) tf_rx = await (rx_b.receive(loop.time() + 1)) assert isinstance(tf_rx, TransferFrom) assert tf_rx.transfer_id == 88888888888888 assert tf_rx.fragmented_payload == [memoryview(b"hedgehog")] # Disable the feedback. ses.disable_feedback() # A diversion - enable the feedback in the inferior and make sure it's not propagated. ses._enable_feedback_on_inferior(inf_b) # pylint: disable=protected-access assert await (ses.send( Transfer( timestamp=ts, priority=Priority.OPTIONAL, transfer_id=666666666666666, fragmented_payload=[memoryview(b"horse")], ), loop.time() + 1.0, )) assert ses.sample_statistics().transfers == 5 # We don't check frames because this stat metric is computed quite clumsily atm, this may change later. assert ses.sample_statistics().payload_bytes == 28 assert ses.sample_statistics().drops == 1 assert ses.sample_statistics().inferiors == [ SessionStatistics( transfers=3, frames=3, payload_bytes=19, ), ] assert not feedback assert None is await (rx_a.receive(loop.time() + 1)) tf_rx = await (rx_b.receive(loop.time() + 1)) assert isinstance(tf_rx, TransferFrom) assert tf_rx.transfer_id == 666666666666666 assert tf_rx.fragmented_payload == [memoryview(b"horse")] # Retirement. assert not is_retired ses.close() assert is_retired # Make sure the inferiors have been closed. assert not tr_a.output_sessions assert not tr_b.output_sessions # Idempotency. is_retired = False ses.close() assert not is_retired # Use after close. with pytest.raises(ResourceClosedError): await (ses.send( Transfer( timestamp=ts, priority=Priority.OPTIONAL, transfer_id=1111111111111, fragmented_payload=[memoryview(b"cat")], ), loop.time() + 1.0, )) assert None is await (rx_a.receive(loop.time() + 1)) assert None is await (rx_b.receive(loop.time() + 1)) await asyncio.sleep(2.0)
def _unittest_redundant_output_exceptions() -> None: import pytest from pyuavcan.transport import Transfer, Timestamp, Priority, SessionStatistics from pyuavcan.transport import TransferFrom from pyuavcan.transport.loopback import LoopbackTransport loop = asyncio.get_event_loop() await_ = loop.run_until_complete spec = pyuavcan.transport.OutputSessionSpecifier(pyuavcan.transport.MessageDataSpecifier(4321), None) spec_rx = pyuavcan.transport.InputSessionSpecifier(spec.data_specifier, None) meta = pyuavcan.transport.PayloadMetadata(0x_deadbeef_deadbeef, 30 * 1024 * 1024) ts = Timestamp.now() is_retired = False def retire() -> None: nonlocal is_retired is_retired = True ses = RedundantOutputSession(spec, meta, loop=loop, finalizer=retire) assert not is_retired assert ses.specifier is spec assert ses.payload_metadata is meta assert not ses.inferiors assert ses.sample_statistics() == RedundantSessionStatistics() tr_a = LoopbackTransport(111) tr_b = LoopbackTransport(111) inf_a = tr_a.get_output_session(spec, meta) inf_b = tr_b.get_output_session(spec, meta) rx_a = tr_a.get_input_session(spec_rx, meta) rx_b = tr_b.get_input_session(spec_rx, meta) # noinspection PyProtectedMember ses._add_inferior(inf_a) # noinspection PyProtectedMember ses._add_inferior(inf_b) # Transmission with exceptions. # If at least one transmission succeeds, the call succeeds. inf_a.exception = RuntimeError('EXCEPTION SUKA') assert await_(ses.send_until( Transfer(timestamp=ts, priority=Priority.FAST, transfer_id=444444444444, fragmented_payload=[memoryview(b'exception suka')]), loop.time() + 1.0 )) assert ses.sample_statistics() == RedundantSessionStatistics( transfers=1, frames=1, payload_bytes=len('exception suka'), errors=0, drops=0, inferiors=[ SessionStatistics( transfers=0, frames=0, payload_bytes=0, ), SessionStatistics( transfers=1, frames=1, payload_bytes=len('exception suka'), ), ], ) assert None is await_(rx_a.receive_until(loop.time() + 1)) tf_rx = await_(rx_b.receive_until(loop.time() + 1)) assert isinstance(tf_rx, TransferFrom) assert tf_rx.transfer_id == 444444444444 assert tf_rx.fragmented_payload == [memoryview(b'exception suka')] # Transmission timeout. # One times out, one raises an exception --> the result is timeout. inf_b.should_timeout = True assert not await_(ses.send_until( Transfer(timestamp=ts, priority=Priority.FAST, transfer_id=2222222222222, fragmented_payload=[memoryview(b'exception suka')]), loop.time() + 1.0 )) assert ses.sample_statistics().transfers == 1 assert ses.sample_statistics().payload_bytes == len('exception suka') assert ses.sample_statistics().errors == 0 assert ses.sample_statistics().drops == 1 assert None is await_(rx_a.receive_until(loop.time() + 1)) assert None is await_(rx_b.receive_until(loop.time() + 1)) # Transmission with exceptions. # If all transmissions fail, the call fails. inf_b.exception = RuntimeError('EXCEPTION SUKA') with pytest.raises(RuntimeError, match='EXCEPTION SUKA'): assert await_(ses.send_until( Transfer(timestamp=ts, priority=Priority.FAST, transfer_id=3333333333333, fragmented_payload=[memoryview(b'exception suka')]), loop.time() + 1.0 )) assert ses.sample_statistics().transfers == 1 assert ses.sample_statistics().payload_bytes == len('exception suka') assert ses.sample_statistics().errors == 1 assert ses.sample_statistics().drops == 1 assert None is await_(rx_a.receive_until(loop.time() + 1)) assert None is await_(rx_b.receive_until(loop.time() + 1)) # Retirement. assert not is_retired ses.close() assert is_retired # Make sure the inferiors have been closed. assert not tr_a.output_sessions assert not tr_b.output_sessions # Idempotency. is_retired = False ses.close() assert not is_retired