-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Change the way Serializable classes work
Provide a way to test serialization, deserialization, validation and deserialization errors easily. This fixes Avoid repetition in tests for serialize+deserialize tests #64 and makes it easier to add new data types
- Loading branch information
1 parent
062713c
commit 09ff7b7
Showing
18 changed files
with
799 additions
and
729 deletions.
There are no files selected for viewing
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,90 @@ | ||
- Changed the way `Serializable` classes are handled: | ||
|
||
Here is how a basic `Serializable` class looks like: | ||
|
||
@final | ||
@dataclass | ||
class ToyClass(Serializable): | ||
"""Toy class for testing demonstrating the use of gen_serializable_test on `Serializable`.""" | ||
|
||
|
||
# Attributes can be of any type | ||
a: int | ||
b: str | ||
|
||
# dataclasses.field() can be used to specify additional metadata | ||
|
||
def serialize_to(self, buf: Buffer): | ||
"""Write the object to a buffer.""" | ||
buf.write_varint(self.a) | ||
buf.write_utf(self.b) | ||
|
||
@classmethod | ||
def deserialize(cls, buf: Buffer) -> ToyClass: | ||
"""Deserialize the object from a buffer.""" | ||
a = buf.read_varint() | ||
if a == 0: | ||
raise ZeroDivisionError("a must be non-zero") | ||
b = buf.read_utf() | ||
return cls(a, b) | ||
|
||
def validate(self) -> None: | ||
"""Validate the object's attributes.""" | ||
if self.a == 0: | ||
raise ZeroDivisionError("a must be non-zero") | ||
if len(self.b) > 10: | ||
raise ValueError("b must be less than 10 characters") | ||
|
||
|
||
The `Serializable` class must implement the following methods: | ||
|
||
- `serialize_to(buf: Buffer) -> None`: Serializes the object to a buffer. | ||
- `deserialize(buf: Buffer) -> Serializable`: Deserializes the object from a buffer. | ||
- `validate() -> None`: Validates the object's attributes, raising an exception if they are invalid. | ||
|
||
- Added a test generator for `Serializable` classes: | ||
|
||
The `gen_serializable_test` function generates tests for `Serializable` classes. It takes the following arguments: | ||
|
||
- `context`: The dictionary containing the context in which the generated test class will be placed (e.g. `globals()`). | ||
> Dictionary updates must reflect in the context. This is the case for `globals()` but implementation-specific for `locals()`. | ||
- `cls`: The `Serializable` class to generate tests for. | ||
- `fields`: A list of fields where the test values will be placed. | ||
|
||
> In the example above, the `ToyClass` class has two fields: `a` and `b`. | ||
- `test_data`: A list of tuples containing either: | ||
- `((field1_value, field2_value, ...), expected_bytes)`: The values of the fields and the expected serialized bytes. This needs to work both ways, i.e. `cls(field1_value, field2_value, ...) == cls.deserialize(expected_bytes).` | ||
- `((field1_value, field2_value, ...), exception)`: The values of the fields and the expected exception when validating the object. | ||
- `(exception, bytes)`: The expected exception when deserializing the bytes and the bytes to deserialize. | ||
|
||
The `gen_serializable_test` function generates a test class with the following tests: | ||
|
||
gen_serializable_test( | ||
context=globals(), | ||
cls=ToyClass, | ||
fields=[("a", int), ("b", str)], | ||
test_data=[ | ||
((1, "hello"), b"\x01\x05hello"), | ||
((2, "world"), b"\x02\x05world"), | ||
((0, "hello"), ZeroDivisionError), | ||
((1, "hello world"), ValueError), | ||
(ZeroDivisionError, b"\x00"), | ||
(IOError, b"\x01"), | ||
], | ||
) | ||
|
||
The generated test class will have the following tests: | ||
|
||
class TestGenToyClass: | ||
def test_serialization(self): | ||
# 2 subtests for the cases 1 and 2 | ||
|
||
def test_deserialization(self): | ||
# 2 subtests for the cases 1 and 2 | ||
|
||
def test_validation(self): | ||
# 2 subtests for the cases 3 and 4 | ||
|
||
def test_exceptions(self): | ||
# 2 subtests for the cases 5 and 6 |
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
Oops, something went wrong.