Exemple #1
0
def compiled_dsdl() -> None:
    """
    Ensures that the regulated DSDL namespaces are compiled and importable.
    To force recompilation, remove the output directory.
    """
    output_dir = str(OUTPUT_DIR)
    if output_dir not in sys.path:
        sys.path.insert(0, output_dir)
    try:
        import uavcan  # pylint: disable=unused-import
        import sirius_cyber_corp  # pylint: disable=unused-import
    except ImportError:
        from tests.subprocess import execute_cli
        from yakut.paths import DEFAULT_PUBLIC_REGULATED_DATA_TYPES_ARCHIVE_URI

        sirius_cyber_corp_dir = str(CUSTOM_DATA_TYPES_DIR /
                                    "sirius_cyber_corp")

        args = [
            "compile",
            DEFAULT_PUBLIC_REGULATED_DATA_TYPES_ARCHIVE_URI,
            "--lookup",
            sirius_cyber_corp_dir,
            "-O",
            output_dir,
        ]
        execute_cli(*args, timeout=300.0)

        args = ["compile", sirius_cyber_corp_dir, "--output", output_dir]
        execute_cli(*args, timeout=300.0)

        importlib.invalidate_caches()
Exemple #2
0
def _unittest_doc() -> None:
    assert (len(
        execute_cli("-vv", "doc", timeout=2.0, log=False)[1].splitlines()) >
            10), "The doc output is suspiciously short"

    with pytest.raises(CalledProcessError):
        execute_cli("doc", "nonexistent-entry", timeout=2.0, log=False)
Exemple #3
0
def _unittest_help() -> None:
    """
    Just make sure that the help can be displayed without issues.
    """
    execute_cli("--help", timeout=10.0, log=False)
    for cmd in dir(yakut.cmd):
        if not cmd.startswith("_") and cmd not in ("pyuavcan", "sys"):
            execute_cli(cmd, "--help", timeout=3.0, log=False)
Exemple #4
0
def _unittest_accommodate_swarm(transport_factory: TransportFactory,
                                compiled_dsdl: typing.Any) -> None:
    _ = compiled_dsdl
    # We spawn a lot of processes here, which might strain the test system a little, so beware. I've tested it
    # with 120 processes and it made my workstation (24 GB RAM ~4 GHz Core i7) struggle to the point of being
    # unable to maintain sufficiently real-time operation for the test to pass. Hm.
    used_node_ids = list(range(10))
    pubs = [
        Subprocess.cli(
            f"--transport={transport_factory(idx).expression}",
            f"--path={OUTPUT_DIR}",
            "pub",
            "--period=0.4",
            "--count=60",
        ) for idx in used_node_ids
    ]
    _, stdout, _ = execute_cli(
        "-v",
        f"--path={OUTPUT_DIR}",
        f"--transport={transport_factory(None).expression}",
        "accommodate",
        timeout=100.0,
    )
    assert int(stdout) not in used_node_ids
    for p in pubs:
        p.wait(100.0, interrupt=True)
Exemple #5
0
def _unittest_call_errors(compiled_dsdl: typing.Any) -> None:
    _ = compiled_dsdl
    env = {
        "YAKUT_PATH": str(OUTPUT_DIR),
    }

    # Non-service data type.
    result, stdout, stderr = execute_cli(
        "call",
        "22",
        "222.sirius_cyber_corp.PointXY.1.0",
        environment_variables=env,
        ensure_success=False,
        log=False,
    )
    assert result != 0
    assert stdout == ""
    assert "service type" in stderr

    # Non-existent data type.
    result, stdout, stderr = execute_cli(
        "call",
        "22",
        "222.sirius_cyber_corp.PointXY.1.0",
        ensure_success=False,
        log=False,
    )
    assert result != 0
    assert stdout == ""
    assert "yakut compile" in stderr

    # Invalid YAML.
    result, stdout, stderr = execute_cli(
        f"--path={OUTPUT_DIR}",
        "call",
        "22",
        "222.sirius_cyber_corp.PerformLinearLeastSquaresFit.1.0",
        ": }",
        ensure_success=False,
        log=False,
    )
    assert result != 0
    assert stdout == ""
    assert "parse" in stderr
    assert "request object" in stderr
