]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-142145: Avoid timing measurements in quadratic behavior test (gh-143105)
authorSam Gross <colesbury@gmail.com>
Wed, 24 Dec 2025 13:01:45 +0000 (08:01 -0500)
committerGitHub <noreply@github.com>
Wed, 24 Dec 2025 13:01:45 +0000 (08:01 -0500)
Count the number of Element attribute accesses as a proxy for work done.
With double the amount of work, a ratio of 2.0 indicates linear scaling
and 4.0 quadratic scaling. Use 3.2 as an intermediate threshold.

Lib/test/test_minidom.py

index 69fae957ec7fc9c1e9d15fba344b9de6238b93aa..46249e5138aed528b908f423dd8463bd5aecf752 100644 (file)
@@ -2,7 +2,6 @@
 
 import copy
 import pickle
-import time
 import io
 from test import support
 import unittest
@@ -178,23 +177,36 @@ class MinidomTest(unittest.TestCase):
     def testAppendChildNoQuadraticComplexity(self):
         impl = getDOMImplementation()
 
-        newdoc = impl.createDocument(None, "some_tag", None)
-        top_element = newdoc.documentElement
-        children = [newdoc.createElement(f"child-{i}") for i in range(1, 2 ** 15 + 1)]
-        element = top_element
-
-        start = time.monotonic()
-        for child in children:
-            element.appendChild(child)
-            element = child
-        end = time.monotonic()
-
-        # This example used to take at least 30 seconds.
-        # Conservative assertion due to the wide variety of systems and
-        # build configs timing based tests wind up run under.
-        # A --with-address-sanitizer --with-pydebug build on a rpi5 still
-        # completes this loop in <0.5 seconds.
-        self.assertLess(end - start, 4)
+        def work(n):
+            doc = impl.createDocument(None, "some_tag", None)
+            element = doc.documentElement
+            total_calls = 0
+
+            # Count attribute accesses as a proxy for work done
+            def getattribute_counter(self, attr):
+                nonlocal total_calls
+                total_calls += 1
+                return object.__getattribute__(self, attr)
+
+            with support.swap_attr(Element, "__getattribute__", getattribute_counter):
+                for _ in range(n):
+                    child = doc.createElement("child")
+                    element.appendChild(child)
+                    element = child
+            return total_calls
+
+        # Doubling N should not ~quadruple the work.
+        w1 = work(1024)
+        w2 = work(2048)
+        w3 = work(4096)
+
+        self.assertGreater(w1, 0)
+        r1 = w2 / w1
+        r2 = w3 / w2
+        self.assertLess(
+            max(r1, r2), 3.2,
+            msg=f"Possible quadratic behavior: work={w1,w2,w3} ratios={r1,r2}"
+        )
 
     def testSetAttributeNodeWithoutOwnerDocument(self):
         # regression test for gh-142754