Skip to content

Commit 7edb8ca

Browse files
committed
Increase test coverage.
1 parent 4016687 commit 7edb8ca

File tree

3 files changed

+128
-0
lines changed

3 files changed

+128
-0
lines changed

test/test_numpy.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,29 @@ def test_numpy_datetime_nat(self):
10761076
with pytest.raises(orjson.JSONEncodeError):
10771077
orjson.dumps([numpy.datetime64("NaT")], option=orjson.OPT_SERIALIZE_NUMPY)
10781078

1079+
def test_numpy_array_d0_float16(self):
1080+
array = numpy.array(numpy.float16(1.5))
1081+
assert orjson.dumps(array, option=orjson.OPT_SERIALIZE_NUMPY) == b'1.5'
1082+
1083+
def test_numpy_array_d0_nan_inf(self):
1084+
assert orjson.dumps(numpy.array(numpy.nan), option=orjson.OPT_SERIALIZE_NUMPY) == b'NaN'
1085+
assert orjson.dumps(numpy.array(numpy.inf), option=orjson.OPT_SERIALIZE_NUMPY) == b'Infinity'
1086+
assert orjson.dumps(numpy.array(-numpy.inf), option=orjson.OPT_SERIALIZE_NUMPY) == b'-Infinity'
1087+
1088+
def test_numpy_array_d0_unsigned(self):
1089+
for dtype in [numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]:
1090+
array = numpy.array(42, dtype=dtype)
1091+
assert orjson.loads(orjson.dumps(array, option=orjson.OPT_SERIALIZE_NUMPY)) == 42
1092+
1093+
def test_numpy_array_d0_signed(self):
1094+
for dtype in [numpy.int8, numpy.int16, numpy.int32]:
1095+
array = numpy.array(-7, dtype=dtype)
1096+
assert orjson.loads(orjson.dumps(array, option=orjson.OPT_SERIALIZE_NUMPY)) == -7
1097+
1098+
def test_numpy_array_d0_datetime64(self):
1099+
array = numpy.array(numpy.datetime64("2021-01-01"))
1100+
assert orjson.dumps(array, option=orjson.OPT_SERIALIZE_NUMPY) == b'"2021-01-01T00:00:00"'
1101+
10791102
def test_numpy_repeated(self):
10801103
data = numpy.array([[[1, 2], [3, 4], [5, 6], [7, 8]]], numpy.int64) # type: ignore
10811104
for _ in range(3):

test/test_pytorch.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
from unittest.mock import MagicMock
23

34
import orjson
45
import pytest
@@ -123,3 +124,67 @@ def test_tensor_special_values(self):
123124
orjson.dumps(data, option=orjson.OPT_SERIALIZE_NUMPY),
124125
b'{"nan":NaN,"inf":Infinity,"neg_inf":-Infinity,"mixed":[1.0,NaN,Infinity,-Infinity]}'
125126
)
127+
128+
def test_tensor_in_list(self):
129+
"""PyTorch tensor as element in a Python list"""
130+
assert orjson.dumps([torch.tensor([1, 2])], option=orjson.OPT_SERIALIZE_NUMPY) == b'[[1,2]]'
131+
132+
def test_tensor_3d(self):
133+
"""3D tensor"""
134+
tensor = torch.zeros(2, 3, 4)
135+
result = orjson.loads(orjson.dumps(tensor, option=orjson.OPT_SERIALIZE_NUMPY))
136+
assert len(result) == 2 and len(result[0]) == 3 and len(result[0][0]) == 4
137+
138+
def test_tensor_dtypes(self):
139+
"""Various tensor dtypes"""
140+
for dtype in [torch.float16, torch.float64, torch.int8, torch.int16, torch.int32]:
141+
tensor = torch.tensor([1, 2, 3], dtype=dtype)
142+
result = orjson.loads(orjson.dumps(tensor, option=orjson.OPT_SERIALIZE_NUMPY))
143+
for i, v in enumerate(result):
144+
assert abs(v - [1, 2, 3][i]) < 0.01
145+
146+
def test_non_torch_duck_type(self):
147+
"""Object with numpy/cpu/detach but __module__ not 'torch' is not treated as tensor"""
148+
class FakeTensor:
149+
def numpy(self): return [1, 2]
150+
def cpu(self): return self
151+
def detach(self): return self
152+
with self.assertRaises(orjson.JSONEncodeError):
153+
orjson.dumps(FakeTensor(), option=orjson.OPT_SERIALIZE_NUMPY)
154+
155+
def test_magicmock_not_tensor(self):
156+
"""MagicMock not detected as PyTorch tensor (post4 fix)"""
157+
with self.assertRaises(orjson.JSONEncodeError):
158+
orjson.dumps(MagicMock(), option=orjson.OPT_SERIALIZE_NUMPY)
159+
160+
def test_tensor_pretty(self):
161+
"""PyTorch tensor with OPT_INDENT_2"""
162+
tensor = torch.tensor([[1, 2], [3, 4]])
163+
result = orjson.dumps(tensor, option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_INDENT_2)
164+
assert result == b'[\n [\n 1,\n 2\n ],\n [\n 3,\n 4\n ]\n]'
165+
166+
def test_tensor_conversion_failure(self):
167+
"""Sparse tensor fails numpy conversion - PyTorchTensorConversion error"""
168+
t = torch.sparse_coo_tensor(torch.tensor([[0, 1]]), torch.tensor([1.0, 2.0]), (3,))
169+
with self.assertRaises(orjson.JSONEncodeError) as cm:
170+
orjson.dumps(t, option=orjson.OPT_SERIALIZE_NUMPY)
171+
assert "failed to convert PyTorch tensor to numpy array" in str(cm.exception)
172+
173+
def test_tensor_conversion_failure_with_default(self):
174+
"""Sparse tensor with default callback falls back to default"""
175+
t = torch.sparse_coo_tensor(torch.tensor([[0, 1]]), torch.tensor([1.0, 2.0]), (3,))
176+
result = orjson.dumps(t, option=orjson.OPT_SERIALIZE_NUMPY, default=lambda x: "fallback")
177+
assert result == b'"fallback"'
178+
179+
def test_tensor_unsupported_numpy_dtype(self):
180+
"""Complex tensor: numpy() succeeds but numpy dtype is unsupported"""
181+
tensor = torch.tensor([1+2j, 3+4j])
182+
with self.assertRaises(orjson.JSONEncodeError) as cm:
183+
orjson.dumps(tensor, option=orjson.OPT_SERIALIZE_NUMPY)
184+
assert "unsupported datatype in numpy array" in str(cm.exception)
185+
186+
def test_tensor_unsupported_numpy_dtype_with_default(self):
187+
"""Complex tensor with default: falls back to default via numpy unsupported path"""
188+
tensor = torch.tensor([1+2j, 3+4j])
189+
result = orjson.dumps(tensor, option=orjson.OPT_SERIALIZE_NUMPY, default=lambda x: str(x))
190+
assert len(result) > 0