Exemple #6
0
def _unittest_accommodate_loopback() -> None:
    _, stdout, _ = execute_cli(
        "-v",
        f"--path={OUTPUT_DIR}",
        "accommodate",
        timeout=30.0,
        environment_variables={
            "YAKUT_TRANSPORT": "Loopback(None),Loopback(None)"
        },
    )
    assert 0 <= int(stdout) < 2**64
Exemple #7
0
def _unittest_accommodate_udp_localhost() -> None:
    _, stdout, _ = execute_cli(
        "-v",
        f"--path={OUTPUT_DIR}",
        "accommodate",
        timeout=30.0,
        environment_variables={
            "YAKUT_TRANSPORT": 'UDP("127.0.0.1",anonymous=True)'
        },
    )
    # Exclude zero from the set because an IP address with the host address of zero may cause complications.
    assert 1 <= int(stdout) <= 65534
Exemple #8
0
def _unittest_subscribe() -> None:
    env = {
        "YAKUT_TRANSPORT": "Loopback(1234)",
    }

    # No subjects specified.
    _, _, stderr = execute_cli("-vv",
                               "sub",
                               timeout=5.0,
                               environment_variables=env)
    assert "nothing to do" in stderr.lower()
    assert "no subject" in stderr.lower()

    # Count zero.
    _, _, stderr = execute_cli("-vv",
                               "sub",
                               "4444.uavcan.si.unit.force.Scalar.1.0",
                               "--count=0",
                               timeout=5.0,
                               environment_variables=env)
    assert "nothing to do" in stderr.lower()
    assert "count" in stderr.lower()

    # Compiled DSDL not found.
    result, _, stderr = execute_cli("sub",
                                    "4444.uavcan.si.unit.force.Scalar.1.0",
                                    timeout=5.0,
                                    ensure_success=False,
                                    environment_variables=env)
    assert result != 0
    assert "yakut compile" in stderr.lower()

    # Transport not specified.
    result, _, stderr = execute_cli("sub",
                                    "4444.uavcan.si.unit.force.Scalar.1.0",
                                    timeout=5.0,
                                    ensure_success=False)
    assert result != 0
    assert "transport" in stderr.lower()
Exemple #9
0
def _unittest_publish(compiled_dsdl: typing.Any) -> None:
    _ = compiled_dsdl
    env = {
        "YAKUT_TRANSPORT": "Loopback(1234)",
    }

    # Count zero, nothing to do.
    _, _, stderr = execute_cli(
        "-vv",
        f"--path={OUTPUT_DIR}",
        "pub",
        "4444.uavcan.si.unit.force.Scalar.1.0",
        "{}",
        "--count",
        "0",
        timeout=5.0,
        environment_variables=env,
    )
    assert "nothing to do" in stderr.lower()

    # Compiled DSDL not found.
    result, _, stderr = execute_cli(
        "pub",
        "4444.uavcan.si.unit.force.Scalar.1.0",
        "{}",
        "--count",
        "0",
        timeout=5.0,
        ensure_success=False,
        environment_variables=env,
    )
    assert result != 0
    assert "yakut compile" in stderr.lower()

    # Invalid period.
    result, _, stderr = execute_cli(
        "-vv",
        f"--path={OUTPUT_DIR}",
        "pub",
        "4444.uavcan.si.unit.force.Scalar.1.0",
        "{}",
        "--period=0",
        timeout=5.0,
        ensure_success=False,
        environment_variables=env,
    )
    assert result != 0
    assert "period" in stderr.lower()
    assert "seconds" in stderr.lower()

    # Transport not configured.
    result, _, stderr = execute_cli(
        f"--path={OUTPUT_DIR}",
        "pub",
        "4444.uavcan.si.unit.force.Scalar.1.0",
        "{}",
        timeout=5.0,
        ensure_success=False,
    )
    assert result != 0
    assert "transport" in stderr.lower()
