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()
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)
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)
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)
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
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
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
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()
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()
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() == ""
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)
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