| from test import support | |
| from test.test_json import PyTest, CTest | |
| class JSONTestObject: | |
| pass | |
| class TestRecursion: | |
| def test_listrecursion(self): | |
| x = [] | |
| x.append(x) | |
| try: | |
| self.dumps(x) | |
| except ValueError as exc: | |
| self.assertEqual(exc.__notes__, ["when serializing list item 0"]) | |
| else: | |
| self.fail("didn't raise ValueError on list recursion") | |
| x = [] | |
| y = [x] | |
| x.append(y) | |
| try: | |
| self.dumps(x) | |
| except ValueError as exc: | |
| self.assertEqual(exc.__notes__, ["when serializing list item 0"]*2) | |
| else: | |
| self.fail("didn't raise ValueError on alternating list recursion") | |
| y = [] | |
| x = [y, y] | |
| # ensure that the marker is cleared | |
| self.dumps(x) | |
| def test_dictrecursion(self): | |
| x = {} | |
| x["test"] = x | |
| try: | |
| self.dumps(x) | |
| except ValueError as exc: | |
| self.assertEqual(exc.__notes__, ["when serializing dict item 'test'"]) | |
| else: | |
| self.fail("didn't raise ValueError on dict recursion") | |
| x = {} | |
| y = {"a": x, "b": x} | |
| # ensure that the marker is cleared | |
| self.dumps(x) | |
| def test_defaultrecursion(self): | |
| class RecursiveJSONEncoder(self.json.JSONEncoder): | |
| recurse = False | |
| def default(self, o): | |
| if o is JSONTestObject: | |
| if self.recurse: | |
| return [JSONTestObject] | |
| else: | |
| return 'JSONTestObject' | |
| return self.json.JSONEncoder.default(o) | |
| enc = RecursiveJSONEncoder() | |
| self.assertEqual(enc.encode(JSONTestObject), '"JSONTestObject"') | |
| enc.recurse = True | |
| try: | |
| enc.encode(JSONTestObject) | |
| except ValueError as exc: | |
| self.assertEqual(exc.__notes__, | |
| ["when serializing list item 0", | |
| "when serializing type object"]) | |
| else: | |
| self.fail("didn't raise ValueError on default recursion") | |
| @support.skip_if_pgo_task # fails during PGO training w/ some stack sizes | |
| @support.run_with_limited_c_stack(500_000) | |
| @support.skip_emscripten_stack_overflow() | |
| @support.skip_wasi_stack_overflow() | |
| def test_highly_nested_objects_decoding(self): | |
| very_deep = 500_000 | |
| # test that loading highly-nested objects doesn't segfault when C | |
| # accelerations are used. See #12017 | |
| with self.assertRaises(RecursionError): | |
| with support.infinite_recursion(): | |
| self.loads('{"a":' * very_deep + '1' + '}' * very_deep) | |
| with self.assertRaises(RecursionError): | |
| with support.infinite_recursion(): | |
| self.loads('{"a":' * very_deep + '[1]' + '}' * very_deep) | |
| with self.assertRaises(RecursionError): | |
| with support.infinite_recursion(): | |
| self.loads('[' * very_deep + '1' + ']' * very_deep) | |
| @support.skip_if_unlimited_stack_size | |
| @support.skip_wasi_stack_overflow() | |
| @support.skip_emscripten_stack_overflow() | |
| @support.requires_resource('cpu') | |
| def test_highly_nested_objects_encoding(self): | |
| # See issue gh-56260 | |
| # With max_depth=2_000 on Linux, the Python implementation takes around | |
| # 2 seconds to raise RecursionError, whereas the C implementation only | |
| # takes around 160 ms. | |
| max_depth = 2_000 | |
| l, d = [], {} | |
| for x in range(max_depth * 10): | |
| l, d = [l], {'k':d} | |
| # Don't use assertRaises(RecursionError): on FreeBSD 16 with a default | |
| # stack size of 512 MiB, the C implementation doesn't raise | |
| # RecursionError. | |
| try: | |
| with support.infinite_recursion(max_depth): | |
| self.dumps(l) | |
| except RecursionError: | |
| pass | |
| try: | |
| with support.infinite_recursion(max_depth): | |
| self.dumps(d) | |
| except RecursionError: | |
| pass | |
| @support.skip_if_huge_c_stack() | |
| @support.skip_emscripten_stack_overflow() | |
| @support.skip_wasi_stack_overflow() | |
| def test_endless_recursion(self): | |
| # See issue gh-56260 | |
| class EndlessJSONEncoder(self.json.JSONEncoder): | |
| def default(self, o): | |
| """If check_circular is False, this will keep adding another list.""" | |
| return [o] | |
| with self.assertRaises(RecursionError): | |
| with support.infinite_recursion(1000): | |
| EndlessJSONEncoder(check_circular=False).encode(5j) | |
| class TestPyRecursion(TestRecursion, PyTest): pass | |
| class TestCRecursion(TestRecursion, CTest): pass |