PureXML/Tests/CommentAndPIEscapingDifferentialTests.swift
Mihaela Mihaljevic d359208ff6 perf(serialize): precompute the attribute quote, bulk-copy comment/PI escaping
Two profile-guided serialize passes (#139, docs/benchmarks.md passes 29-30).
The attribute writer now precomputes the quote character as a String once
per element instead of interpolating a Character into every attribute,
avoiding the grapheme-breadcrumb machinery that repeats twice per attribute
for a value that never changes within an element. Escaping.comment and
processingInstruction's escaping fallback (taken whenever a comment has a
hyphen or PI data has a '?', a case pass 8 could skip past but not speed up)
is rewritten in the same byte-scan bulk-copy shape as passes 23-24, so a
value with one rare trigger no longer pays a full character-by-character
rebuild.

Both are same-observable-behavior changes. The quote change is byte-
identical by construction. The comment/PI rewrite is verified against a new
differential suite that reimplements the original character-loop as an
independent oracle and checks adjacency/trailing/combining-mark cases plus
two 20000-case seeded fuzzes, since a byte match inside a combining-mark
cluster is not, on its own, the bare trigger character. Full local suite
(1925 tests) unchanged.

Measured with interleaved same-state A/B: pass 29 ~1.5% at a 100k-item
corpus; pass 30 ~2x on a dedicated comment/PI micro-benchmark (the standard
corpus has no comment/PI content, so real documents with copyright banners
or hyphenated comment prose see this, not the headline ratio). A same-
session multi-append restructuring attempt measured a clear ~6% regression
and was reverted; a suspected hasInlineContent double-scan was profiled at
~0.2% of samples and not worth a pass. Both outcomes are recorded in
docs/benchmarks.md rather than dropped.
2026-07-15 04:05:29 +02:00

121 lines
4.3 KiB
Swift

import Testing
@testable import PureXML
/// Reference oracle for `Escaping.comment`/`processingInstruction`: the original
/// character-by-character algorithm the byte-scanning bulk-copy rewrite must
/// match exactly, including when a hyphen/`?`/`>` combines with a following
/// combining mark into a single extended grapheme cluster (a byte match that is
/// not, on its own, that bare character).
private enum ReferenceEscaping {
static func comment(_ value: String) -> String {
var result = ""
var previousHyphen = false
for character in value {
if character == "-", previousHyphen {
result.append(" ")
}
result.append(character)
previousHyphen = character == "-"
}
if previousHyphen {
result.append(" ")
}
return result
}
static func processingInstruction(_ data: String) -> String {
var result = ""
var previousQuestion = false
for character in data {
if character == ">", previousQuestion {
result.append(" ")
}
result.append(character)
previousQuestion = character == "?"
}
return result
}
}
private struct DifferentialRNG: RandomNumberGenerator {
private var state: UInt64
init(seed: UInt64) {
state = seed == 0 ? 0x9E37_79B9_7F4A_7C15 : seed
}
mutating func next() -> UInt64 {
state ^= state >> 12
state ^= state << 25
state ^= state >> 27
return state &* 0x2545_F491_4F6C_DD1D
}
}
@Suite("Comment/PI escaping: bulk-copy rewrite matches the character-by-character oracle")
struct CommentAndPIEscapingDifferentialTests {
private typealias Escaping = PureXML.Emitting.Escaping
private static let combiningMarks: [Character] = ["\u{0301}", "\u{0332}", "\u{20D0}"]
@Test("comment: hand-picked adjacency and combining-mark cases")
func test_comment_handPicked() {
let cases = [
"", "-", "--", "---", "----",
"a-b", "a--b", "a---b", "a----b",
"-a", "--a", "a-", "a--",
"trailing-", "trailing--", "trailing---",
"-\u{0301}", "-\u{0301}-", "a-\u{0301}-b", "--\u{0301}-",
"hyphen-\u{0332}ated words are fine-ish- probably",
]
for value in cases {
#expect(
Escaping.comment(value) == ReferenceEscaping.comment(value),
"mismatch for \(value.debugDescription)",
)
}
}
@Test("processing instruction: hand-picked adjacency and combining-mark cases")
func test_pi_handPicked() {
let cases = [
"", "?", ">", "?>", "??>", "?>>", "??>>", "?a>", "a?>b",
"?\u{0301}>", "?\u{0301}>b", "a?\u{0301}>", "?>?>?>",
"?a?b>c", "a>b?c",
]
for value in cases {
#expect(
Escaping.processingInstruction(value) == ReferenceEscaping.processingInstruction(value),
"mismatch for \(value.debugDescription)",
)
}
}
@Test("comment: seeded fuzz across hyphen/combining-mark alphabets")
func test_comment_fuzz() {
var rng = DifferentialRNG(seed: 12345)
let alphabet: [Character] = ["a", "b", "-"] + Self.combiningMarks
for _ in 0 ..< 20000 {
let length = Int.random(in: 0 ..< 12, using: &rng)
let value = String((0 ..< length).map { _ in alphabet[Int.random(in: 0 ..< alphabet.count, using: &rng)] })
#expect(
Escaping.comment(value) == ReferenceEscaping.comment(value),
"mismatch for \(value.debugDescription)",
)
}
}
@Test("processing instruction: seeded fuzz across ?/>/combining-mark alphabets")
func test_pi_fuzz() {
var rng = DifferentialRNG(seed: 67890)
let alphabet: [Character] = ["a", "b", "?", ">"] + Self.combiningMarks
for _ in 0 ..< 20000 {
let length = Int.random(in: 0 ..< 12, using: &rng)
let value = String((0 ..< length).map { _ in alphabet[Int.random(in: 0 ..< alphabet.count, using: &rng)] })
#expect(
Escaping.processingInstruction(value) == ReferenceEscaping.processingInstruction(value),
"mismatch for \(value.debugDescription)",
)
}
}
}