import Foundation @testable import PureYAML import Testing /// Differential proof for the public `PureYAML.Streaming.Scanner`, the thin /// translation wrapper around the internal `LibYAML.Scanner.scanStep()` /// already proven by `StreamingScannerDifferentialTests`. That existing proof /// covers `scanStep()` itself; this one covers the translation layer on top /// of it (`Streaming.Token`/`Streaming.Token.Kind`/`Streaming.Token.ScalarStyle` /// mapped from the internal `LibYAML.Token`/`TokenType`/`ScalarStyle`), since a /// wrong case mapping or a dropped field would not be caught by a proof that /// never looks at the public types at all. @Suite("Public streaming scanner") struct PublicStreamingScannerTests { @Test("Public Scanner matches the internal scanAll() reference, translated, at several chunk sizes", arguments: [ "title: Example API", """ title: Example servers: - url: / """, "flow: {a: 1, b: [2, 3]}", """ base: &anchor value: 1 derived: *anchor """, "block: |\n line one\n line two\n", "&a a: b\nc: &d d\n", ]) func test_publicScannerMatchesTranslatedReference(yaml: String) throws { try assertPublicScannerMatchesReference(yaml) } @Test("Public Scanner matches the internal scanAll() reference over the whole yaml-test-suite corpus") func test_publicScannerMatchesReferenceOverWholeSuite() throws { guard ProcessInfo.processInfo.environment["PUREYAML_RUN_YAML_TEST_SUITE"] == "1" else { return } let manifest = try YAMLTestSuiteManifest.load() var mismatches: [String] = [] for testCase in manifest.cases { let yaml = try testCase.loadInput() do { try assertPublicScannerMatchesReference(yaml) } catch { mismatches.append("\(testCase.id): \(error)") } } #expect(mismatches.isEmpty, "public/internal mismatches (\(mismatches.count)): \(mismatches.prefix(10).joined(separator: "; "))") } } private func assertPublicScannerMatchesReference(_ yaml: String) throws { let reference = translatedBatchReference(yaml) for chunkSize in [1, 2, 3, 7, 64, yaml.unicodeScalars.count + 1] { let observed = try publicScan(yaml, chunkSize: chunkSize) #expect(observed == reference, "chunk size \(chunkSize) diverged from the translated reference for: \(yaml.debugDescription)") } } private enum PublicScanResult: Equatable { case tokens([PureYAML.Streaming.Token]) case threw(String) } /// The reference: the batch scanner's own tokens, run through the exact same /// `Streaming.Token.init(_:)` translator the public `Scanner` uses, so this /// proof isolates "does the public wrapper preserve what the internal scanner /// already produces" from "is the internal scanner itself correct" (the /// latter is `StreamingScannerDifferentialTests`'s job, not this one's). private func translatedBatchReference(_ yaml: String) -> PublicScanResult { var scanner = PureYAML.Parsing.LibYAML.Scanner(yaml) do { return try .tokens(scanner.scanAll().map(PureYAML.Streaming.Token.init)) } catch { return .threw("\(error)") } } private func publicScan(_ yaml: String, chunkSize: Int) throws -> PublicScanResult { let scalars = Array(yaml.unicodeScalars) var chunks: [String] = [] var index = 0 while index < scalars.count { let end = min(index + chunkSize, scalars.count) chunks.append(String(String.UnicodeScalarView(scalars[index ..< end]))) index = end } var scanner = PureYAML.Streaming.Scanner() var nextChunk = 0 var tokens: [PureYAML.Streaming.Token] = [] do { while true { switch try scanner.next() { case let .token(token): tokens.append(token) case .done: return .tokens(tokens) case .needMoreInput: if nextChunk < chunks.count { scanner.feed(chunks[nextChunk]) nextChunk += 1 } else { scanner.finish() } } } } catch { return .threw("\(error)") } }