import pytest from ecu_framework.lin.base import LinFrame class TestMockLinInterface: """Test suite validating the pure-Python mock LIN interface behavior. Coverage goals: - REQ-001: Echo loopback for local testing (send -> receive same frame) - REQ-002: Deterministic master request responses (no randomness) - REQ-003: Frame ID filtering in receive() - REQ-004: Graceful handling of timeout when no frame is available Notes: - These tests run entirely without hardware and should be fast and stable. - The injected mock interface enqueues frames on transmit to emulate a bus. - Deterministic responses allow exact byte-for-byte assertions. """ @pytest.mark.smoke @pytest.mark.req_001 @pytest.mark.req_003 def test_mock_send_receive_echo(self, lin, rp): """ Title: Mock LIN Interface - Send/Receive Echo Test Description: Validates that the mock LIN interface correctly echoes frames sent on the bus, enabling loopback testing without hardware dependencies. Requirements: REQ-001, REQ-003 Test Steps: 1. Create a LIN frame with specific ID and data payload 2. Send the frame via the mock interface 3. Attempt to receive the echoed frame with ID filtering 4. Verify the received frame matches the transmitted frame exactly Expected Result: - Frame is successfully echoed by mock interface - Received frame ID matches transmitted frame ID (0x12) - Received frame data payload matches transmitted data [1, 2, 3] """ # Step 1: Create test frame with known ID and payload test_frame = LinFrame(id=0x12, data=bytes([1, 2, 3])) rp("lin_type", "mock") rp("tx_id", f"0x{test_frame.id:02X}") rp("tx_data", list(test_frame.data)) # Step 2: Transmit frame via mock interface (mock will enqueue to RX) lin.send(test_frame) # Step 3: Receive echoed frame with ID filtering and timeout received_frame = lin.receive(id=0x12, timeout=0.5) rp("rx_present", received_frame is not None) if received_frame is not None: rp("rx_id", f"0x{received_frame.id:02X}") rp("rx_data", list(received_frame.data)) # Step 4: Validate echo functionality and payload integrity assert received_frame is not None, "Mock interface should echo transmitted frames" assert received_frame.id == test_frame.id, f"Expected ID {test_frame.id:#x}, got {received_frame.id:#x}" assert received_frame.data == test_frame.data, f"Expected data {test_frame.data!r}, got {received_frame.data!r}" @pytest.mark.smoke @pytest.mark.req_002 def test_mock_request_synthesized_response(self, lin, rp): """ Title: Mock LIN Interface - Master Request Response Test Description: Validates that the mock interface synthesizes deterministic responses for master request operations, simulating slave node behavior. Requirements: REQ-002 Test Steps: 1. Issue a master request for specific frame ID and data length 2. Verify mock interface generates a response frame 3. Validate response frame ID matches request ID 4. Verify response data length matches requested length 5. Confirm response data is deterministic (not random) Expected Result: - Mock interface generates response within timeout period - Response frame ID matches request ID (0x21) - Response data length equals requested length (4 bytes) - Response data follows deterministic pattern: [id+0, id+1, id+2, id+3] """ # Step 1: Issue master request with specific parameters request_id = 0x21 requested_length = 4 # Step 2: Execute request operation; mock synthesizes deterministic bytes rp("lin_type", "mock") rp("req_id", f"0x{request_id:02X}") rp("req_len", requested_length) response_frame = lin.request(id=request_id, length=requested_length, timeout=0.5) # Step 3: Validate response generation assert response_frame is not None, "Mock interface should generate response for master requests" # Step 4: Verify response frame properties (ID and length) assert response_frame.id == request_id, f"Response ID {response_frame.id:#x} should match request ID {request_id:#x}" assert len(response_frame.data) == requested_length, f"Response length {len(response_frame.data)} should match requested length {requested_length}" # Step 5: Validate deterministic response pattern expected_data = bytes((request_id + i) & 0xFF for i in range(requested_length)) rp("rx_data", list(response_frame.data) if response_frame else None) rp("expected_data", list(expected_data)) assert response_frame.data == expected_data, f"Response data {response_frame.data!r} should follow deterministic pattern {expected_data!r}" @pytest.mark.smoke @pytest.mark.req_004 def test_mock_receive_timeout_behavior(self, lin, rp): """ Title: Mock LIN Interface - Receive Timeout Test Description: Validates that the mock interface properly handles timeout scenarios when no matching frames are available for reception. Requirements: REQ-004 Test Steps: 1. Attempt to receive a frame with non-existent ID 2. Use short timeout to avoid blocking test execution 3. Verify timeout behavior returns None rather than blocking indefinitely Expected Result: - Receive operation returns None when no matching frames available - Operation completes within specified timeout period - No exceptions or errors during timeout scenario """ # Step 1: Attempt to receive frame with ID that hasn't been transmitted non_existent_id = 0xFF short_timeout = 0.1 # 100ms timeout # Step 2: Execute receive with timeout (should return None quickly) rp("lin_type", "mock") rp("rx_id", f"0x{non_existent_id:02X}") rp("timeout_s", short_timeout) result = lin.receive(id=non_existent_id, timeout=short_timeout) rp("rx_present", result is not None) # Step 3: Verify proper timeout behavior (no exceptions, returns None) assert result is None, "Receive operation should return None when no matching frames available" @pytest.mark.boundary @pytest.mark.req_001 @pytest.mark.req_003 @pytest.mark.parametrize("frame_id,data_payload", [ (0x01, bytes([0x55])), (0x3F, bytes([0xAA, 0x55])), (0x20, bytes([0x01, 0x02, 0x03, 0x04, 0x05])), (0x15, bytes([0xFF, 0x00, 0xCC, 0x33, 0xF0, 0x0F, 0xA5, 0x5A])), ]) def test_mock_frame_validation_boundaries(self, lin, rp, frame_id, data_payload): """ Title: Mock LIN Interface - Frame Validation Boundaries Test Description: Validates mock interface handling of various frame configurations including boundary conditions for frame IDs and data lengths. Requirements: REQ-001, REQ-003 Test Steps: 1. Test various valid frame ID values (0x01 to 0x3F) 2. Test different data payload lengths (1 to 8 bytes) 3. Verify proper echo behavior for all valid combinations Expected Result: - All valid frame configurations are properly echoed - Frame ID and data integrity preserved across echo operation """ # Step 1: Create frame with parameterized values test_frame = LinFrame(id=frame_id, data=data_payload) rp("lin_type", "mock") rp("tx_id", f"0x{frame_id:02X}") rp("tx_len", len(data_payload)) # Step 2: Send and receive frame lin.send(test_frame) received_frame = lin.receive(id=frame_id, timeout=0.5) # Step 3: Validate frame integrity across IDs and payload sizes assert received_frame is not None, f"Frame with ID {frame_id:#x} should be echoed" assert received_frame.id == frame_id, f"Frame ID should be preserved: expected {frame_id:#x}" assert received_frame.data == data_payload, f"Frame data should be preserved for ID {frame_id:#x}"