Exemple #10
0
def _unittest_pub_sub_regular(transport_factory: TransportFactory,
                              compiled_dsdl: typing.Any) -> None:
    _ = compiled_dsdl
    env = {
        "YAKUT_TRANSPORT": transport_factory(None).expression,
        "YAKUT_PATH": str(OUTPUT_DIR),
    }
    proc_sub_heartbeat = Subprocess.cli(
        "--format=json",
        "sub",
        "uavcan.node.Heartbeat.1.0",
        environment_variables=env,
    )
    proc_sub_diagnostic = Subprocess.cli(
        "--format=json",
        "sub",
        "4321.uavcan.diagnostic.Record.1.1",
        "--count=3",
        environment_variables=env,
    )
    proc_sub_diagnostic_wrong_pid = Subprocess.cli(
        "--format=yaml",
        "sub",
        "uavcan.diagnostic.Record.1.1",
        "--count=3",
        environment_variables=env,
    )
    proc_sub_temperature = Subprocess.cli(
        "--format=json",
        "sub",
        "555.uavcan.si.sample.temperature.Scalar.1.0",
        "--count=3",
        "--no-metadata",
        environment_variables=env,
    )
    time.sleep(
        1.0)  # Time to let the background processes finish initialization

    proc_pub = Subprocess.cli(
        "-v",
        "--heartbeat-vssc=54",
        "--heartbeat-priority=high",
        "--node-info",
        "{software_image_crc: [0xdeadbeef]}",
        f"--transport={transport_factory(51).expression}",  # Takes precedence over the environment variable.
        "pub",
        "4321.uavcan.diagnostic.Record.1.1",
        '{severity: {value: 6}, timestamp: {microsecond: 123456}, text: "Hello world!"}',
        "1234.uavcan.diagnostic.Record.1.1",
        '{text: "Goodbye world."}',
        "555.uavcan.si.sample.temperature.Scalar.1.0",
        "{kelvin: 123.456}",
        "--count=3",
        "--period=2",
        "--priority=slow",
        environment_variables=env,
    )
    time.sleep(2.0)  # Time to let the publisher boot up properly.

    # Request GetInfo from the publisher we just launched.
    _, stdout, _ = execute_cli(
        f"--transport={transport_factory(52).expression}",
        f"--path={OUTPUT_DIR}",
        "call",
        "51",
        "uavcan.node.GetInfo.1.0",
        "--no-metadata",
        "--timeout=5",
        timeout=10.0,
    )
    parsed = yakut.yaml.YAMLLoader().load(stdout)
    assert parsed[430]["protocol_version"] == {
        "major": pyuavcan.UAVCAN_SPECIFICATION_VERSION[0],
        "minor": pyuavcan.UAVCAN_SPECIFICATION_VERSION[1],
    }
    assert parsed[430]["software_version"] == {
        "major": yakut.__version_info__[0],
        "minor": yakut.__version_info__[1],
    }
    assert parsed[430]["software_image_crc"] == [0xDEADBEEF]
    assert parsed[430]["name"] == "org.uavcan.yakut.publish"

    proc_pub.wait(10.0)
    time.sleep(1.0)  # Time to sync up

    # Parse the output from the subscribers and validate it.
    out_sub_heartbeat = proc_sub_heartbeat.wait(
        1.0, interrupt=True)[1].splitlines()
    out_sub_diagnostic = proc_sub_diagnostic.wait(
        1.0, interrupt=True)[1].splitlines()
    out_sub_temperature = proc_sub_temperature.wait(
        1.0, interrupt=True)[1].splitlines()

    heartbeats = list(map(json.loads, out_sub_heartbeat))
    diagnostics = list(map(json.loads, out_sub_diagnostic))
    temperatures = list(map(json.loads, out_sub_temperature))

    print("heartbeats:", *heartbeats, sep="\n\t")
    print("diagnostics:", *diagnostics, sep="\n\t")
    print("temperatures:", *temperatures, sep="\n\t")

    assert 1 <= len(heartbeats) <= 20
    for m in heartbeats:
        src_nid = m["7509"]["_metadata_"]["source_node_id"]
        if src_nid == 51:  # The publisher
            assert "high" in m["7509"]["_metadata_"]["priority"].lower()
            assert m["7509"]["_metadata_"]["transfer_id"] >= 0
            assert m["7509"]["uptime"] in range(10)
            assert m["7509"]["vendor_specific_status_code"] == 54
        elif src_nid == 52:  # The caller (GetInfo)
            assert "nominal" in m["7509"]["_metadata_"]["priority"].lower()
            assert m["7509"]["_metadata_"]["transfer_id"] >= 0
            assert m["7509"]["uptime"] in range(4)
        else:
            assert False

    assert len(diagnostics) == 3
    for m in diagnostics:
        assert "slow" in m["4321"]["_metadata_"]["priority"].lower()
        assert m["4321"]["_metadata_"]["transfer_id"] >= 0
        assert m["4321"]["_metadata_"]["source_node_id"] == 51
        assert m["4321"]["timestamp"]["microsecond"] == 123456
        assert m["4321"]["text"] == "Hello world!"

    assert len(temperatures) == 3
    assert all(
        map(lambda mt: mt["555"]["kelvin"] == pytest.approx(123.456),
            temperatures))

    assert proc_sub_diagnostic_wrong_pid.alive
    assert proc_sub_diagnostic_wrong_pid.wait(1.0,
                                              interrupt=True)[1].strip() == ""
