PureYAML/Tests/PreflightScannerTests.swift
Mihaela Mihaljevic 208b892019 feat(parser): harden diagnostics and streaming
Add structured validation diagnostics, JSON and Yams comparison gates, and lazy token/event parsing so production behavior is measured without retaining full intermediate parser arrays.
2026-06-06 07:57:48 +02:00

350 lines
9.1 KiB
Swift

@testable import PureYAML
import Testing
@Suite("Preflight Scanner")
struct PreflightScannerTests {
@Test("Reports exact line diagnostics for common production spacing failures", arguments: preflightLineCases)
func test_reportsExactLineDiagnosticsForCommonProductionSpacingFailures(testCase: PreflightLineCase) {
let scanner = PureYAML.Validation.PreflightScanner()
#expect(scanner.diagnostics(
in: testCase.line,
file: "input.yaml",
line: testCase.lineNumber,
) == [testCase.expected])
}
@Test("Does not report false positives for valid scalar-like text")
func test_doesNotReportFalsePositivesForValidScalarLikeText() {
let scanner = PureYAML.Validation.PreflightScanner()
let yaml = [
"negative: -1",
"url: https://example.com",
"flow: {name:value}",
"- plain sequence item",
"---",
"# comment:without space",
].joined(separator: "\n")
#expect(scanner.diagnostics(in: yaml).isEmpty)
}
@Test("Scans the full damaged file instead of stopping at the first bad line")
func test_scansFullDamagedFileInsteadOfStoppingAtTheFirstBadLine() {
let scanner = PureYAML.Validation.PreflightScanner()
let yaml = productionLikeFaultyYAML
#expect(scanner.diagnostics(in: yaml, file: "production.yaml") == productionLikeFaultyDiagnostics)
}
}
struct PreflightLineCase: CustomStringConvertible {
var name: String
var line: String
var lineNumber: Int
var expected: PureYAML.Validation.Diagnostic
var description: String {
name
}
}
let preflightLineCases: [PreflightLineCase] = [
.init(
name: "tab indentation",
line: "\tname: value",
lineNumber: 7,
expected: .init(
kind: .parse,
code: "tabIndentation",
severity: .error,
file: "input.yaml",
line: 7,
column: 1,
reason: "tab used for indentation; YAML indentation must use spaces",
),
),
.init(
name: "missing mapping space",
line: "summary:Missing space",
lineNumber: 8,
expected: .init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "input.yaml",
line: 8,
column: 9,
reason: "missing space after ':' in mapping entry",
),
),
.init(
name: "missing sequence space",
line: " -name: id",
lineNumber: 9,
expected: .init(
kind: .parse,
code: "missingSequenceSpace",
severity: .error,
file: "input.yaml",
line: 9,
column: 10,
reason: "missing space after '-' in sequence entry",
),
),
.init(
name: "trailing whitespace",
line: "description: OK ",
lineNumber: 10,
expected: .init(
kind: .parse,
code: "trailingWhitespace",
severity: .warning,
file: "input.yaml",
line: 10,
column: 16,
reason: "trailing whitespace",
),
),
.init(
name: "invalid control character",
line: "control: \u{0007}",
lineNumber: 11,
expected: .init(
kind: .parse,
code: "invalidControlCharacter",
severity: .error,
file: "input.yaml",
line: 11,
column: 10,
reason: "invalid control character in YAML source",
),
),
]
let productionLikeFaultyYAML = [
"\tapiVersion: v1",
"kind:Pod",
"metadata:",
" name: app",
" labels:",
" app:demo",
"spec:",
" containers:",
" -name: app",
" - image:nginx",
" - ports:",
" -containerPort:80",
"env:",
" - name:MODE",
" - value:prod",
"description: ok ",
"control: \u{0007}",
"url: https://example.com",
"negative: -1",
"flow: {name:value}",
"replicas:3",
" -host: example.com",
" - port:8080",
" timeout:30",
" enabled:true",
" path:/health",
" query:?bad",
" item:[]",
" owner:me",
" tag:v1",
" mode:prod",
].joined(separator: "\n")
let productionLikeFaultyDiagnostics: [PureYAML.Validation.Diagnostic] = [
.init(
kind: .parse,
code: "tabIndentation",
severity: .error,
file: "production.yaml",
line: 1,
column: 1,
reason: "tab used for indentation; YAML indentation must use spaces",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 2,
column: 6,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 6,
column: 9,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingSequenceSpace",
severity: .error,
file: "production.yaml",
line: 9,
column: 6,
reason: "missing space after '-' in sequence entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 10,
column: 13,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingSequenceSpace",
severity: .error,
file: "production.yaml",
line: 12,
column: 10,
reason: "missing space after '-' in sequence entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 14,
column: 10,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 15,
column: 11,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "trailingWhitespace",
severity: .warning,
file: "production.yaml",
line: 16,
column: 16,
reason: "trailing whitespace",
),
.init(
kind: .parse,
code: "invalidControlCharacter",
severity: .error,
file: "production.yaml",
line: 17,
column: 10,
reason: "invalid control character in YAML source",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 21,
column: 10,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingSequenceSpace",
severity: .error,
file: "production.yaml",
line: 22,
column: 4,
reason: "missing space after '-' in sequence entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 23,
column: 10,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 24,
column: 11,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 25,
column: 11,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 26,
column: 8,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 27,
column: 9,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 28,
column: 8,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 29,
column: 9,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 30,
column: 7,
reason: "missing space after ':' in mapping entry",
),
.init(
kind: .parse,
code: "missingMappingSpace",
severity: .error,
file: "production.yaml",
line: 31,
column: 8,
reason: "missing space after ':' in mapping entry",
),
]