IobJsonTests.swift 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import Foundation
  2. import Testing
  3. @testable import Trio
  4. class BundleReference {}
  5. /// This test suite is to help us debug and verify iob errors from Trio devices
  6. ///
  7. /// There are two key components. First, we have a version of the Javascript that has a number
  8. /// of bugs fixed. We don't want to fix the real Javascript, so we put this fixed Javascript in the
  9. /// testing bundle and use it to run comparisons. If the error we see in the field is one that we know
  10. /// about and have fixed in JS, the Swift and JS implementations will produce the same results. You
  11. /// can find the fixed JS here:
  12. /// https://github.com/kingst/trio-oref/tree/tcd-fixes-for-swift-comparison
  13. ///
  14. /// Second, we have a server that runs (part of `trio-oref-logs`) to serve error logs captured
  15. /// from the field. This server needs to run on the same machine as the simulator where this test runs.
  16. /// You can find more information about it from the `trio-oref-logs` repo.
  17. @Suite("IoB using real pump history JSON", .serialized) struct IobJsonTests {
  18. private var originalTZ: String? = ProcessInfo.processInfo.environment["TZ"]
  19. // Helper function to set timezone
  20. private func setTimezone(identifier: String) {
  21. setenv("TZ", identifier, 1)
  22. tzset() // Make the change take effect
  23. }
  24. // Helper function to reset timezone
  25. private func resetTimezone() {
  26. // Restore system timezone
  27. if let originalTZ = originalTZ {
  28. setenv("TZ", originalTZ, 1)
  29. } else {
  30. unsetenv("TZ")
  31. }
  32. tzset()
  33. }
  34. // Note: This test case has a memory leak so limit your inputs
  35. // to about 250 files at a time
  36. @Test(
  37. "should produce same results for fixed JS and different for bundle JS",
  38. .enabled(if: false)
  39. ) func replayErrorInputs() async throws {
  40. let url = URL(string: "http://localhost:8123/list")!
  41. let (data, _) = try await URLSession.shared.data(from: url)
  42. let files = try JSONDecoder().decode([String].self, from: data)
  43. let fileDataDecoder = JSONDecoder()
  44. fileDataDecoder.dateDecodingStrategy = .secondsSince1970
  45. for filePath in files {
  46. let dataUrl = URL(string: "http://localhost:8123\(filePath)")!
  47. let (data, _) = try await URLSession.shared.data(from: dataUrl)
  48. let algorithmComparison = try fileDataDecoder.decode(AlgorithmComparison.self, from: data)
  49. print("Checking \(filePath) @ \(algorithmComparison.createdAt)")
  50. guard let iobInputs = algorithmComparison.iobInput else {
  51. print("Skipping, no iobInputs found")
  52. if let str = algorithmComparison.comparisonError {
  53. print(str)
  54. }
  55. if let str = algorithmComparison.swiftException {
  56. print(str)
  57. }
  58. continue
  59. }
  60. setTimezone(identifier: algorithmComparison.timezone)
  61. try await checkFixedJsAgainstSwift(iobInputs: algorithmComparison.iobInput!)
  62. try await checkBundleJsAgainstSwift(iobInputs: algorithmComparison.iobInput!)
  63. resetTimezone()
  64. }
  65. }
  66. func checkFixedJsAgainstSwift(iobInputs: IobInputs) async throws {
  67. let openAps = OpenAPSFixed()
  68. let (iobResultSwift, _) = OpenAPSSwift.iob(
  69. pumphistory: iobInputs.history,
  70. profile: try JSONBridge.to(iobInputs.profile),
  71. clock: iobInputs.clock,
  72. autosens: try JSONBridge.to(iobInputs.autosens)
  73. )
  74. let iobResultJavascript = await openAps.iobJavascript(
  75. pumphistory: iobInputs.history,
  76. profile: try JSONBridge.to(iobInputs.profile),
  77. clock: iobInputs.clock,
  78. autosens: try JSONBridge.to(iobInputs.autosens)
  79. )
  80. let comparison = JSONCompare.createComparison(
  81. function: .iob,
  82. swift: iobResultSwift,
  83. swiftDuration: 0.1,
  84. javascript: iobResultJavascript,
  85. javascriptDuration: 0.1,
  86. iobInputs: nil
  87. )
  88. if comparison.resultType == .valueDifference {
  89. print(comparison.differences!.prettyPrintedJSON!)
  90. }
  91. if comparison.resultType != .matching {
  92. print("REPLAY ERROR: Fixed JS didn't match")
  93. }
  94. #expect(comparison.resultType == .matching)
  95. }
  96. func checkBundleJsAgainstSwift(iobInputs: IobInputs) async throws {
  97. let openAps = OpenAPS(storage: BaseFileStorage())
  98. let (iobResultSwift, _) = OpenAPSSwift.iob(
  99. pumphistory: iobInputs.history,
  100. profile: try JSONBridge.to(iobInputs.profile),
  101. clock: iobInputs.clock,
  102. autosens: try JSONBridge.to(iobInputs.autosens)
  103. )
  104. let iobResultJavascript = await openAps.iobJavascript(
  105. pumphistory: iobInputs.history,
  106. profile: try JSONBridge.to(iobInputs.profile),
  107. clock: iobInputs.clock,
  108. autosens: try JSONBridge.to(iobInputs.autosens)
  109. )
  110. let comparison = JSONCompare.createComparison(
  111. function: .iob,
  112. swift: iobResultSwift,
  113. swiftDuration: 0.1,
  114. javascript: iobResultJavascript,
  115. javascriptDuration: 0.1,
  116. iobInputs: nil
  117. )
  118. if comparison.resultType != .valueDifference {
  119. print("REPLAY ERROR: bundle JS did't produce value difference")
  120. }
  121. #expect(comparison.resultType == .valueDifference)
  122. }
  123. /// simple utility for creating inputs for Javascript for use in testing
  124. @Test("format inputs for Javascript", .enabled(if: false)) func generateJavascriptInputs() throws {
  125. let testBundle = Bundle(for: BundleReference.self)
  126. let path = testBundle.path(forResource: "iob-error-log", ofType: "json")!
  127. let data = try Data(contentsOf: URL(fileURLWithPath: path))
  128. let decoder = JSONDecoder()
  129. decoder.dateDecodingStrategy = .secondsSince1970
  130. let algorithmComparison = try decoder.decode(AlgorithmComparison.self, from: data)
  131. let iobInputs = algorithmComparison.iobInput!
  132. let encoder = JSONCoding.encoder
  133. let output = try encoder.encode(iobInputs)
  134. let sharedDir = FileManager.default.temporaryDirectory
  135. let outputURL = sharedDir.appendingPathComponent("js_iob_input_error.json")
  136. // Print the path so you can find it
  137. print("Writing to: \(outputURL.path)")
  138. try output.write(to: outputURL)
  139. let treatments = try IobHistory.calcTempTreatments(
  140. history: iobInputs.history.map { $0.computedEvent() },
  141. profile: iobInputs.profile,
  142. clock: iobInputs.clock,
  143. autosens: iobInputs.autosens,
  144. zeroTempDuration: nil
  145. )
  146. let treatmentsOut = try encoder.encode(treatments)
  147. let treatmentsUrl = sharedDir.appendingPathComponent("treatments.json")
  148. print("Writing to: \(treatmentsUrl.path)")
  149. try treatmentsOut.write(to: treatmentsUrl)
  150. }
  151. }