Exemple #11
0
def _unittest_error() -> None:
    with pytest.raises(CalledProcessError):
        execute_cli("invalid-command", timeout=2.0, log=False)

    with pytest.raises(CalledProcessError):  # Ambiguous abbreviation.
        execute_cli("c", timeout=2.0, log=False)
Exemple #12
0
def _unittest_call_custom(transport_factory: TransportFactory,
                          compiled_dsdl: typing.Any) -> None:
    _ = compiled_dsdl
    env = {
        "YAKUT_TRANSPORT": transport_factory(88).expression,
        "YAKUT_PATH": str(OUTPUT_DIR),
    }

    from sirius_cyber_corp import PerformLinearLeastSquaresFit_1_0

    # Set up the server that we will be testing the client against.
    server_transport = construct_transport(transport_factory(22).expression)
    server_presentation = pyuavcan.presentation.Presentation(server_transport)
    server = server_presentation.get_server(PerformLinearLeastSquaresFit_1_0,
                                            222)
    last_metadata: typing.Optional[
        pyuavcan.presentation.ServiceRequestMetadata] = None

    async def handle_request(
        request: PerformLinearLeastSquaresFit_1_0.Request,
        metadata: pyuavcan.presentation.ServiceRequestMetadata,
    ) -> PerformLinearLeastSquaresFit_1_0.Response:
        nonlocal last_metadata
        last_metadata = metadata
        print("REQUEST OBJECT  :", request)
        print("REQUEST METADATA:", metadata)
        sum_x = sum(map(lambda p: p.x, request.points))  # type: ignore
        sum_y = sum(map(lambda p: p.y, request.points))  # type: ignore
        a = sum_x * sum_y - len(request.points) * sum(
            map(lambda p: p.x * p.y, request.points))  # type: ignore
        b = sum_x * sum_x - len(request.points) * sum(
            map(lambda p: p.x**2, request.points))  # type: ignore
        slope = a / b
        y_intercept = (sum_y - slope * sum_x) / len(request.points)
        response = PerformLinearLeastSquaresFit_1_0.Response(
            slope=slope, y_intercept=y_intercept)
        print("RESPONSE OBJECT:", response)
        return response

    # Invoke the service and then run the server for a few seconds to let it process the request.
    proc = Subprocess.cli(
        "-v",
        "--format=json",
        "call",
        "22",
        "222.sirius_cyber_corp.PerformLinearLeastSquaresFit.1.0",
        "points: [{x: 10, y: 1}, {x: 20, y: 2}]",
        "--priority=SLOW",
        "--with-metadata",
        environment_variables=env,
    )
    asyncio.get_event_loop().run_until_complete(
        server.serve_for(handle_request, 3.0))
    result, stdout, _ = proc.wait(5.0)
    assert result == 0
    assert last_metadata is not None
    assert last_metadata.priority == pyuavcan.transport.Priority.SLOW
    assert last_metadata.client_node_id == 88

    # Finalize to avoid warnings in the output.
    server_presentation.close()

    # Parse the output and validate it.
    parsed = json.loads(stdout)
    print("PARSED RESPONSE:", parsed)
    assert parsed["222"]["_metadata_"]["priority"] == "slow"
    assert parsed["222"]["_metadata_"]["source_node_id"] == 22
    assert parsed["222"]["slope"] == pytest.approx(0.1)
    assert parsed["222"]["y_intercept"] == pytest.approx(0.0)

    # Timed-out request.
    result, stdout, stderr = execute_cli(
        "call",
        "--timeout=0.1",
        "22",
        "222.sirius_cyber_corp.PerformLinearLeastSquaresFit.1.0",
        "points: [{x: 10, y: 1}, {x: 20, y: 2}]",
        environment_variables=env,
        ensure_success=False,
        log=False,
    )
    assert result == 1
    assert stdout == ""
    assert "timed out" in stderr