Prechádzať zdrojové kódy

Merge pull request #552 from nightscout/oref-swift-automate-replay-tests

Small change to oref swift unit tests to enable automated replay
Sam King 10 mesiacov pred
rodič
commit
dabf6c42bf

+ 6 - 6
Trio.xcodeproj/project.pbxproj

@@ -263,6 +263,7 @@
 		3B4BA78F2D8DC0EC0069D5B8 /* TidepoolServiceKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B4BA7882D8DC0EC0069D5B8 /* TidepoolServiceKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		3B4BA7902D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B4BA7892D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework */; };
 		3B4BA7912D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B4BA7892D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		3B4D17132E1D8A0D007FB180 /* AutosensJsonExtraTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B4D17122E1D89FE007FB180 /* AutosensJsonExtraTests.swift */; };
 		3B5CD1EC2D4912A600CE213C /* OpenAPSSwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5CD1E92D4912A600CE213C /* OpenAPSSwift.swift */; };
 		3B5CD1ED2D4912A600CE213C /* JSONBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5CD1EA2D4912A600CE213C /* JSONBridge.swift */; };
 		3B5CD2982D4AEA3C00CE213C /* Carbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5CD2922D4AEA3C00CE213C /* Carbs.swift */; };
@@ -314,7 +315,6 @@
 		3BD9687F2D8DDD8800899469 /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 3BD9687E2D8DDD8800899469 /* CryptoSwift */; };
 		3BE2F1E82E030E2F009E2900 /* MealCobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BE2F1E72E030E2F009E2900 /* MealCobTests.swift */; };
 		3BE2F1EA2E031951009E2900 /* MealCobBucketingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BE2F1E92E031951009E2900 /* MealCobBucketingTests.swift */; };
-		3BE4C0DB2E17549E00C8520C /* as_error_iob_inputs.json in Resources */ = {isa = PBXBuildFile; fileRef = 3BE4C0DA2E17549E00C8520C /* as_error_iob_inputs.json */; };
 		3BEA3AE02D58F79700A67A1D /* OrefFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BEA3ADE2D58F79700A67A1D /* OrefFunction.swift */; };
 		3BEA3AE12D58F79700A67A1D /* AlgorithmComparison.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BEA3ADB2D58F79700A67A1D /* AlgorithmComparison.swift */; };
 		3BEA3AE22D58F79700A67A1D /* JsSwiftOrefComparisonLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BEA3ADD2D58F79700A67A1D /* JsSwiftOrefComparisonLogger.swift */; };
@@ -1180,6 +1180,7 @@
 		3B4BA7692D8DBD690069D5B8 /* RileyLinkKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		3B4BA7882D8DC0EC0069D5B8 /* TidepoolServiceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TidepoolServiceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		3B4BA7892D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TidepoolServiceKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		3B4D17122E1D89FE007FB180 /* AutosensJsonExtraTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutosensJsonExtraTests.swift; sourceTree = "<group>"; };
 		3B5CD1E92D4912A600CE213C /* OpenAPSSwift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAPSSwift.swift; sourceTree = "<group>"; };
 		3B5CD1EA2D4912A600CE213C /* JSONBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONBridge.swift; sourceTree = "<group>"; };
 		3B5CD2912D4AEA3C00CE213C /* Basal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Basal.swift; sourceTree = "<group>"; };
@@ -1230,7 +1231,6 @@
 		3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorProvider.swift; sourceTree = "<group>"; };
 		3BE2F1E72E030E2F009E2900 /* MealCobTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MealCobTests.swift; sourceTree = "<group>"; };
 		3BE2F1E92E031951009E2900 /* MealCobBucketingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MealCobBucketingTests.swift; sourceTree = "<group>"; };
-		3BE4C0DA2E17549E00C8520C /* as_error_iob_inputs.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = as_error_iob_inputs.json; sourceTree = "<group>"; };
 		3BEA3ADB2D58F79700A67A1D /* AlgorithmComparison.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlgorithmComparison.swift; sourceTree = "<group>"; };
 		3BEA3ADC2D58F79700A67A1D /* JSONCompare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONCompare.swift; sourceTree = "<group>"; };
 		3BEA3ADD2D58F79700A67A1D /* JsSwiftOrefComparisonLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsSwiftOrefComparisonLogger.swift; sourceTree = "<group>"; };
@@ -2817,7 +2817,6 @@
 			isa = PBXGroup;
 			children = (
 				3B8B5D2C2DF5234C00365ED3 /* autosens */,
