Пример #1
0
def _unittest_redundant_transport_reconfiguration() -> None:
    from pyuavcan.transport import OutputSessionSpecifier, MessageDataSpecifier, PayloadMetadata

    tr = RedundantTransport()
    tr.attach_inferior(LoopbackTransport(1234))
    ses = tr.get_output_session(OutputSessionSpecifier(MessageDataSpecifier(5555), None), PayloadMetadata(0))
    assert ses
    tr.detach_inferior(tr.inferiors[0])
    tr.attach_inferior(LoopbackTransport(1235))  # Different node-ID
    tr.detach_inferior(tr.inferiors[0])
    tr.attach_inferior(LoopbackTransport(None, allow_anonymous_transfers=True))  # Anonymous
    with pytest.raises(pyuavcan.transport.OperationNotDefinedForAnonymousNodeError):
        tr.attach_inferior(LoopbackTransport(None, allow_anonymous_transfers=False))
    assert len(tr.inferiors) == 1
Пример #2
0
def _unittest_redundant_transport_capture() -> None:
    def mon(_x: object) -> None:
        return None

    tr = RedundantTransport()
    inf_a = LoopbackTransport(1234)
    inf_b = LoopbackTransport(1234)
    tr.begin_capture(mon)
    assert inf_a.capture_handlers == []
    assert inf_b.capture_handlers == []
    tr.attach_inferior(inf_a)
    assert inf_a.capture_handlers == [mon]
    assert inf_b.capture_handlers == []
    tr.attach_inferior(inf_b)
    assert inf_a.capture_handlers == [mon]
    assert inf_b.capture_handlers == [mon]
Пример #3
0
async def _unittest_slow_diagnostic(generated_packages: typing.List[
    pyuavcan.dsdl.GeneratedPackageInfo], caplog: typing.Any) -> None:
    from pyuavcan.application import diagnostic
    from uavcan.time import SynchronizedTimestamp_1_0

    assert generated_packages

    pres = Presentation(LoopbackTransport(2222))
    pub = pres.make_publisher_with_fixed_subject_id(diagnostic.Record)
    diag = diagnostic.DiagnosticSubscriber(pres)

    diag.start()

    caplog.clear()
    await pub.publish(
        diagnostic.Record(
            timestamp=SynchronizedTimestamp_1_0(123456789),
            severity=diagnostic.Severity(diagnostic.Severity.INFO),
            text="Hello world!",
        ))
    await asyncio.sleep(1.0)
    print("Captured log records:")
    for lr in caplog.records:
        print("   ", lr)
        assert isinstance(lr, logging.LogRecord)
        pat = r"uavcan\.diagnostic\.Record: node=2222 severity=2 ts_sync=123\.456789 ts_local=\S+:\nHello world!"
        if lr.levelno == logging.INFO and re.match(pat, lr.message):
            break
    else:
        assert False, "Expected log message not captured"

    diag.close()
    pub.close()
    pres.close()
    await asyncio.sleep(1.0)  # Let the background tasks terminate.
Пример #4
0
def _make_loopback(
        registers: MutableMapping[str, ValueProxy],
        node_id: Optional[int]) -> Iterator[pyuavcan.transport.Transport]:
    # Not sure if exposing this is a good idea because the loopback transport is hardly useful outside of test envs.
    if registers.setdefault("uavcan.loopback", ValueProxy(False)):
        from pyuavcan.transport.loopback import LoopbackTransport

        yield LoopbackTransport(node_id)
Пример #5
0
async def _unittest_slow_diagnostic_subscriber(compiled: typing.List[
    pyuavcan.dsdl.GeneratedPackageInfo], caplog: typing.Any) -> None:
    from pyuavcan.application import make_node, NodeInfo, diagnostic, make_registry
    from uavcan.time import SynchronizedTimestamp_1_0

    assert compiled
    asyncio.get_running_loop().slow_callback_duration = 1.0

    node = make_node(
        NodeInfo(),
        make_registry(None, typing.cast(Dict[str, bytes], {})),
        transport=LoopbackTransport(2222),
    )
    node.start()
    pub = node.make_publisher(diagnostic.Record)
    diagnostic.DiagnosticSubscriber(node)

    caplog.clear()
    await pub.publish(
        diagnostic.Record(
            timestamp=SynchronizedTimestamp_1_0(123456789),
            severity=diagnostic.Severity(diagnostic.Severity.INFO),
            text="Hello world!",
        ))
    await asyncio.sleep(1.0)
    print("Captured log records:")
    for lr in caplog.records:
        print("   ", lr)
        assert isinstance(lr, logging.LogRecord)
        pat = r"uavcan\.diagnostic\.Record: node=2222 severity=2 ts_sync=123\.456789 ts_local=\S+:\nHello world!"
        if lr.levelno == logging.INFO and re.match(pat, lr.message):
            break
    else:
        assert False, "Expected log message not captured"

    pub.close()
    node.close()
    await asyncio.sleep(1.0)  # Let the background tasks terminate.
