This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 72b16e516 fix(python): fix collection null elements read/write (#3149)
72b16e516 is described below
commit 72b16e5162c3af7335d4d08232d690872412afe1
Author: Shawn Yang <[email protected]>
AuthorDate: Wed Jan 14 19:35:03 2026 +0800
fix(python): fix collection null elements read/write (#3149)
## Why?
## What does this PR do?
## Related issues
Closes #3145
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---
python/pyfory/collection.pxi | 89 ++++++--
python/pyfory/collection.py | 44 +++-
python/pyfory/registry.py | 4 +-
python/pyfory/tests/test_collection.py | 376 +++++++++++++++++++++++++++++++++
4 files changed, 489 insertions(+), 24 deletions(-)
diff --git a/python/pyfory/collection.pxi b/python/pyfory/collection.pxi
index 7c335c6e7..45b95654d 100644
--- a/python/pyfory/collection.pxi
+++ b/python/pyfory/collection.pxi
@@ -112,19 +112,23 @@ cdef class CollectionSerializer(Serializer):
cdef c_bool tracking_ref
cdef c_bool has_null
if (collect_flag & COLL_IS_SAME_TYPE) != 0:
- if elem_type is str:
- self._write_string(buffer, value)
- elif serializer is Int64Serializer:
- self._write_int(buffer, value)
- elif elem_type is bool:
- self._write_bool(buffer, value)
- elif serializer is Float64Serializer:
- self._write_float(buffer, value)
- else:
- if (collect_flag & COLL_TRACKING_REF) == 0:
+ if (collect_flag & COLL_HAS_NULL) == 0:
+ if elem_type is str:
+ self._write_string(buffer, value)
+ elif serializer is Int64Serializer:
+ self._write_int(buffer, value)
+ elif elem_type is bool:
+ self._write_bool(buffer, value)
+ elif serializer is Float64Serializer:
+ self._write_float(buffer, value)
+ elif (collect_flag & COLL_TRACKING_REF) == 0:
self._write_same_type_no_ref(buffer, value, elem_typeinfo)
else:
self._write_same_type_ref(buffer, value, elem_typeinfo)
+ elif (collect_flag & COLL_TRACKING_REF) != 0:
+ self._write_same_type_ref(buffer, value, elem_typeinfo)
+ else:
+ self._write_same_type_has_null(buffer, value, elem_typeinfo)
else:
# Check tracking_ref and has_null flags for different types writing
tracking_ref = (collect_flag & COLL_TRACKING_REF) != 0
@@ -248,6 +252,41 @@ cdef class CollectionSerializer(Serializer):
self._add_element(collection_, i, obj)
self.fory.dec_depth()
+ cpdef _write_same_type_has_null(self, Buffer buffer, value, TypeInfo
typeinfo):
+ if self.is_py:
+ for s in value:
+ if s is None:
+ buffer.write_int8(NULL_FLAG)
+ else:
+ buffer.write_int8(NOT_NULL_VALUE_FLAG)
+ typeinfo.serializer.write(buffer, s)
+ else:
+ for s in value:
+ if s is None:
+ buffer.write_int8(NULL_FLAG)
+ else:
+ buffer.write_int8(NOT_NULL_VALUE_FLAG)
+ typeinfo.serializer.xwrite(buffer, s)
+
+ cpdef _read_same_type_has_null(self, Buffer buffer, int64_t len_, object
collection_, TypeInfo typeinfo):
+ cdef int8_t flag
+ self.fory.inc_depth()
+ if self.is_py:
+ for i in range(len_):
+ flag = buffer.read_int8()
+ if flag == NULL_FLAG:
+ self._add_element(collection_, i, None)
+ else:
+ self._add_element(collection_, i,
typeinfo.serializer.read(buffer))
+ else:
+ for i in range(len_):
+ flag = buffer.read_int8()
+ if flag == NULL_FLAG:
+ self._add_element(collection_, i, None)
+ else:
+ self._add_element(collection_, i,
typeinfo.serializer.xread(buffer))
+ self.fory.dec_depth()
+
cpdef _write_same_type_ref(self, Buffer buffer, value, TypeInfo typeinfo):
cdef MapRefResolver ref_resolver = self.ref_resolver
cdef TypeResolver type_resolver = self.type_resolver
@@ -319,10 +358,14 @@ cdef class ListSerializer(CollectionSerializer):
elif type_id == <int32_t>TypeId.FLOAT64:
self._read_float(buffer, len_, list_)
return list_
- if (collect_flag & COLL_TRACKING_REF) == 0:
- self._read_same_type_no_ref(buffer, len_, list_, typeinfo)
- else:
+ elif (collect_flag & COLL_TRACKING_REF) == 0:
+ self._read_same_type_no_ref(buffer, len_, list_, typeinfo)
+ else:
+ self._read_same_type_ref(buffer, len_, list_, typeinfo)
+ elif (collect_flag & COLL_TRACKING_REF) != 0:
self._read_same_type_ref(buffer, len_, list_, typeinfo)
+ else:
+ self._read_same_type_has_null(buffer, len_, list_, typeinfo)
else:
self.fory.inc_depth()
# Check tracking_ref and has_null flags for different types
handling
@@ -437,10 +480,14 @@ cdef class TupleSerializer(CollectionSerializer):
if type_id == <int32_t>TypeId.FLOAT64:
self._read_float(buffer, len_, tuple_)
return tuple_
- if (collect_flag & COLL_TRACKING_REF) == 0:
- self._read_same_type_no_ref(buffer, len_, tuple_, typeinfo)
- else:
+ elif (collect_flag & COLL_TRACKING_REF) == 0:
+ self._read_same_type_no_ref(buffer, len_, tuple_, typeinfo)
+ else:
+ self._read_same_type_ref(buffer, len_, tuple_, typeinfo)
+ elif (collect_flag & COLL_TRACKING_REF) != 0:
self._read_same_type_ref(buffer, len_, tuple_, typeinfo)
+ else:
+ self._read_same_type_has_null(buffer, len_, tuple_, typeinfo)
else:
self.fory.inc_depth()
# Check tracking_ref and has_null flags for different types
handling
@@ -530,10 +577,14 @@ cdef class SetSerializer(CollectionSerializer):
if type_id == <int32_t>TypeId.FLOAT64:
self._read_float(buffer, len_, instance)
return instance
- if (collect_flag & COLL_TRACKING_REF) == 0:
- self._read_same_type_no_ref(buffer, len_, instance, typeinfo)
- else:
+ elif (collect_flag & COLL_TRACKING_REF) == 0:
+ self._read_same_type_no_ref(buffer, len_, instance,
typeinfo)
+ else:
+ self._read_same_type_ref(buffer, len_, instance, typeinfo)
+ elif (collect_flag & COLL_TRACKING_REF) != 0:
self._read_same_type_ref(buffer, len_, instance, typeinfo)
+ else:
+ self._read_same_type_has_null(buffer, len_, instance, typeinfo)
else:
self.fory.inc_depth()
# Check tracking_ref and has_null flags for different types
handling
diff --git a/python/pyfory/collection.py b/python/pyfory/collection.py
index f8a026ddc..ac4efd6c5 100644
--- a/python/pyfory/collection.py
+++ b/python/pyfory/collection.py
@@ -114,10 +114,12 @@ class CollectionSerializer(Serializer):
return
collect_flag, typeinfo = self.write_header(buffer, value)
if (collect_flag & COLL_IS_SAME_TYPE) != 0:
- if (collect_flag & COLL_TRACKING_REF) == 0:
+ if (collect_flag & COLL_TRACKING_REF) != 0:
+ self._write_same_type_ref(buffer, value, typeinfo)
+ elif (collect_flag & COLL_HAS_NULL) == 0:
self._write_same_type_no_ref(buffer, value, typeinfo)
else:
- self._write_same_type_ref(buffer, value, typeinfo)
+ self._write_same_type_has_null(buffer, value, typeinfo)
else:
self._write_different_types(buffer, value, collect_flag)
@@ -129,6 +131,22 @@ class CollectionSerializer(Serializer):
for s in value:
typeinfo.serializer.xwrite(buffer, s)
+ def _write_same_type_has_null(self, buffer, value, typeinfo):
+ if self.is_py:
+ for s in value:
+ if s is None:
+ buffer.write_int8(NULL_FLAG)
+ else:
+ buffer.write_int8(NOT_NULL_VALUE_FLAG)
+ typeinfo.serializer.write(buffer, s)
+ else:
+ for s in value:
+ if s is None:
+ buffer.write_int8(NULL_FLAG)
+ else:
+ buffer.write_int8(NOT_NULL_VALUE_FLAG)
+ typeinfo.serializer.xwrite(buffer, s)
+
def _write_same_type_ref(self, buffer, value, typeinfo):
if self.is_py:
for s in value:
@@ -186,10 +204,12 @@ class CollectionSerializer(Serializer):
typeinfo = self.type_resolver.read_typeinfo(buffer)
else:
typeinfo = self.elem_typeinfo
- if (collect_flag & COLL_TRACKING_REF) == 0:
+ if (collect_flag & COLL_TRACKING_REF) != 0:
+ self._read_same_type_ref(buffer, len_, collection_, typeinfo)
+ elif (collect_flag & COLL_HAS_NULL) == 0:
self._read_same_type_no_ref(buffer, len_, collection_,
typeinfo)
else:
- self._read_same_type_ref(buffer, len_, collection_, typeinfo)
+ self._read_same_type_has_null(buffer, len_, collection_,
typeinfo)
else:
self._read_different_types(buffer, len_, collection_, collect_flag)
return collection_
@@ -210,6 +230,22 @@ class CollectionSerializer(Serializer):
self._add_element(collection_,
typeinfo.serializer.xread(buffer))
self.fory.dec_depth()
+ def _read_same_type_has_null(self, buffer, len_, collection_, typeinfo):
+ self.fory.inc_depth()
+ if self.is_py:
+ for _ in range(len_):
+ if buffer.read_int8() == NULL_FLAG:
+ self._add_element(collection_, None)
+ else:
+ self._add_element(collection_,
typeinfo.serializer.read(buffer))
+ else:
+ for _ in range(len_):
+ if buffer.read_int8() == NULL_FLAG:
+ self._add_element(collection_, None)
+ else:
+ self._add_element(collection_,
typeinfo.serializer.xread(buffer))
+ self.fory.dec_depth()
+
def _read_same_type_ref(self, buffer, len_, collection_, typeinfo):
self.fory.inc_depth()
for _ in range(len_):
diff --git a/python/pyfory/registry.py b/python/pyfory/registry.py
index daec5fc9b..79c16311c 100644
--- a/python/pyfory/registry.py
+++ b/python/pyfory/registry.py
@@ -279,7 +279,9 @@ class TypeResolver:
def _initialize_common(self):
register = functools.partial(self._register_type, internal=True)
- register(None, type_id=TypeId.UNKNOWN, serializer=NoneSerializer)
+ register(type(None), type_id=TypeId.NONE, serializer=NoneSerializer)
+ # Also register None value to map to type(None) for get_typeinfo(None)
calls
+ self._types_info[None] = self._types_info[type(None)]
register(bool, type_id=TypeId.BOOL, serializer=BooleanSerializer)
# Signed integers
# Note: int32/int64 use VARINT32/VARINT64 for xlang compatibility
(matches Java/Rust)
diff --git a/python/pyfory/tests/test_collection.py
b/python/pyfory/tests/test_collection.py
new file mode 100644
index 000000000..93cb8ff2a
--- /dev/null
+++ b/python/pyfory/tests/test_collection.py
@@ -0,0 +1,376 @@
+# 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.
+
+"""
+Test cases for collection serialization edge cases including None handling.
+"""
+
+from dataclasses import dataclass
+from typing import List, Dict, Set, Optional
+
+import pytest
+
+import pyfory
+
+
+class TestListWithNone:
+ """Test list serialization with None elements."""
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_list_with_single_none(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = [None]
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_list_with_multiple_none(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = [None, None, None]
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_list_with_mixed_none_and_int(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = [1, None, 2, None, 3]
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_list_with_mixed_none_and_string(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = ["a", None, "b", None]
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_nested_list_with_none(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = [5, [5, None]]
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_deeply_nested_list_with_none(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = [1, [2, [3, None, [4, None]]]]
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+
+class TestSetWithNone:
+ """Test set serialization with None elements."""
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_set_with_single_none(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = {None}
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_set_with_none_and_values(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = {None, 1, 2, 3}
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_set_with_none_and_strings(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = {None, "a", "b"}
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+
+class TestTupleWithNone:
+ """Test tuple serialization with None elements."""
+
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_tuple_with_single_none(self, ref):
+ # Tuple is Python-only, xlang=False
+ fory = pyfory.Fory(xlang=False, ref=ref)
+ data = (None,)
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_tuple_with_multiple_none(self, ref):
+ fory = pyfory.Fory(xlang=False, ref=ref)
+ data = (None, None, None)
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_tuple_with_mixed_none(self, ref):
+ fory = pyfory.Fory(xlang=False, ref=ref)
+ data = (None, 1, None, "a")
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_nested_tuple_with_none(self, ref):
+ fory = pyfory.Fory(xlang=False, ref=ref)
+ data = (1, (2, None, (3, None)))
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+
+class TestDictWithNone:
+ """Test dict serialization with None keys/values."""
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_dict_with_none_value(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = {"key": None}
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_dict_with_none_key(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = {None: "value"}
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_dict_with_none_key_and_value(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = {None: None}
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_dict_with_list_containing_none(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = {"a": [None]}
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_dict_with_multiple_none_values(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = {"a": None, "b": None, "c": 1}
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_nested_dict_with_none(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = {"outer": {"inner": None, "list": [1, None, 3]}}
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+
+class TestComplexNestedStructures:
+ """Test complex nested structures with None."""
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_complex_nested_with_none(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = {"a": [1, None, 3], "b": None, "c": [None, None]}
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_list_of_dicts_with_none(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = [{"a": None}, {"b": [None, 1]}, None]
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_dict_of_sets_with_none(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = {"set1": {1, None, 2}, "set2": {None}}
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+
+class TestStructWithCollections:
+ """Test dataclass/struct fields containing collections with None."""
+
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_struct_with_list_containing_none(self, ref):
+ # Struct tests are Python-only
+ @dataclass
+ class MyStruct:
+ items: List[Optional[int]]
+
+ fory = pyfory.Fory(xlang=False, ref=ref)
+ fory.register(MyStruct)
+ data = MyStruct(items=[1, None, 3])
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_struct_with_dict_field(self, ref):
+ @dataclass
+ class MyStruct:
+ mapping: Dict[Optional[str], Optional[int]]
+
+ fory = pyfory.Fory(xlang=False, ref=ref)
+ fory.register(MyStruct)
+ # Test normal dict
+ data = MyStruct(mapping={"a": 1, "b": 2})
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+ # Test with None value
+ data2 = MyStruct(mapping={"a": None, "b": 2})
+ result2 = fory.loads(fory.dumps(data2))
+ assert result2 == data2
+ # Test with None key
+ data3 = MyStruct(mapping={None: 1, "b": 2})
+ result3 = fory.loads(fory.dumps(data3))
+ assert result3 == data3
+ # Test with both None key and value
+ data4 = MyStruct(mapping={None: None, "a": 1})
+ result4 = fory.loads(fory.dumps(data4))
+ assert result4 == data4
+
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_struct_with_set_field(self, ref):
+ @dataclass
+ class MyStruct:
+ items: Set[Optional[int]]
+
+ fory = pyfory.Fory(xlang=False, ref=ref)
+ fory.register(MyStruct)
+ # Test normal set
+ data = MyStruct(items={1, 2, 3})
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+ # Test set with None
+ data2 = MyStruct(items={1, None, 3})
+ result2 = fory.loads(fory.dumps(data2))
+ assert result2 == data2
+ # Test set with only None
+ data3 = MyStruct(items={None})
+ result3 = fory.loads(fory.dumps(data3))
+ assert result3 == data3
+
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_struct_with_nested_collections(self, ref):
+ @dataclass
+ class MyStruct:
+ nested: List[Optional[Dict[Optional[str],
Optional[List[Optional[int]]]]]]
+
+ fory = pyfory.Fory(xlang=False, ref=ref)
+ fory.register(MyStruct)
+ # Test normal nested structure
+ data = MyStruct(nested=[{"a": [1, 2]}, {"b": [3, 4]}])
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+ # Test with None in outer list
+ data2 = MyStruct(nested=[{"a": [1, 2]}, None])
+ result2 = fory.loads(fory.dumps(data2))
+ assert result2 == data2
+ # Test with None key in dict
+ data3 = MyStruct(nested=[{None: [1, 2], "b": [3]}])
+ result3 = fory.loads(fory.dumps(data3))
+ assert result3 == data3
+ # Test with None value in dict (list is None)
+ data4 = MyStruct(nested=[{"a": None, "b": [1, 2]}])
+ result4 = fory.loads(fory.dumps(data4))
+ assert result4 == data4
+ # Test with None in inner list
+ data5 = MyStruct(nested=[{"a": [1, None, 3]}])
+ result5 = fory.loads(fory.dumps(data5))
+ assert result5 == data5
+ # Test deeply nested None
+ data6 = MyStruct(nested=[None, {None: None}, {"a": [None]}])
+ result6 = fory.loads(fory.dumps(data6))
+ assert result6 == data6
+
+
+class TestEdgeCases:
+ """Test edge cases for collection serialization."""
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_empty_list(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = []
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_empty_set(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = set()
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_empty_dict(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = {}
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_empty_tuple(self, ref):
+ # Tuple is Python-only
+ fory = pyfory.Fory(xlang=False, ref=ref)
+ data = ()
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_single_element_collections(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ for data in [[1], {1}, {"a": 1}]:
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_large_list_with_none(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = [i if i % 3 != 0 else None for i in range(100)]
+ result = fory.loads(fory.dumps(data))
+ assert result == data
+
+ @pytest.mark.parametrize("xlang", [False, True])
+ @pytest.mark.parametrize("ref", [False, True])
+ def test_list_with_different_types_and_none(self, xlang, ref):
+ fory = pyfory.Fory(xlang=xlang, ref=ref)
+ data = [1, "string", 3.14, None, True, [1, 2], {"a": 1}]
+ result = fory.loads(fory.dumps(data))
+ assert result == data
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]