-
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.
Improve
gen_test_serializable
for more flexibility on error checking
- Loading branch information
1 parent
d6f2921
commit 98ee687
Showing
12 changed files
with
312 additions
and
263 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 |
---|---|---|
@@ -1,57 +1,34 @@ | ||
- Changed the way `Serializable` classes are handled: | ||
|
||
Here is how a basic `Serializable` class looks like: | ||
|
||
```python | ||
@final | ||
@dataclass | ||
class ToyClass(Serializable): | ||
"""Toy class for testing demonstrating the use of gen_serializable_test on `Serializable`.""" | ||
|
||
a: int | ||
b: str | int | ||
|
||
@override | ||
def __attrs_post_init__(self): | ||
"""Initialize the object.""" | ||
if isinstance(self.b, int): | ||
self.b = str(self.b) | ||
|
||
super().__attrs_post_init__() # This will call validate() | ||
|
||
@override | ||
def serialize_to(self, buf: Buffer): | ||
"""Write the object to a buffer.""" | ||
self.b = cast(str, self.b) # Handled by the __attrs_post_init__ method | ||
buf.write_varint(self.a) | ||
buf.write_utf(self.b) | ||
|
||
@classmethod | ||
@override | ||
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) | ||
|
||
@override | ||
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 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. | ||
|
||
And the following optional methods: | ||
|
||
- `validate() -> None`: Validates the object's attributes, raising an exception if they are invalid. | ||
- `__attrs_post_init__() -> None`: Initializes the object. Call `super().__attrs_post_init__()` to validate the object. | ||
- **Function**: `gen_serializable_test` | ||
- Generates tests for serializable classes, covering serialization, deserialization, validation, and error handling. | ||
- **Parameters**: | ||
- `context` (dict): Context to add the test functions to (usually `globals()`). | ||
- `cls` (type): The serializable class to test. | ||
- `fields` (list): Tuples of field names and types of the serializable class. | ||
- `serialize_deserialize` (list, optional): Tuples for testing successful serialization/deserialization. | ||
- `validation_fail` (list, optional): Tuples for testing validation failures with expected exceptions. | ||
- `deserialization_fail` (list, optional): Tuples for testing deserialization failures with expected exceptions. | ||
- **Note**: Implement `__eq__` in the class for accurate comparison. | ||
|
||
- The `gen_serializable_test` function generates a test class with the following tests: | ||
|
||
.. literalinclude:: /../tests/mcproto/utils/test_serializable.py | ||
:language: python | ||
:start-after: # region Test ToyClass | ||
:end-before: # endregion Test ToyClass | ||
|
||
- The generated test class will have the following tests: | ||
|
||
```python | ||
class TestGenToyClass: | ||
def test_serialization(self): | ||
# 3 subtests for the cases 1, 2, 3 (serialize_deserialize) | ||
|
||
def test_deserialization(self): | ||
# 3 subtests for the cases 1, 2, 3 (serialize_deserialize) | ||
|
||
def test_validation(self): | ||
# 3 subtests for the cases 4, 5, 6 (validation_fail) | ||
|
||
def test_exceptions(self): | ||
# 3 subtests for the cases 7, 8, 9 (deserialization_fail) | ||
``` |
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 |
---|---|---|
@@ -1,53 +1,16 @@ | ||
- 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: | ||
|
||
```python | ||
gen_serializable_test( | ||
context=globals(), | ||
cls=ToyClass, | ||
fields=[("a", int), ("b", str)], | ||
test_data=[ | ||
((1, "hello"), b"\x01\x05hello"), | ||
((2, "world"), b"\x02\x05world"), | ||
((3, 1234567890), b"\x03\x0a1234567890"), | ||
((0, "hello"), ZeroDivisionError("a must be non-zero")), # With an error message | ||
((1, "hello world"), ValueError), # No error message | ||
((1, 12345678900), ValueError("b must be less than 10 .*")), # With an error message and regex | ||
(ZeroDivisionError, b"\x00"), | ||
(ZeroDivisionError, b"\x01\x05hello"), | ||
(IOError, b"\x01"), | ||
], | ||
) | ||
``` | ||
|
||
The generated test class will have the following tests: | ||
|
||
```python | ||
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 | ||
``` | ||
- **Class**: `Serializable` | ||
- Base class for types that should be (de)serializable into/from `mcproto.Buffer` data. | ||
- **Methods**: | ||
- `__attrs_post_init__()`: Runs validation after object initialization, override to define custom behavior. | ||
- `serialize() -> Buffer`: Returns the object as a `Buffer`. | ||
- `serialize_to(buf: Buffer)`: Abstract method to write the object to a `Buffer`. | ||
- `validate()`: Validates the object's attributes; can be overridden for custom validation. | ||
- `deserialize(cls, buf: Buffer) -> Self`: Abstract method to construct the object from a `Buffer`. | ||
- **Note**: Use the `dataclass` decorator when adding parameters to subclasses. | ||
|
||
- Exemple: | ||
|
||
.. literalinclude:: /../tests/mcproto/utils/test_serializable.py | ||
:language: python | ||
:start-after: # region ToyClass | ||
:end-before: # endregion ToyClass |
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
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.