Files
Outpost/Vendor/swift-markdown-engine/Tests/MarkdownEngineTests/InlineSpanDensityTests.swift
T
Puranjay Savar Mattas 6e1ffb96fd chore: vendor swift-markdown-engine as plain tracked source
Convert from git submodule to plain vendored copy. No push access to
upstream nodes-app/swift-markdown-engine meant our local fix commit
(05c1720) was stranded and unreachable from any remote on fresh
clones/CI. Vendoring as plain files folds it into normal repo history
instead.
2026-08-20 13:26:13 +01:00

155 lines
6.7 KiB
Swift

//
// InlineSpanDensityTests.swift
// MarkdownEngineTests
//
// Inline parse cost against span density within one region (#109).
//
// Two halves, and the first is the one that matters: the containment rewrite
// has to be a pure performance change. `corpusFingerprint` folds the parsed
// tree of 4000 pseudo-random inputs into one value, recorded on the PRE-rewrite
// parser at the merge base (1a2bd74, i.e. with #118) — in the spirit of
// `GoldenCorpusTests`, except the baseline covers shapes nobody would think to
// write by hand.
//
// Re-record it ONLY on a parser that predates the rewrite, otherwise it just
// ratifies whatever the rewrite does. It is also a bare hash: when it fails,
// diff `String(describing:)` per input against the old parser to see what moved.
//
// The second half asserts the cost curve is linear in spans rather than
// quadratic, so the scans can't quietly come back. It is OPT-IN via
// `MDE_PERF=1 swift test` — see `perfGateEnabled`.
//
import Foundation
import Testing
@testable import MarkdownEngine
/// The cost-curve assertions run only under `MDE_PERF=1 swift test`.
///
/// A wall-clock RATIO is not portable, which is easy to miss because it looks
/// like it should be: the same parser measures 5.3x on an M-series laptop and
/// 10.9x on a shared `macos-15` runner, where `swift test --parallel` has 55
/// suites competing for cores throughout the measurement window. Gating CI on
/// that number buys flakiness, not safety — and no bound fixes it, since the
/// pre-rewrite floor (11.3x here) sits below the post-rewrite CI reading.
///
/// `corpusFingerprint` is the regression net that DOES hold everywhere, and it
/// stays on by default.
private let perfGateEnabled = ProcessInfo.processInfo.environment["MDE_PERF"] != nil
@Suite("Inline parse cost vs. span density")
struct InlineSpanDensityTests {
// MARK: - Corpus
/// Deterministic LCG — the corpus must be identical across builds for the
/// fingerprint to mean anything, and `SystemRandomNumberGenerator` isn't.
private struct LCG {
var state: UInt64 = 0x2545F4914F6CDD1D
mutating func next(_ bound: Int) -> Int {
state = state &* 6364136223846793005 &+ 1442695040888963407
return Int((state >> 33) % UInt64(bound))
}
}
/// Fragments chosen to collide: bare and paired delimiters, escapes, and
/// the openers of every claimed-span construct, so the corpus is dense in
/// half-formed and nested spans rather than in valid markdown.
private static let atoms = [
"a", "bb", " ", " ", "*", "**", "_", "__", "`", "``", "\\", "\\*", "\\`",
"[", "]", "(", ")", "![", "[[", "]]", "|", "$", "==", "~~", "url", "http://e.com/x",
"\n", "word", ".", "!", "*a*", "**b**", "`c`", "[d](e)", "[[f|g]]", "$h$",
]
private func corpus(_ count: Int) -> [String] {
var rng = LCG()
return (0..<count).map { _ in
var s = ""
for _ in 0..<(3 + rng.next(14)) { s += Self.atoms[rng.next(Self.atoms.count)] }
return s
}
}
/// Swift's `Hasher` is per-process seeded, so fold by hand.
private func fingerprint(_ strings: [String], registry: ExtensionRegistry) -> String {
var fnv: UInt64 = 0xcbf29ce484222325
func fold(_ s: String) {
for b in s.utf8 { fnv = (fnv ^ UInt64(b)) &* 0x100000001b3 }
}
for s in strings {
fold(s)
fold(String(describing: InlineParser.parse(s, registry: registry)))
}
return String(fnv, radix: 16)
}
@Test("the containment rewrite changes no tree in a 4000-input corpus")
func corpusFingerprint() {
let registry = MarkdownEditorConfiguration(
extensions: [HighlightExtension(), StrikethroughExtension()]
).extensionRegistry
#expect(fingerprint(corpus(4000), registry: registry) == "b74649ffbbbe237a")
}
// MARK: - Cost curve
/// Minimum of several runs: scheduler noise only ever adds time, so the
/// floor is the stable statistic. Means would make this a flake.
private func msPerParse(_ text: String, registry: ExtensionRegistry) -> Double {
var best = Double.infinity
for _ in 0..<7 {
let start = DispatchTime.now().uptimeNanoseconds
for _ in 0..<20 { _ = DocumentAST.parse(text, registry: registry) }
let ms = Double(DispatchTime.now().uptimeNanoseconds - start) / 20 / 1_000_000
best = min(best, ms)
}
return best
}
private func paragraph(_ n: Int, _ make: (Int) -> String) -> String {
(0..<n).map(make).joined(separator: " ")
}
/// 6x the spans in one paragraph should cost ~6x, not ~30x.
///
/// 8 is the geometric midpoint of the gap measured on Apple silicon —
/// worst case after the rewrite is 5.6x (code), best case before it is
/// 11.3x (highlight). Recalibrate against your own machine before reading
/// a failure as a regression; the message prints the measured value.
private func expectLinearInSpans(_ label: String, _ make: (Int) -> String) {
let registry = MarkdownEditorConfiguration(extensions: [HighlightExtension()]).extensionRegistry
let small = msPerParse(paragraph(40, make), registry: registry)
let large = msPerParse(paragraph(240, make), registry: registry)
let growth = large / small
#expect(growth < 8, "\(label): 6x spans cost \(String(format: "%.1f", growth))x parse")
}
@Test("code spans: parse cost is linear in spans per paragraph", .enabled(if: perfGateEnabled))
func codeSpanDensity() { expectLinearInSpans("code") { "`word\($0)`" } }
@Test("links: parse cost is linear in spans per paragraph", .enabled(if: perfGateEnabled))
func linkDensity() { expectLinearInSpans("links") { "[word\($0)](https://e.com/\($0))" } }
@Test("emphasis: parse cost is linear in spans per paragraph", .enabled(if: perfGateEnabled))
func emphasisDensity() { expectLinearInSpans("emphasis") { "*word\($0)*" } }
@Test("highlights: parse cost is linear in spans per paragraph", .enabled(if: perfGateEnabled))
func highlightDensity() { expectLinearInSpans("highlight") { "==word\($0)==" } }
/// The pathological case the issue was filed from: escapes and code spans
/// together, where every later pass used to rescan every claimed range.
@Test("a paragraph mixing claimed-span kinds stays linear", .enabled(if: perfGateEnabled))
func mixedDensity() {
expectLinearInSpans("mixed") { i in
switch i % 4 {
case 0: return "`code\(i)`"
case 1: return "\\*lit\(i)\\*"
case 2: return "*em\(i)*"
default: return "[l\(i)](u\(i))"
}
}
}
}