test: measure conformance against the official YAML test-suite
Adds an opt-in harness that runs PureYAML over the canonical YAML 1.2 corpus (github.com/yaml/yaml-test-suite) and reports a parse-level pass rate: a should-parse case must parse, a should-error case must be rejected. The data is fetched into a gitignored location by scripts/fetch-yaml-test-suite.sh, never vendored, and the run is gated behind PUREYAML_RUN_YAML_TEST_SUITE=1 so the default suite is unaffected. This turns 'best YAML library' from an assertion into a tracked number. Baseline: 211/333 (63.4%) parse-level: 177/255 valid documents parse, 34/78 invalid documents are rejected. So 78 valid docs are not yet parsed and 44 invalid docs are wrongly accepted. The stricter value-level check against each case's in.json is the next step.
This commit is contained in:
parent
985bd7baac
commit
9298536e52
12
CHANGELOG.md
12
CHANGELOG.md
@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- An opt-in conformance harness against the official YAML test-suite, the canonical
|
||||
YAML 1.2 corpus (`Tests/YAMLTestSuiteConformanceTests.swift`,
|
||||
`scripts/fetch-yaml-test-suite.sh`). Gated behind `PUREYAML_RUN_YAML_TEST_SUITE=1`
|
||||
and a separate fetch, so the data is never vendored. It reports parse-level
|
||||
conformance (a should-parse case must parse; a should-error case must be
|
||||
rejected). Current baseline: 211/333 (63.4%), with 78 valid documents not yet
|
||||
parsed and 44 invalid documents not yet rejected. This turns "best YAML library"
|
||||
from an assertion into a tracked number; the stricter value-level check against
|
||||
each case's `in.json` is the next step.
|
||||
|
||||
### Changed
|
||||
|
||||
- Split multi-type source files so each file declares exactly one top-level type
|
||||
|
||||
85
Tests/YAMLTestSuiteConformanceTests.swift
Normal file
85
Tests/YAMLTestSuiteConformanceTests.swift
Normal file
@ -0,0 +1,85 @@
|
||||
import Foundation
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
/// Measures PureYAML against the official YAML test-suite (https://github.com/yaml/yaml-test-suite),
|
||||
/// the canonical YAML 1.2 conformance corpus. Opt-in, like the real-world corpus suite: the data is
|
||||
/// large and fetched separately, so the run is gated behind `PUREYAML_RUN_YAML_TEST_SUITE=1` and
|
||||
/// `bash scripts/fetch-yaml-test-suite.sh`. It reports parse-level conformance: a should-parse case
|
||||
/// must parse and a should-error case (one shipping an `error` marker) must be rejected. The
|
||||
/// stricter value-level check, comparing the parsed tree to each case's `in.json`, is the next step.
|
||||
/// The suite prints the pass rate to stderr and asserts only that the corpus was found, so it is a
|
||||
/// measurement harness, not yet a threshold gate (the threshold is set once the number is known).
|
||||
@Suite("YAML test-suite conformance")
|
||||
struct YAMLTestSuiteConformanceTests {
|
||||
private struct Case {
|
||||
let id: String
|
||||
let input: String
|
||||
let expectsError: Bool
|
||||
}
|
||||
|
||||
private func loadCases() -> [Case]? {
|
||||
let manager = FileManager.default
|
||||
let root = ProcessInfo.processInfo.environment["PUREYAML_YAML_TEST_SUITE_DIR"]
|
||||
?? manager.currentDirectoryPath + "/.build/yaml-test-suite"
|
||||
guard let names = try? manager.contentsOfDirectory(atPath: root) else {
|
||||
return nil
|
||||
}
|
||||
var cases: [Case] = []
|
||||
for name in names.sorted() {
|
||||
let dir = root + "/" + name
|
||||
guard let input = try? String(contentsOfFile: dir + "/in.yaml", encoding: .utf8) else {
|
||||
continue
|
||||
}
|
||||
cases.append(Case(
|
||||
id: name,
|
||||
input: input,
|
||||
expectsError: manager.fileExists(atPath: dir + "/error"),
|
||||
))
|
||||
}
|
||||
return cases
|
||||
}
|
||||
|
||||
@Test("Parse-level conformance against the official yaml-test-suite")
|
||||
func parseLevelConformance() throws {
|
||||
guard ProcessInfo.processInfo.environment["PUREYAML_RUN_YAML_TEST_SUITE"] == "1" else {
|
||||
return
|
||||
}
|
||||
let cases = try #require(loadCases(), "fetch the suite first: bash scripts/fetch-yaml-test-suite.sh")
|
||||
var okExpected = 0, okPassed = 0, errExpected = 0, errPassed = 0
|
||||
var shouldParseFailures: [String] = []
|
||||
var shouldRejectFailures: [String] = []
|
||||
for testCase in cases {
|
||||
let parsed = (try? PureYAML.parseStream(testCase.input)) != nil
|
||||
if testCase.expectsError {
|
||||
errExpected += 1
|
||||
if parsed { shouldRejectFailures.append(testCase.id) } else { errPassed += 1 }
|
||||
} else {
|
||||
okExpected += 1
|
||||
if parsed { okPassed += 1 } else { shouldParseFailures.append(testCase.id) }
|
||||
}
|
||||
}
|
||||
let total = okExpected + errExpected
|
||||
let passed = okPassed + errPassed
|
||||
report("""
|
||||
YAML test-suite parse-level: \(passed)/\(total) (\(percent(passed, total)))
|
||||
should-parse handled: \(okPassed)/\(okExpected) (\(percent(okPassed, okExpected)))
|
||||
should-error rejected: \(errPassed)/\(errExpected) (\(percent(errPassed, errExpected)))
|
||||
should-parse failures (\(shouldParseFailures.count)): \(sample(shouldParseFailures))
|
||||
should-error misses (\(shouldRejectFailures.count)): \(sample(shouldRejectFailures))
|
||||
""")
|
||||
#expect(total > 0)
|
||||
}
|
||||
|
||||
private func percent(_ part: Int, _ whole: Int) -> String {
|
||||
whole == 0 ? "n/a" : String(format: "%.1f%%", Double(part) / Double(whole) * 100)
|
||||
}
|
||||
|
||||
private func sample(_ ids: [String]) -> String {
|
||||
ids.prefix(25).joined(separator: " ") + (ids.count > 25 ? " …" : "")
|
||||
}
|
||||
|
||||
private func report(_ text: String) {
|
||||
FileHandle.standardError.write(Data("\n\(text)\n".utf8))
|
||||
}
|
||||
}
|
||||
23
scripts/fetch-yaml-test-suite.sh
Executable file
23
scripts/fetch-yaml-test-suite.sh
Executable file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fetch the official YAML test-suite (the canonical YAML 1.2 conformance corpus) into a gitignored
|
||||
# location for the opt-in conformance run. The data is not vendored, keeping the package free of
|
||||
# external content; this script puts it under .build/ (ignored) like the other fetched corpora.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/fetch-yaml-test-suite.sh
|
||||
# PUREYAML_RUN_YAML_TEST_SUITE=1 swift test --filter YAMLTestSuiteConformanceTests
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
DEST="$ROOT_DIR/.build/yaml-test-suite"
|
||||
|
||||
if [ -d "$DEST/.git" ]; then
|
||||
git -C "$DEST" fetch --quiet --depth 1 origin data
|
||||
git -C "$DEST" reset --hard --quiet FETCH_HEAD
|
||||
else
|
||||
rm -rf "$DEST"
|
||||
git clone --quiet --depth 1 --branch data https://github.com/yaml/yaml-test-suite "$DEST"
|
||||
fi
|
||||
|
||||
cases="$(find "$DEST" -maxdepth 2 -name in.yaml | wc -l | tr -d ' ')"
|
||||
echo "yaml-test-suite ready at $DEST ($cases cases)"
|
||||
Loading…
Reference in New Issue
Block a user