Replace the line-based --- heuristic with a scan over PureYAML's resumable token scanner, so a --- inside a quoted scalar can no longer misfire as a document boundary and directives stay attached to the document they introduce. Add a differential suite that checks the new scanner against the old line-based reference across generated streams and the full Geekbench corpus.
158 lines
5.1 KiB
Swift
158 lines
5.1 KiB
Swift
import Foundation
|
|
import PureYAML
|
|
import Testing
|
|
@testable import PureYAMLStreaming
|
|
|
|
@Suite("PureYAML Streaming")
|
|
struct StreamingTests {
|
|
@Test("chunked reader preserves UTF-8 split across chunks")
|
|
func chunkedReaderPreservesSplitUTF8() throws {
|
|
var reader = try PureYAMLStreaming.ChunkedUTF8Reader(
|
|
data: Data("aé\nb".utf8),
|
|
chunkSize: 2
|
|
)
|
|
|
|
var chunks: [String] = []
|
|
while let chunk = try reader.readChunk() {
|
|
chunks.append(chunk)
|
|
}
|
|
|
|
#expect(chunks.joined() == "aé\nb")
|
|
#expect(reader.mark == .init(line: 2, column: 2, index: 5))
|
|
}
|
|
|
|
@Test("document scanner emits documents across chunk boundaries")
|
|
func scannerEmitsDocumentsAcrossChunks() throws {
|
|
var scanner = PureYAMLStreaming.ResumableDocumentScanner()
|
|
var documents: [String] = []
|
|
|
|
documents += try scanner.consume("---\na: 1\n--")
|
|
documents += try scanner.consume("-\nb: 2\n")
|
|
documents += try scanner.finish()
|
|
|
|
#expect(documents == ["---\na: 1\n", "---\nb: 2\n"])
|
|
}
|
|
|
|
@Test("parser streams documents in order")
|
|
func parserStreamsDocumentsInOrder() throws {
|
|
let yaml = Data(
|
|
"""
|
|
---
|
|
name: one
|
|
---
|
|
name: two
|
|
""".utf8
|
|
)
|
|
let parser = PureYAMLStreaming.Parser(chunkSize: 5)
|
|
let documents = try parser.collectDocuments(data: yaml)
|
|
|
|
#expect(documents.count == 2)
|
|
#expect(documents[0].index == 0)
|
|
#expect(documents[1].index == 1)
|
|
#expect(stringValue("name", in: documents[0].value) == "one")
|
|
#expect(stringValue("name", in: documents[1].value) == "two")
|
|
}
|
|
|
|
@Test("streaming matches PureYAML on Geekbench fixtures")
|
|
func streamingMatchesGeekbenchFixtures() throws {
|
|
let fixtureRoot = geekbenchFixtureRoot()
|
|
guard FileManager.default.fileExists(atPath: fixtureRoot.path) else {
|
|
return
|
|
}
|
|
|
|
let files = try FileManager.default.contentsOfDirectory(
|
|
at: fixtureRoot,
|
|
includingPropertiesForKeys: nil
|
|
).filter { url in
|
|
url.pathExtension == "yaml" || url.pathExtension == "yml"
|
|
}
|
|
|
|
#expect(files.count >= 100)
|
|
|
|
let streamingParser = PureYAMLStreaming.Parser(chunkSize: 31)
|
|
let pureParser = PureYAML.Parsing.Parser()
|
|
for file in files {
|
|
let data = try Data(contentsOf: file)
|
|
var streamed: [PureYAML.Model.Value] = []
|
|
try streamingParser.parseDocuments(from: file) { document in
|
|
streamed.append(document.value)
|
|
}
|
|
let direct = try pureParser.parseStream(String(decoding: data, as: UTF8.self)).map(\.value)
|
|
#expect(streamed == direct)
|
|
}
|
|
}
|
|
|
|
@Test("parser streams generated large file without collecting documents")
|
|
func parserStreamsGeneratedLargeFileWithoutCollectingDocuments() throws {
|
|
let fileURL = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("pureyaml-stream-\(UUID().uuidString).yaml")
|
|
try writeGeneratedStream(to: fileURL, documentCount: 5000)
|
|
defer {
|
|
try? FileManager.default.removeItem(at: fileURL)
|
|
}
|
|
|
|
let parser = PureYAMLStreaming.Parser(chunkSize: 37)
|
|
var count = 0
|
|
var firstName: String?
|
|
var lastName: String?
|
|
|
|
try parser.parseDocuments(from: fileURL) { document in
|
|
let name = stringValue("name", in: document.value)
|
|
if count == 0 {
|
|
firstName = name
|
|
}
|
|
lastName = name
|
|
#expect(document.index == count)
|
|
count += 1
|
|
}
|
|
|
|
#expect(count == 5000)
|
|
#expect(firstName == "doc-0")
|
|
#expect(lastName == "doc-4999")
|
|
}
|
|
|
|
private func stringValue(_ key: String, in value: PureYAML.Model.Value) -> String? {
|
|
guard case let .mapping(mapping) = value,
|
|
case let .string(string)? = mapping[key]
|
|
else {
|
|
return nil
|
|
}
|
|
return string
|
|
}
|
|
|
|
private func geekbenchFixtureRoot() -> URL {
|
|
URL(fileURLWithPath: #filePath)
|
|
.deletingLastPathComponent()
|
|
.deletingLastPathComponent()
|
|
.deletingLastPathComponent()
|
|
.deletingLastPathComponent()
|
|
.appendingPathComponent("PureYAMLGeekbench")
|
|
.appendingPathComponent("Fixtures")
|
|
.appendingPathComponent("real-yaml")
|
|
}
|
|
|
|
private func writeGeneratedStream(to fileURL: URL, documentCount: Int) throws {
|
|
FileManager.default.createFile(atPath: fileURL.path, contents: nil)
|
|
let handle = try FileHandle(forWritingTo: fileURL)
|
|
defer {
|
|
try? handle.close()
|
|
}
|
|
|
|
for index in 0 ..< documentCount {
|
|
let document = """
|
|
---
|
|
name: doc-\(index)
|
|
enabled: \(index.isMultiple(of: 2) ? "true" : "false")
|
|
nested:
|
|
index: \(index)
|
|
tags:
|
|
- real
|
|
- streaming
|
|
- chunk-\(index % 17)
|
|
|
|
"""
|
|
try handle.write(contentsOf: Data(document.utf8))
|
|
}
|
|
}
|
|
}
|