Skip to content

Commit

Permalink
Add a getMaxSize() Method To Types (#104)
Browse files Browse the repository at this point in the history
* add in max size function to core model types

* fixing positioning of @classmethod

* unittests for max size

* formatting

* fixing missing comment
  • Loading branch information
LeStarch committed Nov 14, 2022
1 parent c20dc3a commit ea2f196
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 40 deletions.
7 changes: 6 additions & 1 deletion src/fprime/common/models/serialize/array_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,10 @@ def deserialize(self, data, offset):
self._val = values

def getSize(self):
"""Return the size of the array"""
"""Return the size in bytes of the array"""
return sum(item.getSize() for item in self._val)

@classmethod
def getMaxSize(cls):
"""Return the maximum size in bytes of the array"""
return cls.MEMBER_TYPE.getMaxSize() * cls.LENGTH
8 changes: 7 additions & 1 deletion src/fprime/common/models/serialize/bool_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,11 @@ def deserialize(self, data, offset):
except struct.error:
raise DeserializeException("Not enough bytes to deserialize bool.")

def getSize(self):
@classmethod
def getSize(cls):
return struct.calcsize("B")

@classmethod
def getMaxSize(cls):
"""Maximum size of type"""
return cls.getSize() # Always the same as getSize
8 changes: 7 additions & 1 deletion src/fprime/common/models/serialize/enum_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ def deserialize(self, data, offset):
else:
raise TypeRangeException(int_val)

def getSize(self):
@classmethod
def getSize(cls):
"""Calculates the size based on the size of an integer used to store it"""
return struct.calcsize(">i")

@classmethod
def getMaxSize(cls):
"""Maximum size of type"""
return cls.getSize() # Always the same as getSize
5 changes: 5 additions & 0 deletions src/fprime/common/models/serialize/numerical_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ def getSize(cls):
"""Gets the size of the integer based on the size specified in the class name"""
return int(cls.get_bits() >> 3) # Divide by 8 quickly

@classmethod
def getMaxSize(cls):
"""Maximum size of type"""
return cls.getSize() # Always the same as getSize

@staticmethod
@abc.abstractmethod
def get_serialize_format():
Expand Down
5 changes: 5 additions & 0 deletions src/fprime/common/models/serialize/serializable_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ def getSize(self):
"""The size of a struct is the size of all the members"""
return sum(self._val.get(name).getSize() for name, _, _, _ in self.MEMBER_LIST)

@classmethod
def getMaxSize(cls):
"""Return the maximum size in bytes of the array"""
return sum(member_type.getMaxSize() for _, member_type, _, _ in cls.MEMBER_LIST)

def to_jsonable(self):
"""
JSONable type for a serializable
Expand Down
5 changes: 5 additions & 0 deletions src/fprime/common/models/serialize/string_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,8 @@ def getSize(self):
Get the size of this object
"""
return struct.calcsize(">H") + len(self.val)

@classmethod
def getMaxSize(cls):
"""Get maximum size of the type"""
return struct.calcsize(">H") + cls.MAX_LENGTH
49 changes: 26 additions & 23 deletions src/fprime/common/models/serialize/time_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from enum import Enum

# Custom Python Modules
import fprime.common.models.serialize.numerical_types
from fprime.common.models.serialize.numerical_types import U8Type, U16Type, U32Type
from fprime.common.models.serialize import type_base
from fprime.common.models.serialize.type_exceptions import TypeRangeException

Expand Down Expand Up @@ -82,14 +82,10 @@ def __init__(self, time_base=0, time_context=0, seconds=0, useconds=0):
self._check_time_base(time_base)
self._check_useconds(useconds)

self.__timeBase = fprime.common.models.serialize.numerical_types.U16Type(
time_base
)
self.__timeContext = fprime.common.models.serialize.numerical_types.U8Type(
time_context
)
self.__secs = fprime.common.models.serialize.numerical_types.U32Type(seconds)
self.__usecs = fprime.common.models.serialize.numerical_types.U32Type(useconds)
self.__timeBase = U16Type(time_base)
self.__timeContext = U8Type(time_context)
self.__secs = U32Type(seconds)
self.__usecs = U32Type(useconds)

@staticmethod
def _check_useconds(useconds):
Expand Down Expand Up @@ -141,23 +137,23 @@ def timeBase(self):
@timeBase.setter
def timeBase(self, val):
self._check_time_base(val)
self.__timeBase = fprime.common.models.serialize.numerical_types.U16Type(val)
self.__timeBase = U16Type(val)

@property
def timeContext(self):
return self.__timeContext.val

@timeContext.setter
def timeContext(self, val):
self.__timeContext = fprime.common.models.serialize.numerical_types.U8Type(val)
self.__timeContext = U8Type(val)

@property
def seconds(self):
return self.__secs.val

@seconds.setter
def seconds(self, val):
self.__secs = fprime.common.models.serialize.numerical_types.U32Type(val)
self.__secs = U32Type(val)

@property
def useconds(self):
Expand All @@ -166,7 +162,7 @@ def useconds(self):
@useconds.setter
def useconds(self, val):
self._check_useconds(val)
self.__usecs = fprime.common.models.serialize.numerical_types.U32Type(val)
self.__usecs = U32Type(val)

def serialize(self):
"""
Expand Down Expand Up @@ -209,20 +205,29 @@ def deserialize(self, data, offset):
self.__usecs.deserialize(data, offset)
offset += self.__usecs.getSize()

def getSize(self):
@classmethod
def getSize(cls):
"""
Return the size of the time type object when serialized
Returns:
The size of the time type object when serialized
"""
return (
self.__timeBase.getSize()
+ self.__timeContext.getSize()
+ self.__secs.getSize()
+ self.__usecs.getSize()
U16Type.getSize() + U8Type.getSize() + U32Type.getSize() + U32Type.getSize()
)

@classmethod
def getMaxSize(cls):
"""
Return the size of the time type object when serialized
Returns:
The size of the time type object when serialized
"""
# Always the same as getSize. Must be updated when time types are configurable.
return cls.getSize()

@staticmethod
def compare(t1, t2):
"""
Expand Down Expand Up @@ -330,11 +335,9 @@ def set_datetime(self, dt, time_base=0xFFFF):
self._check_time_base(time_base)
self._check_useconds(useconds)

self.__timeBase = fprime.common.models.serialize.numerical_types.U16Type(
time_base
)
self.__secs = fprime.common.models.serialize.numerical_types.U32Type(seconds)
self.__usecs = fprime.common.models.serialize.numerical_types.U32Type(useconds)
self.__timeBase = U16Type(time_base)
self.__secs = U32Type(seconds)
self.__usecs = U32Type(useconds)

# The following Python special methods add support for rich comparison of TimeTypes to other
# TimeTypes and numbers.
Expand Down
6 changes: 6 additions & 0 deletions src/fprime/common/models/serialize/type_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ def getSize(self):
"""
raise AbstractMethodException("getSize")

@classmethod
@abc.abstractmethod
def getMaxSize(cls):
"""Get maximum size of the type"""
return AbstractMethodException("getMaxSize")

def __repr__(self):
"""Produces a string representation of a given type"""
return self.__class__.__name__.replace("Type", "")
Expand Down
67 changes: 53 additions & 14 deletions test/fprime/common/models/serialize/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def valid_values_test(type_input, valid_values, sizes):
assert (
serialized == new_serialized_bytes
), "Repeated serialization has failed"
return instantiation


def invalid_values_test(
Expand Down Expand Up @@ -161,7 +162,9 @@ def ser_deser_time_test(t_base, t_context, secs, usecs):

def test_boolean_nominal():
"""Tests the nominal cases of a BoolType"""
valid_values_test(BoolType, [True, False], 1)
instance = valid_values_test(BoolType, [True, False], 1)
# Make sure max size is the same as the size and can be derived from the class
assert instance.getSize() == instance.__class__.getMaxSize()


def test_boolean_off_nominal():
Expand All @@ -175,7 +178,9 @@ def test_int_types_nominal():
"""Tests the integer types"""
for type_input, size in [(I8Type, 1), (I16Type, 2), (I32Type, 4), (I64Type, 8)]:
total = pow(2, (size * 8) - 1)
valid_values_test(type_input, [0, -1, 1, -total, total - 1], size)
instance = valid_values_test(type_input, [0, -1, 1, -total, total - 1], size)
# Make sure max size is the same as the size and can be derived from the class
assert instance.getSize() == instance.__class__.getMaxSize()


def test_int_types_off_nominal():
Expand All @@ -195,7 +200,9 @@ def test_uint_types_nominal():
"""Tests the integer types"""
for type_input, size in [(U8Type, 1), (U16Type, 2), (U32Type, 4), (U64Type, 8)]:
max_int = pow(2, (size * 8)) - 1
valid_values_test(type_input, [0, 1, max_int - 1, max_int], size)
instance = valid_values_test(type_input, [0, 1, max_int - 1, max_int], size)
# Make sure max size is the same as the size and can be derived from the class
assert instance.getSize() == instance.__class__.getMaxSize()


def test_uint_types_off_nominal():
Expand All @@ -215,8 +222,16 @@ def test_uint_types_off_nominal():

def test_float_types_nominal():
"""Tests the integer types"""
valid_values_test(F32Type, [0.31415000557899475, 0.0, -3.141590118408203], 4)
valid_values_test(F64Type, [0.31415000557899475, 0.0, -3.141590118408203], 8)
instance = valid_values_test(
F32Type, [0.31415000557899475, 0.0, -3.141590118408203], 4
)
# Make sure max size is the same as the size and can be derived from the class
assert instance.getSize() == instance.__class__.getMaxSize()
instance = valid_values_test(
F64Type, [0.31415000557899475, 0.0, -3.141590118408203], 8
)
# Make sure max size is the same as the size and can be derived from the class
assert instance.getSize() == instance.__class__.getMaxSize()


def test_float_types_off_nominal():
Expand All @@ -238,7 +253,9 @@ def test_enum_nominal():
members = {"MEMB1": 0, "MEMB2": 6, "MEMB3": 9}
enum_class = EnumType.construct_type("SomeEnum", members)
valid = ["MEMB1", "MEMB2", "MEMB3"]
valid_values_test(enum_class, valid, [4] * len(valid))
instance = valid_values_test(enum_class, valid, [4] * len(valid))
# Make sure max size is the same as the size and can be derived from the class
assert instance.getSize() == instance.__class__.getMaxSize()


def test_enum_off_nominal():
Expand All @@ -259,9 +276,11 @@ def test_string_nominal():
"""Tests named string types"""
py_string = "ABC123DEF456"
string_type = StringType.construct_type("MyFancyString", max_length=10)
valid_values_test(
instance = valid_values_test(
string_type, [py_string[:10], py_string[:4], py_string[:7]], [12, 6, 9]
)
# String type defined a max-size of 10 plus 2 for the size data
assert instance.__class__.getMaxSize() == 10 + 2


def test_string_off_nominal():
Expand Down Expand Up @@ -294,15 +313,20 @@ def test_serializable_basic():
],
]
valid_values = [
({"member1": 123, "member2": 456, "member3": -234}, 4 + 4 + 8),
({"member4": "345", "member5": "abc1", "member6": 213}, 5 + 6 + 8),
({"member1": 123, "member2": 456, "member3": -234}, 4 + 4 + 8, 4 + 4 + 8),
({"member4": "345", "member5": "abc1", "member6": 213}, 5 + 6 + 8, 12 + 6 + 8),
]

for index, (members, (valid, size)) in enumerate(zip(member_list, valid_values)):
for index, (members, (valid, size, max_size)) in enumerate(
zip(member_list, valid_values)
):
serializable_type = SerializableType.construct_type(
f"BasicSerializable{index}", members
)
valid_values_test(serializable_type, [valid], [size])
instance = valid_values_test(serializable_type, [valid], [size])
assert (
instance.__class__.getMaxSize() == max_size
) # Sum of sizes of member list


def test_serializable_basic_off_nominal():
Expand Down Expand Up @@ -368,11 +392,14 @@ def test_serializable_advanced():
"field4": ["", "123", "6"],
"field5": {"subfield1": 3234, "subfield2": ["abc", "def", "abc"]},
}
valid_values_test(
instance = valid_values_test(
serializable_class,
[serializable1],
[5 + 4 + 4 + (2 + 5 + 3) + (4 + (5 + 5 + 5))],
)
assert instance.__class__.getMaxSize() == (
5 + 4 + 4 + (3 * 5) + (4 + (3 * 5))
) # Sum of sizes of member list


def test_array_type():
Expand All @@ -391,9 +418,13 @@ def test_array_type():
]
values = [[32, 1], [0, 1, 2, 3], ["one", "1234", "1"]]
sizes = [8, 4, 14]
for ctor_args, values, size in zip(extra_ctor_args, values, sizes):
max_sizes = [8, 4, (2 + 18) * 3]
for ctor_args, values, size, max_size in zip(
extra_ctor_args, values, sizes, max_sizes
):
type_input = ArrayType.construct_type(*ctor_args)
valid_values_test(type_input, [values], [size])
instance = valid_values_test(type_input, [values], [size])
assert instance.__class__.getMaxSize() == max_size


def test_array_type_off_nominal():
Expand Down Expand Up @@ -438,6 +469,7 @@ def test_time_type():
val = TimeType()
size = val.getSize()
assert size == TIME_SIZE
assert val.getMaxSize() == TIME_SIZE

for (t_base, t_context, secs, usecs) in in_no_err_list:
ser_deser_time_test(t_base, t_context, secs, usecs)
Expand All @@ -458,6 +490,10 @@ def deserialize(self, data, offset):
def getSize(self):
return 0

@classmethod
def getMaxSize(cls):
return 902

def to_jsonable(self):
return {"name": "dummy"}

Expand All @@ -476,6 +512,9 @@ def test_base_type():
d = Dummy()
assert d.serialize() == "serialized"
assert d.getSize() == 0
assert Dummy.getMaxSize() == 902
assert d.getMaxSize() == 902

with pytest.raises(AbstractMethodException):
# In the Dummy class above, the deserialize method
# is set to call the super class, which is just the
Expand Down

0 comments on commit ea2f196

Please sign in to comment.