More datatype abstraction:

* NumberDatatype
* StringDatatype

_raise_serialization_value_error_data is now a classmethod instead of a classmethod.

Tests for MIN and MAX number values of NumberDatatypes have been made more abstract.

TEST_DATA_* has been removed from variable names. Sometimes it can get a little too rediculous with these var-names, don't you think?
This commit is contained in:
Jeppe Klitgaard 2015-04-16 09:02:54 +02:00
parent 73b990cc81
commit a31f1c543f
2 changed files with 196 additions and 62 deletions

View File

@ -6,7 +6,7 @@ These datatypes are used by the packet definitions.
"""
__all__ = ["ENDIANNESS",
"Datatype",
"Datatype", "NumberDatatype", "StringDatatype",
"Boolean",
"Byte", "UnsignedByte",
"Short", "UnsignedShort",
@ -92,6 +92,7 @@ class Datatype(object):
"""
error_message = "'data's type ('{}') is not an allowed type."
error_message = error_message.format(type(data).__name__)
if (cls.ALLOWED_SERIALIZATION_TYPES and
not any([isinstance(data, type_) for type_
in cls.ALLOWED_SERIALIZATION_TYPES])):
@ -106,8 +107,8 @@ class Datatype(object):
return None
@staticmethod
def _raise_serialization_value_error_data(data):
@classmethod
def _raise_serialization_value_error_data(cls, data):
"""
Raises a ValueError if ``data`` is not valid.
@ -137,13 +138,51 @@ class Datatype(object):
if cls.SIZE != len(data):
err = "'data' must have a length of {}, not {}"
err = err.format(str(cls.SIZE), len(data))
err = err.format(str(cls.SIZE), str(len(data)))
raise TypeError(err)
raise ValueError(err)
return None
class NumberDatatype(Datatype):
"""
Base abstract class for all number-like minecraft networking datatypes.
.. note::
Numbers to be serialized must be between this classes
``MIN_NUMBER_VALUE`` and ``MAX_NUMBER_VALUE``, or a ``ValueError`` will
be raised.
If ``MIN_NUMBER_VALUE`` or ``MAX_NUMBER_VALUE`` are ``None``
(as in the case of float), checking is left to the ``struct`` module.
"""
MIN_NUMBER_VALUE = None
MAX_NUMBER_VALUE = None
ALLOWED_SERIALIZATION_TYPES = (int,)
DISALLOWED_SERIALIZATION_TYPES = (bool,)
@classmethod
def _raise_serialization_value_error_data(cls, data):
if (cls.MIN_NUMBER_VALUE is not None
and cls.MAX_NUMBER_VALUE is not None):
if not cls.MIN_NUMBER_VALUE <= data <= cls.MAX_NUMBER_VALUE:
err = "'data' must be an integer with value between {} and {}."
err = err.format(str(cls.MIN_NUMBER_VALUE),
str(cls.MAX_NUMBER_VALUE))
raise ValueError(err)
return None
class StringDatatype(Datatype):
pass
class Boolean(Datatype):
FORMAT = "?"
SIZE = 1
@ -152,76 +191,97 @@ class Boolean(Datatype):
ALLOWED_DESERIALIZATION_TYPES = (collections.Sequence,)
class Byte(Datatype):
class Byte(NumberDatatype):
FORMAT = "b"
SIZE = 1
ALLOWED_SERIALIZATION_TYPES = (int,)
DISALLOWED_SERIALIZATION_TYPES = (bool,)
@staticmethod
def _raise_serialization_value_error_data(data):
if not -128 <= data <= 127:
e = "'data' must be an integer with value between -128 and 127."
raise ValueError(e)
MIN_NUMBER_VALUE = -128
MAX_NUMBER_VALUE = 127
class UnsignedByte(Datatype):
class UnsignedByte(NumberDatatype):
FORMAT = "B"
SIZE = 1
MIN_NUMBER_VALUE = 0
MAX_NUMBER_VALUE = 255
class Short(Datatype):
class Short(NumberDatatype):
FORMAT = "h"
SIZE = 2
MIN_NUMBER_VALUE = -32768
MAX_NUMBER_VALUE = 32767
class UnsignedShort(Datatype):
class UnsignedShort(NumberDatatype):
FORMAT = "H"
SIZE = 2
MIN_NUMBER_VALUE = 0
MAX_NUMBER_VALUE = 65535
class Integer(Datatype):
class Integer(NumberDatatype):
FORMAT = "i"
SIZE = 4
MIN_NUMBER_VALUE = -2147483648
MAX_NUMBER_VALUE = 2147483647
class UnsignedInteger(Datatype):
class UnsignedInteger(NumberDatatype):
FORMAT = "I"
SIZE = 4
MIN_NUMBER_VALUE = 0
MAX_NUMBER_VALUE = 4294967295
class Long(Datatype):
class Long(NumberDatatype):
FORMAT = "l"
SIZE = 4
MIN_NUMBER_VALUE = -2147483648
MAX_NUMBER_VALUE = 2147483647
class UnsignedLong(Datatype):
class UnsignedLong(NumberDatatype):
FORMAT = "L"
SIZE = 4
MIN_NUMBER_VALUE = 0
MAX_NUMBER_VALUE = 4294967295
class LongLong(Datatype):
class LongLong(NumberDatatype):
FORMAT = "q"
SIZE = 8
MIN_NUMBER_VALUE = -9223372036854775808
MAX_NUMBER_VALUE = 9223372036854775807
class UnsignedLongLong(Datatype):
class UnsignedLongLong(NumberDatatype):
FORMAT = "Q"
SIZE = 8
MIN_NUMBER_VALUE = 0
MAX_NUMBER_VALUE = 18446744073709551615
class Float(Datatype):
class Float(NumberDatatype):
FORMAT = "f"
SIZE = 4
class Double(Datatype):
class Double(NumberDatatype):
FORMAT = "d"
SIZE = 8
class VarInt(Datatype):
class VarInt(NumberDatatype):
# See: https://developers.google.com/protocol-buffers/docs/encoding#varints
# See: https://github.com/ammaraskar/pyCraft/blob/7e8df473520d57ca22fb57888681f51705128cdc/network/types.py#l123 # noqa
# See: https://github.com/google/protobuf/blob/0c59f2e6fc0a2cb8e8e3b4c7327f650e8586880a/python/google/protobuf/internal/decoder.py#l107 # noqa

View File

@ -27,21 +27,21 @@ import unittest
class BaseDatatypeTester(unittest.TestCase):
DATATYPE_CLS = Datatype # We use Datatype as a an example here.
# TEST_DATA_VALID_VALUES should have the following format:
# VALID_VALUES should have the following format:
# [(DESERIALIZED_VALUE, SERIALIZED_VALUE), ...]
#
# So that DESERIALIZED_VALUE is SERIALIZED_VALUE when serialized
# and vice versa.
TEST_DATA_VALID_VALUES = []
VALID_VALUES = []
# TEST_DATA_INVALID_SERIALIZATION_VALUES should be a list of tuples
# INVALID_SERIALIZATION_VALUES should be a list of tuples
# containing the value and the expected exception.
TEST_DATA_INVALID_SERIALIZATION_VALUES = []
INVALID_SERIALIZATION_VALUES = []
# TEST_DATA_INVALID_DESERIALIZATION_VALUES should be a list of tuples
# INVALID_DESERIALIZATION_VALUES should be a list of tuples
# containing the value and the expected exception.
TEST_DATA_INVALID_DESERIALIZATION_VALUES = []
INVALID_DESERIALIZATION_VALUES = []
def test_init(self):
d = self.DATATYPE_CLS() # noqa
@ -52,40 +52,84 @@ class BaseDatatypeTester(unittest.TestCase):
d = self.DATATYPE_CLS("This is a positional argument...") # noqa
def test_valid_data_serialization_values(self):
for deserialized_val, serialized_val in self.TEST_DATA_VALID_VALUES:
for deserialized_val, serialized_val in self.VALID_VALUES:
self.assertEqual(self.DATATYPE_CLS.serialize(deserialized_val),
serialized_val)
def test_valid_data_deserialization_values(self):
for deserialized_val, serialized_val in self.TEST_DATA_VALID_VALUES:
for deserialized_val, serialized_val in self.VALID_VALUES:
self.assertEqual(self.DATATYPE_CLS.deserialize(serialized_val),
deserialized_val)
def test_invalid_data_serialization_values(self):
for value, exception in self.TEST_DATA_INVALID_SERIALIZATION_VALUES:
for value, exception in self.INVALID_SERIALIZATION_VALUES:
with self.assertRaises(exception):
print(value)
self.DATATYPE_CLS.serialize(value)
def test_invalid_data_deserialization_values(self):
for value, exception in self.TEST_DATA_INVALID_DESERIALIZATION_VALUES:
for value, exception in self.INVALID_DESERIALIZATION_VALUES:
with self.assertRaises(exception):
self.DATATYPE_CLS.deserialize(value)
class BaseNumberDatatypeTester(BaseDatatypeTester):
BASE_NUMBER_INVALID_SERIALIZATION_VALUES = [
("", TypeError),
("Test", TypeError),
(b"\x00", TypeError),
(b"\x80", TypeError),
(True, TypeError),
(False, TypeError)
]
def base_number_invalid_data_serialization_values(self):
values_to_test = BASE_INVALID_SERIALIZATION_VALUES
values_to_test.extend([
(self.DATATYPE_CLS.MIN_NUMBER_VALUE - 1, ValueError),
(self.DATATYPE_CLS.MAX_NUMBER_VALUE + 1, ValueError)
])
for value, exception in values_to_test:
with self.assertRaises(exception):
self.DATATYPE_CLS.serialize(value)
class BaseStringDatatypeTester(BaseDatatypeTester):
pass
BASE_INVALID_DESERIALIZATION_VALUES = [
(-1, TypeError),
(0, TypeError),
(1, TypeError),
("", ValueError),
("Test", ValueError),
(True, TypeError),
(False, TypeError)
]
class DatatypeTest(BaseDatatypeTester):
DATATYPE_CLS = Datatype
class NumberDatatypeTest(BaseNumberDatatypeTester):
DATATYPE_CLS = NumberDatatype
class StringDatatypeTest(BaseStringDatatypeTester):
DATATYPE_CLS = StringDatatype
class BooleanTest(BaseDatatypeTester):
DATATYPE_CLS = Boolean
TEST_DATA_VALID_VALUES = [
VALID_VALUES = [
(True, b"\x01"),
(False, b"\x00")
]
TEST_DATA_INVALID_SERIALIZATION_VALUES = [
INVALID_SERIALIZATION_VALUES = [
("\x00", TypeError),
("\x01", TypeError),
("\x02", TypeError),
@ -96,21 +140,19 @@ class BooleanTest(BaseDatatypeTester):
("Test", TypeError)
]
TEST_DATA_INVALID_DESERIALIZATION_VALUES = [
(-1, TypeError),
(0, TypeError),
(1, TypeError),
("", TypeError),
("Test", TypeError),
(True, TypeError),
(False, TypeError)
]
# Use list(BASE_INVALID_DESERIALIZATION_VALUES) instead of
# just = BASE_INVALID_DESERIALIZATION_VALUES, cause we want a COPY
# of the list, NOT a reference (that we'll later extend!)
INVALID_DESERIALIZATION_VALUES = list(BASE_INVALID_DESERIALIZATION_VALUES)
INVALID_DESERIALIZATION_VALUES.extend([
(b"\x00\x01", ValueError)
])
class ByteTest(BaseDatatypeTester):
class ByteTest(BaseNumberDatatypeTester):
DATATYPE_CLS = Byte
TEST_DATA_VALID_VALUES = [
VALID_VALUES = [
(-128, b"\x80"),
(-22, b"\xea"),
(0, b"\x00"),
@ -118,22 +160,54 @@ class ByteTest(BaseDatatypeTester):
(127, b"\x7f")
]
TEST_DATA_INVALID_SERIALIZATION_VALUES = [
(-500, ValueError),
(128, ValueError),
(1024, ValueError),
("", TypeError),
("Test", TypeError),
(b"\x00", TypeError),
(b"\x80", TypeError),
(True, TypeError),
(False, TypeError),
INVALID_DESERIALIZATION_VALUES = list(BASE_INVALID_DESERIALIZATION_VALUES)
INVALID_DESERIALIZATION_VALUES.extend([
(b"\x01\x20", ValueError),
])
class UnsignedByteTest(BaseNumberDatatypeTester):
DATATYPE_CLS = UnsignedByte
VALID_VALUES = [
(0, b"\x00"),
(127, b"\x7f"),
(255, b"\xff")
]
TEST_DATA_INVALID_DESERIALIZATION_VALUES = [
INVALID_DESERIALIZATION_VALUES = ByteTest.INVALID_DESERIALIZATION_VALUES
class ShortTest(BaseNumberDatatypeTester):
DATATYPE_CLS = Short
VALID_VALUES = [
(-32768, b"\x80\x00"),
(-10000, b"\xd8\xf0"),
(0, b"\x00\x00"),
(5000, b"\x13\x88"),
(32767, b"\x7f\xff")
]
INVALID_DESERIALIZATION_VALUES = list(BASE_INVALID_DESERIALIZATION_VALUES)
INVALID_DESERIALIZATION_VALUES.extend([
(b"\xff", ValueError),
(b"\xff\x01\x6e", ValueError)
])
class UnsignedShortTest(BaseNumberDatatypeTester):
DATATYPE_CLS = UnsignedShort
VALID_VALUES = [
(0, b"\x00\x00"),
(10000, b"'\x10"),
(32767, b"\x7f\xff"),
(65535, b"\xff\xff")
]
INVALID_DESERIALIZATION_VALUES = ShortTest.INVALID_DESERIALIZATION_VALUES
# def _bin(binstr):
# """
# Accepts a pretty looking string of binary numbers and