summaryrefslogtreecommitdiff
path: root/tests/serializers/test_xml.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/serializers/test_xml.py')
-rw-r--r--tests/serializers/test_xml.py55
1 files changed, 54 insertions, 1 deletions
diff --git a/tests/serializers/test_xml.py b/tests/serializers/test_xml.py
index c9df2f2a5b..03462cfed5 100644
--- a/tests/serializers/test_xml.py
+++ b/tests/serializers/test_xml.py
@@ -1,7 +1,10 @@
+import gc
+import time
from xml.dom import minidom
from django.core import serializers
-from django.core.serializers.xml_serializer import DTDForbidden
+from django.core.serializers.xml_serializer import Deserializer, DTDForbidden
+from django.db import models
from django.test import TestCase, TransactionTestCase
from .tests import SerializersTestBase, SerializersTransactionTestBase
@@ -90,6 +93,56 @@ class XmlSerializerTestCase(SerializersTestBase, TestCase):
with self.assertRaises(DTDForbidden):
next(serializers.deserialize("xml", xml))
+ def test_crafted_xml_performance(self):
+ """The time to process invalid inputs is not quadratic."""
+
+ def build_crafted_xml(depth, leaf_text_len):
+ nested_open = "<nested>" * depth
+ nested_close = "</nested>" * depth
+ leaf = "x" * leaf_text_len
+ field_content = f"{nested_open}{leaf}{nested_close}"
+ return f"""
+ <django-objects version="1.0">
+ <object model="contenttypes.contenttype" pk="1">
+ <field name="app_label">{field_content}</field>
+ <field name="model">m</field>
+ </object>
+ </django-objects>
+ """
+
+ def deserialize(crafted_xml):
+ iterator = Deserializer(crafted_xml)
+ gc.collect()
+
+ start_time = time.perf_counter()
+ result = list(iterator)
+ end_time = time.perf_counter()
+
+ self.assertEqual(len(result), 1)
+ self.assertIsInstance(result[0].object, models.Model)
+ return end_time - start_time
+
+ def assertFactor(label, params, factor=2):
+ factors = []
+ prev_time = None
+ for depth, length in params:
+ crafted_xml = build_crafted_xml(depth, length)
+ elapsed = deserialize(crafted_xml)
+ if prev_time is not None:
+ factors.append(elapsed / prev_time)
+ prev_time = elapsed
+
+ with self.subTest(label):
+ # Assert based on the average factor to reduce test flakiness.
+ self.assertLessEqual(sum(factors) / len(factors), factor)
+
+ assertFactor(
+ "varying depth, varying length",
+ [(50, 2000), (100, 4000), (200, 8000), (400, 16000), (800, 32000)],
+ 2,
+ )
+ assertFactor("constant depth, varying length", [(100, 1), (100, 1000)], 2)
+
class XmlSerializerTransactionTestCase(
SerializersTransactionTestBase, TransactionTestCase