fix(tests): compile conformance on Linux without CoreFoundation

YAMLTestSuiteConformanceTests resolved a JSON bool vs number with CFGetTypeID and
CFBooleanGetTypeID, CoreFoundation functions absent on Linux and Windows, so the
test target failed to compile there and the Linux and Windows CI gates were red.
Make JSONNode Decodable and read the expected in.json with JSONDecoder, which
distinguishes a bool from a number natively and reads top-level fragments via a
single-value container. Value-level conformance is unchanged at 206/206 on
macOS, and the parse no longer touches CoreFoundation.
This commit is contained in:
Mihaela M 2026-06-29 13:31:54 +02:00
parent 5e5e568e2c
commit a8c5de540a

View File

@ -127,14 +127,38 @@ struct YAMLTestSuiteConformanceTests {
}
/// A JSON-comparable normalization: ints and floats both become a number, so a value-level
/// match reflects structure and resolved value the way `in.json` records them.
private indirect enum JSONNode: Equatable {
/// match reflects structure and resolved value the way `in.json` records them. `Decodable` so the
/// expected `in.json` is read with `JSONDecoder`, which distinguishes a JSON bool from a number
/// natively. The previous `JSONSerialization` path needed CoreFoundation (`CFGetTypeID`) for that,
/// which is Darwin-only and failed to compile on Linux and Windows.
private indirect enum JSONNode: Equatable, Decodable {
case null
case bool(Bool)
case number(Double)
case string(String)
case array([JSONNode])
case object([String: JSONNode])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// Order matters: a JSON bool must be tried before a number, since `true`/`false` are a
// distinct JSON type the number decode would otherwise not see.
if container.decodeNil() {
self = .null
} else if let bool = try? container.decode(Bool.self) {
self = .bool(bool)
} else if let number = try? container.decode(Double.self) {
self = .number(number)
} else if let string = try? container.decode(String.self) {
self = .string(string)
} else if let array = try? container.decode([JSONNode].self) {
self = .array(array)
} else if let object = try? container.decode([String: JSONNode].self) {
self = .object(object)
} else {
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "unrecognized JSON value"))
}
}
}
private func jsonNode(from value: PureYAML.Model.Value) -> JSONNode? {
@ -168,39 +192,13 @@ struct YAMLTestSuiteConformanceTests {
}
private func jsonNode(fromJSONText text: String) -> JSONNode? {
guard let data = text.data(using: .utf8),
let object = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed])
else {
return nil
}
return jsonNode(fromJSONObject: object)
}
private func jsonNode(fromJSONObject object: Any) -> JSONNode? {
switch object {
case is NSNull:
return .null
case let number as NSNumber:
return CFGetTypeID(number) == CFBooleanGetTypeID() ? .bool(number.boolValue) : .number(number.doubleValue)
case let text as String:
return .string(text)
case let array as [Any]:
var nodes: [JSONNode] = []
for item in array {
guard let node = jsonNode(fromJSONObject: item) else { return nil }
nodes.append(node)
}
return .array(nodes)
case let dictionary as [String: Any]:
var result: [String: JSONNode] = [:]
for (key, value) in dictionary {
guard let node = jsonNode(fromJSONObject: value) else { return nil }
result[key] = node
}
return .object(result)
default:
guard let data = text.data(using: .utf8) else {
return nil
}
// The suite's `in.json` may be a top-level fragment (a bare string, number, or null), which
// `JSONDecoder` accepts in Swift 6. `JSONNode`'s decoder uses a single-value container, so it
// reads fragments and containers alike, cross-platform and without CoreFoundation.
return try? JSONDecoder().decode(JSONNode.self, from: data)
}
private func percent(_ part: Int, _ whole: Int) -> String {