PureXML/Tests/TreeNodeAdoptingInitTests.swift
Mihaela Mihaljevic cc2e1aaf7c
perf(parse): adopt prepared children when materializing the tree (#293)
Building the mutable TreeNode tree from a parsed value Node ran append for every
child. append -> insert detaches the child from any previous parent, rejects a
self/ancestor cycle, and inserts into a children array that grows element by
element. The children the conversion passes come from element.children.map and
are freshly built and parentless, so all three guards are no-ops and the
incremental growth rebuilds an array that was already prepared.

TreeNode.init(adopting:) adopts the prepared array directly and wires each
child's parent. The tree is identical: the same children in the same order with
the same parent links. The adopting initializer is used only by the value-Node
conversion; every public factory keeps the detaching, cycle-checking append path
for externally supplied children that may already live in a tree.

Measured parse ~12% faster on the 20k-item generated corpus via a same-state
A/B (7.7x to ~6.5x slower than libxml2). Verified by TreeNodeAdoptingInitTests
(parent-wiring and post-adoption mutability), the W3C XML conformance corpora,
the Xalan XSLT gold-output corpus, the full suite, the WASM build, and an
adversarial equivalence review.
2026-06-25 13:02:20 +02:00

71 lines
3.0 KiB
Swift

import Testing
@testable import PureXML
/// `parseTree` materializes the mutable tree through `TreeNode.init(adopting:)`,
/// which adopts a prepared array of freshly built children and wires their
/// parents directly instead of appending them one at a time. These pin that the
/// adopted tree is indistinguishable from one built through `append`: every
/// child's `parent` points back to its container, sibling order is preserved,
/// and the tree stays correctly mutable afterwards (so the bypassed `append`
/// bookkeeping left nothing inconsistent).
@Suite("TreeNode adopting-init equivalence")
struct TreeNodeAdoptingInitTests {
private static let source = #"""
<catalog xmlns:m="urn:m">
<item id="i0" m:rank="0"><name>first</name><price>1</price></item>
<item id="i1"><name>second</name><note>a &amp; b</note></item>
</catalog>
"""#
private func assertParentsConsistent(_ node: PureXML.Model.TreeNode) {
for child in node.children {
#expect(child.parent === node)
assertParentsConsistent(child)
}
}
@Test("every child's parent points back to its container")
func test_parentWiring() throws {
let tree = try PureXML.parseTree(Self.source)
#expect(tree.parent == nil)
assertParentsConsistent(tree)
}
@Test("sibling order is preserved through adoption")
func test_childOrder() throws {
let tree = try PureXML.parseTree(Self.source)
guard let catalog = tree.children.first(where: { $0.kind == .element }) else {
Issue.record("no catalog")
return
}
let items = catalog.children.filter { $0.kind == .element }
#expect(items.count == 2)
#expect(items[0].attributes.first { $0.name.localName == "id" }?.value == "i0")
#expect(items[1].attributes.first { $0.name.localName == "id" }?.value == "i1")
let firstItemChildren = items[0].children.filter { $0.kind == .element }.map { $0.name?.localName }
#expect(firstItemChildren == ["name", "price"])
}
@Test("an adopted tree is still correctly mutable")
func test_stillMutable() throws {
let tree = try PureXML.parseTree(Self.source)
guard let catalog = tree.children.first(where: { $0.kind == .element }),
let firstItem = catalog.children.first(where: { $0.kind == .element })
else {
Issue.record("no item")
return
}
let originalCount = catalog.children.count
let added = PureXML.Model.TreeNode.element(PureXML.Model.QualifiedName("added"))
catalog.append(added)
#expect(added.parent === catalog)
#expect(catalog.children.count == originalCount + 1)
// Re-parenting a node already in the adopted tree detaches it cleanly.
let secondItem = PureXML.Model.TreeNode.element(PureXML.Model.QualifiedName("wrapper"))
catalog.append(secondItem)
secondItem.append(firstItem)
#expect(firstItem.parent === secondItem)
#expect(!catalog.children.contains { $0 === firstItem })
}
}