Пример #6
0
def _unittest_output_tid_file_path() -> None:
    from pyuavcan.transport.redundant import RedundantTransport
    from pyuavcan.transport.loopback import LoopbackTransport

    def once(tr: Transport) -> typing.Optional[pathlib.Path]:
        return _get_output_transfer_id_map_path(tr)

    assert once(LoopbackTransport(None)) is None
    assert once(LoopbackTransport(123)) == OUTPUT_TRANSFER_ID_MAP_DIR / "123"

    red = RedundantTransport()
    assert once(red) is None
    red.attach_inferior(LoopbackTransport(4000))
    red.attach_inferior(LoopbackTransport(4000))
    assert once(red) == OUTPUT_TRANSFER_ID_MAP_DIR / "4000"

    red = RedundantTransport()
    red.attach_inferior(LoopbackTransport(None))
    red.attach_inferior(LoopbackTransport(None))
    assert once(red) is None
Пример #7
0
def _unittest_redundant_input_monotonic() -> None:
    import pytest
    from pyuavcan.transport import Transfer, Timestamp, Priority
    from pyuavcan.transport.loopback import LoopbackTransport

    loop = asyncio.get_event_loop()
    await_ = loop.run_until_complete

    spec = pyuavcan.transport.InputSessionSpecifier(
        pyuavcan.transport.MessageDataSpecifier(4321), None)
    spec_tx = pyuavcan.transport.OutputSessionSpecifier(
        spec.data_specifier, None)
    meta = pyuavcan.transport.PayloadMetadata(30)

    ts = Timestamp.now()

    tr_a = LoopbackTransport(111)
    tr_b = LoopbackTransport(111)
    tx_a = tr_a.get_output_session(spec_tx, meta)
    tx_b = tr_b.get_output_session(spec_tx, meta)
    inf_a = tr_a.get_input_session(spec, meta)
    inf_b = tr_b.get_input_session(spec, meta)

    inf_a.transfer_id_timeout = 1.1  # This is used to ensure that the transfer-ID timeout is handled correctly.

    ses = RedundantInputSession(
        spec,
        meta,
        tid_modulo_provider=lambda:
        None,  # Like UDP or serial - infinite modulo.
        loop=loop,
        finalizer=lambda: None,
    )
    assert ses.specifier is spec
    assert ses.payload_metadata is meta
    assert not ses.inferiors
    assert ses.sample_statistics() == RedundantSessionStatistics()
    assert pytest.approx(0.0) == ses.transfer_id_timeout

    # Add inferiors.
    ses._add_inferior(inf_a)  # No change, added above    # pylint: disable=protected-access
    assert ses.inferiors == [inf_a]
    ses._add_inferior(inf_b)  # pylint: disable=protected-access
    assert ses.inferiors == [inf_a, inf_b]

    ses.transfer_id_timeout = 1.1
    assert ses.transfer_id_timeout == pytest.approx(1.1)
    assert inf_a.transfer_id_timeout == pytest.approx(1.1)
    assert inf_b.transfer_id_timeout == pytest.approx(1.1)

    # Redundant reception from multiple interfaces concurrently.
    for tx_x in (tx_a, tx_b):
        assert await_(
            tx_x.send(
                Transfer(
                    timestamp=Timestamp.now(),
                    priority=Priority.HIGH,
                    transfer_id=2,
                    fragmented_payload=[memoryview(b"def")],
                ),
                loop.time() + 1.0,
            ))
        assert await_(
            tx_x.send(
                Transfer(
                    timestamp=Timestamp.now(),
                    priority=Priority.HIGH,
                    transfer_id=3,
                    fragmented_payload=[memoryview(b"ghi")],
                ),
                loop.time() + 1.0,
            ))

    tr = await_(ses.receive(loop.time() + 0.1))
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 2
    assert tr.fragmented_payload == [memoryview(b"def")]

    tr = await_(ses.receive(loop.time() + 0.1))
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 3
    assert tr.fragmented_payload == [memoryview(b"ghi")]

    assert None is await_(
        ses.receive(loop.time() + 2.0))  # Nothing left to read now.

    # This one will be accepted despite a smaller transfer-ID because of the TID timeout.
    assert await_(
        tx_a.send(
            Transfer(
                timestamp=Timestamp.now(),
                priority=Priority.HIGH,
                transfer_id=1,
                fragmented_payload=[memoryview(b"acc")],
            ),
            loop.time() + 1.0,
        ))
    tr = await_(ses.receive(loop.time() + 0.1))
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 1
    assert tr.fragmented_payload == [memoryview(b"acc")]
    assert tr.inferior_session == inf_a

    # Stats check.
    assert ses.sample_statistics() == RedundantSessionStatistics(
        transfers=3,
        frames=inf_a.sample_statistics().frames +
        inf_b.sample_statistics().frames,
        payload_bytes=9,
        errors=0,
        drops=0,
        inferiors=[
            inf_a.sample_statistics(),
            inf_b.sample_statistics(),
        ],
    )

    ses.close()
