from __future__ import annotations import io import os import sys import tempfile import unittest from pathlib import Path from unittest.mock import Mock, patch from cognitive_agent.infrastructure.can.gateway import ( ZlgCanBridgeProcess, ZlgChannelConfig, ) from cognitive_agent.infrastructure.can.hardware import VehicleCanSettings from cognitive_agent.infrastructure.can.zlgcan_linux_bridge import ( EXPECTED_ABI_SIZES, Driver, _acceptance_filters, _verified_init, abi_sizes, ) REPOSITORY_ROOT = Path(__file__).resolve().parents[2] class LinuxZlgBridgeTests(unittest.TestCase): def test_ctypes_layout_matches_vendor_packed_abi(self) -> None: self.assertEqual(abi_sizes(), EXPECTED_ABI_SIZES) def test_verified_60mhz_timing_matches_can_dev_baseline(self) -> None: init = _verified_init( { "arbitration_bitrate": 500_000, "data_bitrate": 2_000_000, "can_fd_iso": True, } ) self.assertEqual(init.clk, 60_000_000) self.assertEqual(init.mode, 0) self.assertEqual( (init.aset.tseg1, init.aset.tseg2, init.aset.sjw, init.aset.brp), (14, 3, 2, 5), ) self.assertEqual( (init.dset.tseg1, init.dset.tseg2, init.dset.sjw, init.dset.brp), (10, 2, 2, 1), ) with self.assertRaisesRegex(ValueError, "locked"): _verified_init( {"arbitration_bitrate": 1_000_000, "data_bitrate": 4_000_000} ) def test_passive_state_filter_uses_exact_standard_identifiers(self) -> None: frame_ids = frozenset({0x342, 0x407, 0x4EB}) filters = _acceptance_filters({"receive_ids": frame_ids}) self.assertEqual(filters.size, 3 * 12) self.assertEqual( [ ( filters.table[index].type, filters.table[index].sid, filters.table[index].eid, ) for index in range(3) ], [(0, 0x342, 0x342), (0, 0x407, 0x407), (0, 0x4EB, 0x4EB)], ) def test_channel_config_serializes_receive_filter_only_when_requested(self) -> None: filtered = ZlgChannelConfig(channel=1, receive_ids=(0x342, 0x4EB)) unfiltered = ZlgChannelConfig(channel=1) self.assertEqual(filtered.public()["receive_ids"], [0x342, 0x4EB]) self.assertNotIn("receive_ids", unfiltered.public()) def test_monitor_only_rejects_send_before_touching_sdk(self) -> None: driver = object.__new__(Driver) driver.monitor_only = True driver.configs = {1: {"channel": 1, "frame_format": "can_fd"}} driver.sdk = Mock() with self.assertRaisesRegex(PermissionError, "monitor-only"): driver.send( 1, 0x123, bytes.fromhex("0001020304050607"), is_fd=True, bitrate_switch=True, ) driver.sdk.VCI_TransmitFD.assert_not_called() driver.sdk.VCI_Transmit.assert_not_called() def test_linux_state_can_be_ready_while_transmit_stays_locked(self) -> None: with tempfile.TemporaryDirectory() as directory: library = Path(directory) / "libusbcanfd.so" library.write_bytes(b"test") environment = { "COGNITIVE_AGENT_VEHICLE_CAN_ENABLED": "true", "COGNITIVE_AGENT_VEHICLE_CAN_BACKEND": "zlgcan-linux", "COGNITIVE_AGENT_VEHICLE_CAN_CHANNEL": "1", "COGNITIVE_AGENT_VEHICLE_CAN_REQUIRED_CHANNEL": "1", "COGNITIVE_AGENT_ZLGCAN_PYTHON": sys.executable, "COGNITIVE_AGENT_ZLGCAN_LINUX_LIBRARY": str(library), } with patch.dict(os.environ, environment, clear=True): settings = VehicleCanSettings.from_env(REPOSITORY_ROOT) self.assertEqual(settings.device_type, 33) self.assertTrue(settings.state_configured) self.assertFalse(settings.configured) self.assertTrue(settings.bridge_script.name.endswith("linux_bridge.py")) self.assertTrue(settings.public_info()["state_configured"]) def test_process_passes_monitor_and_trace_flags_to_bridge(self) -> None: process = Mock() process.stdout = io.StringIO('{"ok":true,"event":"ready"}\n') process.stderr = io.StringIO() process.stdin = io.StringIO() process.poll.return_value = 0 with patch( "cognitive_agent.infrastructure.can.gateway.subprocess.Popen", return_value=process, ) as popen: bridge = ZlgCanBridgeProcess( python_executable=Path(sys.executable), bridge_script=Path("bridge.py"), dll_path=Path("libusbcanfd.so"), device_type=33, device_index=0, channels=[ZlgChannelConfig(channel=1, termination_enabled=False)], monitor_only=True, trace_all=True, ) command = popen.call_args.args[0] self.assertIn("--monitor-only", command) self.assertIn("--trace-all", command) bridge.close() if __name__ == "__main__": unittest.main()