191 lines
8.5 KiB
Python
191 lines
8.5 KiB
Python
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}"
|