Пример #8
0
def _unittest_redundant_input_cyclic() -> None:
    import time
    import pytest
    from pyuavcan.transport import Transfer, Timestamp, Priority, ResourceClosedError
    from pyuavcan.transport.loopback import LoopbackTransport

    loop = asyncio.get_event_loop()
    await_ = loop.run_until_complete

    spec = pyuavcan.transport.InputSessionSpecifier(
        pyuavcan.transport.MessageDataSpecifier(4321), None)
    spec_tx = pyuavcan.transport.OutputSessionSpecifier(
        spec.data_specifier, None)
    meta = pyuavcan.transport.PayloadMetadata(30)

    ts = Timestamp.now()

    tr_a = LoopbackTransport(111)
    tr_b = LoopbackTransport(111)
    tx_a = tr_a.get_output_session(spec_tx, meta)
    tx_b = tr_b.get_output_session(spec_tx, meta)
    inf_a = tr_a.get_input_session(spec, meta)
    inf_b = tr_b.get_input_session(spec, meta)

    inf_a.transfer_id_timeout = 1.1  # This is used to ensure that the transfer-ID timeout is handled correctly.

    is_retired = False

    def retire() -> None:
        nonlocal is_retired
        is_retired = True

    ses = RedundantInputSession(
        spec,
        meta,
        tid_modulo_provider=lambda: 32,
        loop=loop,
        finalizer=retire  # Like CAN, for example.
    )
    assert not is_retired
    assert ses.specifier is spec
    assert ses.payload_metadata is meta
    assert not ses.inferiors
    assert ses.sample_statistics() == RedundantSessionStatistics()
    assert pytest.approx(0.0) == ses.transfer_id_timeout

    # Empty inferior set reception.
    time_before = loop.time()
    assert not await_(ses.receive(loop.time() + 2.0))
    assert 1.0 < loop.time(
    ) - time_before < 5.0, "The method should have returned in about two seconds."

    # Begin reception, then add an inferior while the reception is in progress.
    assert await_(
        tx_a.send(
            Transfer(
                timestamp=Timestamp.now(),
                priority=Priority.HIGH,
                transfer_id=1,
                fragmented_payload=[memoryview(b"abc")],
            ),
            loop.time() + 1.0,
        ))

    async def add_inferior(inferior: pyuavcan.transport.InputSession) -> None:
        await asyncio.sleep(1.0)
        ses._add_inferior(inferior)  # pylint: disable=protected-access

    time_before = loop.time()
    tr, _ = await_(
        asyncio.gather(
            # Start reception here. It would stall for two seconds because no inferiors.
            ses.receive(loop.time() + 2.0),
            # While the transmission is stalled, add one inferior with a delay.
            add_inferior(inf_a),
        ))
    assert 0.0 < loop.time(
    ) - time_before < 5.0, "The method should have returned in about one second."
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 1
    assert tr.fragmented_payload == [memoryview(b"abc")]
    assert tr.inferior_session == inf_a

    # More inferiors
    assert ses.transfer_id_timeout == pytest.approx(1.1)
    ses._add_inferior(inf_a)  # No change, added above    # pylint: disable=protected-access
    assert ses.inferiors == [inf_a]
    ses._add_inferior(inf_b)  # pylint: disable=protected-access
    assert ses.inferiors == [inf_a, inf_b]
    assert ses.transfer_id_timeout == pytest.approx(1.1)
    assert inf_b.transfer_id_timeout == pytest.approx(1.1)

    # Redundant reception - new transfers accepted because the iface switch timeout is exceeded.
    time.sleep(ses.transfer_id_timeout
               )  # Just to make sure that it is REALLY exceeded.
    assert await_(
        tx_b.send(
            Transfer(
                timestamp=Timestamp.now(),
                priority=Priority.HIGH,
                transfer_id=2,
                fragmented_payload=[memoryview(b"def")],
            ),
            loop.time() + 1.0,
        ))
    assert await_(
        tx_b.send(
            Transfer(
                timestamp=Timestamp.now(),
                priority=Priority.HIGH,
                transfer_id=3,
                fragmented_payload=[memoryview(b"ghi")],
            ),
            loop.time() + 1.0,
        ))

    tr = await_(ses.receive(loop.time() + 0.1))
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 2
    assert tr.fragmented_payload == [memoryview(b"def")]
    assert tr.inferior_session == inf_b

    tr = await_(ses.receive(loop.time() + 0.1))
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 3
    assert tr.fragmented_payload == [memoryview(b"ghi")]
    assert tr.inferior_session == inf_b

    assert None is await_(
        ses.receive(loop.time() + 1.0))  # Nothing left to read now.

    # This one will be rejected because wrong iface and the switch timeout is not yet exceeded.
    assert await_(
        tx_a.send(
            Transfer(
                timestamp=Timestamp.now(),
                priority=Priority.HIGH,
                transfer_id=4,
                fragmented_payload=[memoryview(b"rej")],
            ),
            loop.time() + 1.0,
        ))
    assert None is await_(ses.receive(loop.time() + 0.1))

    # Transfer-ID timeout reconfiguration.
    ses.transfer_id_timeout = 3.0
    with pytest.raises(ValueError):
        ses.transfer_id_timeout = -0.0
    assert ses.transfer_id_timeout == pytest.approx(3.0)
    assert inf_a.transfer_id_timeout == pytest.approx(3.0)
    assert inf_a.transfer_id_timeout == pytest.approx(3.0)

    # Inferior removal resets the state of the deduplicator.
    ses._close_inferior(0)  # pylint: disable=protected-access
    ses._close_inferior(1)  # Out of range, no effect.  # pylint: disable=protected-access
    assert ses.inferiors == [inf_b]

    assert await_(
        tx_b.send(
            Transfer(
                timestamp=Timestamp.now(),
                priority=Priority.HIGH,
                transfer_id=1,
                fragmented_payload=[memoryview(b"acc")],
            ),
            loop.time() + 1.0,
        ))
    tr = await_(ses.receive(loop.time() + 0.1))
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 1
    assert tr.fragmented_payload == [memoryview(b"acc")]
    assert tr.inferior_session == inf_b

    # Stats check.
    assert ses.sample_statistics() == RedundantSessionStatistics(
        transfers=4,
        frames=inf_b.sample_statistics().frames,
        payload_bytes=12,
        errors=0,
        drops=0,
        inferiors=[
            inf_b.sample_statistics(),
        ],
    )

    # Closure.
    assert not is_retired
    ses.close()
    assert is_retired
    is_retired = False
    ses.close()
    assert not is_retired
    assert not ses.inferiors
    with pytest.raises(ResourceClosedError):
        await_(ses.receive(0))