-				3BE4C0DA2E17549E00C8520C /* as_error_iob_inputs.json */,
 				3BF92F392D86F1AA006B545A /* iob-error-log.json */,
 			);
 			path = json;
@@ -2911,14 +2910,15 @@
 		3B5CD2C72D4AECD500CE213C /* OpenAPSSwiftTests */ = {
 			isa = PBXGroup;
 			children = (
-				DD30B9FF2E0745C400DA677C /* DetermineBasalDeltaCalculationTests.swift */,
-				DD30B9FD2E0742E200DA677C /* DetermineBasalSMBEnablementTests.swift */,
 				3BF92F2C2D86DEE9006B545A /* javascript */,
 				3B1C5C3C2D68E269004E9273 /* json */,
 				3BFA5BF72D989F380072B082 /* mocks */,
 				3B1C5C3F2D68E269004E9273 /* utils */,
+				3B4D17122E1D89FE007FB180 /* AutosensJsonExtraTests.swift */,
 				3B8B5D3B2DF523B800365ED3 /* AutosensJsonTests.swift */,
 				3BBC22622DF5B93900169236 /* AutosensTests.swift */,
+				DD30B9FF2E0745C400DA677C /* DetermineBasalDeltaCalculationTests.swift */,
+				DD30B9FD2E0742E200DA677C /* DetermineBasalSMBEnablementTests.swift */,
 				3B1C5C352D68E269004E9273 /* IobCalculateTests.swift */,
 				3B1C5C362D68E269004E9273 /* IobHistoryTests.swift */,
 				3BC4053A2D931620006A03E9 /* IobJsonTests.swift */,
@@ -4411,7 +4411,6 @@
 				3BF92F322D86DEE9006B545A /* glucose-get-last.js in Resources */,
 				3BF92F332D86DEE9006B545A /* iob.js in Resources */,
 				3BF92F352D86DEE9006B545A /* basal-set-temp.js in Resources */,
-				3BE4C0DB2E17549E00C8520C /* as_error_iob_inputs.json in Resources */,
 				3BF92F362D86DEE9006B545A /* autotune-core.js in Resources */,
 				3BC0AA3B2DA74C87000DF7B7 /* iob-total.js in Resources */,
 				3BC0AA3E2DA817EC000DF7B7 /* iob-calculate.js in Resources */,
@@ -5217,6 +5216,7 @@
 				BD8FC0602D6619DB00B95AED /* CarbsStorageTests.swift in Sources */,
 				DD30BA002E0745C400DA677C /* DetermineBasalDeltaCalculationTests.swift in Sources */,
 				BD8FC05E2D6618CE00B95AED /* BolusCalculatorTests.swift in Sources */,
+				3B4D17132E1D8A0D007FB180 /* AutosensJsonExtraTests.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 2 - 0
TrioTests/Info.plist

@@ -16,5 +16,7 @@
 	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
 	<key>EnableReplayTests</key>
 	<string>$(ENABLE_REPLAY_TESTS)</string>
+	<key>ReplayTestTimezone</key>
+	<string>$(REPLAY_TEST_TIMEZONE)</string>
 </dict>
 </plist>

+ 54 - 0
TrioTests/OpenAPSSwiftTests/AutosensJsonExtraTests.swift

@@ -0,0 +1,54 @@
+import Foundation
+import Testing
+@testable import Trio
+
+@Suite("Autosens using real JSON from bundle", .serialized) struct AutosensJsonExtraTests {
+    let timeZoneForTests = TimeZoneForTests()
+
+    // static func from<T: Decodable>(string: String) throws -> T
+    func loadJson<T: Decodable>(_ name: String) throws -> T {
+        let testBundle = Bundle(for: BundleReference.self)
+        let path = testBundle.path(forResource: name, ofType: "json")!
+        let data = try Data(contentsOf: URL(fileURLWithPath: path))
+        return try JSONCoding.decoder.decode(T.self, from: data)
+    }
+
+    @Test("Test with resistance") func generateJavascriptInputs() throws {
+        let glucose: [BloodGlucose] = try loadJson("as-glucose")
+        let pump: [PumpHistoryEvent] = try loadJson("as-pump")
+        let basalProfile: [BasalProfileEntry] = try loadJson("as-basal")
+        let profile: Profile = try loadJson("as-profile")
+        let carbs: [CarbsEntry] = try loadJson("as-carbs")
+        let tempTargets: [TempTarget] = try loadJson("as-temp-targets")
+
+        let clock = Date("2025-06-08T00:14:35.481Z")!
+
+        timeZoneForTests.setTimezone(identifier: "America/Los_Angeles")
+
+        let autosensResult = try AutosensGenerator.generate(
+            glucose: glucose,
+            pumpHistory: pump,
+            basalProfile: basalProfile,
+            profile: profile,
+            carbs: carbs,
+            tempTargets: tempTargets,
+            maxDeviations: 96,
+            clock: clock,
+            includeDeviationsForTesting: true
+        )
+
+        let deviationsUnsorted: [Decimal] = try loadJson("deviationsUnsorted")
+
+        #expect(autosensResult.ratio == 1.2)
+        #expect(autosensResult.newisf == 46)
+        #expect(deviationsUnsorted.count == autosensResult.deviationsUnsorted?.count)
+
+        for (ref, calc) in zip(deviationsUnsorted, autosensResult.deviationsUnsorted!) {
+            // we can get differences due to rounding inconsistencies between
+            // javascript and swift with negative numbers
+            #expect(ref.isWithin(0.01, of: calc))
+        }
+
+        timeZoneForTests.resetTimezone()
+    }
+}

+ 2 - 87
TrioTests/OpenAPSSwiftTests/AutosensJsonTests.swift

@@ -5,53 +5,6 @@ import Testing
 @Suite("Autosens using real JSON", .serialized) struct AutosensJsonTests {
     let timeZoneForTests = TimeZoneForTests()
 
-    // static func from<T: Decodable>(string: String) throws -> T
-    func loadJson<T: Decodable>(_ name: String) throws -> T {
-        let testBundle = Bundle(for: BundleReference.self)
-        let path = testBundle.path(forResource: name, ofType: "json")!
-        let data = try Data(contentsOf: URL(fileURLWithPath: path))
-        return try JSONCoding.decoder.decode(T.self, from: data)
-    }
-
-    @Test("Test with resistance") func generateJavascriptInputs() throws {
-        let glucose: [BloodGlucose] = try loadJson("as-glucose")
-        let pump: [PumpHistoryEvent] = try loadJson("as-pump")
-        let basalProfile: [BasalProfileEntry] = try loadJson("as-basal")
-        let profile: Profile = try loadJson("as-profile")
-        let carbs: [CarbsEntry] = try loadJson("as-carbs")
-        let tempTargets: [TempTarget] = try loadJson("as-temp-targets")
-
-        let clock = Date("2025-06-08T00:14:35.481Z")!
-
-        timeZoneForTests.setTimezone(identifier: "America/Los_Angeles")
-
-        let autosensResult = try AutosensGenerator.generate(
-            glucose: glucose,
-            pumpHistory: pump,
-            basalProfile: basalProfile,
-            profile: profile,
-            carbs: carbs,
-            tempTargets: tempTargets,
-            maxDeviations: 96,
-            clock: clock,
-            includeDeviationsForTesting: true
-        )
-
-        let deviationsUnsorted: [Decimal] = try loadJson("deviationsUnsorted")
-
-        #expect(autosensResult.ratio == 1.2)
-        #expect(autosensResult.newisf == 46)
-        #expect(deviationsUnsorted.count == autosensResult.deviationsUnsorted?.count)
-
-        for (ref, calc) in zip(deviationsUnsorted, autosensResult.deviationsUnsorted!) {
-            // we can get differences due to rounding inconsistencies between
-            // javascript and swift with negative numbers
-            #expect(ref.isWithin(0.01, of: calc))
-        }
-
-        timeZoneForTests.resetTimezone()
-    }
-
     func checkFixedJsAgainstSwift(autosensInputs: AutosensInputs) async throws {
         let openAps = OpenAPSFixed()
         let (autosensResultSwift, _) = OpenAPSSwift.autosense(
@@ -160,14 +113,12 @@ import Testing
         "should produce same results for autosens for fixed JS",
         .enabled(if: ReplayTests.enabled)
     ) func replayErrorInputs() async throws {
-        let timezone = "America/Los_Angeles"
-        var skippedTimezones = Set<String>()
+        let timezone = ReplayTests.timezone
         let files = try await HttpFiles.listFiles()
         for filePath in files {
             let algorithmComparison = try await HttpFiles.downloadFile(at: filePath)
             print("Checking \(filePath) @ \(algorithmComparison.createdAt)")
             guard timezone == algorithmComparison.timezone else {
-                skippedTimezones.insert(algorithmComparison.timezone)
                 continue
             }
             guard let autosensInputs = algorithmComparison.autosensInput else {
@@ -188,14 +139,9 @@ import Testing
 
             timeZoneForTests.resetTimezone()
         }
-
-        print("Skipped timezones:")
-        for skippedTimezone in skippedTimezones {
-            print("  - \(skippedTimezone)")
-        }
     }
 
-    @Test("Format autosens inputs for running in JS", .enabled(if: ReplayTests.enabled)) func formatInputs() async throws {
+    @Test("Format autosens inputs for running in JS", .enabled(if: false)) func formatInputs() async throws {
         // this test is meant for one-off analysis so it's ok to hard code
         // a file, just make sure to _not_ check in updates to this to
         // avoid polluting our change logs
@@ -242,35 +188,4 @@ import Testing
 
         timeZoneForTests.resetTimezone()
     }
-
-    @Test(
-        "Testing IoB difference with Autosens",
-        .enabled(if: ReplayTests.enabled)
-    ) func testAutosensErrorWithIoB() async throws {
-        let currentBasalRate = Decimal(0.55)
-        let currentGlucoseDate = Date("2025-06-27T13:56:54.596Z")!
-        let iobInputs: IobInputs = try loadJson("as_error_iob_inputs")
-
-        let treatments = try IobHistory.calcTempTreatments(
-            history: iobInputs.history.map({ $0.computedEvent() }),
-            profile: iobInputs.profile,
-            clock: iobInputs.clock,
-            autosens: nil,
-            zeroTempDuration: nil
-        )
-
-        let encoder = JSONCoding.encoder
-        let output = try encoder.encode(treatments)
-
-        let sharedDir = FileManager.default.temporaryDirectory
-        let outputURL = sharedDir.appendingPathComponent("treatments-swift.json")
-        try output.write(to: outputURL)
-        print("WROTE FILE TO: \(outputURL.path)")
-
-        var simulatedProfile = iobInputs.profile
-        simulatedProfile.currentBasal = currentBasalRate
-        simulatedProfile.temptargetSet = false
-        let iob = try IobCalculation.iobTotal(treatments: treatments, profile: simulatedProfile, time: currentGlucoseDate)
-        print(iob)
-    }
 }

+ 6 - 2
TrioTests/OpenAPSSwiftTests/IobJsonTests.swift

@@ -44,9 +44,13 @@ import Testing
         .enabled(if: ReplayTests.enabled)
     ) func replayErrorInputs() async throws {
         let files = try await HttpFiles.listFiles()
+        let testingTimezone = ReplayTests.timezone
         for filePath in files {
             let algorithmComparison = try await HttpFiles.downloadFile(at: filePath)
             print("Checking \(filePath) @ \(algorithmComparison.createdAt)")
+            guard algorithmComparison.timezone == testingTimezone else {
+                continue
+            }
             guard let iobInputs = algorithmComparison.iobInput else {
                 print("Skipping, no iobInputs found")
                 if let str = algorithmComparison.comparisonError {
@@ -178,7 +182,7 @@ import Testing
         }
     }
 
-    @Test("Debug utility for checking iob-history", .enabled(if: ReplayTests.enabled)) func debugIobHistory() async throws {
+    @Test("Debug utility for checking iob-history", .enabled(if: false)) func debugIobHistory() async throws {
         let testBundle = Bundle(for: BundleReference.self)
         let path = testBundle.path(forResource: "iob-error-log", ofType: "json")!
         let data = try Data(contentsOf: URL(fileURLWithPath: path))
@@ -227,7 +231,7 @@ import Testing
     }
 
     /// simple utility for creating inputs for Javascript for use in testing
-    @Test("format inputs for Javascript", .enabled(if: ReplayTests.enabled)) func generateJavascriptInputs() throws {
+    @Test("format inputs for Javascript", .enabled(if: false)) func generateJavascriptInputs() throws {
         let testBundle = Bundle(for: BundleReference.self)
         let path = testBundle.path(forResource: "iob-error-log", ofType: "json")!
         let data = try Data(contentsOf: URL(fileURLWithPath: path))

+ 2 - 14
TrioTests/OpenAPSSwiftTests/MealJsonTests.swift

@@ -12,15 +12,12 @@ import Testing
         // Note: This test case can only test one timezone per invocation
         // so you need to manually change this to try out errors from
         // different timezones
-        let testingTimezone = "Europe/Berlin"
+        let testingTimezone = ReplayTests.timezone
         let files = try await HttpFiles.listFiles()
-        var skippedTimezones = Set<String>()
         for filePath in files {
             let algorithmComparison = try await HttpFiles.downloadFile(at: filePath)
             print("Checking \(filePath) @ \(algorithmComparison.createdAt)")
             guard algorithmComparison.timezone == testingTimezone else {
-                print("Skipping timezone \(algorithmComparison.timezone)")
-                skippedTimezones.insert(algorithmComparison.timezone)
                 continue
             }
             guard let mealInputs = algorithmComparison.mealInput else {
@@ -40,15 +37,6 @@ import Testing
             print("Checked \(filePath) \(algorithmComparison.timezone)")
             timeZoneForTests.resetTimezone()
         }
-
-        if skippedTimezones.isEmpty {
-            print("Didn't skip any timezones")
-        } else {
-            print("Skipped timezones:")
-            for timezone in skippedTimezones {
-                print("  - \(timezone)")
-            }
-        }
     }
 
     func checkFixedJsAgainstSwift(mealInputs: MealInputs) async throws {
@@ -94,7 +82,7 @@ import Testing
         #expect(comparison.resultType == .matching)
     }
 
-    @Test("Format meal inputs for running in JS", .enabled(if: ReplayTests.enabled)) func formatInputs() async throws {
+    @Test("Format meal inputs for running in JS", .enabled(if: false)) func formatInputs() async throws {
         let openAps = OpenAPSFixed()
 
         // this test is meant for one-off analysis so it's ok to hard code

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 2990
TrioTests/OpenAPSSwiftTests/json/as_error_iob_inputs.json


+ 12 - 0
TrioTests/OpenAPSSwiftTests/utils/ReplayTests.swift

@@ -13,4 +13,16 @@ enum ReplayTests {
         let bundle = Bundle(for: BundleReference.self)
         return bundle.object(forInfoDictionaryKey: "EnableReplayTests") as? String == "YES"
     }
+
+    /// Timezone to use for replay tests.
+    ///
+    /// This is used to filter replay test files by timezone. If not set, it defaults to "America/Los_Angeles".
+    /// To set it, add this line to your ConfigOverride.xcconfig file:
+    /// ```
+    /// REPLAY_TEST_TIMEZONE = Europe/Berlin
+    /// ```
+    static var timezone: String {
+        let bundle = Bundle(for: BundleReference.self)
+        return bundle.object(forInfoDictionaryKey: "ReplayTestTimezone") as? String ?? "America/Los_Angeles"
+    }
 }