blob: 7e2c9630c0d18a53765f4a3155d059b037c210a6 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import dataclasses
from typing import List, Dict
from pyfory import Fory
import pyfory
@dataclasses.dataclass
class SimpleDataClass:
name: str
age: int
active: bool
@dataclasses.dataclass
class SimpleNestedDataClass:
value: int
name: str
@dataclasses.dataclass
class ExtendedDataClass:
name: str
age: int
active: bool
email: str # Additional field
@dataclasses.dataclass
class ReducedDataClass:
name: str
age: int
# Missing 'active' field
@dataclasses.dataclass
class NestedStructClass:
name: str
nested: SimpleNestedDataClass
@dataclasses.dataclass
class NestedStructClassInconsistent:
name: str
nested: ExtendedDataClass # Different nested type
@dataclasses.dataclass
class ListFieldsClass:
name: str
int_list: List[pyfory.int32]
str_list: List[str]
@dataclasses.dataclass
class ListFieldsClassInconsistent:
name: str
int_list: List[str] # Changed from int32 to str
str_list: List[pyfory.int32] # Changed from str to int32
@dataclasses.dataclass
class DictFieldsClass:
name: str
int_dict: Dict[str, pyfory.int32]
str_dict: Dict[str, str]
@dataclasses.dataclass
class DictFieldsClassInconsistent:
name: str
int_dict: Dict[str, str] # Changed from int32 to str
str_dict: Dict[str, pyfory.int32] # Changed from str to int32
class TestMetaShareMode:
def setup_method(self):
"""Setup method to register dataclasses for each test."""
pass
def test_meta_share_enabled(self):
"""Test that meta share mode can be enabled."""
fory = Fory(xlang=True, compatible=True)
assert fory.serialization_context.scoped_meta_share_enabled
assert fory.serialization_context.meta_context is not None
def test_meta_share_disabled(self):
"""Test that meta share mode can be disabled."""
fory = Fory(xlang=True, compatible=False)
assert not fory.serialization_context.scoped_meta_share_enabled
assert fory.serialization_context.meta_context is None
def test_simple_dataclass_serialization(self):
"""Test serialization of simple dataclass with meta share."""
fory = Fory(xlang=True, compatible=True)
# Register the dataclass
fory.register_type(SimpleDataClass)
obj = SimpleDataClass(name="test", age=25, active=True)
buffer = fory.serialize(obj)
# Deserialize
deserialized = fory.deserialize(buffer)
assert deserialized.name == obj.name
assert deserialized.age == obj.age
assert deserialized.active == obj.active
def test_multiple_objects_same_type(self):
"""Test that multiple objects of same type reuse type definition."""
fory = Fory(xlang=True, compatible=True)
# Register the dataclass
fory.register_type(SimpleDataClass)
obj1 = SimpleDataClass(name="test1", age=25, active=True)
obj2 = SimpleDataClass(name="test2", age=30, active=False)
# Serialize both objects
buffer1 = fory.serialize(obj1)
buffer2 = fory.serialize(obj2)
# Create a new fory instance with the same meta context for deserialization
fory2 = Fory(xlang=True, compatible=True)
fory2.register_type(SimpleDataClass)
# Copy the meta context from the first fory instance
fory2.serialization_context.meta_context = fory.serialization_context.meta_context
# Deserialize both
deserialized1 = fory2.deserialize(buffer1)
deserialized2 = fory2.deserialize(buffer2)
assert deserialized1.name == obj1.name
assert deserialized2.name == obj2.name
assert deserialized1.age == obj1.age
assert deserialized2.age == obj2.age
def test_simple_nested_dataclass_serialization(self):
"""Test serialization of simple nested dataclass with meta share."""
fory = Fory(xlang=True, compatible=True)
# Register the dataclass
fory.register_type(SimpleNestedDataClass)
obj = SimpleNestedDataClass(value=42, name="test")
buffer = fory.serialize(obj)
deserialized = fory.deserialize(buffer)
assert deserialized.value == obj.value
assert deserialized.name == obj.name
def test_serialization_without_meta_share(self):
"""Test that serialization works without meta share mode."""
fory = Fory(xlang=True, compatible=False)
# Register the dataclass
fory.register_type(SimpleDataClass)
obj = SimpleDataClass(name="test", age=25, active=True)
buffer = fory.serialize(obj)
deserialized = fory.deserialize(buffer)
assert deserialized.name == obj.name
assert deserialized.age == obj.age
assert deserialized.active == obj.active
def test_schema_evolution_more_fields(self):
# Serialize with original schema
fory1 = Fory(xlang=True, compatible=True)
fory1.register_type(SimpleDataClass)
obj = SimpleDataClass(name="test", age=25, active=True)
buffer = fory1.serialize(obj)
# Deserialize with extended schema (more fields)
fory2 = Fory(xlang=True, compatible=True)
fory2.register_type(ExtendedDataClass)
deserialized = fory2.deserialize(buffer)
# Current behavior: deserialized object is of the new registered type
assert isinstance(deserialized, ExtendedDataClass)
assert deserialized.name == obj.name
assert deserialized.age == obj.age
assert deserialized.active == obj.active
assert not hasattr(deserialized, "email")
def test_schema_evolution_fewer_fields(self):
# Serialize with original schema
fory1 = Fory(xlang=True, compatible=True)
fory1.register_type(SimpleDataClass)
obj = SimpleDataClass(name="test", age=25, active=True)
buffer = fory1.serialize(obj)
# Deserialize with reduced schema (fewer fields)
fory2 = Fory(xlang=True, compatible=True)
fory2.register_type(ReducedDataClass)
deserialized = fory2.deserialize(buffer)
assert isinstance(deserialized, ReducedDataClass)
assert deserialized.name == obj.name
assert deserialized.age == obj.age
# The missing field should not be present
assert not hasattr(deserialized, "active")
def test_schema_inconsistent_nested_struct(self):
"""Test schema inconsistency with nested struct types."""
# Serialize with original schema
fory1 = Fory(xlang=True, compatible=True)
fory1.register_type(NestedStructClass)
fory1.register_type(SimpleNestedDataClass)
obj = NestedStructClass(name="test", nested=SimpleNestedDataClass(value=42, name="nested_test"))
buffer = fory1.serialize(obj)
# Deserialize with inconsistent schema (different nested type)
fory2 = Fory(xlang=True, compatible=True)
fory2.register_type(NestedStructClassInconsistent)
fory2.register_type(ExtendedDataClass)
# This should handle the schema inconsistency gracefully
deserialized = fory2.deserialize(buffer)
assert isinstance(deserialized, NestedStructClassInconsistent)
assert deserialized.name == obj.name
# The nested field type has changed, so we expect different behavior
assert hasattr(deserialized, "nested")
def test_schema_inconsistent_list_fields(self):
"""Test schema inconsistency with List field types."""
# Serialize with original schema
fory1 = Fory(xlang=True, compatible=True)
fory1.register_type(ListFieldsClass)
obj = ListFieldsClass(name="test", int_list=[1, 2, 3], str_list=["a", "b", "c"])
buffer = fory1.serialize(obj)
# Deserialize with inconsistent schema (swapped List types)
fory2 = Fory(xlang=True, compatible=True)
fory2.register_type(ListFieldsClassInconsistent)
# This should handle the schema inconsistency gracefully
deserialized = fory2.deserialize(buffer)
assert isinstance(deserialized, ListFieldsClassInconsistent)
assert deserialized.name == obj.name
# The field types have been swapped, so we expect different behavior
assert hasattr(deserialized, "int_list")
assert hasattr(deserialized, "str_list")
def test_schema_inconsistent_dict_fields(self):
"""Test schema inconsistency with Dict field types."""
# Serialize with original schema
fory1 = Fory(xlang=True, compatible=True)
fory1.register_type(DictFieldsClass)
obj = DictFieldsClass(
name="test",
int_dict={"key1": 1, "key2": 2},
str_dict={"key1": "value1", "key2": "value2"},
)
buffer = fory1.serialize(obj)
# Deserialize with inconsistent schema (swapped Dict value types)
fory2 = Fory(xlang=True, compatible=True)
fory2.register_type(DictFieldsClassInconsistent)
# This should handle the schema inconsistency gracefully
deserialized = fory2.deserialize(buffer)
assert isinstance(deserialized, DictFieldsClassInconsistent)
assert deserialized.name == obj.name
# The field value types have been swapped, so we expect different behavior
assert hasattr(deserialized, "int_dict")
assert hasattr(deserialized, "str_dict")