PureYAML/Tests/CodingTests.swift

223 lines
7.2 KiB
Swift

@testable import PureYAML
import Testing
@Suite("Typed Coding")
struct CodingTests {
@Test("Decodes scalar values with exact results")
func test_decodesScalarValuesWithExactResults() throws {
#expect(try PureYAML.decode(String.self, from: .string("hello")) == "hello")
#expect(try PureYAML.decode(Bool.self, from: .bool(true)))
#expect(try PureYAML.decode(Int.self, from: .int(42)) == 42)
#expect(try PureYAML.decode(Double.self, from: .double(1.25)) == 1.25)
#expect(try PureYAML.decode(Double.self, from: .int(2)) == 2.0)
let optional = try PureYAML.decode(String?.self, from: .null)
#expect(optional == nil)
}
@Test("Parses YAML before decoding scalar values")
func test_parsesYAMLBeforeDecodingScalarValues() throws {
#expect(try PureYAML.decode(String.self, from: "hello") == "hello")
#expect(try PureYAML.decode(Bool.self, from: "true"))
#expect(try PureYAML.decode(Int.self, from: "7") == 7)
#expect(try PureYAML.decode(Double.self, from: "1.5") == 1.5)
}
@Test("Encoding scalar values produces exact value trees and YAML")
func test_encodingScalarValuesProducesExactValueTreesAndYAML() throws {
#expect(try PureYAML.encode("hello") == .string("hello"))
#expect(try PureYAML.encode(true) == .bool(true))
#expect(try PureYAML.encode(42) == .int(42))
#expect(try PureYAML.encode(1.25) == .double(1.25))
let nilString: String? = nil
#expect(try PureYAML.encode(nilString) == .null)
#expect(try PureYAML.encodeToYAML("hello") == "\"hello\"\n")
#expect(try !((PureYAML.encodeToYAML("hello")).contains("null")))
}
@Test("Decoding failures report exact validation-style errors", arguments: [
CodingErrorCase(
name: "string from int",
value: .int(1),
kind: .string,
expected: .typeMismatch(expected: "String", actual: "int", path: .root),
),
CodingErrorCase(
name: "int from double",
value: .double(1.5),
kind: .int,
expected: .typeMismatch(expected: "Int", actual: "double", path: .root),
),
CodingErrorCase(
name: "bool from null",
value: .null,
kind: .bool,
expected: .typeMismatch(expected: "Bool", actual: "null", path: .root),
),
CodingErrorCase(
name: "int8 from out-of-range int",
value: .int(1000),
kind: .int8,
expected: .integerOutOfRange(type: "Int8", path: .root),
),
])
func test_decodingFailuresReportExactValidationStyleErrors(
testCase: CodingErrorCase,
) {
testCase.expectFailure()
}
@Test("Unsupported decoding containers report exact errors")
func test_unsupportedDecodingContainersReportExactErrors() {
expectDecodingError(
.unsupportedContainer(kind: "keyed", path: .root),
) {
_ = try PureYAML.decode(ExampleStruct.self, from: .mapping(.init([
.init(key: "name", value: .string("hello")),
])))
}
expectDecodingError(
.unsupportedContainer(kind: "unkeyed", path: .root),
) {
_ = try PureYAML.decode([Int].self, from: .sequence([.int(1)]))
}
}
@Test("Unsupported encoding containers report exact errors")
func test_unsupportedEncodingContainersReportExactErrors() {
expectEncodingError(
.unsupportedContainer(kind: "keyed", path: .init([.key("name")])),
) {
_ = try PureYAML.encode(ExampleStruct(name: "hello"))
}
expectEncodingError(
.unsupportedContainer(kind: "unkeyed", path: .init([.index(0)])),
) {
_ = try PureYAML.encode([1, 2])
}
expectEncodingError(
.unsupportedContainer(kind: "keyed", path: .root),
) {
_ = try PureYAML.encode(EmptyStruct())
}
expectEncodingError(
.unsupportedContainer(kind: "unkeyed", path: .root),
) {
_ = try PureYAML.encode([Int]())
}
}
@Test("Out of range integer encoding reports exact errors")
func test_outOfRangeIntegerEncodingReportsExactErrors() {
expectEncodingError(
.integerOutOfRange(type: "UInt64", path: .root),
) {
_ = try PureYAML.encode(UInt64.max)
}
}
@Test("Coding error descriptions are exact")
func test_codingErrorDescriptionsAreExact() {
#expect(PureYAML.Decoding.Error.typeMismatch(
expected: "String",
actual: "int",
path: .root,
).description == "Expected String at $, found int")
#expect(PureYAML.Encoding.Error.unsupportedContainer(
kind: "keyed",
path: .init([.key("name")]),
).description == "Unsupported keyed encoding container at $.name")
#expect(PureYAML.Decoding.Error.integerOutOfRange(
type: "Int8",
path: .root,
).description == "Int8 value is outside PureYAML integer range at $")
#expect(PureYAML.Encoding.Error.integerOutOfRange(
type: "UInt64",
path: .root,
).description == "UInt64 value is outside PureYAML integer range at $")
}
}
struct CodingErrorCase: CustomStringConvertible {
enum Kind {
case string
case int
case int8
case bool
}
var name: String
var value: PureYAML.Model.Value
var kind: Kind
var expected: PureYAML.Decoding.Error
var description: String {
name
}
func expectFailure() {
do {
switch kind {
case .string:
_ = try PureYAML.decode(String.self, from: value)
case .int:
_ = try PureYAML.decode(Int.self, from: value)
case .int8:
_ = try PureYAML.decode(Int8.self, from: value)
case .bool:
_ = try PureYAML.decode(Bool.self, from: value)
}
recordIssue("expected decoding error")
} catch let error as PureYAML.Decoding.Error {
#expect(error == expected)
#expect(error.description == expected.description)
} catch {
recordIssue("unexpected error \(error)")
}
}
}
private struct ExampleStruct: Codable, Equatable {
var name: String
}
private struct EmptyStruct: Encodable {}
private func expectDecodingError(
_ expected: PureYAML.Decoding.Error,
operation: () throws -> some Any,
) {
do {
_ = try operation()
recordIssue("expected decoding error")
} catch let error as PureYAML.Decoding.Error {
#expect(error == expected)
#expect(error.description == expected.description)
} catch {
recordIssue("unexpected error \(error)")
}
}
private func expectEncodingError(
_ expected: PureYAML.Encoding.Error,
operation: () throws -> some Any,
) {
do {
_ = try operation()
recordIssue("expected encoding error")
} catch let error as PureYAML.Encoding.Error {
#expect(error == expected)
#expect(error.description == expected.description)
} catch {
recordIssue("unexpected error \(error)")
}
}