Пример #9
0
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.
Пример #10
0
async def _unittest_spoofer(caplog: pytest.LogCaptureFixture) -> None:
    dcs_pres = Presentation(LoopbackTransport(1234))
    dcs_pub_spoof = dcs_pres.make_publisher(Spoof, 1)
    spoofer = Spoofer(dcs_pres.make_subscriber(Spoof, 1))

    # No target transports configured -- spoofing will do nothing except incrementing the transfer-ID counter.
    assert await dcs_pub_spoof.publish(
        Spoof(
            timeout=uavcan.si.unit.duration.Scalar_1_0(1.0),
            priority=org_uavcan_yukon.io.transfer.Priority_1_0(3),
            session=org_uavcan_yukon.io.transfer.Session_0_1(
                subject=org_uavcan_yukon.io.transfer.SubjectSession_0_1(
                    subject_id=uavcan.node.port.SubjectID_1_0(6666),
                    source=[uavcan.node.ID_1_0(1234)])),
            transfer_id=[],
            iface_id=[],
            payload=org_uavcan_yukon.io.transfer.Payload_1_0(b"Hello world!"),
        ))

    await asyncio.sleep(0.5)

    # Validate the transfer-ID map.
    assert len(spoofer._transfer_id_map) == 1
    assert list(spoofer._transfer_id_map.keys())[0].source_node_id == 1234
    assert list(spoofer._transfer_id_map.values())[0]._value == 1

    # Configure transports.
    cap_a: typing.List[pyuavcan.transport.Capture] = []
    cap_b: typing.List[pyuavcan.transport.Capture] = []
    target_tr_a = LoopbackTransport(None)
    target_tr_b = LoopbackTransport(None)
    target_tr_a.begin_capture(cap_a.append)
    target_tr_b.begin_capture(cap_b.append)
    spoofer.add_iface(111, target_tr_a)
    spoofer.add_iface(222, target_tr_b)

    # Spoof on both, successfully.
    spoof = Spoof(
        timeout=uavcan.si.unit.duration.Scalar_1_0(1.0),
        priority=org_uavcan_yukon.io.transfer.Priority_1_0(3),
        session=org_uavcan_yukon.io.transfer.Session_0_1(
            subject=org_uavcan_yukon.io.transfer.SubjectSession_0_1(
                subject_id=uavcan.node.port.SubjectID_1_0(6666),
                source=[uavcan.node.ID_1_0(1234)])),
        transfer_id=[9876543210],  # This transfer will not touch the TID map.
        iface_id=[],  # All ifaces.
        payload=org_uavcan_yukon.io.transfer.Payload_1_0(b"abcd"),
    )
    assert await dcs_pub_spoof.publish(spoof)
    await asyncio.sleep(0.5)

    assert len(cap_a) == len(cap_b)
    (cap, ) = cap_a
    cap_a.clear()
    cap_b.clear()
    assert isinstance(cap, LoopbackCapture)
    assert cap.transfer.metadata.transfer_id == 9876543210
    assert len(spoofer._transfer_id_map) == 1  # New entry was not created.
    assert spoofer.status == {
        111:
        IfaceStatus(num_bytes=4,
                    num_errors=0,
                    num_timeouts=0,
                    num_transfers=1,
                    backlog=0,
                    backlog_peak=0),
        222:
        IfaceStatus(num_bytes=4,
                    num_errors=0,
                    num_timeouts=0,
                    num_transfers=1,
                    backlog=0,
                    backlog_peak=0),
    }

    # Make one time out, the other raise an error, third one is closed.
    target_tr_a.spoof_result = False
    target_tr_b.spoof_result = RuntimeError("Intended exception")
    target_tr_c = LoopbackTransport(None)
    target_tr_c.close()
    spoofer.add_iface(0, target_tr_c)

    with caplog.at_level(logging.CRITICAL):
        assert await dcs_pub_spoof.publish(spoof)
        await asyncio.sleep(2.0)
    assert not cap_a
    assert not cap_b
    old_status = spoofer.status
    assert old_status == {
        0:
        IfaceStatus(num_bytes=0,
                    num_errors=1,
                    num_timeouts=0,
                    num_transfers=0,
                    backlog=0,
                    backlog_peak=0),
        111:
        IfaceStatus(num_bytes=4,
                    num_errors=0,
                    num_timeouts=1,
                    num_transfers=1,
                    backlog=0,
                    backlog_peak=0),
        222:
        IfaceStatus(num_bytes=4,
                    num_errors=1,
                    num_timeouts=0,
                    num_transfers=1,
                    backlog=0,
                    backlog_peak=0),
    }

    # Force only one iface out of three. Check that the backlog counter goes up.
    spoof.iface_id = [0]
    assert await dcs_pub_spoof.publish(spoof)
    assert await dcs_pub_spoof.publish(spoof)
    assert await dcs_pub_spoof.publish(spoof)
    await asyncio.sleep(2.0)
    assert spoofer.status[0].backlog > 0
    assert spoofer.status[0].backlog_peak > 0
    assert spoofer.status[111] == old_status[111]
    assert spoofer.status[222] == old_status[222]

    # Finalize.
    spoofer.close()
    target_tr_a.close()
    target_tr_b.close()
    target_tr_c.close()
    dcs_pres.close()
    await asyncio.sleep(1.0)
Пример #11
0
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)
Пример #12
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)
Пример #13
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