// // FixtureSupport.swift // Metrika // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Copyright © 2026 Simon-Pierre Boucher. All rights reserved. // import Foundation import Testing /// Golden values produced by Tests/Fixtures/generate.R. struct Fixtures { let expected: [String: Double] let datasetURL: URL init() throws { guard let tsv = Bundle.module.url( forResource: "expected", withExtension: "tsv", subdirectory: "Fixtures" ), let csv = Bundle.module.url( forResource: "regression", withExtension: "csv", subdirectory: "Fixtures" ) else { throw NSError(domain: "Fixtures", code: 1, userInfo: [ NSLocalizedDescriptionKey: "fixture resources missing — run Tests/Fixtures/generate.R", ]) } var values: [String: Double] = [:] for line in try String(contentsOf: tsv, encoding: .utf8) .split(separator: "\n") { let parts = line.split(separator: "\t") guard parts.count == 2, let value = Double(parts[1]) else { continue } values[String(parts[0])] = value } self.expected = values self.datasetURL = csv } subscript(key: String) -> Double { guard let value = expected[key] else { Issue.record("missing fixture key '\(key)'") return .nan } return value } } /// Relative-tolerance comparison (CLAUDE.md §0: CPU paths match reference /// implementations to at least 1e-10 relative tolerance). func expectClose( _ actual: Double, _ expected: Double, rtol: Double = 1e-10, _ label: String = "", sourceLocation: SourceLocation = #_sourceLocation ) { let scale = max(abs(expected), abs(actual), 1e-300) let relative = abs(actual - expected) / scale #expect( relative <= rtol, "\(label): actual \(actual) vs expected \(expected) (rel. err. \(relative))", sourceLocation: sourceLocation ) }