mcproto icon indicating copy to clipboard operation
mcproto copied to clipboard

New Serializables (v3)

Open LiteApplication opened this issue 1 year ago • 2 comments

This uses the Serializable (#273) methods to validate and use the data in the NBTags (#257) subclasses. The tests also make use of the new test generator introduced in #273.

EDIT : Let's merge this with 273 !

Original PR description :

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

Also this has 100% test coverage for all data types and packets.

LiteApplication avatar May 01 '24 16:05 LiteApplication

Now that NBTag support was merged, this can become a part of #273, or alternatively, this can replace #273.

ItsDrike avatar May 14 '24 19:05 ItsDrike

I'm currently merging the changes added right after #257. I'm OK with replacing #273 with this PR

LiteApplication avatar May 15 '24 19:05 LiteApplication