Skip to content

Commit

Permalink
API for queue with received packets (#116)
Browse files Browse the repository at this point in the history
- define API for queue with received packets
- in module docstrings, add references to knowledge base
- move `is_initial_packet` method to `UDSPacketType` implementation instead of `Segmenter` class.
  • Loading branch information
mdabrowski1990 authored Oct 2, 2021
1 parent c66a8b3 commit 1710be9
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 106 deletions.
1 change: 1 addition & 0 deletions tests/requirements_for_software_tests.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pytest>=6.2.2
pytest-cov>=2.11.1
pytest-asyncio>=0.15.1
mock>=4.0.2
16 changes: 16 additions & 0 deletions tests/software_tests/messages/test_uds_packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
class TestAbstractPacketType:
"""Tests for 'AbstractPacketType' class."""

SCRIPT_LOCATION = "uds.messages.uds_packet"

def setup(self):
self._patcher_validate_member = patch(f"{self.SCRIPT_LOCATION}.ValidatedEnum.validate_member")
self.mock_validate_member = self._patcher_validate_member.start()

def teardown(self):
self._patcher_validate_member.stop()

def test_inheritance__nibble_enum(self):
assert issubclass(AbstractUdsPacketType, NibbleEnum)

Expand All @@ -17,6 +26,13 @@ def test_inheritance__validated_enum(self):
def test_inheritance__extendable_enum(self):
assert issubclass(AbstractUdsPacketType, ExtendableEnum)

# is_initial_packet_type

@pytest.mark.parametrize("value", [None, False, Mock()])
def test_is_initial_packet_type(self, value):
AbstractUdsPacketType.is_initial_packet_type(value=value)
self.mock_validate_member.assert_called_once_with(value)


class TestAbstractUdsPacket:
"""Tests for 'AbstractUdsPacket' class."""
Expand Down
65 changes: 2 additions & 63 deletions tests/software_tests/segmentation/test_abstract_segmenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,50 +66,6 @@ def test_is_supported_packets_sequence__true(self, value):
assert AbstractSegmenter.is_supported_packets_sequence(self=self.mock_abstract_segmenter, value=value) is True
self.mock_abstract_segmenter.is_supported_packet.assert_called()

# is_initial_packet

@pytest.mark.parametrize("packet", [Mock(spec=AbstractUdsPacket), Mock(spec=AbstractUdsPacket)])
def test_is_initial_packet__type_error(self, packet):
self.mock_abstract_segmenter.is_supported_packet.return_value = False
with pytest.raises(TypeError):
AbstractSegmenter.is_initial_packet(self=self.mock_abstract_segmenter, packet=packet)
self.mock_abstract_segmenter.is_supported_packet.assert_called_once_with(packet)

@pytest.mark.parametrize("packet", [Mock(spec=AbstractUdsPacket), Mock(spec=AbstractUdsPacket)])
def test_is_initial_packet__false(self, packet):
self.mock_abstract_segmenter.is_supported_packet.return_value = True
mock_initial_packet_types = MagicMock()
mock_initial_packet_types.__contains__.return_value = False
self.mock_abstract_segmenter.initial_packet_types = mock_initial_packet_types
assert AbstractSegmenter.is_initial_packet(self=self.mock_abstract_segmenter, packet=packet) is False
self.mock_abstract_segmenter.is_supported_packet.assert_called_once_with(packet)
mock_initial_packet_types.__contains__.assert_called_once_with(packet.packet_type)

@pytest.mark.parametrize("packet", [Mock(spec=AbstractUdsPacket), Mock(spec=AbstractUdsPacket)])
def test_is_initial_packet__true(self, packet):
self.mock_abstract_segmenter.is_supported_packet.return_value = True
mock_initial_packet_types = MagicMock()
mock_initial_packet_types.__contains__.return_value = True
self.mock_abstract_segmenter.initial_packet_types = mock_initial_packet_types
assert AbstractSegmenter.is_initial_packet(self=self.mock_abstract_segmenter, packet=packet) is True
self.mock_abstract_segmenter.is_supported_packet.assert_called_once_with(packet)
mock_initial_packet_types.__contains__.assert_called_once_with(packet.packet_type)

# get_consecutive_packets_number

@pytest.mark.parametrize("packet", [Mock(spec=AbstractUdsPacket), Mock(spec=AbstractUdsPacketRecord)])
def test_get_consecutive_packets_number__value_error(self, packet):
self.mock_abstract_segmenter.is_initial_packet.return_value = False
with pytest.raises(ValueError):
AbstractSegmenter.get_consecutive_packets_number(self=self.mock_abstract_segmenter, first_packet=packet)
self.mock_abstract_segmenter.is_initial_packet.assert_called_once_with(packet)

@pytest.mark.parametrize("packet", [Mock(spec=AbstractUdsPacket), Mock(spec=AbstractUdsPacketRecord)])
def test_get_consecutive_packets_number__valid(self, packet):
self.mock_abstract_segmenter.is_initial_packet.return_value = True
AbstractSegmenter.get_consecutive_packets_number(self=self.mock_abstract_segmenter, first_packet=packet)
self.mock_abstract_segmenter.is_initial_packet.assert_called_once_with(packet)

# is_following_packets_sequence

@pytest.mark.parametrize("packets", [
Expand All @@ -124,21 +80,6 @@ def test_is_following_packets_sequence__value_error(self, packets):
with pytest.raises(ValueError):
AbstractSegmenter.is_following_packets_sequence(self=self.mock_abstract_segmenter, packets=packets)
self.mock_abstract_segmenter.is_supported_packets_sequence.assert_called_once_with(packets)
self.mock_abstract_segmenter.is_initial_packet.assert_not_called()

@pytest.mark.parametrize("packets", [
(1, 2, 3, 4),
(1, 2.1, 3, 4.4),
[2.2, 3.3, 4.4],
[None, True, False],
(True, False),
])
def test_is_following_packets_sequence__false(self, packets):
self.mock_abstract_segmenter.is_supported_packets_sequence.return_value = True
self.mock_abstract_segmenter.is_initial_packet.return_value = False
assert AbstractSegmenter.is_following_packets_sequence(self=self.mock_abstract_segmenter, packets=packets) is False
self.mock_abstract_segmenter.is_supported_packets_sequence.assert_called_once_with(packets)
self.mock_abstract_segmenter.is_initial_packet.assert_called_once_with(packets[0])

@pytest.mark.parametrize("packets", [
(1, 2, 3, 4),
Expand All @@ -147,12 +88,10 @@ def test_is_following_packets_sequence__false(self, packets):
[None, True, False],
(True, False),
])
def test_is_following_packets_sequence__valid(self, packets):
def test_is_following_packets_sequence__valid_input(self, packets):
self.mock_abstract_segmenter.is_supported_packets_sequence.return_value = True
self.mock_abstract_segmenter.is_initial_packet.return_value = True
assert AbstractSegmenter.is_following_packets_sequence(self=self.mock_abstract_segmenter, packets=packets) is None
AbstractSegmenter.is_following_packets_sequence(self=self.mock_abstract_segmenter, packets=packets)
self.mock_abstract_segmenter.is_supported_packets_sequence.assert_called_once_with(packets)
self.mock_abstract_segmenter.is_initial_packet.assert_called_once_with(packets[0])

# is_complete_packets_sequence

Expand Down
Empty file.
55 changes: 55 additions & 0 deletions tests/software_tests/transport_interface/test_packet_queue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import pytest
from mock import Mock

from uds.transport_interface.packet_queue import ReceivedPacketsQueue


class TestReceivedPacketsQueue:
"""Tests for `ReceivedPacketsQueue class."""

def setup(self):
self.mock_received_packets_queue = Mock(spec=ReceivedPacketsQueue)

# __init__

def test_init(self):
with pytest.raises(NotImplementedError):
ReceivedPacketsQueue.__init__(self=self.mock_received_packets_queue, packet_class=Mock())

# __del__

def test_del(self):
with pytest.raises(NotImplementedError):
ReceivedPacketsQueue.__del__(self=self.mock_received_packets_queue)

# __len__

def test_len(self):
with pytest.raises(NotImplementedError):
ReceivedPacketsQueue.__len__(self=self.mock_received_packets_queue)

# is_empty

def test_is_empty(self):
with pytest.raises(NotImplementedError):
ReceivedPacketsQueue.is_empty(self=self.mock_received_packets_queue)

# packet_task_done

def test_packet_task_done(self):
with pytest.raises(NotImplementedError):
ReceivedPacketsQueue.packet_task_done(self=self.mock_received_packets_queue)

# get_packet

@pytest.mark.asyncio
async def test_get_packet(self):
with pytest.raises(NotImplementedError):
await ReceivedPacketsQueue.get_packet(self=self.mock_received_packets_queue)

# put_packet

@pytest.mark.asyncio
async def test_put_packet(self):
with pytest.raises(NotImplementedError):
await ReceivedPacketsQueue.put_packet(self=self.mock_received_packets_queue, packet=Mock())
6 changes: 3 additions & 3 deletions uds/messages/nrc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Module with entire Negative Response Codes (NRC) implementation."""
"""Module with entire :ref:`Negative Response Codes (NRC) <knowledge-base-nrc>` implementation."""

__all__ = ["NRC"]

Expand All @@ -10,9 +10,9 @@
@unique
class NRC(ByteEnum, ValidatedEnum, ExtendableEnum):
"""
Negative Response Codes (NRC) values defined in ISO 14229-1:2020.
Negative Response Codes (NRC) values.
Explanation of NRC meaning is located in appendix A1 of ISO 14229-1 standard.
Explanation of :ref:`NRC <knowledge-base-nrc>` values meaning is located in appendix A1 of ISO 14229-1 standard.
"""

# PositiveResponse = 0x00
Expand Down
6 changes: 3 additions & 3 deletions uds/messages/service_identifiers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Service Identifiers (SID) implementation."""
""":ref:`Service Identifiers (SID) <knowledge-base-sid>` implementation."""

__all__ = ["RequestSID", "ResponseSID", "POSSIBLE_REQUEST_SIDS", "POSSIBLE_RESPONSE_SIDS", "UnrecognizedSIDWarning"]

Expand Down Expand Up @@ -40,7 +40,7 @@ class RequestSID(ByteEnum, ValidatedEnum, ExtendableEnum):
"""
Request Service Identifier values for all services that are defined in ISO 14229-1:2020.
Note: Request SID is always the first payload byte of all request messages.
Note: Request :ref:`SID <knowledge-base-sid>` is always the first payload byte of all request messages.
"""

Expand Down Expand Up @@ -100,7 +100,7 @@ class ResponseSID(ByteEnum, ValidatedEnum, ExtendableEnum):
"""
Response Service Identifier values for all services that are defined in ISO 14229-1:2020.
Note: Response SID is always the first payload byte of all request messages.
Note: Response :ref:`SID <knowledge-base-sid>` is always the first payload byte of all request messages.
Note: This Enum contains multiple members (for all the services as RequestSID), but most of them are
dynamically (implicitly) added and invisible in the documentation.
Expand Down
2 changes: 1 addition & 1 deletion uds/messages/uds_message.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Module with common implementation of all diagnostic messages (requests and responses).
Diagnostic messages are defined on higher layers of UDS OSI Model.
:ref:`Diagnostic messages <knowledge-base-diagnostic-message>` are defined on higher layers of UDS OSI Model.
"""

__all__ = ["UdsMessage", "UdsMessageRecord"]
Expand Down
16 changes: 14 additions & 2 deletions uds/messages/uds_packet.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Module with common implementation of UDS packets for all bus types.
UDS Packets are defined on middle layers of UDS OSI Model.
:ref:`UDS Packets <knowledge-base-uds-packet>` are defined on middle layers of UDS OSI Model.
"""

__all__ = ["AbstractUdsPacketType", "AbstractUdsPacket", "AbstractUdsPacketRecord",
Expand All @@ -24,12 +24,24 @@ class AbstractUdsPacketType(NibbleEnum, ValidatedEnum, ExtendableEnum):
"""
Abstract definition of UDS packet type.
Packet type information is carried by Network Protocol Control Information (N_PCI).
Packet type information is carried by :ref:`Network Protocol Control Information (N_PCI) <knowledge-base-n-pci>`.
Enums with packet types (N_PCI) values for certain buses (e.g. CAN, LIN, FlexRay) must inherit after this class.
Note: There are some differences in values for each bus (e.g. LIN does not use Flow Control).
"""

@classmethod
@abstractmethod
def is_initial_packet_type(cls, value: Any) -> bool: # type: ignore
"""
Check whether given argument is a member or a value of packet type that initiates a diagnostic message.
:param value: Value to check.
:return: True if given argument is a packet type that initiates a diagnostic message, else False.
"""
cls.validate_member(value)


class AbstractUdsPacket(ABC):
"""Abstract definition of UDS Packet (Network Protocol Data Unit - N_PDU)."""
Expand Down
50 changes: 16 additions & 34 deletions uds/segmentation/abstract_segmenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ class AbstractSegmenter(ABC):
Segmenter classes defines UDS segmentation and desegmentation
`strategies <https://www.tutorialspoint.com/design_pattern/strategy_pattern.htm>`_.
They contain helper methods that are essential for successful segmentation and desegmentation execution.
They contain helper methods that are essential for successful
:ref:`segmentation <knowledge-base-message-segmentation>` and
:ref:`desegmentation <knowledge-base-packets-desegmentation>` execution.
Each concrete segmenter class handles a single bus.
"""

Expand All @@ -28,7 +30,7 @@ class AbstractSegmenter(ABC):
def supported_packet_classes(self) -> Tuple[type]:
"""Classes that define packet objects supported by this segmenter."""

@property
@property # noqa: F841
@abstractmethod
def initial_packet_types(self) -> PacketTypesTuple:
"""Types of packets that initiates a diagnostic message transmission for the managed bus."""
Expand Down Expand Up @@ -60,36 +62,6 @@ def is_supported_packets_sequence(self, value: Any) -> bool:
# check if all packets are the same type
return len({type(element) for element in value}) == 1

def is_initial_packet(self, packet: PacketTyping) -> bool:
"""
Check whether a provided packet initiates a diagnostic message.
:param packet: Packet to check.
:raise TypeError: Provided value is not an object of a supported packet type.
:return: True if the packet is the only or the first packet of a diagnostic message.
"""
if not self.is_supported_packet(packet):
raise TypeError(f"Provided value is not a packet object that is supported by this Segmenter. "
f"Actual value: {packet}.")
return packet.packet_type in self.initial_packet_types

@abstractmethod
def get_consecutive_packets_number(self, first_packet: PacketTyping) -> int: # type: ignore
"""
Get number of consecutive packets that must follow this packet to fully store a diagnostic message.
:param first_packet: The first packet of a segmented diagnostic message.
:raise ValueError: Provided value is not an an initial packet.
:return: Number of following packets that together carry a diagnostic message.
"""
if not self.is_initial_packet(first_packet):
raise ValueError(f"Provided value is not an initial packet of a type supported by this Segmenter. "
f"Actual value: {first_packet}.")

@abstractmethod
def is_following_packets_sequence(self, packets: PacketsSequence) -> bool: # noqa
"""
Expand All @@ -110,8 +82,6 @@ def is_following_packets_sequence(self, packets: PacketsSequence) -> bool: # no
if not self.is_supported_packets_sequence(packets):
raise ValueError(f"Provided value is not a packets sequence of a supported type."
f"Actual value: {packets}.")
if not self.is_initial_packet(packets[0]):
return False

def is_complete_packets_sequence(self, packets: PacketsSequence) -> bool:
"""
Expand All @@ -125,6 +95,18 @@ def is_complete_packets_sequence(self, packets: PacketsSequence) -> bool:
return self.is_following_packets_sequence(packets) and \
self.get_consecutive_packets_number(packets[0]) == len(packets)

@abstractmethod
def get_consecutive_packets_number(self, first_packet: PacketTyping) -> int: # noqa: F841
"""
Get number of consecutive packets that must follow this packet to fully store a diagnostic message.
:param first_packet: The first packet of a segmented diagnostic message.
:raise ValueError: Provided value is not an an initial packet.
:return: Number of following packets that together carry a diagnostic message.
"""

@abstractmethod
def segmentation(self, message: UdsMessage) -> PacketsDefinitionTuple: # type: ignore
"""
Expand Down
5 changes: 5 additions & 0 deletions uds/transport_interface/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
A subpackage with UDS middle layers (Transport and Network) implementation.
TODO: provide more information when implementing tasks that expands capabilities of this package
"""
Loading

0 comments on commit 1710be9

Please sign in to comment.