1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import datetime
import inspect
import json
import re
import pytest
import orjson
SIMPLE_TYPES = (1, 1.0, -1, None, "str", True, False)
LOADS_RECURSION_LIMIT = 1024
def default(obj):
return str(obj)
class TestApi:
def test_loads_trailing(self):
"""
loads() handles trailing whitespace
"""
assert orjson.loads("{}\n\t ") == {}
def test_loads_trailing_invalid(self):
"""
loads() handles trailing invalid
"""
pytest.raises(orjson.JSONDecodeError, orjson.loads, "{}\n\t a")
def test_simple_json(self):
"""
dumps() equivalent to json on simple types
"""
for obj in SIMPLE_TYPES:
assert orjson.dumps(obj) == json.dumps(obj).encode("utf-8")
def test_simple_round_trip(self):
"""
dumps(), loads() round trip on simple types
"""
for obj in SIMPLE_TYPES:
assert orjson.loads(orjson.dumps(obj)) == obj
def test_loads_type(self):
"""
loads() invalid type
"""
for val in (1, 3.14, [], {}, None): # type: ignore
pytest.raises(orjson.JSONDecodeError, orjson.loads, val)
def test_loads_recursion_partial(self):
"""
loads() recursion limit partial
"""
pytest.raises(orjson.JSONDecodeError, orjson.loads, "[" * (1024 * 1024))
def test_loads_recursion_valid_limit_array(self):
"""
loads() recursion limit at limit array
"""
n = LOADS_RECURSION_LIMIT + 1
value = b"[" * n + b"]" * n
pytest.raises(orjson.JSONDecodeError, orjson.loads, value)
def test_loads_recursion_valid_limit_object(self):
"""
loads() recursion limit at limit object
"""
n = LOADS_RECURSION_LIMIT
value = b'{"key":' * n + b'{"key":true}' + b"}" * n
pytest.raises(orjson.JSONDecodeError, orjson.loads, value)
def test_loads_recursion_valid_limit_mixed(self):
"""
loads() recursion limit at limit mixed
"""
n = LOADS_RECURSION_LIMIT
value = b"[" b'{"key":' * n + b'{"key":true}' + b"}" * n + b"]"
pytest.raises(orjson.JSONDecodeError, orjson.loads, value)
def test_loads_recursion_valid_excessive_array(self):
"""
loads() recursion limit excessively high value
"""
n = 10000000
value = b"[" * n + b"]" * n
pytest.raises(orjson.JSONDecodeError, orjson.loads, value)
def test_loads_recursion_valid_limit_array_pretty(self):
"""
loads() recursion limit at limit array pretty
"""
n = LOADS_RECURSION_LIMIT + 1
value = b"[\n " * n + b"]" * n
pytest.raises(orjson.JSONDecodeError, orjson.loads, value)
def test_loads_recursion_valid_limit_object_pretty(self):
"""
loads() recursion limit at limit object pretty
"""
n = LOADS_RECURSION_LIMIT
value = b'{\n "key":' * n + b'{"key":true}' + b"}" * n
pytest.raises(orjson.JSONDecodeError, orjson.loads, value)
def test_loads_recursion_valid_limit_mixed_pretty(self):
"""
loads() recursion limit at limit mixed pretty
"""
n = LOADS_RECURSION_LIMIT
value = b"[\n " b'{"key":' * n + b'{"key":true}' + b"}" * n + b"]"
pytest.raises(orjson.JSONDecodeError, orjson.loads, value)
def test_loads_recursion_valid_excessive_array_pretty(self):
"""
loads() recursion limit excessively high value pretty
"""
n = 10000000
value = b"[\n " * n + b"]" * n
pytest.raises(orjson.JSONDecodeError, orjson.loads, value)
def test_version(self):
"""
__version__
"""
assert re.match(r"^\d+\.\d+(\.\d+)?$", orjson.__version__)
def test_valueerror(self):
"""
orjson.JSONDecodeError is a subclass of ValueError
"""
pytest.raises(orjson.JSONDecodeError, orjson.loads, "{")
pytest.raises(ValueError, orjson.loads, "{")
def test_optional_none(self):
"""
dumps() option, default None
"""
assert orjson.dumps([], option=None) == b"[]"
assert orjson.dumps([], default=None) == b"[]"
assert orjson.dumps([], option=None, default=None) == b"[]"
assert orjson.dumps([], None, None) == b"[]"
def test_option_not_int(self):
"""
dumps() option not int or None
"""
with pytest.raises(orjson.JSONEncodeError):
orjson.dumps(True, option=True)
def test_option_invalid_int(self):
"""
dumps() option invalid 64-bit number
"""
with pytest.raises(orjson.JSONEncodeError):
orjson.dumps(True, option=9223372036854775809)
def test_option_range_low(self):
"""
dumps() option out of range low
"""
with pytest.raises(orjson.JSONEncodeError):
orjson.dumps(True, option=-1)
def test_option_range_high(self):
"""
dumps() option out of range high
"""
with pytest.raises(orjson.JSONEncodeError):
orjson.dumps(True, option=1 << 12)
def test_opts_multiple(self):
"""
dumps() multiple option
"""
assert (
orjson.dumps(
[1, datetime.datetime(2000, 1, 1, 2, 3, 4)],
option=orjson.OPT_STRICT_INTEGER | orjson.OPT_NAIVE_UTC,
)
== b'[1,"2000-01-01T02:03:04+00:00"]'
)
def test_default_positional(self):
"""
dumps() positional arg
"""
with pytest.raises(TypeError):
orjson.dumps(__obj={}) # type: ignore
with pytest.raises(TypeError):
orjson.dumps(zxc={}) # type: ignore
def test_default_unknown_kwarg(self):
"""
dumps() unknown kwarg
"""
with pytest.raises(TypeError):
orjson.dumps({}, zxc=default) # type: ignore
def test_default_empty_kwarg(self):
"""
dumps() empty kwarg
"""
assert orjson.dumps(None, **{}) == b"null"
def test_default_twice(self):
"""
dumps() default twice
"""
with pytest.raises(TypeError):
orjson.dumps({}, default, default=default) # type: ignore
def test_option_twice(self):
"""
dumps() option twice
"""
with pytest.raises(TypeError):
orjson.dumps({}, None, orjson.OPT_NAIVE_UTC, option=orjson.OPT_NAIVE_UTC) # type: ignore
def test_option_mixed(self):
"""
dumps() option one arg, one kwarg
"""
class Custom:
def __str__(self):
return "zxc"
assert (
orjson.dumps(
[Custom(), datetime.datetime(2000, 1, 1, 2, 3, 4)],
default,
option=orjson.OPT_NAIVE_UTC,
)
== b'["zxc","2000-01-01T02:03:04+00:00"]'
)
def test_dumps_signature(self):
"""
dumps() valid __text_signature__
"""
assert (
str(inspect.signature(orjson.dumps))
== "(obj, /, default=None, option=None)"
)
inspect.signature(orjson.dumps).bind("str")
inspect.signature(orjson.dumps).bind("str", default=default, option=1)
inspect.signature(orjson.dumps).bind("str", default=None, option=None)
def test_loads_signature(self):
"""
loads() valid __text_signature__
"""
assert str(inspect.signature(orjson.loads)), "(obj == /)"
inspect.signature(orjson.loads).bind("[]")
def test_dumps_module_str(self):
"""
orjson.dumps.__module__ is a str
"""
assert orjson.dumps.__module__ == "orjson"
def test_loads_module_str(self):
"""
orjson.loads.__module__ is a str
"""
assert orjson.loads.__module__ == "orjson"
def test_bytes_buffer(self):
"""
dumps() trigger buffer growing where length is greater than growth
"""
a = "a" * 900
b = "b" * 4096
c = "c" * 4096 * 4096
assert orjson.dumps([a, b, c]) == f'["{a}","{b}","{c}"]'.encode("utf-8")
def test_bytes_null_terminated(self):
"""
dumps() PyBytesObject buffer is null-terminated
"""
# would raise ValueError: invalid literal for int() with base 10: b'1596728892'
int(orjson.dumps(1596728892))
|