test/test_type.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,46 @@ def test_tuple(self):
548548
assert orjson.dumps(obj) == ref.encode("utf-8")
549549
assert orjson.loads(ref) == list(obj)
550550

551+
def test_nan_inf_in_list(self):
552+
"""NaN/Infinity in a list"""
553+
assert orjson.dumps([1.0, float("nan"), float("inf"), float("-inf")]) == b'[1.0,NaN,Infinity,-Infinity]'
554+
555+
def test_nan_inf_in_dict_value(self):
556+
"""NaN/Infinity as dict values"""
557+
assert orjson.dumps({"a": float("nan")}) == b'{"a":NaN}'
558+
assert orjson.dumps({"b": float("inf")}) == b'{"b":Infinity}'
559+
assert orjson.dumps({"c": float("-inf")}) == b'{"c":-Infinity}'
560+
561+
def test_nan_inf_pretty(self):
562+
"""NaN/Infinity with OPT_INDENT_2"""
563+
assert orjson.dumps({"val": float("nan"), "inf": float("inf")}, option=orjson.OPT_INDENT_2) == b'{\n "val": NaN,\n "inf": Infinity\n}'
564+
565+
def test_nan_inf_roundtrip(self):
566+
"""NaN/Infinity roundtrip through dumps/loads"""
567+
assert math.isnan(orjson.loads(orjson.dumps(float("nan"))))
568+
assert orjson.loads(orjson.dumps(float("inf"))) == float("inf")
569+
assert orjson.loads(orjson.dumps(float("-inf"))) == float("-inf")
570+
571+
def test_nan_loads_top_level(self):
572+
"""NaN/Infinity as top-level JSON values"""
573+
assert math.isnan(orjson.loads("NaN"))
574+
assert orjson.loads("Infinity") == float("inf")
575+
assert orjson.loads("-Infinity") == float("-inf")
576+
577+
def test_nan_loads_in_object(self):
578+
"""NaN/Infinity inside JSON objects"""
579+
result = orjson.loads('{"a":NaN,"b":Infinity,"c":-Infinity}')
580+
assert math.isnan(result["a"])
581+
assert result["b"] == float("inf")
582+
assert result["c"] == float("-inf")
583+
584+
def test_nan_loads_bytes(self):
585+
"""NaN/Infinity from bytes, bytearray, memoryview"""
586+
for val in [b"[NaN]", bytearray(b"[NaN]"), memoryview(b"[NaN]")]:
587+
assert math.isnan(orjson.loads(val)[0])
588+
for val in [b"[Infinity]", bytearray(b"[Infinity]"), memoryview(b"[Infinity]")]:
589+
assert orjson.loads(val)[0] == float("inf")
590+
551591
def test_object(self):
552592
"""
553593
object() dumps()

0 commit comments

Comments
 (0)