PureYAML/Tests/ParsingNestingLimitTests.swift

79 lines
3.1 KiB
Swift

import Foundation
@testable import PureYAML
import Testing
@Suite("Parsing Nesting Limit")
struct ParsingNestingLimitTests {
/// Result of parsing on a dedicated, generously sized stack.
///
/// Driving the parser to its nesting limit takes more stack than a default
/// test-runner thread may have, so these tests run the parse on their own
/// thread with a large stack. That isolates the assertion (does the parser
/// reject the input with a diagnostic?) from the runner's thread-stack size.
private final class ParseOutcome: @unchecked Sendable {
var value: PureYAML.Model.Value?
var parseError: PureYAML.Parsing.ParseError?
var otherError: (any Error)?
}
private func parseOnGenerousStack(_ yaml: String) -> ParseOutcome {
let outcome = ParseOutcome()
let done = DispatchSemaphore(value: 0)
let thread = Thread {
do {
outcome.value = try PureYAML.parse(yaml)
} catch let error as PureYAML.Parsing.ParseError {
outcome.parseError = error
} catch {
outcome.otherError = error
}
done.signal()
}
thread.stackSize = 16 * 1024 * 1024
thread.start()
done.wait()
return outcome
}
@Test("Deeply nested flow collections throw nestingTooDeep instead of overflowing the stack")
func test_deepFlowThrowsCleanly() {
let depth = 400
let yaml = "x: " + String(repeating: "[", count: depth) + String(repeating: "]", count: depth)
let outcome = parseOnGenerousStack(yaml)
#expect(outcome.otherError == nil)
#expect(outcome.value == nil)
#expect(outcome.parseError?.diagnosticCode == "nestingTooDeep")
}
@Test("Deeply nested block mappings throw nestingTooDeep instead of overflowing the stack")
func test_deepBlockThrowsCleanly() {
var yaml = "deep:\n"
for level in 0 ..< 400 {
yaml += String(repeating: " ", count: level + 1) + "k:\n"
}
yaml += String(repeating: " ", count: 401) + "v: 1\n"
let outcome = parseOnGenerousStack(yaml)
#expect(outcome.otherError == nil)
#expect(outcome.parseError?.diagnosticCode == "nestingTooDeep")
}
@Test("Nesting within real-world depth still parses to the expected structure")
func test_moderateNestingParses() throws {
// Real specs in the corpus nest no deeper than the high 20s; this stays
// well inside the limit and must round-trip.
let depth = 24
let yaml = "x: " + String(repeating: "[", count: depth) + "1" + String(repeating: "]", count: depth)
let outcome = parseOnGenerousStack(yaml)
#expect(outcome.parseError == nil)
#expect(outcome.otherError == nil)
var current = try #require(outcome.value).mapping?["x"]
var measured = 0
while case let .sequence(items)? = current, let first = items.first {
measured += 1
current = first
}
#expect(measured == depth)
#expect(current == .int(1))
}
}