From a8c5de540ac671c417076d2e31b44b2510a438f3 Mon Sep 17 00:00:00 2001 From: Mihaela M Date: Mon, 29 Jun 2026 13:31:54 +0200 Subject: [PATCH] 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. --- Tests/YAMLTestSuiteConformanceTests.swift | 64 +++++++++++------------ 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/Tests/YAMLTestSuiteConformanceTests.swift b/Tests/YAMLTestSuiteConformanceTests.swift index 936aa2d..426d18e 100644 --- a/Tests/YAMLTestSuiteConformanceTests.swift +++ b/Tests/YAMLTestSuiteConformanceTests.swift @@ -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 {