-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
RawDataRecord with unit tests (#297)
* creating RawDataRecord class with unittests, reorganizing Data Records file structure * mypy fixes * addressing review comments * improving unit tests for RawDataRecord * fixes for static code analyzer * addressing review comments * unittests and modification of encode method in RawDataRecord * pylint fixes * addressing comments
- Loading branch information
Showing
8 changed files
with
321 additions
and
29 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 161 additions & 0 deletions
161
tests/software_tests/database/data_record/test_raw_data_record.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import pytest | ||
from mock import Mock, patch | ||
|
||
from uds.database.data_record.raw_data_record import RawDataRecord | ||
|
||
SCRIPT_LOCATION = "uds.database.data_record.raw_data_record" | ||
|
||
|
||
class TestRawDataRecord: | ||
|
||
def setup_method(self): | ||
self.mock_data_record = Mock(spec=RawDataRecord) | ||
|
||
# __init__ | ||
|
||
@pytest.mark.parametrize( | ||
"name, length", [ | ||
("TestRawDataRecord", 8), | ||
(Mock(), Mock()), | ||
] | ||
) | ||
@patch(f"{SCRIPT_LOCATION}.AbstractDataRecord.__init__") | ||
def test_init__valid(self, mock_abstract, name, length): | ||
assert RawDataRecord.__init__(self.mock_data_record, name, length) is None | ||
mock_abstract.assert_called_once_with(name) | ||
assert self.mock_data_record.length == length | ||
|
||
# length | ||
|
||
def test_length_getter(self): | ||
self.mock_data_record._RawDataRecord__length = Mock() | ||
assert RawDataRecord.length.fget(self.mock_data_record) == self.mock_data_record._RawDataRecord__length | ||
|
||
@pytest.mark.parametrize( | ||
"value", [1, 8] | ||
) | ||
def test_length_setter_valid(self, value): | ||
RawDataRecord.length.fset(self.mock_data_record, value) | ||
assert self.mock_data_record._RawDataRecord__length == value | ||
|
||
@pytest.mark.parametrize( | ||
"value", ["test", None] | ||
) | ||
@patch(f"{SCRIPT_LOCATION}.isinstance") | ||
def test_length_setter_type_error(self, mock_isinstance, value): | ||
mock_isinstance.return_value = False | ||
with pytest.raises(TypeError): | ||
RawDataRecord.length.fset(self.mock_data_record, value) | ||
mock_isinstance.assert_called_once_with(value, int) | ||
|
||
@pytest.mark.parametrize( | ||
"value", [0, -1] | ||
) | ||
def test_length_setter_value_error(self, value): | ||
with pytest.raises(ValueError): | ||
RawDataRecord.length.fset(self.mock_data_record, value) | ||
|
||
# max_raw_value | ||
|
||
@pytest.mark.parametrize( | ||
"length, value", [ | ||
(2, 3), | ||
(5, 31), | ||
(8, 255), | ||
] | ||
) | ||
def test_max_raw_value_getter(self, length, value): | ||
raw_data_record = RawDataRecord("TestRawDataRecord", length) | ||
assert raw_data_record.max_raw_value == value | ||
|
||
# is_reoccurring | ||
|
||
def test_is_reoccurring_getter(self): | ||
assert RawDataRecord.is_reoccurring.fget(self.mock_data_record) is False | ||
|
||
# min_occurrences | ||
|
||
def test_min_occurrences_getter(self): | ||
assert RawDataRecord.min_occurrences.fget(self.mock_data_record) == 1 | ||
|
||
# max_occurrences | ||
|
||
def test_max_occurrences_getter(self): | ||
assert RawDataRecord.max_occurrences.fget(self.mock_data_record) == 1 | ||
|
||
# contains | ||
|
||
def test_contains_getter(self): | ||
assert RawDataRecord.contains.fget(self.mock_data_record) == () | ||
|
||
# decode | ||
|
||
@pytest.mark.parametrize( | ||
"value", [1, 4] | ||
) | ||
@patch(f"{SCRIPT_LOCATION}.DecodedDataRecord") | ||
def test_decode(self, mock_decoded_data_record, value): | ||
self.mock_data_record.max_raw_value = 8 | ||
assert RawDataRecord.decode(self.mock_data_record, value) == mock_decoded_data_record.return_value | ||
mock_decoded_data_record.assert_called_once_with( | ||
name=self.mock_data_record.name, | ||
raw_value=value, | ||
physical_value=value | ||
) | ||
|
||
@pytest.mark.parametrize( | ||
"value", ["test", None] | ||
) | ||
@patch(f"{SCRIPT_LOCATION}.isinstance") | ||
def test_decode_type_error(self, mock_isinstance, value): | ||
mock_isinstance.return_value = False | ||
with pytest.raises(TypeError): | ||
RawDataRecord.decode(self.mock_data_record, value) | ||
mock_isinstance.assert_called_once_with(value, int) | ||
|
||
@pytest.mark.parametrize( | ||
"value, max_raw_value", [ | ||
(-1, 2), | ||
(3, 2), | ||
(16, 6), | ||
] | ||
) | ||
def test_decode_value_error(self, value, max_raw_value): | ||
self.mock_data_record.max_raw_value = max_raw_value | ||
with pytest.raises(ValueError): | ||
RawDataRecord.decode(self.mock_data_record, value) | ||
|
||
# encode | ||
|
||
@pytest.mark.parametrize( | ||
"value, max_raw_value", [ | ||
(0, 2), | ||
(3, 3), | ||
(16, 16), | ||
] | ||
) | ||
def test_encode(self, value, max_raw_value): | ||
self.mock_data_record.max_raw_value = max_raw_value | ||
assert RawDataRecord.encode(self.mock_data_record, value) == value | ||
|
||
@pytest.mark.parametrize( | ||
"value", ["test", None] | ||
) | ||
@patch(f"{SCRIPT_LOCATION}.isinstance") | ||
def test_encode_type_error(self, mock_isinstance, value): | ||
mock_isinstance.return_value = False | ||
with pytest.raises(TypeError): | ||
RawDataRecord.encode(self.mock_data_record, value) | ||
mock_isinstance.assert_called_once_with(value, int) | ||
|
||
@pytest.mark.parametrize( | ||
"value, max_raw_value", [ | ||
(-1, 2), | ||
(3, 2), | ||
(16, 6), | ||
] | ||
) | ||
def test_encode_value_error(self, value, max_raw_value): | ||
self.mock_data_record.max_raw_value = max_raw_value | ||
with pytest.raises(ValueError): | ||
RawDataRecord.encode(self.mock_data_record, value) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
""" | ||
Package with implementation for all type of Data Records. | ||
Each Data Record contains mapping (translation) of raw data (sequence of bits in diagnostic message payload) to some | ||
meaningful information (e.g. physical value, text). | ||
""" | ||
|
||
from .abstract_data_record import AbstractDataRecord, DecodedDataRecord | ||
from .raw_data_record import RawDataRecord |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
"""Definition of RawDataRecord.""" | ||
|
||
__all__ = ["RawDataRecord"] | ||
|
||
from typing import Optional, Tuple | ||
|
||
from .abstract_data_record import AbstractDataRecord, DataRecordPhysicalValueAlias, DecodedDataRecord | ||
|
||
|
||
class RawDataRecord(AbstractDataRecord): | ||
"""Implementation and interface for Raw Data Record.""" | ||
|
||
def __init__(self, name: str, length: int) -> None: | ||
""" | ||
Initialize Raw Data Record. | ||
:param name: Name to assign to this Data Record. | ||
:param length: Number of bits that this Raw Data Record is stored over. | ||
:raise TypeError: Provided name is not str type. | ||
:raise ValueError: Provided length is not a positive integer. | ||
""" | ||
super().__init__(name) | ||
self.length = length | ||
|
||
@property | ||
def length(self) -> int: | ||
"""Get number of bits that this Data Record is stored over.""" | ||
return self.__length | ||
|
||
@length.setter | ||
def length(self, value: int) -> None: | ||
""" | ||
Set the length, ensuring it's an integer and within an acceptable range. | ||
:param value: Number of bits that this Data Record is stored over. | ||
:raise TypeError: Provided value is not int type. | ||
:raise ValueError: Provided value is less or equal 0. | ||
""" | ||
if not isinstance(value, int): | ||
raise TypeError("Length must be an integer.") | ||
if value <= 0: | ||
raise ValueError("Length must be a positive integer.") | ||
self.__length = value | ||
|
||
@property # noqa: F841 | ||
def is_reoccurring(self) -> bool: | ||
""" | ||
Whether this Data Record might occur multiple times. | ||
Values meaning: | ||
- False - exactly one occurrence in every diagnostic message | ||
- True - number of occurrences might vary | ||
""" | ||
return False | ||
|
||
@property # noqa: F841 | ||
def min_occurrences(self) -> int: | ||
""" | ||
Minimal number of this Data Record occurrences. | ||
.. note:: Relevant only if :attr:`~uds.database.data_record.raw_data_record.RawDataRecord.is_reoccurring` | ||
equals True. | ||
""" | ||
return 1 | ||
|
||
@property # noqa: F841 | ||
def max_occurrences(self) -> Optional[int]: | ||
""" | ||
Maximal number of this Data Record occurrences. | ||
.. note:: Relevant only if :attr:`~uds.database.data_record.raw_data_record.RawDataRecord.is_reoccurring` | ||
equals True. | ||
.. warning:: No maximal number (infinite number of occurrences) is represented by None value. | ||
""" | ||
return 1 | ||
|
||
@property # noqa: F841 | ||
def contains(self) -> Tuple[AbstractDataRecord, ...]: | ||
"""Get Data Records contained by this Data Record.""" | ||
return () | ||
|
||
def decode(self, raw_value: int) -> DecodedDataRecord: | ||
""" | ||
Decode physical value for provided raw value. | ||
:param raw_value: Raw (bit) value of Data Record. | ||
:return: Dictionary with physical value for this Data Record. | ||
:raises TypeError: Provided `raw_value` is not int type. | ||
:raises ValueError: Provided `raw_value` is out of range (0 <= raw_value <= max_raw_value). | ||
""" | ||
if not isinstance(raw_value, int): | ||
raise TypeError(f"Expected raw_value to be an int type, got '{type(raw_value).__name__}' instead.") | ||
|
||
if not 0 <= raw_value <= self.max_raw_value: | ||
raise ValueError( | ||
"Provided value of raw_value is out of range: " | ||
f"must be between 0 and {self.max_raw_value}, got {raw_value}." | ||
) | ||
return DecodedDataRecord(name=self.name, raw_value=raw_value, physical_value=raw_value) | ||
|
||
def encode(self, physical_value: DataRecordPhysicalValueAlias) -> int: | ||
""" | ||
Encode raw value for provided physical value. | ||
:param physical_value: Physical (meaningful e.g. float, str type) value of this Data Record. | ||
:return: Raw Value of this Data Record. | ||
""" | ||
if not isinstance(physical_value, int): | ||
raise TypeError( | ||
f"Expected physical_value to be an int type, got '{type(physical_value).__name__}' instead." | ||
) | ||
|
||
if not 0 <= physical_value <= self.max_raw_value: | ||
raise ValueError( | ||
"Provided value of physical_value is out of range: " | ||
f"must be between 0 and {self.max_raw_value}, got {physical_value}." | ||
) | ||
return physical_value # type: ignore |
Oops, something went wrong.