test: cover numeric ladder, NaN hashing, unkeyed edges, error paths
Fill the highest-value coverage gaps found in the Codable bridge and parser: the full integer-width decode ladder and per-width out-of-range errors, Float round-trip, NaN equality and hash consistency (the bespoke nan == nan invariant), unkeyed decodeNil false-branch and nested-index mismatch, and the previously untested expectedNode, unterminated flow mapping, explicit block-scalar under-indent, and buffered invalid-merge error paths.
This commit is contained in:
parent
fadde56a05
commit
934c9eafa3
82
Tests/NumericCodingLadderTests.swift
Normal file
82
Tests/NumericCodingLadderTests.swift
Normal file
@ -0,0 +1,82 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
@Suite("Numeric Coding Ladder")
|
||||
struct NumericCodingLadderTests {
|
||||
@Test("Decodes every integer width from an int value")
|
||||
func test_decodesFullIntegerWidthLadder() throws {
|
||||
#expect(try PureYAML.decode(Int8.self, from: .int(100)) == 100)
|
||||
#expect(try PureYAML.decode(Int16.self, from: .int(30000)) == 30000)
|
||||
#expect(try PureYAML.decode(Int32.self, from: .int(2_000_000_000)) == 2_000_000_000)
|
||||
#expect(try PureYAML.decode(Int64.self, from: .int(9_000_000_000)) == 9_000_000_000)
|
||||
#expect(try PureYAML.decode(UInt.self, from: .int(42)) == 42)
|
||||
#expect(try PureYAML.decode(UInt8.self, from: .int(200)) == 200)
|
||||
#expect(try PureYAML.decode(UInt16.self, from: .int(60000)) == 60000)
|
||||
#expect(try PureYAML.decode(UInt32.self, from: .int(4_000_000_000)) == 4_000_000_000)
|
||||
#expect(try PureYAML.decode(UInt64.self, from: .int(42)) == 42)
|
||||
}
|
||||
|
||||
@Test("Reports the exact type when an int value is out of range per width")
|
||||
func test_outOfRangeDecodeReportsExactTypePerWidth() {
|
||||
expectDecodeOutOfRange(Int8.self, "Int8", .int(1000))
|
||||
expectDecodeOutOfRange(Int16.self, "Int16", .int(40000))
|
||||
expectDecodeOutOfRange(Int32.self, "Int32", .int(5_000_000_000))
|
||||
expectDecodeOutOfRange(UInt8.self, "UInt8", .int(300))
|
||||
expectDecodeOutOfRange(UInt16.self, "UInt16", .int(70000))
|
||||
expectDecodeOutOfRange(UInt32.self, "UInt32", .int(5_000_000_000))
|
||||
// Negative into an unsigned width is also out of range.
|
||||
expectDecodeOutOfRange(UInt8.self, "UInt8", .int(-1))
|
||||
expectDecodeOutOfRange(UInt.self, "UInt", .int(-1))
|
||||
expectDecodeOutOfRange(UInt64.self, "UInt64", .int(-1))
|
||||
}
|
||||
|
||||
@Test("Round-trips Float through the double representation")
|
||||
func test_floatEncodeDecodeRoundTrip() throws {
|
||||
#expect(try PureYAML.encode(Float(1.5)) == .double(1.5))
|
||||
#expect(try PureYAML.decode(Float.self, from: .double(1.5)) == 1.5)
|
||||
#expect(try PureYAML.decode(Float.self, from: .int(2)) == 2.0)
|
||||
}
|
||||
|
||||
@Test("Double decoding rejects non-numeric kinds")
|
||||
func test_doubleDecodeRejectsNonNumericKinds() {
|
||||
expectDecodingError(.typeMismatch(expected: "Double", actual: "bool", path: .root)) {
|
||||
try PureYAML.decode(Double.self, from: .bool(true))
|
||||
}
|
||||
expectDecodingError(.typeMismatch(expected: "Double", actual: "string", path: .root)) {
|
||||
try PureYAML.decode(Double.self, from: .string("x"))
|
||||
}
|
||||
expectDecodingError(.typeMismatch(expected: "Double", actual: "null", path: .root)) {
|
||||
try PureYAML.decode(Double.self, from: .null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func expectDecodeOutOfRange(
|
||||
_ type: (some Decodable).Type,
|
||||
_ typeName: String,
|
||||
_ value: PureYAML.Model.Value,
|
||||
sourceLocation: SourceLocation = #_sourceLocation,
|
||||
) {
|
||||
expectDecodingError(
|
||||
.integerOutOfRange(type: typeName, path: .root),
|
||||
sourceLocation: sourceLocation,
|
||||
) {
|
||||
try PureYAML.decode(type, from: value)
|
||||
}
|
||||
}
|
||||
|
||||
private func expectDecodingError(
|
||||
_ expected: PureYAML.Decoding.Error,
|
||||
sourceLocation: SourceLocation = #_sourceLocation,
|
||||
operation: () throws -> some Any,
|
||||
) {
|
||||
do {
|
||||
_ = try operation()
|
||||
Issue.record("expected decoding error \(expected)", sourceLocation: sourceLocation)
|
||||
} catch let error as PureYAML.Decoding.Error {
|
||||
#expect(error == expected, sourceLocation: sourceLocation)
|
||||
#expect(error.description == expected.description, sourceLocation: sourceLocation)
|
||||
} catch {
|
||||
Issue.record("unexpected error \(error)", sourceLocation: sourceLocation)
|
||||
}
|
||||
}
|
||||
37
Tests/ParsingErrorPathTests.swift
Normal file
37
Tests/ParsingErrorPathTests.swift
Normal file
@ -0,0 +1,37 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
@Suite("Parsing Error Paths")
|
||||
struct ParsingErrorPathTests {
|
||||
@Test("A node property with no following node throws expectedNode")
|
||||
func test_danglingNodePropertyThrowsExpectedNode() {
|
||||
expectParseError("&anchor\n", .expectedNode(line: 1, column: 1))
|
||||
expectParseError("root:\n key: &a\nother: 1\n", .expectedNode(line: 2, column: 8))
|
||||
}
|
||||
|
||||
@Test("An unterminated flow mapping throws at stream end")
|
||||
func test_unterminatedFlowMappingThrows() {
|
||||
expectParseError("x: {a: b", .unexpectedToken(
|
||||
expected: "flow entry or flow mapping end",
|
||||
actual: "streamEnd",
|
||||
line: 1,
|
||||
column: 9,
|
||||
))
|
||||
}
|
||||
|
||||
@Test("Block scalar content less indented than the explicit indicator throws")
|
||||
func test_explicitBlockScalarIndentationUnderIndentThrows() {
|
||||
expectParseError("k: |2\n a\n", .unexpectedIndentation(line: 2))
|
||||
}
|
||||
|
||||
@Test("Diagnostic validation reports an invalid merge value via the buffered composer")
|
||||
func test_diagnosticValidationRejectsInvalidMergeValue() {
|
||||
let report = PureYAML.diagnosticValidationReport("a: &x 1\nb:\n <<: *x\n", file: "merge.yaml")
|
||||
#expect(!report.isValid)
|
||||
#expect(report.diagnostics.count == 1)
|
||||
let diagnostic = report.diagnostics.first
|
||||
#expect(diagnostic?.kind == .parse)
|
||||
#expect(diagnostic?.code == "invalidMergeValue")
|
||||
#expect(diagnostic?.line == 3)
|
||||
}
|
||||
}
|
||||
23
Tests/UnkeyedContainerEdgeTests.swift
Normal file
23
Tests/UnkeyedContainerEdgeTests.swift
Normal file
@ -0,0 +1,23 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
@Suite("Unkeyed Container Edges")
|
||||
struct UnkeyedContainerEdgeTests {
|
||||
@Test("decodeNil returns false for present values and still decodes them")
|
||||
func test_unkeyedDecodeNilFalseBranchDecodesPresentValues() throws {
|
||||
let decoded = try PureYAML.decode([Int?].self, from: .sequence([.int(1), .null, .int(2)]))
|
||||
#expect(decoded == [1, nil, 2])
|
||||
}
|
||||
|
||||
@Test("A nested unkeyed container type mismatch reports the index path")
|
||||
func test_unkeyedNestedContainerMismatchReportsIndexPath() {
|
||||
do {
|
||||
_ = try PureYAML.decode([[Int]].self, from: .sequence([.int(1)]))
|
||||
Issue.record("expected a decoding error")
|
||||
} catch let error as PureYAML.Decoding.Error {
|
||||
#expect(error.description == "Expected sequence at $[0], found int")
|
||||
} catch {
|
||||
Issue.record("unexpected error \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Tests/ValueHashableTests.swift
Normal file
44
Tests/ValueHashableTests.swift
Normal file
@ -0,0 +1,44 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
@Suite("Value Hashing")
|
||||
struct ValueHashableTests {
|
||||
typealias Value = PureYAML.Model.Value
|
||||
|
||||
@Test("NaN double values compare equal to themselves")
|
||||
func test_doubleNaNValuesAreEqual() {
|
||||
#expect(Value.double(.nan) == Value.double(.nan))
|
||||
#expect(Value.double(.nan) != Value.double(1.0))
|
||||
#expect(Value.double(1.0) == Value.double(1.0))
|
||||
}
|
||||
|
||||
@Test("Value hashing is consistent with equality, including NaN")
|
||||
func test_valueHashingIsConsistentWithEqualityIncludingNaN() {
|
||||
// Two equal NaN values must collapse to one Set element, which only
|
||||
// holds if hash(into:) agrees with the custom == for NaN.
|
||||
let set: Set<Value> = [.double(.nan), .double(.nan)]
|
||||
#expect(set.count == 1)
|
||||
|
||||
// Distinct values stay distinct as dictionary keys.
|
||||
let table: [Value: Int] = [
|
||||
.double(.nan): 1,
|
||||
.int(1): 2,
|
||||
.string("1"): 3,
|
||||
.bool(true): 4,
|
||||
.null: 5,
|
||||
]
|
||||
#expect(table.count == 5)
|
||||
#expect(table[.double(.nan)] == 1)
|
||||
#expect(table[.int(1)] == 2)
|
||||
}
|
||||
|
||||
@Test("Equal values across cases hash and compare distinctly")
|
||||
func test_distinctCasesAreNotEqual() {
|
||||
#expect(Value.int(1) != Value.string("1"))
|
||||
#expect(Value.int(1) != Value.double(1.0))
|
||||
#expect(Value.bool(true) != Value.int(1))
|
||||
#expect(Value.null != Value.string("null"))
|
||||
let set: Set<Value> = [.int(1), .string("1"), .double(1.0), .bool(true), .null]
|
||||
#expect(set.count == 5)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user