Group @testable imports at the bottom, per public/rules-swift's swiftformat. A behavior-preserving import-ordering reformat across 252 files (mostly tests). --trailingclosures was deliberately not adopted (cosmetic, and swiftformat flags it as caution-worthy).
53 lines
2.4 KiB
Swift
53 lines
2.4 KiB
Swift
import Testing
|
|
@testable import PureXML
|
|
|
|
/// Guards the content-model NFA builder against occurrence-explosion (#129).
|
|
/// The per-particle unroll cap bounds one repetition, but nested high-`maxOccurs`
|
|
/// particles multiply: the XSTS msMeta Particles set drove the builder to 8 GB
|
|
/// and OOM-killed the suite. A total state ceiling caps the whole automaton,
|
|
/// degrading further repetition to star. This locks that ceiling in: the
|
|
/// pathological model must build, stay bounded, and still match without
|
|
/// crashing, while ordinary models are untouched.
|
|
@Suite("Content NFA state budget")
|
|
struct ContentNFAStateBudgetTests {
|
|
private func element(_ name: String, min: Int, max: Int?) -> PureXML.Schema.Particle {
|
|
.init(minOccurs: min, maxOccurs: max, term: .element(name: .init(name), type: nil, typeName: nil))
|
|
}
|
|
|
|
/// A sequence repeated `min` times, each repeat holding an element repeated
|
|
/// `min` times: naively `min * min` automaton states.
|
|
private func nested(min: Int) -> PureXML.Schema.Particle {
|
|
let inner = element("a", min: min, max: min)
|
|
return .init(
|
|
minOccurs: min,
|
|
maxOccurs: min,
|
|
term: .group(.init(compositor: .sequence, particles: [inner])),
|
|
)
|
|
}
|
|
|
|
@Test("A nested occurrence explosion stays bounded instead of exhausting memory")
|
|
func test_nestedExplosionBounded() {
|
|
// 16384 * 16384 ~ 2.7e8 states without a total cap; the ceiling is 2^20.
|
|
let nfa = PureXML.Schema.ContentNFABuilder.build(nested(min: 16384))
|
|
// Bounded to the ceiling plus at most one in-flight term expansion,
|
|
// far below the naive product that previously allocated until death.
|
|
#expect(nfa.states.count < 1_500_000)
|
|
// The degraded automaton must still answer queries without crashing.
|
|
let names = Array(repeating: PureXML.Model.QualifiedName("a"), count: 32)
|
|
_ = nfa.matchesWhole(names)
|
|
}
|
|
|
|
@Test("Ordinary content models are unaffected by the ceiling")
|
|
func test_ordinaryModelUnchanged() {
|
|
let particle = PureXML.Schema.Particle(
|
|
minOccurs: 1,
|
|
maxOccurs: nil,
|
|
term: .group(.init(compositor: .sequence, particles: [element("a", min: 1, max: 1)])),
|
|
)
|
|
let nfa = PureXML.Schema.ContentNFABuilder.build(particle)
|
|
#expect(nfa.states.count < 64)
|
|
#expect(nfa.matchesWhole([.init("a"), .init("a"), .init("a")]))
|
|
#expect(!nfa.matchesWhole([.init("b")]))
|
|
}
|
|
}
|