Skip to content

Commit cc9cf09

Browse files
committed
gh-142145: Avoid timing measurements in quadratic behavior test
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.
1 parent f783cc3 commit cc9cf09

File tree

1 file changed

+32
-18
lines changed

1 file changed

+32
-18
lines changed

Lib/test/test_minidom.py

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -174,27 +174,41 @@ def testAppendChild(self):
174174
self.assertEqual(dom.documentElement.childNodes[-1].data, "Hello")
175175
dom.unlink()
176176

177-
@support.requires_resource('cpu')
178177
def testAppendChildNoQuadraticComplexity(self):
178+
# Don't use wall-clock timing (too flaky). Instead count a proxy for the
179+
# old quadratic behavior: repeated attribute access, such as of
180+
# parentNode/nodeType during document-membership checks.
179181
impl = getDOMImplementation()
180182

181-
newdoc = impl.createDocument(None, "some_tag", None)
182-
top_element = newdoc.documentElement
183-
children = [newdoc.createElement(f"child-{i}") for i in range(1, 2 ** 15 + 1)]
184-
element = top_element
185-
186-
start = time.monotonic()
187-
for child in children:
188-
element.appendChild(child)
189-
element = child
190-
end = time.monotonic()
191-
192-
# This example used to take at least 30 seconds.
193-
# Conservative assertion due to the wide variety of systems and
194-
# build configs timing based tests wind up run under.
195-
# A --with-address-sanitizer --with-pydebug build on a rpi5 still
196-
# completes this loop in <0.5 seconds.
197-
self.assertLess(end - start, 4)
183+
def work(n):
184+
doc = impl.createDocument(None, "some_tag", None)
185+
element = doc.documentElement
186+
total_calls = 0
187+
188+
def getattribute_counter(self, attr):
189+
nonlocal total_calls
190+
total_calls += 1
191+
return object.__getattribute__(self, attr)
192+
193+
with support.swap_attr(Element, "__getattribute__", getattribute_counter):
194+
for _ in range(n):
195+
child = doc.createElement("child")
196+
element.appendChild(child)
197+
element = child
198+
return total_calls
199+
200+
# Doubling N should not ~quadruple the work.
201+
w1 = work(1024)
202+
w2 = work(2048)
203+
w3 = work(4096)
204+
205+
self.assertGreater(w1, 0)
206+
r1 = w2 / w1
207+
r2 = w3 / w2
208+
self.assertLess(
209+
max(r1, r2), 3.2,
210+
msg=f"Possible quadratic behavior: work={w1,w2,w3} ratios={r1,r2}"
211+
)
198212

199213
def testSetAttributeNodeWithoutOwnerDocument(self):
200214
# regression test for gh-142754

0 commit comments

Comments
 (0)